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        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
20373        self.write_keyword("LISTAGG");
20374        self.write("(");
20375        if f.distinct {
20376            self.write_keyword("DISTINCT");
20377            self.write_space();
20378        }
20379        self.generate_expression(&f.this)?;
20380        if let Some(ref sep) = f.separator {
20381            self.write(", ");
20382            self.generate_expression(sep)?;
20383        } else if matches!(
20384            self.config.dialect,
20385            Some(DialectType::Trino) | Some(DialectType::Presto)
20386        ) {
20387            // Trino/Presto require explicit separator; default to ','
20388            self.write(", ','");
20389        }
20390        if let Some(ref overflow) = f.on_overflow {
20391            self.write_space();
20392            self.write_keyword("ON OVERFLOW");
20393            self.write_space();
20394            match overflow {
20395                ListAggOverflow::Error => self.write_keyword("ERROR"),
20396                ListAggOverflow::Truncate { filler, with_count } => {
20397                    self.write_keyword("TRUNCATE");
20398                    if let Some(ref fill) = filler {
20399                        self.write_space();
20400                        self.generate_expression(fill)?;
20401                    }
20402                    if *with_count {
20403                        self.write_space();
20404                        self.write_keyword("WITH COUNT");
20405                    } else {
20406                        self.write_space();
20407                        self.write_keyword("WITHOUT COUNT");
20408                    }
20409                }
20410            }
20411        }
20412        if order_inside_args {
20413            if let Some(ref order_by) = f.order_by {
20414                self.write_space();
20415                self.write_keyword("ORDER BY");
20416                self.write_space();
20417                for (i, ord) in order_by.iter().enumerate() {
20418                    if i > 0 {
20419                        self.write(", ");
20420                    }
20421                    self.generate_ordered(ord)?;
20422                }
20423            }
20424        }
20425        self.write(")");
20426        if !order_inside_args {
20427            if let Some(ref order_by) = f.order_by {
20428                self.write_space();
20429                self.write_keyword("WITHIN GROUP");
20430                self.write(" (");
20431                self.write_keyword("ORDER BY");
20432                self.write_space();
20433                for (i, ord) in order_by.iter().enumerate() {
20434                    if i > 0 {
20435                        self.write(", ");
20436                    }
20437                    self.generate_ordered(ord)?;
20438                }
20439                self.write(")");
20440            }
20441        }
20442        if let Some(ref filter) = f.filter {
20443            self.write_space();
20444            self.write_keyword("FILTER");
20445            self.write("(");
20446            self.write_keyword("WHERE");
20447            self.write_space();
20448            self.generate_expression(filter)?;
20449            self.write(")");
20450        }
20451        Ok(())
20452    }
20453
20454    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20455        self.write_keyword("SUM_IF");
20456        self.write("(");
20457        self.generate_expression(&f.this)?;
20458        self.write(", ");
20459        self.generate_expression(&f.condition)?;
20460        self.write(")");
20461        if let Some(ref filter) = f.filter {
20462            self.write_space();
20463            self.write_keyword("FILTER");
20464            self.write("(");
20465            self.write_keyword("WHERE");
20466            self.write_space();
20467            self.generate_expression(filter)?;
20468            self.write(")");
20469        }
20470        Ok(())
20471    }
20472
20473    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20474        self.write_keyword("APPROX_PERCENTILE");
20475        self.write("(");
20476        self.generate_expression(&f.this)?;
20477        self.write(", ");
20478        self.generate_expression(&f.percentile)?;
20479        if let Some(ref acc) = f.accuracy {
20480            self.write(", ");
20481            self.generate_expression(acc)?;
20482        }
20483        self.write(")");
20484        if let Some(ref filter) = f.filter {
20485            self.write_space();
20486            self.write_keyword("FILTER");
20487            self.write("(");
20488            self.write_keyword("WHERE");
20489            self.write_space();
20490            self.generate_expression(filter)?;
20491            self.write(")");
20492        }
20493        Ok(())
20494    }
20495
20496    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20497        self.write_keyword(name);
20498        self.write("(");
20499        self.generate_expression(&f.percentile)?;
20500        self.write(")");
20501        if let Some(ref order_by) = f.order_by {
20502            self.write_space();
20503            self.write_keyword("WITHIN GROUP");
20504            self.write(" (");
20505            self.write_keyword("ORDER BY");
20506            self.write_space();
20507            self.generate_expression(&f.this)?;
20508            for ord in order_by.iter() {
20509                if ord.desc {
20510                    self.write_space();
20511                    self.write_keyword("DESC");
20512                }
20513            }
20514            self.write(")");
20515        }
20516        if let Some(ref filter) = f.filter {
20517            self.write_space();
20518            self.write_keyword("FILTER");
20519            self.write("(");
20520            self.write_keyword("WHERE");
20521            self.write_space();
20522            self.generate_expression(filter)?;
20523            self.write(")");
20524        }
20525        Ok(())
20526    }
20527
20528    // Window function generators
20529
20530    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20531        self.write_keyword("NTILE");
20532        self.write("(");
20533        if let Some(num_buckets) = &f.num_buckets {
20534            self.generate_expression(num_buckets)?;
20535        }
20536        if let Some(order_by) = &f.order_by {
20537            self.write_keyword(" ORDER BY ");
20538            for (i, ob) in order_by.iter().enumerate() {
20539                if i > 0 {
20540                    self.write(", ");
20541                }
20542                self.generate_ordered(ob)?;
20543            }
20544        }
20545        self.write(")");
20546        Ok(())
20547    }
20548
20549    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20550        self.write_keyword(name);
20551        self.write("(");
20552        self.generate_expression(&f.this)?;
20553        if let Some(ref offset) = f.offset {
20554            self.write(", ");
20555            self.generate_expression(offset)?;
20556            if let Some(ref default) = f.default {
20557                self.write(", ");
20558                self.generate_expression(default)?;
20559            }
20560        }
20561        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20562        if self.config.ignore_nulls_in_func {
20563            match f.ignore_nulls {
20564                Some(true) => {
20565                    self.write_space();
20566                    self.write_keyword("IGNORE NULLS");
20567                }
20568                Some(false) => {
20569                    self.write_space();
20570                    self.write_keyword("RESPECT NULLS");
20571                }
20572                None => {}
20573            }
20574        }
20575        self.write(")");
20576        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20577        if !self.config.ignore_nulls_in_func {
20578            match f.ignore_nulls {
20579                Some(true) => {
20580                    self.write_space();
20581                    self.write_keyword("IGNORE NULLS");
20582                }
20583                Some(false) => {
20584                    self.write_space();
20585                    self.write_keyword("RESPECT NULLS");
20586                }
20587                None => {}
20588            }
20589        }
20590        Ok(())
20591    }
20592
20593    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20594        self.write_keyword(name);
20595        self.write("(");
20596        self.generate_expression(&f.this)?;
20597        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20598        if !f.order_by.is_empty() {
20599            self.write_space();
20600            self.write_keyword("ORDER BY");
20601            self.write_space();
20602            for (i, ordered) in f.order_by.iter().enumerate() {
20603                if i > 0 {
20604                    self.write(", ");
20605                }
20606                self.generate_ordered(ordered)?;
20607            }
20608        }
20609        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20610        if self.config.ignore_nulls_in_func {
20611            match f.ignore_nulls {
20612                Some(true) => {
20613                    self.write_space();
20614                    self.write_keyword("IGNORE NULLS");
20615                }
20616                Some(false) => {
20617                    self.write_space();
20618                    self.write_keyword("RESPECT NULLS");
20619                }
20620                None => {}
20621            }
20622        }
20623        self.write(")");
20624        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20625        if !self.config.ignore_nulls_in_func {
20626            match f.ignore_nulls {
20627                Some(true) => {
20628                    self.write_space();
20629                    self.write_keyword("IGNORE NULLS");
20630                }
20631                Some(false) => {
20632                    self.write_space();
20633                    self.write_keyword("RESPECT NULLS");
20634                }
20635                None => {}
20636            }
20637        }
20638        Ok(())
20639    }
20640
20641    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20642    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20643    fn generate_value_func_with_ignore_nulls_bool(
20644        &mut self,
20645        name: &str,
20646        f: &ValueFunc,
20647    ) -> Result<()> {
20648        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20649            self.write_keyword(name);
20650            self.write("(");
20651            self.generate_expression(&f.this)?;
20652            self.write(", ");
20653            self.write_keyword("TRUE");
20654            self.write(")");
20655            return Ok(());
20656        }
20657        self.generate_value_func(name, f)
20658    }
20659
20660    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20661        self.write_keyword("NTH_VALUE");
20662        self.write("(");
20663        self.generate_expression(&f.this)?;
20664        self.write(", ");
20665        self.generate_expression(&f.offset)?;
20666        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20667        if self.config.ignore_nulls_in_func {
20668            match f.ignore_nulls {
20669                Some(true) => {
20670                    self.write_space();
20671                    self.write_keyword("IGNORE NULLS");
20672                }
20673                Some(false) => {
20674                    self.write_space();
20675                    self.write_keyword("RESPECT NULLS");
20676                }
20677                None => {}
20678            }
20679        }
20680        self.write(")");
20681        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20682        if matches!(
20683            self.config.dialect,
20684            Some(crate::dialects::DialectType::Snowflake)
20685        ) {
20686            match f.from_first {
20687                Some(true) => {
20688                    self.write_space();
20689                    self.write_keyword("FROM FIRST");
20690                }
20691                Some(false) => {
20692                    self.write_space();
20693                    self.write_keyword("FROM LAST");
20694                }
20695                None => {}
20696            }
20697        }
20698        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20699        if !self.config.ignore_nulls_in_func {
20700            match f.ignore_nulls {
20701                Some(true) => {
20702                    self.write_space();
20703                    self.write_keyword("IGNORE NULLS");
20704                }
20705                Some(false) => {
20706                    self.write_space();
20707                    self.write_keyword("RESPECT NULLS");
20708                }
20709                None => {}
20710            }
20711        }
20712        Ok(())
20713    }
20714
20715    // Additional string function generators
20716
20717    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20718        // Standard syntax: POSITION(substr IN str)
20719        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20720        if matches!(
20721            self.config.dialect,
20722            Some(crate::dialects::DialectType::ClickHouse)
20723        ) {
20724            self.write_keyword("POSITION");
20725            self.write("(");
20726            self.generate_expression(&f.string)?;
20727            self.write(", ");
20728            self.generate_expression(&f.substring)?;
20729            if let Some(ref start) = f.start {
20730                self.write(", ");
20731                self.generate_expression(start)?;
20732            }
20733            self.write(")");
20734            return Ok(());
20735        }
20736
20737        self.write_keyword("POSITION");
20738        self.write("(");
20739        self.generate_expression(&f.substring)?;
20740        self.write_space();
20741        self.write_keyword("IN");
20742        self.write_space();
20743        self.generate_expression(&f.string)?;
20744        if let Some(ref start) = f.start {
20745            self.write(", ");
20746            self.generate_expression(start)?;
20747        }
20748        self.write(")");
20749        Ok(())
20750    }
20751
20752    // Additional math function generators
20753
20754    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20755        // Teradata RANDOM(lower, upper)
20756        if f.lower.is_some() || f.upper.is_some() {
20757            self.write_keyword("RANDOM");
20758            self.write("(");
20759            if let Some(ref lower) = f.lower {
20760                self.generate_expression(lower)?;
20761            }
20762            if let Some(ref upper) = f.upper {
20763                self.write(", ");
20764                self.generate_expression(upper)?;
20765            }
20766            self.write(")");
20767            return Ok(());
20768        }
20769        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20770        let func_name = match self.config.dialect {
20771            Some(crate::dialects::DialectType::Snowflake)
20772            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20773            _ => "RAND",
20774        };
20775        self.write_keyword(func_name);
20776        self.write("(");
20777        // DuckDB doesn't support seeded RANDOM, so skip the seed
20778        if !matches!(
20779            self.config.dialect,
20780            Some(crate::dialects::DialectType::DuckDB)
20781        ) {
20782            if let Some(ref seed) = f.seed {
20783                self.generate_expression(seed)?;
20784            }
20785        }
20786        self.write(")");
20787        Ok(())
20788    }
20789
20790    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20791        self.write_keyword("TRUNCATE");
20792        self.write("(");
20793        self.generate_expression(&f.this)?;
20794        if let Some(ref decimals) = f.decimals {
20795            self.write(", ");
20796            self.generate_expression(decimals)?;
20797        }
20798        self.write(")");
20799        Ok(())
20800    }
20801
20802    // Control flow generators
20803
20804    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20805        self.write_keyword("DECODE");
20806        self.write("(");
20807        self.generate_expression(&f.this)?;
20808        for (search, result) in &f.search_results {
20809            self.write(", ");
20810            self.generate_expression(search)?;
20811            self.write(", ");
20812            self.generate_expression(result)?;
20813        }
20814        if let Some(ref default) = f.default {
20815            self.write(", ");
20816            self.generate_expression(default)?;
20817        }
20818        self.write(")");
20819        Ok(())
20820    }
20821
20822    // Date/time function generators
20823
20824    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20825        self.write_keyword(name);
20826        self.write("(");
20827        self.generate_expression(&f.this)?;
20828        self.write(", ");
20829        self.generate_expression(&f.format)?;
20830        self.write(")");
20831        Ok(())
20832    }
20833
20834    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20835        self.write_keyword("FROM_UNIXTIME");
20836        self.write("(");
20837        self.generate_expression(&f.this)?;
20838        if let Some(ref format) = f.format {
20839            self.write(", ");
20840            self.generate_expression(format)?;
20841        }
20842        self.write(")");
20843        Ok(())
20844    }
20845
20846    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20847        self.write_keyword("UNIX_TIMESTAMP");
20848        self.write("(");
20849        if let Some(ref expr) = f.this {
20850            self.generate_expression(expr)?;
20851            if let Some(ref format) = f.format {
20852                self.write(", ");
20853                self.generate_expression(format)?;
20854            }
20855        } else if matches!(
20856            self.config.dialect,
20857            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20858        ) {
20859            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20860            self.write_keyword("CURRENT_TIMESTAMP");
20861            self.write("()");
20862        }
20863        self.write(")");
20864        Ok(())
20865    }
20866
20867    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20868        self.write_keyword("MAKE_DATE");
20869        self.write("(");
20870        self.generate_expression(&f.year)?;
20871        self.write(", ");
20872        self.generate_expression(&f.month)?;
20873        self.write(", ");
20874        self.generate_expression(&f.day)?;
20875        self.write(")");
20876        Ok(())
20877    }
20878
20879    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20880        self.write_keyword("MAKE_TIMESTAMP");
20881        self.write("(");
20882        self.generate_expression(&f.year)?;
20883        self.write(", ");
20884        self.generate_expression(&f.month)?;
20885        self.write(", ");
20886        self.generate_expression(&f.day)?;
20887        self.write(", ");
20888        self.generate_expression(&f.hour)?;
20889        self.write(", ");
20890        self.generate_expression(&f.minute)?;
20891        self.write(", ");
20892        self.generate_expression(&f.second)?;
20893        if let Some(ref tz) = f.timezone {
20894            self.write(", ");
20895            self.generate_expression(tz)?;
20896        }
20897        self.write(")");
20898        Ok(())
20899    }
20900
20901    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20902    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20903        match expr {
20904            Expression::Struct(s) => {
20905                if s.fields.iter().all(|(name, _)| name.is_some()) {
20906                    Some(
20907                        s.fields
20908                            .iter()
20909                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20910                            .collect(),
20911                    )
20912                } else {
20913                    None
20914                }
20915            }
20916            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20917                // Check if all args are Alias (named fields)
20918                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20919                    Some(
20920                        f.args
20921                            .iter()
20922                            .filter_map(|a| {
20923                                if let Expression::Alias(alias) = a {
20924                                    Some(alias.alias.name.clone())
20925                                } else {
20926                                    None
20927                                }
20928                            })
20929                            .collect(),
20930                    )
20931                } else {
20932                    None
20933                }
20934            }
20935            _ => None,
20936        }
20937    }
20938
20939    /// Check if a struct expression has any unnamed fields
20940    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20941        match expr {
20942            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20943            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20944                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20945            }
20946            _ => false,
20947        }
20948    }
20949
20950    /// Get the field count of a struct expression
20951    fn struct_field_count(expr: &Expression) -> usize {
20952        match expr {
20953            Expression::Struct(s) => s.fields.len(),
20954            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20955            _ => 0,
20956        }
20957    }
20958
20959    /// Apply field names to an unnamed struct expression, producing a new expression with names
20960    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20961        match expr {
20962            Expression::Struct(s) => {
20963                let mut new_fields = Vec::with_capacity(s.fields.len());
20964                for (i, (name, value)) in s.fields.iter().enumerate() {
20965                    if name.is_none() && i < field_names.len() {
20966                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20967                    } else {
20968                        new_fields.push((name.clone(), value.clone()));
20969                    }
20970                }
20971                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20972            }
20973            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20974                let mut new_args = Vec::with_capacity(f.args.len());
20975                for (i, arg) in f.args.iter().enumerate() {
20976                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20977                        // Wrap the value in an Alias with the inherited name
20978                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20979                            this: arg.clone(),
20980                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20981                            column_aliases: Vec::new(),
20982                            alias_explicit_as: false,
20983                            alias_keyword: None,
20984                            pre_alias_comments: Vec::new(),
20985                            trailing_comments: Vec::new(),
20986                            inferred_type: None,
20987                        })));
20988                    } else {
20989                        new_args.push(arg.clone());
20990                    }
20991                }
20992                Expression::Function(Box::new(crate::expressions::Function {
20993                    name: f.name.clone(),
20994                    args: new_args,
20995                    distinct: f.distinct,
20996                    trailing_comments: f.trailing_comments.clone(),
20997                    use_bracket_syntax: f.use_bracket_syntax,
20998                    no_parens: f.no_parens,
20999                    quoted: f.quoted,
21000                    span: None,
21001                    inferred_type: None,
21002                }))
21003            }
21004            _ => expr.clone(),
21005        }
21006    }
21007
21008    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
21009    /// This implements BigQuery's implicit field name inheritance for struct arrays.
21010    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
21011    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
21012        let first = match expressions.first() {
21013            Some(e) => e,
21014            None => return expressions.to_vec(),
21015        };
21016
21017        let field_names = match Self::extract_struct_field_names(first) {
21018            Some(names) if !names.is_empty() => names,
21019            _ => return expressions.to_vec(),
21020        };
21021
21022        let mut result = Vec::with_capacity(expressions.len());
21023        for (idx, expr) in expressions.iter().enumerate() {
21024            if idx == 0 {
21025                result.push(expr.clone());
21026                continue;
21027            }
21028            // Check if this is a struct with unnamed fields that needs name propagation
21029            if Self::struct_field_count(expr) == field_names.len()
21030                && Self::struct_has_unnamed_fields(expr)
21031            {
21032                result.push(Self::apply_struct_field_names(expr, &field_names));
21033            } else {
21034                result.push(expr.clone());
21035            }
21036        }
21037        result
21038    }
21039
21040    // Array function generators
21041
21042    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
21043        // Apply struct name inheritance for target dialects that need it
21044        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
21045        let needs_inheritance = matches!(
21046            self.config.dialect,
21047            Some(DialectType::DuckDB)
21048                | Some(DialectType::Spark)
21049                | Some(DialectType::Databricks)
21050                | Some(DialectType::Hive)
21051                | Some(DialectType::Snowflake)
21052                | Some(DialectType::Presto)
21053                | Some(DialectType::Trino)
21054        );
21055        let propagated: Vec<Expression>;
21056        let expressions = if needs_inheritance && f.expressions.len() > 1 {
21057            propagated = Self::inherit_struct_field_names(&f.expressions);
21058            &propagated
21059        } else {
21060            &f.expressions
21061        };
21062
21063        // Check if elements should be split onto multiple lines (pretty + too wide)
21064        let should_split = if self.config.pretty && !expressions.is_empty() {
21065            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
21066            for expr in expressions {
21067                let mut temp_gen = Generator::with_arc_config(self.config.clone());
21068                Arc::make_mut(&mut temp_gen.config).pretty = false;
21069                temp_gen.generate_expression(expr)?;
21070                expr_strings.push(temp_gen.output);
21071            }
21072            self.too_wide(&expr_strings)
21073        } else {
21074            false
21075        };
21076
21077        if f.bracket_notation {
21078            // For Spark/Databricks, use ARRAY(...) with parens
21079            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
21080            // For others (DuckDB, Snowflake), use bare [...]
21081            let (open, close) = match self.config.dialect {
21082                None
21083                | Some(DialectType::Generic)
21084                | Some(DialectType::Spark)
21085                | Some(DialectType::Databricks)
21086                | Some(DialectType::Hive) => {
21087                    self.write_keyword("ARRAY");
21088                    ("(", ")")
21089                }
21090                Some(DialectType::Presto)
21091                | Some(DialectType::Trino)
21092                | Some(DialectType::PostgreSQL)
21093                | Some(DialectType::Redshift)
21094                | Some(DialectType::Materialize)
21095                | Some(DialectType::RisingWave)
21096                | Some(DialectType::CockroachDB) => {
21097                    self.write_keyword("ARRAY");
21098                    ("[", "]")
21099                }
21100                _ => ("[", "]"),
21101            };
21102            self.write(open);
21103            if should_split {
21104                self.write_newline();
21105                self.indent_level += 1;
21106                for (i, expr) in expressions.iter().enumerate() {
21107                    self.write_indent();
21108                    self.generate_expression(expr)?;
21109                    if i + 1 < expressions.len() {
21110                        self.write(",");
21111                    }
21112                    self.write_newline();
21113                }
21114                self.indent_level -= 1;
21115                self.write_indent();
21116            } else {
21117                for (i, expr) in expressions.iter().enumerate() {
21118                    if i > 0 {
21119                        self.write(", ");
21120                    }
21121                    self.generate_expression(expr)?;
21122                }
21123            }
21124            self.write(close);
21125        } else {
21126            // Use LIST keyword if that was the original syntax (DuckDB)
21127            if f.use_list_keyword {
21128                self.write_keyword("LIST");
21129            } else {
21130                self.write_keyword("ARRAY");
21131            }
21132            // For Spark/Hive, always use ARRAY(...) with parens
21133            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
21134            let has_subquery = expressions
21135                .iter()
21136                .any(|e| matches!(e, Expression::Select(_)));
21137            let (open, close) = if matches!(
21138                self.config.dialect,
21139                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
21140            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
21141                && has_subquery)
21142            {
21143                ("(", ")")
21144            } else {
21145                ("[", "]")
21146            };
21147            self.write(open);
21148            if should_split {
21149                self.write_newline();
21150                self.indent_level += 1;
21151                for (i, expr) in expressions.iter().enumerate() {
21152                    self.write_indent();
21153                    self.generate_expression(expr)?;
21154                    if i + 1 < expressions.len() {
21155                        self.write(",");
21156                    }
21157                    self.write_newline();
21158                }
21159                self.indent_level -= 1;
21160                self.write_indent();
21161            } else {
21162                for (i, expr) in expressions.iter().enumerate() {
21163                    if i > 0 {
21164                        self.write(", ");
21165                    }
21166                    self.generate_expression(expr)?;
21167                }
21168            }
21169            self.write(close);
21170        }
21171        Ok(())
21172    }
21173
21174    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
21175        self.write_keyword("ARRAY_SORT");
21176        self.write("(");
21177        self.generate_expression(&f.this)?;
21178        if let Some(ref comp) = f.comparator {
21179            self.write(", ");
21180            self.generate_expression(comp)?;
21181        }
21182        self.write(")");
21183        Ok(())
21184    }
21185
21186    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
21187        self.write_keyword(name);
21188        self.write("(");
21189        self.generate_expression(&f.this)?;
21190        self.write(", ");
21191        self.generate_expression(&f.separator)?;
21192        if let Some(ref null_rep) = f.null_replacement {
21193            self.write(", ");
21194            self.generate_expression(null_rep)?;
21195        }
21196        self.write(")");
21197        Ok(())
21198    }
21199
21200    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
21201        self.write_keyword("UNNEST");
21202        self.write("(");
21203        self.generate_expression(&f.this)?;
21204        for extra in &f.expressions {
21205            self.write(", ");
21206            self.generate_expression(extra)?;
21207        }
21208        self.write(")");
21209        if f.with_ordinality {
21210            self.write_space();
21211            if self.config.unnest_with_ordinality {
21212                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
21213                self.write_keyword("WITH ORDINALITY");
21214            } else if f.offset_alias.is_some() {
21215                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
21216                // Alias (if any) comes BEFORE WITH OFFSET
21217                if let Some(ref alias) = f.alias {
21218                    self.write_keyword("AS");
21219                    self.write_space();
21220                    self.generate_identifier(alias)?;
21221                    self.write_space();
21222                }
21223                self.write_keyword("WITH OFFSET");
21224                if let Some(ref offset_alias) = f.offset_alias {
21225                    self.write_space();
21226                    self.write_keyword("AS");
21227                    self.write_space();
21228                    self.generate_identifier(offset_alias)?;
21229                }
21230            } else {
21231                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
21232                self.write_keyword("WITH OFFSET");
21233                if f.alias.is_none() {
21234                    self.write(" AS offset");
21235                }
21236            }
21237        }
21238        if let Some(ref alias) = f.alias {
21239            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
21240            let should_add_alias = if !f.with_ordinality {
21241                true
21242            } else if self.config.unnest_with_ordinality {
21243                // Presto/Trino: alias comes after WITH ORDINALITY
21244                true
21245            } else if f.offset_alias.is_some() {
21246                // BigQuery expansion: alias already handled above
21247                false
21248            } else {
21249                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
21250                true
21251            };
21252            if should_add_alias {
21253                self.write_space();
21254                self.write_keyword("AS");
21255                self.write_space();
21256                self.generate_identifier(alias)?;
21257            }
21258        }
21259        Ok(())
21260    }
21261
21262    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
21263        self.write_keyword("FILTER");
21264        self.write("(");
21265        self.generate_expression(&f.this)?;
21266        self.write(", ");
21267        self.generate_expression(&f.filter)?;
21268        self.write(")");
21269        Ok(())
21270    }
21271
21272    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
21273        self.write_keyword("TRANSFORM");
21274        self.write("(");
21275        self.generate_expression(&f.this)?;
21276        self.write(", ");
21277        self.generate_expression(&f.transform)?;
21278        self.write(")");
21279        Ok(())
21280    }
21281
21282    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
21283        self.write_keyword(name);
21284        self.write("(");
21285        self.generate_expression(&f.start)?;
21286        self.write(", ");
21287        self.generate_expression(&f.stop)?;
21288        if let Some(ref step) = f.step {
21289            self.write(", ");
21290            self.generate_expression(step)?;
21291        }
21292        self.write(")");
21293        Ok(())
21294    }
21295
21296    // Struct function generators
21297
21298    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
21299        self.write_keyword("STRUCT");
21300        self.write("(");
21301        for (i, (name, expr)) in f.fields.iter().enumerate() {
21302            if i > 0 {
21303                self.write(", ");
21304            }
21305            if let Some(ref id) = name {
21306                self.generate_identifier(id)?;
21307                self.write(" ");
21308                self.write_keyword("AS");
21309                self.write(" ");
21310            }
21311            self.generate_expression(expr)?;
21312        }
21313        self.write(")");
21314        Ok(())
21315    }
21316
21317    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
21318    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
21319        // Extract named/unnamed fields from function args
21320        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
21321        let mut names: Vec<Option<String>> = Vec::new();
21322        let mut values: Vec<&Expression> = Vec::new();
21323        let mut all_named = true;
21324
21325        for arg in &func.args {
21326            match arg {
21327                Expression::Alias(a) => {
21328                    names.push(Some(a.alias.name.clone()));
21329                    values.push(&a.this);
21330                }
21331                _ => {
21332                    names.push(None);
21333                    values.push(arg);
21334                    all_named = false;
21335                }
21336            }
21337        }
21338
21339        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21340            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
21341            self.write("{");
21342            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21343                if i > 0 {
21344                    self.write(", ");
21345                }
21346                if let Some(n) = name {
21347                    self.write("'");
21348                    self.write(n);
21349                    self.write("'");
21350                } else {
21351                    self.write("'_");
21352                    self.write(&i.to_string());
21353                    self.write("'");
21354                }
21355                self.write(": ");
21356                self.generate_expression(value)?;
21357            }
21358            self.write("}");
21359            return Ok(());
21360        }
21361
21362        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21363            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
21364            self.write_keyword("OBJECT_CONSTRUCT");
21365            self.write("(");
21366            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21367                if i > 0 {
21368                    self.write(", ");
21369                }
21370                if let Some(n) = name {
21371                    self.write("'");
21372                    self.write(n);
21373                    self.write("'");
21374                } else {
21375                    self.write("'_");
21376                    self.write(&i.to_string());
21377                    self.write("'");
21378                }
21379                self.write(", ");
21380                self.generate_expression(value)?;
21381            }
21382            self.write(")");
21383            return Ok(());
21384        }
21385
21386        if matches!(
21387            self.config.dialect,
21388            Some(DialectType::Presto) | Some(DialectType::Trino)
21389        ) {
21390            if all_named && !names.is_empty() {
21391                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
21392                // Need to infer types from values
21393                self.write_keyword("CAST");
21394                self.write("(");
21395                self.write_keyword("ROW");
21396                self.write("(");
21397                for (i, value) in values.iter().enumerate() {
21398                    if i > 0 {
21399                        self.write(", ");
21400                    }
21401                    self.generate_expression(value)?;
21402                }
21403                self.write(")");
21404                self.write(" ");
21405                self.write_keyword("AS");
21406                self.write(" ");
21407                self.write_keyword("ROW");
21408                self.write("(");
21409                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21410                    if i > 0 {
21411                        self.write(", ");
21412                    }
21413                    if let Some(n) = name {
21414                        self.write(n);
21415                    }
21416                    self.write(" ");
21417                    let type_str = Self::infer_sql_type_for_presto(value);
21418                    self.write_keyword(&type_str);
21419                }
21420                self.write(")");
21421                self.write(")");
21422            } else {
21423                // Unnamed: ROW(values...)
21424                self.write_keyword("ROW");
21425                self.write("(");
21426                for (i, value) in values.iter().enumerate() {
21427                    if i > 0 {
21428                        self.write(", ");
21429                    }
21430                    self.generate_expression(value)?;
21431                }
21432                self.write(")");
21433            }
21434            return Ok(());
21435        }
21436
21437        // Default: ROW(values...) for other dialects
21438        self.write_keyword("ROW");
21439        self.write("(");
21440        for (i, value) in values.iter().enumerate() {
21441            if i > 0 {
21442                self.write(", ");
21443            }
21444            self.generate_expression(value)?;
21445        }
21446        self.write(")");
21447        Ok(())
21448    }
21449
21450    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21451    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21452        match expr {
21453            Expression::Literal(lit)
21454                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21455            {
21456                "VARCHAR".to_string()
21457            }
21458            Expression::Literal(lit)
21459                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21460            {
21461                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21462                    unreachable!()
21463                };
21464                if n.contains('.') {
21465                    "DOUBLE".to_string()
21466                } else {
21467                    "INTEGER".to_string()
21468                }
21469            }
21470            Expression::Boolean(_) => "BOOLEAN".to_string(),
21471            Expression::Literal(lit)
21472                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21473            {
21474                "DATE".to_string()
21475            }
21476            Expression::Literal(lit)
21477                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21478            {
21479                "TIMESTAMP".to_string()
21480            }
21481            Expression::Literal(lit)
21482                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21483            {
21484                "TIMESTAMP".to_string()
21485            }
21486            Expression::Array(_) | Expression::ArrayFunc(_) => {
21487                // Try to infer element type from first element
21488                "ARRAY(VARCHAR)".to_string()
21489            }
21490            // For nested structs - generate a nested ROW type by inspecting fields
21491            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21492            Expression::Function(f) => {
21493                if f.name.eq_ignore_ascii_case("STRUCT") {
21494                    "ROW".to_string()
21495                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21496                    "DATE".to_string()
21497                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21498                    || f.name.eq_ignore_ascii_case("NOW")
21499                {
21500                    "TIMESTAMP".to_string()
21501                } else {
21502                    "VARCHAR".to_string()
21503                }
21504            }
21505            _ => "VARCHAR".to_string(),
21506        }
21507    }
21508
21509    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21510        // DuckDB uses STRUCT_EXTRACT function syntax
21511        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21512            self.write_keyword("STRUCT_EXTRACT");
21513            self.write("(");
21514            self.generate_expression(&f.this)?;
21515            self.write(", ");
21516            // Output field name as string literal
21517            self.write("'");
21518            self.write(&f.field.name);
21519            self.write("'");
21520            self.write(")");
21521            return Ok(());
21522        }
21523        self.generate_expression(&f.this)?;
21524        self.write(".");
21525        self.generate_identifier(&f.field)
21526    }
21527
21528    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21529        if matches!(
21530            self.config.dialect,
21531            Some(DialectType::Spark | DialectType::Databricks)
21532        ) {
21533            self.write_keyword("STRUCT");
21534            self.write("(");
21535            for (i, (name, value)) in f.pairs.iter().enumerate() {
21536                if i > 0 {
21537                    self.write(", ");
21538                }
21539                self.generate_expression(value)?;
21540                self.write(" ");
21541                self.write_keyword("AS");
21542                self.write(" ");
21543                if let Expression::Literal(lit) = name {
21544                    if let Literal::String(field_name) = lit.as_ref() {
21545                        self.generate_identifier(&Identifier::new(field_name))?;
21546                    } else {
21547                        self.generate_expression(name)?;
21548                    }
21549                } else {
21550                    self.generate_expression(name)?;
21551                }
21552            }
21553            self.write(")");
21554            return Ok(());
21555        }
21556
21557        self.write_keyword("NAMED_STRUCT");
21558        self.write("(");
21559        for (i, (name, value)) in f.pairs.iter().enumerate() {
21560            if i > 0 {
21561                self.write(", ");
21562            }
21563            self.generate_expression(name)?;
21564            self.write(", ");
21565            self.generate_expression(value)?;
21566        }
21567        self.write(")");
21568        Ok(())
21569    }
21570
21571    // Map function generators
21572
21573    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21574        if f.curly_brace_syntax {
21575            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21576            if f.with_map_keyword {
21577                self.write_keyword("MAP");
21578                self.write(" ");
21579            }
21580            self.write("{");
21581            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21582                if i > 0 {
21583                    self.write(", ");
21584                }
21585                self.generate_expression(key)?;
21586                self.write(": ");
21587                self.generate_expression(val)?;
21588            }
21589            self.write("}");
21590        } else {
21591            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21592            self.write_keyword("MAP");
21593            self.write("(");
21594            self.write_keyword("ARRAY");
21595            self.write("[");
21596            for (i, key) in f.keys.iter().enumerate() {
21597                if i > 0 {
21598                    self.write(", ");
21599                }
21600                self.generate_expression(key)?;
21601            }
21602            self.write("], ");
21603            self.write_keyword("ARRAY");
21604            self.write("[");
21605            for (i, val) in f.values.iter().enumerate() {
21606                if i > 0 {
21607                    self.write(", ");
21608                }
21609                self.generate_expression(val)?;
21610            }
21611            self.write("])");
21612        }
21613        Ok(())
21614    }
21615
21616    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21617        self.write_keyword(name);
21618        self.write("(");
21619        self.generate_expression(&f.this)?;
21620        self.write(", ");
21621        self.generate_expression(&f.transform)?;
21622        self.write(")");
21623        Ok(())
21624    }
21625
21626    // JSON function generators
21627
21628    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21629        use crate::dialects::DialectType;
21630
21631        // Check if we should use arrow syntax (-> or ->>)
21632        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21633
21634        if use_arrow {
21635            // Output arrow syntax: expr -> path or expr ->> path
21636            self.generate_expression(&f.this)?;
21637            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21638                self.write(" ->> ");
21639            } else {
21640                self.write(" -> ");
21641            }
21642            self.generate_expression(&f.path)?;
21643            return Ok(());
21644        }
21645
21646        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21647        if f.hash_arrow_syntax
21648            && matches!(
21649                self.config.dialect,
21650                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21651            )
21652        {
21653            self.generate_expression(&f.this)?;
21654            self.write(" #>> ");
21655            self.generate_expression(&f.path)?;
21656            return Ok(());
21657        }
21658
21659        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21660        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21661        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21662            match name {
21663                "JSON_EXTRACT_SCALAR"
21664                | "JSON_EXTRACT_PATH_TEXT"
21665                | "JSON_EXTRACT"
21666                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21667                _ => name,
21668            }
21669        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21670            match name {
21671                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21672                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21673                _ => name,
21674            }
21675        } else {
21676            name
21677        };
21678
21679        self.write_keyword(func_name);
21680        self.write("(");
21681        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21682        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21683            if let Expression::Cast(ref cast) = f.this {
21684                if matches!(cast.to, crate::expressions::DataType::Json) {
21685                    self.generate_expression(&cast.this)?;
21686                } else {
21687                    self.generate_expression(&f.this)?;
21688                }
21689            } else {
21690                self.generate_expression(&f.this)?;
21691            }
21692        } else {
21693            self.generate_expression(&f.this)?;
21694        }
21695        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21696        // decompose JSON path into separate string arguments
21697        if matches!(
21698            self.config.dialect,
21699            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21700        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21701        {
21702            if let Expression::Literal(ref lit) = f.path {
21703                if let Literal::String(ref s) = lit.as_ref() {
21704                    let parts = Self::decompose_json_path(s);
21705                    for part in &parts {
21706                        self.write(", '");
21707                        self.write(part);
21708                        self.write("'");
21709                    }
21710                }
21711            } else {
21712                self.write(", ");
21713                self.generate_expression(&f.path)?;
21714            }
21715        } else {
21716            self.write(", ");
21717            self.generate_expression(&f.path)?;
21718        }
21719
21720        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21721        // These go BEFORE the closing parenthesis
21722        if let Some(ref wrapper) = f.wrapper_option {
21723            self.write_space();
21724            self.write_keyword(wrapper);
21725        }
21726        if let Some(ref quotes) = f.quotes_option {
21727            self.write_space();
21728            self.write_keyword(quotes);
21729            if f.on_scalar_string {
21730                self.write_space();
21731                self.write_keyword("ON SCALAR STRING");
21732            }
21733        }
21734        if let Some(ref on_err) = f.on_error {
21735            self.write_space();
21736            self.write_keyword(on_err);
21737        }
21738        if let Some(ref ret_type) = f.returning {
21739            self.write_space();
21740            self.write_keyword("RETURNING");
21741            self.write_space();
21742            self.generate_data_type(ret_type)?;
21743        }
21744
21745        self.write(")");
21746        Ok(())
21747    }
21748
21749    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21750    fn dialect_supports_json_arrow(&self) -> bool {
21751        use crate::dialects::DialectType;
21752        match self.config.dialect {
21753            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21754            Some(DialectType::PostgreSQL) => true,
21755            Some(DialectType::MySQL) => true,
21756            Some(DialectType::DuckDB) => true,
21757            Some(DialectType::CockroachDB) => true,
21758            Some(DialectType::StarRocks) => true,
21759            Some(DialectType::SQLite) => true,
21760            // Other dialects use function syntax
21761            _ => false,
21762        }
21763    }
21764
21765    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21766        use crate::dialects::DialectType;
21767
21768        // PostgreSQL uses #> operator for JSONB path extraction
21769        if matches!(
21770            self.config.dialect,
21771            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21772        ) && name == "JSON_EXTRACT_PATH"
21773        {
21774            self.generate_expression(&f.this)?;
21775            self.write(" #> ");
21776            if f.paths.len() == 1 {
21777                self.generate_expression(&f.paths[0])?;
21778            } else {
21779                // Multiple paths: ARRAY[path1, path2, ...]
21780                self.write_keyword("ARRAY");
21781                self.write("[");
21782                for (i, path) in f.paths.iter().enumerate() {
21783                    if i > 0 {
21784                        self.write(", ");
21785                    }
21786                    self.generate_expression(path)?;
21787                }
21788                self.write("]");
21789            }
21790            return Ok(());
21791        }
21792
21793        self.write_keyword(name);
21794        self.write("(");
21795        self.generate_expression(&f.this)?;
21796        for path in &f.paths {
21797            self.write(", ");
21798            self.generate_expression(path)?;
21799        }
21800        self.write(")");
21801        Ok(())
21802    }
21803
21804    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21805        use crate::dialects::DialectType;
21806
21807        self.write_keyword("JSON_OBJECT");
21808        self.write("(");
21809        if f.star {
21810            self.write("*");
21811        } else {
21812            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21813            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21814            // Also respect the json_key_value_pair_sep config
21815            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21816                || matches!(
21817                    self.config.dialect,
21818                    Some(DialectType::BigQuery)
21819                        | Some(DialectType::MySQL)
21820                        | Some(DialectType::SQLite)
21821                );
21822
21823            for (i, (key, value)) in f.pairs.iter().enumerate() {
21824                if i > 0 {
21825                    self.write(", ");
21826                }
21827                self.generate_expression(key)?;
21828                if use_comma_syntax {
21829                    self.write(", ");
21830                } else {
21831                    self.write(": ");
21832                }
21833                self.generate_expression(value)?;
21834            }
21835        }
21836        if let Some(null_handling) = f.null_handling {
21837            self.write_space();
21838            match null_handling {
21839                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21840                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21841            }
21842        }
21843        if f.with_unique_keys {
21844            self.write_space();
21845            self.write_keyword("WITH UNIQUE KEYS");
21846        }
21847        if let Some(ref ret_type) = f.returning_type {
21848            self.write_space();
21849            self.write_keyword("RETURNING");
21850            self.write_space();
21851            self.generate_data_type(ret_type)?;
21852            if f.format_json {
21853                self.write_space();
21854                self.write_keyword("FORMAT JSON");
21855            }
21856            if let Some(ref enc) = f.encoding {
21857                self.write_space();
21858                self.write_keyword("ENCODING");
21859                self.write_space();
21860                self.write(enc);
21861            }
21862        }
21863        self.write(")");
21864        Ok(())
21865    }
21866
21867    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21868        self.write_keyword(name);
21869        self.write("(");
21870        self.generate_expression(&f.this)?;
21871        for (path, value) in &f.path_values {
21872            self.write(", ");
21873            self.generate_expression(path)?;
21874            self.write(", ");
21875            self.generate_expression(value)?;
21876        }
21877        self.write(")");
21878        Ok(())
21879    }
21880
21881    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21882        self.write_keyword("JSON_ARRAYAGG");
21883        self.write("(");
21884        self.generate_expression(&f.this)?;
21885        if let Some(ref order_by) = f.order_by {
21886            self.write_space();
21887            self.write_keyword("ORDER BY");
21888            self.write_space();
21889            for (i, ord) in order_by.iter().enumerate() {
21890                if i > 0 {
21891                    self.write(", ");
21892                }
21893                self.generate_ordered(ord)?;
21894            }
21895        }
21896        if let Some(null_handling) = f.null_handling {
21897            self.write_space();
21898            match null_handling {
21899                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21900                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21901            }
21902        }
21903        self.write(")");
21904        if let Some(ref filter) = f.filter {
21905            self.write_space();
21906            self.write_keyword("FILTER");
21907            self.write("(");
21908            self.write_keyword("WHERE");
21909            self.write_space();
21910            self.generate_expression(filter)?;
21911            self.write(")");
21912        }
21913        Ok(())
21914    }
21915
21916    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21917        self.write_keyword("JSON_OBJECTAGG");
21918        self.write("(");
21919        self.generate_expression(&f.key)?;
21920        self.write(": ");
21921        self.generate_expression(&f.value)?;
21922        if let Some(null_handling) = f.null_handling {
21923            self.write_space();
21924            match null_handling {
21925                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21926                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21927            }
21928        }
21929        self.write(")");
21930        if let Some(ref filter) = f.filter {
21931            self.write_space();
21932            self.write_keyword("FILTER");
21933            self.write("(");
21934            self.write_keyword("WHERE");
21935            self.write_space();
21936            self.generate_expression(filter)?;
21937            self.write(")");
21938        }
21939        Ok(())
21940    }
21941
21942    // Type casting/conversion generators
21943
21944    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21945        use crate::dialects::DialectType;
21946
21947        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21948        if self.config.dialect == Some(DialectType::Redshift) {
21949            self.write_keyword("CAST");
21950            self.write("(");
21951            self.generate_expression(&f.this)?;
21952            self.write_space();
21953            self.write_keyword("AS");
21954            self.write_space();
21955            self.generate_data_type(&f.to)?;
21956            self.write(")");
21957            return Ok(());
21958        }
21959
21960        self.write_keyword("CONVERT");
21961        self.write("(");
21962        self.generate_data_type(&f.to)?;
21963        self.write(", ");
21964        self.generate_expression(&f.this)?;
21965        if let Some(ref style) = f.style {
21966            self.write(", ");
21967            self.generate_expression(style)?;
21968        }
21969        self.write(")");
21970        Ok(())
21971    }
21972
21973    // Additional expression generators
21974
21975    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21976        if f.colon {
21977            // DuckDB syntax: LAMBDA x : expr
21978            self.write_keyword("LAMBDA");
21979            self.write_space();
21980            for (i, param) in f.parameters.iter().enumerate() {
21981                if i > 0 {
21982                    self.write(", ");
21983                }
21984                self.generate_identifier(param)?;
21985            }
21986            self.write(" : ");
21987        } else {
21988            // Standard syntax: x -> expr or (x, y) -> expr
21989            if f.parameters.len() == 1 {
21990                self.generate_identifier(&f.parameters[0])?;
21991            } else {
21992                self.write("(");
21993                for (i, param) in f.parameters.iter().enumerate() {
21994                    if i > 0 {
21995                        self.write(", ");
21996                    }
21997                    self.generate_identifier(param)?;
21998                }
21999                self.write(")");
22000            }
22001            self.write(" -> ");
22002        }
22003        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22004            if let Expression::Lambda(inner) = &f.body {
22005                self.generate_lambda_with_parenthesized_single_param(inner)?;
22006                return Ok(());
22007            }
22008        }
22009
22010        self.generate_expression(&f.body)
22011    }
22012
22013    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
22014        if f.colon {
22015            return self.generate_lambda(f);
22016        }
22017
22018        self.write("(");
22019        for (i, param) in f.parameters.iter().enumerate() {
22020            if i > 0 {
22021                self.write(", ");
22022            }
22023            self.generate_identifier(param)?;
22024        }
22025        self.write(") -> ");
22026        self.generate_expression(&f.body)
22027    }
22028
22029    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
22030        self.generate_identifier(&f.name)?;
22031        match f.separator {
22032            NamedArgSeparator::DArrow => self.write(" => "),
22033            NamedArgSeparator::ColonEq => self.write(" := "),
22034            NamedArgSeparator::Eq => self.write(" = "),
22035        }
22036        self.generate_expression(&f.value)
22037    }
22038
22039    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
22040        self.write_keyword(&f.prefix);
22041        self.write(" ");
22042        self.generate_expression(&f.this)
22043    }
22044
22045    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
22046        match f.style {
22047            ParameterStyle::Question => self.write("?"),
22048            ParameterStyle::Dollar => {
22049                self.write("$");
22050                if let Some(idx) = f.index {
22051                    self.write(&idx.to_string());
22052                } else if let Some(ref name) = f.name {
22053                    // Session variable like $x or $query_id
22054                    self.write(name);
22055                }
22056            }
22057            ParameterStyle::DollarBrace => {
22058                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
22059                self.write("${");
22060                if let Some(ref name) = f.name {
22061                    self.write(name);
22062                }
22063                if let Some(ref expr) = f.expression {
22064                    self.write(":");
22065                    self.write(expr);
22066                }
22067                self.write("}");
22068            }
22069            ParameterStyle::Colon => {
22070                self.write(":");
22071                if let Some(idx) = f.index {
22072                    self.write(&idx.to_string());
22073                } else if let Some(ref name) = f.name {
22074                    self.write(name);
22075                }
22076            }
22077            ParameterStyle::At => {
22078                self.write("@");
22079                if let Some(ref name) = f.name {
22080                    if f.string_quoted {
22081                        self.write("'");
22082                        self.write(name);
22083                        self.write("'");
22084                    } else if f.quoted {
22085                        self.write("\"");
22086                        self.write(name);
22087                        self.write("\"");
22088                    } else {
22089                        self.write(name);
22090                    }
22091                }
22092            }
22093            ParameterStyle::DoubleAt => {
22094                self.write("@@");
22095                if let Some(ref name) = f.name {
22096                    self.write(name);
22097                }
22098            }
22099            ParameterStyle::DoubleDollar => {
22100                self.write("$$");
22101                if let Some(ref name) = f.name {
22102                    self.write(name);
22103                }
22104            }
22105            ParameterStyle::Percent => {
22106                if let Some(ref name) = f.name {
22107                    // %(name)s format
22108                    self.write("%(");
22109                    self.write(name);
22110                    self.write(")s");
22111                } else {
22112                    // %s format
22113                    self.write("%s");
22114                }
22115            }
22116            ParameterStyle::Brace => {
22117                // Spark/Databricks widget template variable: {name}
22118                // ClickHouse query parameter may include kind: {name: Type}
22119                self.write("{");
22120                if let Some(ref name) = f.name {
22121                    self.write(name);
22122                }
22123                if let Some(ref expr) = f.expression {
22124                    self.write(": ");
22125                    self.write(expr);
22126                }
22127                self.write("}");
22128            }
22129        }
22130        Ok(())
22131    }
22132
22133    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
22134        self.write("?");
22135        if let Some(idx) = f.index {
22136            self.write(&idx.to_string());
22137        }
22138        Ok(())
22139    }
22140
22141    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
22142        if f.is_block {
22143            self.write("/*");
22144            self.write(&f.text);
22145            self.write("*/");
22146        } else {
22147            self.write("--");
22148            self.write(&f.text);
22149        }
22150        Ok(())
22151    }
22152
22153    // Additional predicate generators
22154
22155    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
22156        self.generate_expression(&f.this)?;
22157        if f.not {
22158            self.write_space();
22159            self.write_keyword("NOT");
22160        }
22161        self.write_space();
22162        self.write_keyword("SIMILAR TO");
22163        self.write_space();
22164        self.generate_expression(&f.pattern)?;
22165        if let Some(ref escape) = f.escape {
22166            self.write_space();
22167            self.write_keyword("ESCAPE");
22168            self.write_space();
22169            self.generate_expression(escape)?;
22170        }
22171        Ok(())
22172    }
22173
22174    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
22175        self.generate_expression(&f.this)?;
22176        self.write_space();
22177        // Output comparison operator if present
22178        if let Some(op) = &f.op {
22179            match op {
22180                QuantifiedOp::Eq => self.write("="),
22181                QuantifiedOp::Neq => self.write("<>"),
22182                QuantifiedOp::Lt => self.write("<"),
22183                QuantifiedOp::Lte => self.write("<="),
22184                QuantifiedOp::Gt => self.write(">"),
22185                QuantifiedOp::Gte => self.write(">="),
22186            }
22187            self.write_space();
22188        }
22189        self.write_keyword(name);
22190
22191        // If the child is a Subquery, it provides its own parens — output with space
22192        if matches!(&f.subquery, Expression::Subquery(_)) {
22193            self.write_space();
22194            self.generate_expression(&f.subquery)?;
22195        } else {
22196            let is_statement = matches!(
22197                &f.subquery,
22198                Expression::Select(_)
22199                    | Expression::Union(_)
22200                    | Expression::Intersect(_)
22201                    | Expression::Except(_)
22202            );
22203            if is_statement
22204                && !self.config.quantified_no_paren_space
22205                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
22206            {
22207                self.write_space();
22208            }
22209            self.write("(");
22210
22211            if self.config.pretty && is_statement {
22212                self.write_newline();
22213                self.indent_level += 1;
22214                self.write_indent();
22215            }
22216            self.generate_expression(&f.subquery)?;
22217            if self.config.pretty && is_statement {
22218                self.write_newline();
22219                self.indent_level -= 1;
22220                self.write_indent();
22221            }
22222            self.write(")");
22223        }
22224        Ok(())
22225    }
22226
22227    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
22228        // Check if this is a simple binary form (this OVERLAPS expression)
22229        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
22230            self.generate_expression(this)?;
22231            self.write_space();
22232            self.write_keyword("OVERLAPS");
22233            self.write_space();
22234            self.generate_expression(expr)?;
22235        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
22236            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
22237        {
22238            // Full ANSI form: (a, b) OVERLAPS (c, d)
22239            self.write("(");
22240            self.generate_expression(ls)?;
22241            self.write(", ");
22242            self.generate_expression(le)?;
22243            self.write(")");
22244            self.write_space();
22245            self.write_keyword("OVERLAPS");
22246            self.write_space();
22247            self.write("(");
22248            self.generate_expression(rs)?;
22249            self.write(", ");
22250            self.generate_expression(re)?;
22251            self.write(")");
22252        }
22253        Ok(())
22254    }
22255
22256    // Type conversion generators
22257
22258    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
22259        use crate::dialects::DialectType;
22260
22261        // SingleStore uses !:> syntax for try cast
22262        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22263            self.generate_expression(&cast.this)?;
22264            self.write(" !:> ");
22265            self.generate_data_type(&cast.to)?;
22266            return Ok(());
22267        }
22268
22269        // Teradata uses TRYCAST (no underscore)
22270        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
22271            self.write_keyword("TRYCAST");
22272            self.write("(");
22273            self.generate_expression(&cast.this)?;
22274            self.write_space();
22275            self.write_keyword("AS");
22276            self.write_space();
22277            self.generate_data_type(&cast.to)?;
22278            self.write(")");
22279            return Ok(());
22280        }
22281
22282        // Dialects without TRY_CAST: generate as regular CAST
22283        let keyword = if matches!(
22284            self.config.dialect,
22285            Some(DialectType::Hive)
22286                | Some(DialectType::MySQL)
22287                | Some(DialectType::SQLite)
22288                | Some(DialectType::Oracle)
22289                | Some(DialectType::ClickHouse)
22290                | Some(DialectType::Redshift)
22291                | Some(DialectType::PostgreSQL)
22292                | Some(DialectType::StarRocks)
22293                | Some(DialectType::Doris)
22294        ) {
22295            "CAST"
22296        } else {
22297            "TRY_CAST"
22298        };
22299
22300        self.write_keyword(keyword);
22301        self.write("(");
22302        self.generate_expression(&cast.this)?;
22303        self.write_space();
22304        self.write_keyword("AS");
22305        self.write_space();
22306        self.generate_data_type(&cast.to)?;
22307
22308        // Output FORMAT clause if present
22309        if let Some(format) = &cast.format {
22310            self.write_space();
22311            self.write_keyword("FORMAT");
22312            self.write_space();
22313            self.generate_expression(format)?;
22314        }
22315
22316        self.write(")");
22317        Ok(())
22318    }
22319
22320    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
22321        self.write_keyword("SAFE_CAST");
22322        self.write("(");
22323        self.generate_expression(&cast.this)?;
22324        self.write_space();
22325        self.write_keyword("AS");
22326        self.write_space();
22327        self.generate_data_type(&cast.to)?;
22328
22329        // Output FORMAT clause if present
22330        if let Some(format) = &cast.format {
22331            self.write_space();
22332            self.write_keyword("FORMAT");
22333            self.write_space();
22334            self.generate_expression(format)?;
22335        }
22336
22337        self.write(")");
22338        Ok(())
22339    }
22340
22341    // Array/struct/map access generators
22342
22343    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
22344        // Wrap the base expression in parentheses when it uses arrow syntax (->)
22345        // which has lower precedence than bracket subscript ([]).
22346        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
22347        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
22348        if needs_parens {
22349            self.write("(");
22350        }
22351        self.generate_expression(&s.this)?;
22352        if needs_parens {
22353            self.write(")");
22354        }
22355        self.write("[");
22356        self.generate_expression(&s.index)?;
22357        self.write("]");
22358        Ok(())
22359    }
22360
22361    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
22362        self.generate_expression(&d.this)?;
22363        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
22364        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
22365        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
22366            && matches!(
22367                &d.this,
22368                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22369            );
22370        if use_colon {
22371            self.write(":");
22372        } else {
22373            self.write(".");
22374        }
22375        self.generate_identifier(&d.field)
22376    }
22377
22378    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22379        self.generate_expression(&m.this)?;
22380        self.write(".");
22381        // Method names after a dot should not be quoted based on reserved keywords
22382        // Only quote if explicitly marked as quoted in the AST
22383        if m.method.quoted {
22384            let q = self.config.identifier_quote;
22385            self.write(&format!("{}{}{}", q, m.method.name, q));
22386        } else {
22387            self.write(&m.method.name);
22388        }
22389        self.write("(");
22390        for (i, arg) in m.args.iter().enumerate() {
22391            if i > 0 {
22392                self.write(", ");
22393            }
22394            self.generate_expression(arg)?;
22395        }
22396        self.write(")");
22397        Ok(())
22398    }
22399
22400    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22401        // Check if we need to wrap the inner expression in parentheses
22402        // JSON arrow expressions have lower precedence than array subscript
22403        let needs_parens = matches!(
22404            &s.this,
22405            Expression::JsonExtract(f) if f.arrow_syntax
22406        ) || matches!(
22407            &s.this,
22408            Expression::JsonExtractScalar(f) if f.arrow_syntax
22409        );
22410
22411        if needs_parens {
22412            self.write("(");
22413        }
22414        self.generate_expression(&s.this)?;
22415        if needs_parens {
22416            self.write(")");
22417        }
22418        self.write("[");
22419        if let Some(start) = &s.start {
22420            self.generate_expression(start)?;
22421        }
22422        self.write(":");
22423        if let Some(end) = &s.end {
22424            self.generate_expression(end)?;
22425        }
22426        self.write("]");
22427        Ok(())
22428    }
22429
22430    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22431        // Generate left expression, but skip trailing comments if they're already in left_comments
22432        // to avoid duplication (comments are captured as both expr.trailing_comments
22433        // and BinaryOp.left_comments during parsing)
22434        match &op.left {
22435            Expression::Column(col) => {
22436                // Generate column with trailing comments but skip them if they're
22437                // already captured in BinaryOp.left_comments to avoid duplication
22438                if let Some(table) = &col.table {
22439                    self.generate_identifier(table)?;
22440                    self.write(".");
22441                }
22442                self.generate_identifier(&col.name)?;
22443                // Oracle-style join marker (+)
22444                if col.join_mark && self.config.supports_column_join_marks {
22445                    self.write(" (+)");
22446                }
22447                // Output column trailing comments if they're not already in left_comments
22448                if op.left_comments.is_empty() {
22449                    for comment in &col.trailing_comments {
22450                        self.write_space();
22451                        self.write_formatted_comment(comment);
22452                    }
22453                }
22454            }
22455            Expression::Add(inner_op)
22456            | Expression::Sub(inner_op)
22457            | Expression::Mul(inner_op)
22458            | Expression::Div(inner_op)
22459            | Expression::Concat(inner_op) => {
22460                // Generate binary op without its trailing comments
22461                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22462                    Expression::Add(_) => "+",
22463                    Expression::Sub(_) => "-",
22464                    Expression::Mul(_) => "*",
22465                    Expression::Div(_) => "/",
22466                    Expression::Concat(_) => "||",
22467                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22468                })?;
22469            }
22470            _ => {
22471                self.generate_expression(&op.left)?;
22472            }
22473        }
22474        // Output comments after left operand
22475        for comment in &op.left_comments {
22476            self.write_space();
22477            self.write_formatted_comment(comment);
22478        }
22479        if self.config.pretty
22480            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22481            && (operator == "AND" || operator == "OR")
22482        {
22483            self.write_newline();
22484            self.write_indent();
22485            self.write_keyword(operator);
22486        } else {
22487            self.write_space();
22488            if operator.chars().all(|c| c.is_alphabetic()) {
22489                self.write_keyword(operator);
22490            } else {
22491                self.write(operator);
22492            }
22493        }
22494        // Output comments after operator (before right operand)
22495        for comment in &op.operator_comments {
22496            self.write_space();
22497            self.write_formatted_comment(comment);
22498        }
22499        self.write_space();
22500        self.generate_expression(&op.right)?;
22501        // Output trailing comments after right operand
22502        for comment in &op.trailing_comments {
22503            self.write_space();
22504            self.write_formatted_comment(comment);
22505        }
22506        Ok(())
22507    }
22508
22509    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22510        let keyword = connector.keyword();
22511        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22512            return self.generate_binary_op(op, keyword);
22513        };
22514
22515        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22516            let should_wrap = matches!(connector, ConnectorOperator::Or)
22517                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22518                && matches!(
22519                    generator.config.source_dialect,
22520                    Some(DialectType::ClickHouse)
22521                )
22522                && matches!(term, Expression::And(_));
22523            if should_wrap {
22524                generator.write("(");
22525                generator.generate_expression(term)?;
22526                generator.write(")");
22527            } else {
22528                generator.generate_expression(term)?;
22529            }
22530            Ok(())
22531        };
22532
22533        wrap_clickhouse_or_term(self, terms[0])?;
22534        for term in terms.iter().skip(1) {
22535            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22536                self.write_newline();
22537                self.write_indent();
22538                self.write_keyword(keyword);
22539            } else {
22540                self.write_space();
22541                self.write_keyword(keyword);
22542            }
22543            self.write_space();
22544            wrap_clickhouse_or_term(self, term)?;
22545        }
22546
22547        Ok(())
22548    }
22549
22550    fn flatten_connector_terms<'a>(
22551        &self,
22552        root: &'a BinaryOp,
22553        connector: ConnectorOperator,
22554    ) -> Option<Vec<&'a Expression>> {
22555        if !root.left_comments.is_empty()
22556            || !root.operator_comments.is_empty()
22557            || !root.trailing_comments.is_empty()
22558        {
22559            return None;
22560        }
22561
22562        let mut terms = Vec::new();
22563        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22564
22565        while let Some(expr) = stack.pop() {
22566            match (connector, expr) {
22567                (ConnectorOperator::And, Expression::And(inner))
22568                    if inner.left_comments.is_empty()
22569                        && inner.operator_comments.is_empty()
22570                        && inner.trailing_comments.is_empty() =>
22571                {
22572                    stack.push(&inner.right);
22573                    stack.push(&inner.left);
22574                }
22575                (ConnectorOperator::Or, Expression::Or(inner))
22576                    if inner.left_comments.is_empty()
22577                        && inner.operator_comments.is_empty()
22578                        && inner.trailing_comments.is_empty() =>
22579                {
22580                    stack.push(&inner.right);
22581                    stack.push(&inner.left);
22582                }
22583                _ => terms.push(expr),
22584            }
22585        }
22586
22587        if terms.len() > 1 {
22588            Some(terms)
22589        } else {
22590            None
22591        }
22592    }
22593
22594    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22595    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22596        self.generate_like_op_inner(op, operator, false)
22597    }
22598
22599    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22600        self.generate_like_op_inner(op, operator, true)
22601    }
22602
22603    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
22604        if negated
22605            && matches!(
22606                self.config.dialect,
22607                Some(DialectType::ClickHouse)
22608                    | Some(DialectType::DataFusion)
22609                    | Some(DialectType::TSQL)
22610                    | Some(DialectType::Fabric)
22611            )
22612        {
22613            self.write_keyword("NOT");
22614            self.write_space();
22615            return self.generate_like_op_inner(op, operator, false);
22616        }
22617
22618        self.generate_expression(&op.left)?;
22619        self.write_space();
22620        if negated {
22621            self.write_keyword("NOT");
22622            self.write_space();
22623        }
22624        // Drill backtick-quotes ILIKE
22625        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22626            self.write("`ILIKE`");
22627        } else {
22628            self.write_keyword(operator);
22629        }
22630        if let Some(quantifier) = &op.quantifier {
22631            self.write_space();
22632            self.write_keyword(quantifier);
22633            // Match Python sqlglot behavior:
22634            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22635            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22636            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22637            let is_any =
22638                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22639            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22640                self.write_space();
22641            }
22642        } else {
22643            self.write_space();
22644        }
22645        self.generate_expression(&op.right)?;
22646        if let Some(escape) = &op.escape {
22647            self.write_space();
22648            self.write_keyword("ESCAPE");
22649            self.write_space();
22650            self.generate_expression(escape)?;
22651        }
22652        Ok(())
22653    }
22654
22655    /// Generate null-safe equality
22656    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22657    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22658        use crate::dialects::DialectType;
22659        self.generate_expression(&op.left)?;
22660        self.write_space();
22661        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22662            self.write("<=>");
22663        } else {
22664            self.write_keyword("IS NOT DISTINCT FROM");
22665        }
22666        self.write_space();
22667        self.generate_expression(&op.right)?;
22668        Ok(())
22669    }
22670
22671    /// Generate IS DISTINCT FROM (null-safe inequality)
22672    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22673        self.generate_expression(&op.left)?;
22674        self.write_space();
22675        self.write_keyword("IS DISTINCT FROM");
22676        self.write_space();
22677        self.generate_expression(&op.right)?;
22678        Ok(())
22679    }
22680
22681    /// Generate binary op without trailing comments (used when nested inside another binary op)
22682    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22683        // Generate left expression, but skip trailing comments
22684        match &op.left {
22685            Expression::Column(col) => {
22686                if let Some(table) = &col.table {
22687                    self.generate_identifier(table)?;
22688                    self.write(".");
22689                }
22690                self.generate_identifier(&col.name)?;
22691                // Oracle-style join marker (+)
22692                if col.join_mark && self.config.supports_column_join_marks {
22693                    self.write(" (+)");
22694                }
22695            }
22696            Expression::Add(inner_op)
22697            | Expression::Sub(inner_op)
22698            | Expression::Mul(inner_op)
22699            | Expression::Div(inner_op)
22700            | Expression::Concat(inner_op) => {
22701                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22702                    Expression::Add(_) => "+",
22703                    Expression::Sub(_) => "-",
22704                    Expression::Mul(_) => "*",
22705                    Expression::Div(_) => "/",
22706                    Expression::Concat(_) => "||",
22707                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22708                })?;
22709            }
22710            _ => {
22711                self.generate_expression(&op.left)?;
22712            }
22713        }
22714        // Output left_comments
22715        for comment in &op.left_comments {
22716            self.write_space();
22717            self.write_formatted_comment(comment);
22718        }
22719        self.write_space();
22720        if operator.chars().all(|c| c.is_alphabetic()) {
22721            self.write_keyword(operator);
22722        } else {
22723            self.write(operator);
22724        }
22725        // Output operator_comments
22726        for comment in &op.operator_comments {
22727            self.write_space();
22728            self.write_formatted_comment(comment);
22729        }
22730        self.write_space();
22731        // Generate right expression, but skip trailing comments if it's a Column
22732        // (the parent's left_comments will output them)
22733        match &op.right {
22734            Expression::Column(col) => {
22735                if let Some(table) = &col.table {
22736                    self.generate_identifier(table)?;
22737                    self.write(".");
22738                }
22739                self.generate_identifier(&col.name)?;
22740                // Oracle-style join marker (+)
22741                if col.join_mark && self.config.supports_column_join_marks {
22742                    self.write(" (+)");
22743                }
22744            }
22745            _ => {
22746                self.generate_expression(&op.right)?;
22747            }
22748        }
22749        // Skip trailing_comments - parent will handle them via its left_comments
22750        Ok(())
22751    }
22752
22753    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22754        if operator.chars().all(|c| c.is_alphabetic()) {
22755            self.write_keyword(operator);
22756            self.write_space();
22757        } else {
22758            self.write(operator);
22759            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22760            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22761                self.write_space();
22762            }
22763        }
22764        self.generate_expression(&op.this)
22765    }
22766
22767    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22768        // Generic mode supports two styles for negated IN:
22769        // - Prefix: NOT a IN (...)
22770        // - Infix:  a NOT IN (...)
22771        let is_generic =
22772            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22773        let use_prefix_not =
22774            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22775        if use_prefix_not {
22776            self.write_keyword("NOT");
22777            self.write_space();
22778        }
22779        self.generate_expression(&in_expr.this)?;
22780        if in_expr.global {
22781            self.write_space();
22782            self.write_keyword("GLOBAL");
22783        }
22784        if in_expr.not && !use_prefix_not {
22785            self.write_space();
22786            self.write_keyword("NOT");
22787        }
22788        self.write_space();
22789        self.write_keyword("IN");
22790
22791        // BigQuery: IN UNNEST(expr)
22792        if let Some(unnest_expr) = &in_expr.unnest {
22793            self.write_space();
22794            self.write_keyword("UNNEST");
22795            self.write("(");
22796            self.generate_expression(unnest_expr)?;
22797            self.write(")");
22798            return Ok(());
22799        }
22800
22801        if let Some(query) = &in_expr.query {
22802            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22803            // vs a subquery (col IN (SELECT ...))
22804            let is_bare = in_expr.expressions.is_empty()
22805                && !matches!(
22806                    query,
22807                    Expression::Select(_)
22808                        | Expression::Union(_)
22809                        | Expression::Intersect(_)
22810                        | Expression::Except(_)
22811                        | Expression::Subquery(_)
22812                );
22813            if is_bare {
22814                // Bare identifier: no parentheses
22815                self.write_space();
22816                self.generate_expression(query)?;
22817            } else {
22818                // Subquery: with parentheses
22819                self.write(" (");
22820                let is_statement = matches!(
22821                    query,
22822                    Expression::Select(_)
22823                        | Expression::Union(_)
22824                        | Expression::Intersect(_)
22825                        | Expression::Except(_)
22826                        | Expression::Subquery(_)
22827                );
22828                if self.config.pretty && is_statement {
22829                    self.write_newline();
22830                    self.indent_level += 1;
22831                    self.write_indent();
22832                }
22833                self.generate_expression(query)?;
22834                if self.config.pretty && is_statement {
22835                    self.write_newline();
22836                    self.indent_level -= 1;
22837                    self.write_indent();
22838                }
22839                self.write(")");
22840            }
22841        } else {
22842            // DuckDB: IN without parentheses for single expression that is NOT a literal
22843            // (array/list membership like 'red' IN tbl.flags)
22844            // ClickHouse: IN without parentheses for single non-array expressions
22845            let is_duckdb = matches!(
22846                self.config.dialect,
22847                Some(crate::dialects::DialectType::DuckDB)
22848            );
22849            let is_clickhouse = matches!(
22850                self.config.dialect,
22851                Some(crate::dialects::DialectType::ClickHouse)
22852            );
22853            let single_expr = in_expr.expressions.len() == 1;
22854            if is_clickhouse && single_expr {
22855                if let Expression::Array(arr) = &in_expr.expressions[0] {
22856                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22857                    self.write(" (");
22858                    for (i, expr) in arr.expressions.iter().enumerate() {
22859                        if i > 0 {
22860                            self.write(", ");
22861                        }
22862                        self.generate_expression(expr)?;
22863                    }
22864                    self.write(")");
22865                } else if in_expr.is_field {
22866                    self.write_space();
22867                    self.generate_expression(&in_expr.expressions[0])?;
22868                } else {
22869                    self.write(" (");
22870                    self.generate_expression(&in_expr.expressions[0])?;
22871                    self.write(")");
22872                }
22873            } else {
22874                let is_bare_ref = single_expr
22875                    && matches!(
22876                        &in_expr.expressions[0],
22877                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22878                    );
22879                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22880                    // Bare field reference (no parens in source): IN identifier
22881                    // Also DuckDB: IN without parentheses for array/list membership
22882                    self.write_space();
22883                    self.generate_expression(&in_expr.expressions[0])?;
22884                } else {
22885                    // Standard IN (list)
22886                    self.write(" (");
22887                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22888                        if i > 0 {
22889                            self.write(", ");
22890                        }
22891                        self.generate_expression(expr)?;
22892                    }
22893                    self.write(")");
22894                }
22895            }
22896        }
22897
22898        Ok(())
22899    }
22900
22901    fn generate_between(&mut self, between: &Between) -> Result<()> {
22902        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22903        let use_prefix_not = between.not
22904            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22905        if use_prefix_not {
22906            self.write_keyword("NOT");
22907            self.write_space();
22908        }
22909        self.generate_expression(&between.this)?;
22910        if between.not && !use_prefix_not {
22911            self.write_space();
22912            self.write_keyword("NOT");
22913        }
22914        self.write_space();
22915        self.write_keyword("BETWEEN");
22916        // Emit SYMMETRIC/ASYMMETRIC if present
22917        if let Some(sym) = between.symmetric {
22918            if sym {
22919                self.write(" SYMMETRIC");
22920            } else {
22921                self.write(" ASYMMETRIC");
22922            }
22923        }
22924        self.write_space();
22925        self.generate_expression(&between.low)?;
22926        self.write_space();
22927        self.write_keyword("AND");
22928        self.write_space();
22929        self.generate_expression(&between.high)
22930    }
22931
22932    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22933        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22934        let use_prefix_not = is_null.not
22935            && (self.config.dialect.is_none()
22936                || self.config.dialect == Some(DialectType::Generic)
22937                || is_null.postfix_form);
22938        if use_prefix_not {
22939            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22940            self.write_keyword("NOT");
22941            self.write_space();
22942            self.generate_expression(&is_null.this)?;
22943            self.write_space();
22944            self.write_keyword("IS");
22945            self.write_space();
22946            self.write_keyword("NULL");
22947        } else {
22948            self.generate_expression(&is_null.this)?;
22949            self.write_space();
22950            self.write_keyword("IS");
22951            if is_null.not {
22952                self.write_space();
22953                self.write_keyword("NOT");
22954            }
22955            self.write_space();
22956            self.write_keyword("NULL");
22957        }
22958        Ok(())
22959    }
22960
22961    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22962        self.generate_expression(&is_true.this)?;
22963        self.write_space();
22964        self.write_keyword("IS");
22965        if is_true.not {
22966            self.write_space();
22967            self.write_keyword("NOT");
22968        }
22969        self.write_space();
22970        self.write_keyword("TRUE");
22971        Ok(())
22972    }
22973
22974    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22975        self.generate_expression(&is_false.this)?;
22976        self.write_space();
22977        self.write_keyword("IS");
22978        if is_false.not {
22979            self.write_space();
22980            self.write_keyword("NOT");
22981        }
22982        self.write_space();
22983        self.write_keyword("FALSE");
22984        Ok(())
22985    }
22986
22987    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22988        self.generate_expression(&is_json.this)?;
22989        self.write_space();
22990        self.write_keyword("IS");
22991        if is_json.negated {
22992            self.write_space();
22993            self.write_keyword("NOT");
22994        }
22995        self.write_space();
22996        self.write_keyword("JSON");
22997
22998        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22999        if let Some(ref json_type) = is_json.json_type {
23000            self.write_space();
23001            self.write_keyword(json_type);
23002        }
23003
23004        // Output key uniqueness constraint if specified
23005        match &is_json.unique_keys {
23006            Some(JsonUniqueKeys::With) => {
23007                self.write_space();
23008                self.write_keyword("WITH UNIQUE KEYS");
23009            }
23010            Some(JsonUniqueKeys::Without) => {
23011                self.write_space();
23012                self.write_keyword("WITHOUT UNIQUE KEYS");
23013            }
23014            Some(JsonUniqueKeys::Shorthand) => {
23015                self.write_space();
23016                self.write_keyword("UNIQUE KEYS");
23017            }
23018            None => {}
23019        }
23020
23021        Ok(())
23022    }
23023
23024    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
23025        self.generate_expression(&is_expr.left)?;
23026        self.write_space();
23027        self.write_keyword("IS");
23028        self.write_space();
23029        self.generate_expression(&is_expr.right)
23030    }
23031
23032    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
23033        if exists.not {
23034            self.write_keyword("NOT");
23035            self.write_space();
23036        }
23037        self.write_keyword("EXISTS");
23038        self.write("(");
23039        let is_statement = matches!(
23040            &exists.this,
23041            Expression::Select(_)
23042                | Expression::Union(_)
23043                | Expression::Intersect(_)
23044                | Expression::Except(_)
23045        );
23046        if self.config.pretty && is_statement {
23047            self.write_newline();
23048            self.indent_level += 1;
23049            self.write_indent();
23050            self.generate_expression(&exists.this)?;
23051            self.write_newline();
23052            self.indent_level -= 1;
23053            self.write_indent();
23054            self.write(")");
23055        } else {
23056            self.generate_expression(&exists.this)?;
23057            self.write(")");
23058        }
23059        Ok(())
23060    }
23061
23062    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
23063        self.generate_expression(&op.left)?;
23064        self.write_space();
23065        self.write_keyword("MEMBER OF");
23066        self.write("(");
23067        self.generate_expression(&op.right)?;
23068        self.write(")");
23069        Ok(())
23070    }
23071
23072    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
23073        if subquery.lateral {
23074            self.write_keyword("LATERAL");
23075            self.write_space();
23076        }
23077
23078        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
23079        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
23080        // to carry the LIMIT modifier without adding more parens
23081        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
23082            matches!(
23083                &p.this,
23084                Expression::Select(_)
23085                    | Expression::Union(_)
23086                    | Expression::Intersect(_)
23087                    | Expression::Except(_)
23088                    | Expression::Subquery(_)
23089            )
23090        } else {
23091            false
23092        };
23093
23094        // Check if inner expression is a statement for pretty formatting
23095        let is_statement = matches!(
23096            &subquery.this,
23097            Expression::Select(_)
23098                | Expression::Union(_)
23099                | Expression::Intersect(_)
23100                | Expression::Except(_)
23101                | Expression::Merge(_)
23102        );
23103
23104        if !skip_outer_parens {
23105            self.write("(");
23106            if self.config.pretty && is_statement {
23107                self.write_newline();
23108                self.indent_level += 1;
23109                self.write_indent();
23110            }
23111        }
23112        self.generate_expression(&subquery.this)?;
23113
23114        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
23115        if subquery.modifiers_inside {
23116            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
23117            if let Some(order_by) = &subquery.order_by {
23118                self.write_space();
23119                self.write_keyword("ORDER BY");
23120                self.write_space();
23121                for (i, ord) in order_by.expressions.iter().enumerate() {
23122                    if i > 0 {
23123                        self.write(", ");
23124                    }
23125                    self.generate_ordered(ord)?;
23126                }
23127            }
23128
23129            if let Some(limit) = &subquery.limit {
23130                self.write_space();
23131                self.write_keyword("LIMIT");
23132                self.write_space();
23133                self.generate_expression(&limit.this)?;
23134                if limit.percent {
23135                    self.write_space();
23136                    self.write_keyword("PERCENT");
23137                }
23138            }
23139
23140            if let Some(offset) = &subquery.offset {
23141                self.write_space();
23142                self.write_keyword("OFFSET");
23143                self.write_space();
23144                self.generate_expression(&offset.this)?;
23145            }
23146        }
23147
23148        if !skip_outer_parens {
23149            if self.config.pretty && is_statement {
23150                self.write_newline();
23151                self.indent_level -= 1;
23152                self.write_indent();
23153            }
23154            self.write(")");
23155        }
23156
23157        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
23158        if !subquery.modifiers_inside {
23159            if let Some(order_by) = &subquery.order_by {
23160                self.write_space();
23161                self.write_keyword("ORDER BY");
23162                self.write_space();
23163                for (i, ord) in order_by.expressions.iter().enumerate() {
23164                    if i > 0 {
23165                        self.write(", ");
23166                    }
23167                    self.generate_ordered(ord)?;
23168                }
23169            }
23170
23171            if let Some(limit) = &subquery.limit {
23172                self.write_space();
23173                self.write_keyword("LIMIT");
23174                self.write_space();
23175                self.generate_expression(&limit.this)?;
23176                if limit.percent {
23177                    self.write_space();
23178                    self.write_keyword("PERCENT");
23179                }
23180            }
23181
23182            if let Some(offset) = &subquery.offset {
23183                self.write_space();
23184                self.write_keyword("OFFSET");
23185                self.write_space();
23186                self.generate_expression(&offset.this)?;
23187            }
23188
23189            // Generate DISTRIBUTE BY (Hive/Spark)
23190            if let Some(distribute_by) = &subquery.distribute_by {
23191                self.write_space();
23192                self.write_keyword("DISTRIBUTE BY");
23193                self.write_space();
23194                for (i, expr) in distribute_by.expressions.iter().enumerate() {
23195                    if i > 0 {
23196                        self.write(", ");
23197                    }
23198                    self.generate_expression(expr)?;
23199                }
23200            }
23201
23202            // Generate SORT BY (Hive/Spark)
23203            if let Some(sort_by) = &subquery.sort_by {
23204                self.write_space();
23205                self.write_keyword("SORT BY");
23206                self.write_space();
23207                for (i, ord) in sort_by.expressions.iter().enumerate() {
23208                    if i > 0 {
23209                        self.write(", ");
23210                    }
23211                    self.generate_ordered(ord)?;
23212                }
23213            }
23214
23215            // Generate CLUSTER BY (Hive/Spark)
23216            if let Some(cluster_by) = &subquery.cluster_by {
23217                self.write_space();
23218                self.write_keyword("CLUSTER BY");
23219                self.write_space();
23220                for (i, ord) in cluster_by.expressions.iter().enumerate() {
23221                    if i > 0 {
23222                        self.write(", ");
23223                    }
23224                    self.generate_ordered(ord)?;
23225                }
23226            }
23227        }
23228
23229        if let Some(alias) = &subquery.alias {
23230            self.write_space();
23231            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
23232                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
23233                    && !subquery.alias_explicit_as);
23234            if !skip_as {
23235                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23236                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
23237                } else {
23238                    self.write_keyword("AS");
23239                }
23240                self.write_space();
23241            }
23242            self.generate_identifier(alias)?;
23243            if !subquery.column_aliases.is_empty() {
23244                self.write("(");
23245                for (i, col) in subquery.column_aliases.iter().enumerate() {
23246                    if i > 0 {
23247                        self.write(", ");
23248                    }
23249                    self.generate_identifier(col)?;
23250                }
23251                self.write(")");
23252            }
23253        }
23254        // Output trailing comments
23255        for comment in &subquery.trailing_comments {
23256            self.write(" ");
23257            self.write_formatted_comment(comment);
23258        }
23259        Ok(())
23260    }
23261
23262    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
23263        // Generate WITH clause if present
23264        if let Some(ref with) = pivot.with {
23265            self.generate_with(with)?;
23266            self.write_space();
23267        }
23268
23269        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
23270
23271        // Check for Redshift UNPIVOT in FROM clause:
23272        // UNPIVOT expr [AS val AT attr]
23273        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
23274        let is_redshift_unpivot = pivot.unpivot
23275            && pivot.expressions.is_empty()
23276            && pivot.fields.is_empty()
23277            && pivot.using.is_empty()
23278            && pivot.into.is_none()
23279            && !matches!(&pivot.this, Expression::Null(_));
23280
23281        if is_redshift_unpivot {
23282            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
23283            self.write_keyword("UNPIVOT");
23284            self.write_space();
23285            self.generate_expression(&pivot.this)?;
23286            // Alias - for Redshift it can be "val AT attr" format
23287            if let Some(alias) = &pivot.alias {
23288                self.write_space();
23289                self.write_keyword("AS");
23290                self.write_space();
23291                // The alias might contain " AT " for the attr part
23292                self.write(&alias.name);
23293            }
23294            return Ok(());
23295        }
23296
23297        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
23298        let is_simplified = !pivot.using.is_empty()
23299            || pivot.into.is_some()
23300            || (pivot.fields.is_empty()
23301                && !pivot.expressions.is_empty()
23302                && !matches!(&pivot.this, Expression::Null(_)));
23303
23304        if is_simplified {
23305            // DuckDB simplified syntax:
23306            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
23307            //   UNPIVOT table ON cols INTO NAME col VALUE col
23308            self.write_keyword(direction);
23309            self.write_space();
23310            self.generate_expression(&pivot.this)?;
23311
23312            if !pivot.expressions.is_empty() {
23313                self.write_space();
23314                self.write_keyword("ON");
23315                self.write_space();
23316                for (i, expr) in pivot.expressions.iter().enumerate() {
23317                    if i > 0 {
23318                        self.write(", ");
23319                    }
23320                    self.generate_expression(expr)?;
23321                }
23322            }
23323
23324            // INTO (for UNPIVOT)
23325            if let Some(into) = &pivot.into {
23326                self.write_space();
23327                self.write_keyword("INTO");
23328                self.write_space();
23329                self.generate_expression(into)?;
23330            }
23331
23332            // USING (for PIVOT)
23333            if !pivot.using.is_empty() {
23334                self.write_space();
23335                self.write_keyword("USING");
23336                self.write_space();
23337                for (i, expr) in pivot.using.iter().enumerate() {
23338                    if i > 0 {
23339                        self.write(", ");
23340                    }
23341                    self.generate_expression(expr)?;
23342                }
23343            }
23344
23345            // GROUP BY
23346            if let Some(group) = &pivot.group {
23347                self.write_space();
23348                self.generate_expression(group)?;
23349            }
23350        } else {
23351            // Standard syntax:
23352            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
23353            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
23354            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
23355            if !matches!(&pivot.this, Expression::Null(_)) {
23356                self.generate_expression(&pivot.this)?;
23357                self.write_space();
23358            }
23359            self.write_keyword(direction);
23360            self.write("(");
23361
23362            // Aggregation expressions
23363            for (i, expr) in pivot.expressions.iter().enumerate() {
23364                if i > 0 {
23365                    self.write(", ");
23366                }
23367                self.generate_expression(expr)?;
23368            }
23369
23370            // FOR...IN fields
23371            if !pivot.fields.is_empty() {
23372                if !pivot.expressions.is_empty() {
23373                    self.write_space();
23374                }
23375                self.write_keyword("FOR");
23376                self.write_space();
23377                for (i, field) in pivot.fields.iter().enumerate() {
23378                    if i > 0 {
23379                        self.write_space();
23380                    }
23381                    // field is an In expression: column IN (values)
23382                    self.generate_expression(field)?;
23383                }
23384            }
23385
23386            // DEFAULT ON NULL
23387            if let Some(default_val) = &pivot.default_on_null {
23388                self.write_space();
23389                self.write_keyword("DEFAULT ON NULL");
23390                self.write(" (");
23391                self.generate_expression(default_val)?;
23392                self.write(")");
23393            }
23394
23395            // GROUP BY inside PIVOT parens
23396            if let Some(group) = &pivot.group {
23397                self.write_space();
23398                self.generate_expression(group)?;
23399            }
23400
23401            self.write(")");
23402        }
23403
23404        // Alias
23405        if let Some(alias) = &pivot.alias {
23406            self.write_space();
23407            self.write_keyword("AS");
23408            self.write_space();
23409            self.generate_identifier(alias)?;
23410            self.generate_alias_column_list(&pivot.alias_columns)?;
23411        }
23412
23413        Ok(())
23414    }
23415
23416    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23417        self.generate_expression(&unpivot.this)?;
23418        self.write_space();
23419        self.write_keyword("UNPIVOT");
23420        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23421        if let Some(include) = unpivot.include_nulls {
23422            self.write_space();
23423            if include {
23424                self.write_keyword("INCLUDE NULLS");
23425            } else {
23426                self.write_keyword("EXCLUDE NULLS");
23427            }
23428            self.write_space();
23429        }
23430        self.write("(");
23431        if unpivot.value_column_parenthesized {
23432            self.write("(");
23433        }
23434        self.generate_identifier(&unpivot.value_column)?;
23435        // Output additional value columns if present
23436        for extra_col in &unpivot.extra_value_columns {
23437            self.write(", ");
23438            self.generate_identifier(extra_col)?;
23439        }
23440        if unpivot.value_column_parenthesized {
23441            self.write(")");
23442        }
23443        self.write_space();
23444        self.write_keyword("FOR");
23445        self.write_space();
23446        self.generate_identifier(&unpivot.name_column)?;
23447        self.write_space();
23448        self.write_keyword("IN");
23449        self.write(" (");
23450        for (i, col) in unpivot.columns.iter().enumerate() {
23451            if i > 0 {
23452                self.write(", ");
23453            }
23454            self.generate_expression(col)?;
23455        }
23456        self.write("))");
23457        if let Some(alias) = &unpivot.alias {
23458            self.write_space();
23459            self.write_keyword("AS");
23460            self.write_space();
23461            self.generate_identifier(alias)?;
23462            self.generate_alias_column_list(&unpivot.alias_columns)?;
23463        }
23464        Ok(())
23465    }
23466
23467    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
23468        if columns.is_empty() {
23469            return Ok(());
23470        }
23471
23472        self.write("(");
23473        for (i, column) in columns.iter().enumerate() {
23474            if i > 0 {
23475                self.write(", ");
23476            }
23477            self.generate_identifier(column)?;
23478        }
23479        self.write(")");
23480        Ok(())
23481    }
23482
23483    fn generate_values(&mut self, values: &Values) -> Result<()> {
23484        self.write_keyword("VALUES");
23485        for (i, row) in values.expressions.iter().enumerate() {
23486            if i > 0 {
23487                self.write(",");
23488            }
23489            self.write(" (");
23490            for (j, expr) in row.expressions.iter().enumerate() {
23491                if j > 0 {
23492                    self.write(", ");
23493                }
23494                self.generate_expression(expr)?;
23495            }
23496            self.write(")");
23497        }
23498        if let Some(alias) = &values.alias {
23499            self.write_space();
23500            self.write_keyword("AS");
23501            self.write_space();
23502            self.generate_identifier(alias)?;
23503            if !values.column_aliases.is_empty() {
23504                self.write("(");
23505                for (i, col) in values.column_aliases.iter().enumerate() {
23506                    if i > 0 {
23507                        self.write(", ");
23508                    }
23509                    self.generate_identifier(col)?;
23510                }
23511                self.write(")");
23512            }
23513        }
23514        Ok(())
23515    }
23516
23517    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23518        // Apply struct name inheritance for target dialects that need it
23519        let needs_inheritance = matches!(
23520            self.config.dialect,
23521            Some(DialectType::DuckDB)
23522                | Some(DialectType::Spark)
23523                | Some(DialectType::Databricks)
23524                | Some(DialectType::Hive)
23525                | Some(DialectType::Snowflake)
23526                | Some(DialectType::Presto)
23527                | Some(DialectType::Trino)
23528        );
23529        let propagated: Vec<Expression>;
23530        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23531            propagated = Self::inherit_struct_field_names(&arr.expressions);
23532            &propagated
23533        } else {
23534            &arr.expressions
23535        };
23536
23537        // Generic mode: ARRAY(1, 2, 3) with parentheses
23538        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23539        let use_parens =
23540            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23541        if !self.config.array_bracket_only {
23542            self.write_keyword("ARRAY");
23543        }
23544        if use_parens {
23545            self.write("(");
23546        } else {
23547            self.write("[");
23548        }
23549        for (i, expr) in expressions.iter().enumerate() {
23550            if i > 0 {
23551                self.write(", ");
23552            }
23553            self.generate_expression(expr)?;
23554        }
23555        if use_parens {
23556            self.write(")");
23557        } else {
23558            self.write("]");
23559        }
23560        Ok(())
23561    }
23562
23563    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23564        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23565        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23566        if tuple.expressions.len() == 2 {
23567            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23568                // First element is the function/expression, second is the TableAlias
23569                self.generate_expression(&tuple.expressions[0])?;
23570                self.write_space();
23571                self.write_keyword("AS");
23572                self.write_space();
23573                self.generate_expression(&tuple.expressions[1])?;
23574                return Ok(());
23575            }
23576        }
23577
23578        // In pretty mode, format long tuples with each element on a new line
23579        // Only expand if total width exceeds threshold
23580        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23581            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23582            for expr in &tuple.expressions {
23583                expr_strings.push(self.generate_to_string(expr)?);
23584            }
23585            self.too_wide(&expr_strings)
23586        } else {
23587            false
23588        };
23589
23590        if expand_tuple {
23591            self.write("(");
23592            self.write_newline();
23593            self.indent_level += 1;
23594            for (i, expr) in tuple.expressions.iter().enumerate() {
23595                if i > 0 {
23596                    self.write(",");
23597                    self.write_newline();
23598                }
23599                self.write_indent();
23600                self.generate_expression(expr)?;
23601            }
23602            self.indent_level -= 1;
23603            self.write_newline();
23604            self.write_indent();
23605            self.write(")");
23606        } else {
23607            self.write("(");
23608            for (i, expr) in tuple.expressions.iter().enumerate() {
23609                if i > 0 {
23610                    self.write(", ");
23611                }
23612                self.generate_expression(expr)?;
23613            }
23614            self.write(")");
23615        }
23616        Ok(())
23617    }
23618
23619    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23620        self.generate_expression(&pipe.this)?;
23621        self.write(" |> ");
23622        self.generate_expression(&pipe.expression)?;
23623        Ok(())
23624    }
23625
23626    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23627        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
23628            && !self.config.null_ordering_supported
23629            && matches!(
23630                self.config.dialect,
23631                Some(DialectType::TSQL) | Some(DialectType::Fabric)
23632            );
23633        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
23634        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
23635            let target_default_nulls_first = !ordered.desc;
23636
23637            unsupported_tsql_null_ordering
23638                && nulls_first != target_default_nulls_first
23639                && !random_ordering
23640        } else {
23641            false
23642        };
23643
23644        if emulate_tsql_null_ordering {
23645            self.write_keyword("CASE WHEN");
23646            self.write_space();
23647            self.generate_expression(&ordered.this)?;
23648            self.write_space();
23649            self.write_keyword("IS NULL THEN 1 ELSE 0 END");
23650            if ordered.nulls_first == Some(true) {
23651                self.write_space();
23652                self.write_keyword("DESC");
23653            }
23654            self.write(", ");
23655        }
23656
23657        self.generate_expression(&ordered.this)?;
23658        if ordered.desc {
23659            self.write_space();
23660            self.write_keyword("DESC");
23661        } else if ordered.explicit_asc {
23662            self.write_space();
23663            self.write_keyword("ASC");
23664        }
23665        if let Some(nulls_first) = ordered.nulls_first {
23666            if !unsupported_tsql_null_ordering
23667                && (self.config.null_ordering_supported
23668                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
23669            {
23670                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23671                // for the dialect. Different dialects have different NULL ordering defaults:
23672                //
23673                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23674                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23675                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23676                //
23677                // nulls_are_small (Spark, Hive, BigQuery, most others):
23678                //   - ASC: NULLS FIRST is default
23679                //   - DESC: NULLS LAST is default
23680                //
23681                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23682                //   - NULLS LAST is always the default regardless of sort direction
23683                let is_asc = !ordered.desc;
23684                let is_nulls_are_large = matches!(
23685                    self.config.dialect,
23686                    Some(DialectType::Oracle)
23687                        | Some(DialectType::PostgreSQL)
23688                        | Some(DialectType::Redshift)
23689                        | Some(DialectType::Snowflake)
23690                );
23691                let is_nulls_are_last = matches!(
23692                    self.config.dialect,
23693                    Some(DialectType::Dremio)
23694                        | Some(DialectType::DuckDB)
23695                        | Some(DialectType::Presto)
23696                        | Some(DialectType::Trino)
23697                        | Some(DialectType::Athena)
23698                        | Some(DialectType::ClickHouse)
23699                        | Some(DialectType::Drill)
23700                        | Some(DialectType::Exasol)
23701                );
23702
23703                // Check if the NULLS ordering matches the default for this dialect
23704                let is_default_nulls = if is_nulls_are_large {
23705                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23706                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23707                } else if is_nulls_are_last {
23708                    // For nulls_are_last: NULLS LAST is always default
23709                    !nulls_first
23710                } else {
23711                    false
23712                };
23713
23714                if !is_default_nulls {
23715                    self.write_space();
23716                    self.write_keyword("NULLS");
23717                    self.write_space();
23718                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23719                }
23720            }
23721        }
23722        // WITH FILL clause (ClickHouse)
23723        if let Some(ref with_fill) = ordered.with_fill {
23724            self.write_space();
23725            self.generate_with_fill(with_fill)?;
23726        }
23727        Ok(())
23728    }
23729
23730    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23731    fn write_clickhouse_type(&mut self, type_str: &str) {
23732        if self.clickhouse_nullable_depth < 0 {
23733            // Map key context: don't wrap in Nullable
23734            self.write(type_str);
23735        } else {
23736            self.write(&format!("Nullable({})", type_str));
23737        }
23738    }
23739
23740    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23741        use crate::dialects::DialectType;
23742
23743        match dt {
23744            DataType::Boolean => {
23745                // Dialect-specific boolean type mappings
23746                match self.config.dialect {
23747                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23748                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23749                    Some(DialectType::Oracle) => {
23750                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23751                        self.write_keyword("NUMBER(1)")
23752                    }
23753                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23754                    _ => self.write_keyword("BOOLEAN"),
23755                }
23756            }
23757            DataType::TinyInt { length } => {
23758                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23759                // Dremio maps TINYINT to INT
23760                // ClickHouse maps TINYINT to Int8
23761                match self.config.dialect {
23762                    Some(DialectType::PostgreSQL)
23763                    | Some(DialectType::Redshift)
23764                    | Some(DialectType::Oracle)
23765                    | Some(DialectType::Exasol) => {
23766                        self.write_keyword("SMALLINT");
23767                    }
23768                    Some(DialectType::Teradata) => {
23769                        // Teradata uses BYTEINT for smallest integer
23770                        self.write_keyword("BYTEINT");
23771                    }
23772                    Some(DialectType::Dremio) => {
23773                        // Dremio maps TINYINT to INT
23774                        self.write_keyword("INT");
23775                    }
23776                    Some(DialectType::ClickHouse) => {
23777                        self.write_clickhouse_type("Int8");
23778                    }
23779                    _ => {
23780                        self.write_keyword("TINYINT");
23781                    }
23782                }
23783                if let Some(n) = length {
23784                    if !matches!(
23785                        self.config.dialect,
23786                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23787                    ) {
23788                        self.write(&format!("({})", n));
23789                    }
23790                }
23791            }
23792            DataType::SmallInt { length } => {
23793                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23794                match self.config.dialect {
23795                    Some(DialectType::Dremio) => {
23796                        self.write_keyword("INT");
23797                    }
23798                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23799                        self.write_keyword("INTEGER");
23800                    }
23801                    Some(DialectType::BigQuery) => {
23802                        self.write_keyword("INT64");
23803                    }
23804                    Some(DialectType::ClickHouse) => {
23805                        self.write_clickhouse_type("Int16");
23806                    }
23807                    _ => {
23808                        self.write_keyword("SMALLINT");
23809                        if let Some(n) = length {
23810                            self.write(&format!("({})", n));
23811                        }
23812                    }
23813                }
23814            }
23815            DataType::Int {
23816                length,
23817                integer_spelling: _,
23818            } => {
23819                // BigQuery uses INT64 for INT
23820                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23821                    self.write_keyword("INT64");
23822                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23823                    self.write_clickhouse_type("Int32");
23824                } else {
23825                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23826                    let use_integer = match self.config.dialect {
23827                        Some(DialectType::TSQL)
23828                        | Some(DialectType::Fabric)
23829                        | Some(DialectType::Presto)
23830                        | Some(DialectType::Trino)
23831                        | Some(DialectType::SQLite)
23832                        | Some(DialectType::Redshift) => true,
23833                        _ => false,
23834                    };
23835                    if use_integer {
23836                        self.write_keyword("INTEGER");
23837                    } else {
23838                        self.write_keyword("INT");
23839                    }
23840                    if let Some(n) = length {
23841                        self.write(&format!("({})", n));
23842                    }
23843                }
23844            }
23845            DataType::BigInt { length } => {
23846                // Dialect-specific bigint type mappings
23847                match self.config.dialect {
23848                    Some(DialectType::Oracle) => {
23849                        // Oracle doesn't have BIGINT, uses INT
23850                        self.write_keyword("INT");
23851                    }
23852                    Some(DialectType::ClickHouse) => {
23853                        self.write_clickhouse_type("Int64");
23854                    }
23855                    _ => {
23856                        self.write_keyword("BIGINT");
23857                        if let Some(n) = length {
23858                            self.write(&format!("({})", n));
23859                        }
23860                    }
23861                }
23862            }
23863            DataType::Float {
23864                precision,
23865                scale,
23866                real_spelling,
23867            } => {
23868                // Dialect-specific float type mappings
23869                // If real_spelling is true, preserve REAL; otherwise use dialect default
23870                // Spark/Hive don't support REAL, always use FLOAT
23871                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23872                    self.write_clickhouse_type("Float32");
23873                } else if *real_spelling
23874                    && !matches!(
23875                        self.config.dialect,
23876                        Some(DialectType::Spark)
23877                            | Some(DialectType::Databricks)
23878                            | Some(DialectType::Hive)
23879                            | Some(DialectType::Snowflake)
23880                            | Some(DialectType::MySQL)
23881                            | Some(DialectType::BigQuery)
23882                    )
23883                {
23884                    self.write_keyword("REAL")
23885                } else {
23886                    match self.config.dialect {
23887                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23888                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23889                        _ => self.write_keyword("FLOAT"),
23890                    }
23891                }
23892                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23893                // Spark/Hive don't support FLOAT(precision)
23894                if !matches!(
23895                    self.config.dialect,
23896                    Some(DialectType::Spark)
23897                        | Some(DialectType::Databricks)
23898                        | Some(DialectType::Hive)
23899                        | Some(DialectType::Presto)
23900                        | Some(DialectType::Trino)
23901                ) {
23902                    if let Some(p) = precision {
23903                        self.write(&format!("({}", p));
23904                        if let Some(s) = scale {
23905                            self.write(&format!(", {})", s));
23906                        } else {
23907                            self.write(")");
23908                        }
23909                    }
23910                }
23911            }
23912            DataType::Double { precision, scale } => {
23913                // Dialect-specific double type mappings
23914                match self.config.dialect {
23915                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23916                        self.write_keyword("FLOAT")
23917                    } // SQL Server/Fabric FLOAT is double
23918                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23919                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23920                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23921                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23922                    Some(DialectType::PostgreSQL)
23923                    | Some(DialectType::Redshift)
23924                    | Some(DialectType::Teradata)
23925                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23926                    _ => self.write_keyword("DOUBLE"),
23927                }
23928                // MySQL supports DOUBLE(precision, scale)
23929                if let Some(p) = precision {
23930                    self.write(&format!("({}", p));
23931                    if let Some(s) = scale {
23932                        self.write(&format!(", {})", s));
23933                    } else {
23934                        self.write(")");
23935                    }
23936                }
23937            }
23938            DataType::Decimal { precision, scale } => {
23939                // Dialect-specific decimal type mappings
23940                match self.config.dialect {
23941                    Some(DialectType::ClickHouse) => {
23942                        self.write("Decimal");
23943                        if let Some(p) = precision {
23944                            self.write(&format!("({}", p));
23945                            if let Some(s) = scale {
23946                                self.write(&format!(", {}", s));
23947                            }
23948                            self.write(")");
23949                        }
23950                    }
23951                    Some(DialectType::Oracle) => {
23952                        // Oracle uses NUMBER instead of DECIMAL
23953                        self.write_keyword("NUMBER");
23954                        if let Some(p) = precision {
23955                            self.write(&format!("({}", p));
23956                            if let Some(s) = scale {
23957                                self.write(&format!(", {}", s));
23958                            }
23959                            self.write(")");
23960                        }
23961                    }
23962                    Some(DialectType::BigQuery) => {
23963                        // BigQuery uses NUMERIC instead of DECIMAL
23964                        self.write_keyword("NUMERIC");
23965                        if let Some(p) = precision {
23966                            self.write(&format!("({}", p));
23967                            if let Some(s) = scale {
23968                                self.write(&format!(", {}", s));
23969                            }
23970                            self.write(")");
23971                        }
23972                    }
23973                    _ => {
23974                        self.write_keyword("DECIMAL");
23975                        if let Some(p) = precision {
23976                            self.write(&format!("({}", p));
23977                            if let Some(s) = scale {
23978                                self.write(&format!(", {}", s));
23979                            }
23980                            self.write(")");
23981                        }
23982                    }
23983                }
23984            }
23985            DataType::Char { length } => {
23986                // Dialect-specific char type mappings
23987                match self.config.dialect {
23988                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23989                        // DuckDB/SQLite maps CHAR to TEXT
23990                        self.write_keyword("TEXT");
23991                    }
23992                    Some(DialectType::Hive)
23993                    | Some(DialectType::Spark)
23994                    | Some(DialectType::Databricks) => {
23995                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23996                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23997                        if length.is_some()
23998                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23999                        {
24000                            self.write_keyword("CHAR");
24001                            if let Some(n) = length {
24002                                self.write(&format!("({})", n));
24003                            }
24004                        } else {
24005                            self.write_keyword("STRING");
24006                        }
24007                    }
24008                    Some(DialectType::Dremio) => {
24009                        // Dremio maps CHAR to VARCHAR
24010                        self.write_keyword("VARCHAR");
24011                        if let Some(n) = length {
24012                            self.write(&format!("({})", n));
24013                        }
24014                    }
24015                    _ => {
24016                        self.write_keyword("CHAR");
24017                        if let Some(n) = length {
24018                            self.write(&format!("({})", n));
24019                        }
24020                    }
24021                }
24022            }
24023            DataType::VarChar {
24024                length,
24025                parenthesized_length,
24026            } => {
24027                // Dialect-specific varchar type mappings
24028                match self.config.dialect {
24029                    Some(DialectType::Oracle) => {
24030                        self.write_keyword("VARCHAR2");
24031                        if let Some(n) = length {
24032                            self.write(&format!("({})", n));
24033                        }
24034                    }
24035                    Some(DialectType::DuckDB) => {
24036                        // DuckDB maps VARCHAR to TEXT, preserving length
24037                        self.write_keyword("TEXT");
24038                        if let Some(n) = length {
24039                            self.write(&format!("({})", n));
24040                        }
24041                    }
24042                    Some(DialectType::SQLite) => {
24043                        // SQLite maps VARCHAR to TEXT, preserving length
24044                        self.write_keyword("TEXT");
24045                        if let Some(n) = length {
24046                            self.write(&format!("({})", n));
24047                        }
24048                    }
24049                    Some(DialectType::MySQL) if length.is_none() => {
24050                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
24051                        self.write_keyword("TEXT");
24052                    }
24053                    Some(DialectType::Hive)
24054                    | Some(DialectType::Spark)
24055                    | Some(DialectType::Databricks)
24056                        if length.is_none() =>
24057                    {
24058                        // Hive/Spark/Databricks: VARCHAR without length → STRING
24059                        self.write_keyword("STRING");
24060                    }
24061                    _ => {
24062                        self.write_keyword("VARCHAR");
24063                        if let Some(n) = length {
24064                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
24065                            if *parenthesized_length {
24066                                self.write(&format!("(({}))", n));
24067                            } else {
24068                                self.write(&format!("({})", n));
24069                            }
24070                        }
24071                    }
24072                }
24073            }
24074            DataType::Text => {
24075                // Dialect-specific text type mappings
24076                match self.config.dialect {
24077                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
24078                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24079                        self.write_keyword("VARCHAR(MAX)")
24080                    }
24081                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24082                    Some(DialectType::Snowflake)
24083                    | Some(DialectType::Dremio)
24084                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
24085                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
24086                    Some(DialectType::Presto)
24087                    | Some(DialectType::Trino)
24088                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24089                    Some(DialectType::Spark)
24090                    | Some(DialectType::Databricks)
24091                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24092                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
24093                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24094                        self.write_keyword("STRING")
24095                    }
24096                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24097                    _ => self.write_keyword("TEXT"),
24098                }
24099            }
24100            DataType::TextWithLength { length } => {
24101                // TEXT(n) - dialect-specific type with length
24102                match self.config.dialect {
24103                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
24104                    Some(DialectType::Hive)
24105                    | Some(DialectType::Spark)
24106                    | Some(DialectType::Databricks) => {
24107                        self.write(&format!("VARCHAR({})", length));
24108                    }
24109                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
24110                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
24111                    Some(DialectType::Snowflake)
24112                    | Some(DialectType::Presto)
24113                    | Some(DialectType::Trino)
24114                    | Some(DialectType::Athena)
24115                    | Some(DialectType::Drill)
24116                    | Some(DialectType::Dremio) => {
24117                        self.write(&format!("VARCHAR({})", length));
24118                    }
24119                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24120                        self.write(&format!("VARCHAR({})", length))
24121                    }
24122                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24123                        self.write(&format!("STRING({})", length))
24124                    }
24125                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24126                    _ => self.write(&format!("TEXT({})", length)),
24127                }
24128            }
24129            DataType::String { length } => {
24130                // STRING type with optional length (BigQuery STRING(n))
24131                match self.config.dialect {
24132                    Some(DialectType::ClickHouse) => {
24133                        // ClickHouse uses String with specific casing
24134                        self.write("String");
24135                        if let Some(n) = length {
24136                            self.write(&format!("({})", n));
24137                        }
24138                    }
24139                    Some(DialectType::BigQuery)
24140                    | Some(DialectType::Hive)
24141                    | Some(DialectType::Spark)
24142                    | Some(DialectType::Databricks)
24143                    | Some(DialectType::StarRocks)
24144                    | Some(DialectType::Doris) => {
24145                        self.write_keyword("STRING");
24146                        if let Some(n) = length {
24147                            self.write(&format!("({})", n));
24148                        }
24149                    }
24150                    Some(DialectType::PostgreSQL) => {
24151                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
24152                        if let Some(n) = length {
24153                            self.write_keyword("VARCHAR");
24154                            self.write(&format!("({})", n));
24155                        } else {
24156                            self.write_keyword("TEXT");
24157                        }
24158                    }
24159                    Some(DialectType::Redshift) => {
24160                        // Redshift: STRING -> VARCHAR(MAX)
24161                        if let Some(n) = length {
24162                            self.write_keyword("VARCHAR");
24163                            self.write(&format!("({})", n));
24164                        } else {
24165                            self.write_keyword("VARCHAR(MAX)");
24166                        }
24167                    }
24168                    Some(DialectType::MySQL) => {
24169                        // MySQL doesn't have STRING - use VARCHAR or TEXT
24170                        if let Some(n) = length {
24171                            self.write_keyword("VARCHAR");
24172                            self.write(&format!("({})", n));
24173                        } else {
24174                            self.write_keyword("TEXT");
24175                        }
24176                    }
24177                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24178                        // TSQL: STRING -> VARCHAR(MAX)
24179                        if let Some(n) = length {
24180                            self.write_keyword("VARCHAR");
24181                            self.write(&format!("({})", n));
24182                        } else {
24183                            self.write_keyword("VARCHAR(MAX)");
24184                        }
24185                    }
24186                    Some(DialectType::Oracle) => {
24187                        // Oracle: STRING -> CLOB
24188                        self.write_keyword("CLOB");
24189                    }
24190                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
24191                        // DuckDB/Materialize uses TEXT for string types
24192                        self.write_keyword("TEXT");
24193                        if let Some(n) = length {
24194                            self.write(&format!("({})", n));
24195                        }
24196                    }
24197                    Some(DialectType::Presto)
24198                    | Some(DialectType::Trino)
24199                    | Some(DialectType::Drill)
24200                    | Some(DialectType::Dremio) => {
24201                        // Presto/Trino/Drill use VARCHAR for string types
24202                        self.write_keyword("VARCHAR");
24203                        if let Some(n) = length {
24204                            self.write(&format!("({})", n));
24205                        }
24206                    }
24207                    Some(DialectType::Snowflake) => {
24208                        // Snowflake: STRING stays as STRING (identity/DDL)
24209                        // CAST context STRING -> VARCHAR is handled in generate_cast
24210                        self.write_keyword("STRING");
24211                        if let Some(n) = length {
24212                            self.write(&format!("({})", n));
24213                        }
24214                    }
24215                    _ => {
24216                        // Default: output STRING with optional length
24217                        self.write_keyword("STRING");
24218                        if let Some(n) = length {
24219                            self.write(&format!("({})", n));
24220                        }
24221                    }
24222                }
24223            }
24224            DataType::Binary { length } => {
24225                // Dialect-specific binary type mappings
24226                match self.config.dialect {
24227                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24228                        self.write_keyword("BYTEA");
24229                        if let Some(n) = length {
24230                            self.write(&format!("({})", n));
24231                        }
24232                    }
24233                    Some(DialectType::Redshift) => {
24234                        self.write_keyword("VARBYTE");
24235                        if let Some(n) = length {
24236                            self.write(&format!("({})", n));
24237                        }
24238                    }
24239                    Some(DialectType::DuckDB)
24240                    | Some(DialectType::SQLite)
24241                    | Some(DialectType::Oracle) => {
24242                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
24243                        self.write_keyword("BLOB");
24244                        if let Some(n) = length {
24245                            self.write(&format!("({})", n));
24246                        }
24247                    }
24248                    Some(DialectType::Presto)
24249                    | Some(DialectType::Trino)
24250                    | Some(DialectType::Athena)
24251                    | Some(DialectType::Drill)
24252                    | Some(DialectType::Dremio) => {
24253                        // These dialects map BINARY to VARBINARY
24254                        self.write_keyword("VARBINARY");
24255                        if let Some(n) = length {
24256                            self.write(&format!("({})", n));
24257                        }
24258                    }
24259                    Some(DialectType::ClickHouse) => {
24260                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
24261                        if self.clickhouse_nullable_depth < 0 {
24262                            self.write("BINARY");
24263                        } else {
24264                            self.write("Nullable(BINARY");
24265                        }
24266                        if let Some(n) = length {
24267                            self.write(&format!("({})", n));
24268                        }
24269                        if self.clickhouse_nullable_depth >= 0 {
24270                            self.write(")");
24271                        }
24272                    }
24273                    _ => {
24274                        self.write_keyword("BINARY");
24275                        if let Some(n) = length {
24276                            self.write(&format!("({})", n));
24277                        }
24278                    }
24279                }
24280            }
24281            DataType::VarBinary { length } => {
24282                // Dialect-specific varbinary type mappings
24283                match self.config.dialect {
24284                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24285                        self.write_keyword("BYTEA");
24286                        if let Some(n) = length {
24287                            self.write(&format!("({})", n));
24288                        }
24289                    }
24290                    Some(DialectType::Redshift) => {
24291                        self.write_keyword("VARBYTE");
24292                        if let Some(n) = length {
24293                            self.write(&format!("({})", n));
24294                        }
24295                    }
24296                    Some(DialectType::DuckDB)
24297                    | Some(DialectType::SQLite)
24298                    | Some(DialectType::Oracle) => {
24299                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
24300                        self.write_keyword("BLOB");
24301                        if let Some(n) = length {
24302                            self.write(&format!("({})", n));
24303                        }
24304                    }
24305                    Some(DialectType::Exasol) => {
24306                        // Exasol maps VARBINARY to VARCHAR
24307                        self.write_keyword("VARCHAR");
24308                    }
24309                    Some(DialectType::Spark)
24310                    | Some(DialectType::Hive)
24311                    | Some(DialectType::Databricks) => {
24312                        // Spark/Hive use BINARY instead of VARBINARY
24313                        self.write_keyword("BINARY");
24314                        if let Some(n) = length {
24315                            self.write(&format!("({})", n));
24316                        }
24317                    }
24318                    Some(DialectType::ClickHouse) => {
24319                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
24320                        self.write_clickhouse_type("String");
24321                    }
24322                    _ => {
24323                        self.write_keyword("VARBINARY");
24324                        if let Some(n) = length {
24325                            self.write(&format!("({})", n));
24326                        }
24327                    }
24328                }
24329            }
24330            DataType::Blob => {
24331                // Dialect-specific blob type mappings
24332                match self.config.dialect {
24333                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
24334                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
24335                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24336                        self.write_keyword("VARBINARY")
24337                    }
24338                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24339                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
24340                    Some(DialectType::Presto)
24341                    | Some(DialectType::Trino)
24342                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24343                    Some(DialectType::DuckDB) => {
24344                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
24345                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
24346                        self.write_keyword("VARBINARY");
24347                    }
24348                    Some(DialectType::Spark)
24349                    | Some(DialectType::Databricks)
24350                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
24351                    Some(DialectType::ClickHouse) => {
24352                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
24353                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
24354                        // This matches Python sqlglot behavior.
24355                        self.write("Nullable(String)");
24356                    }
24357                    _ => self.write_keyword("BLOB"),
24358                }
24359            }
24360            DataType::Bit { length } => {
24361                // Dialect-specific bit type mappings
24362                match self.config.dialect {
24363                    Some(DialectType::Dremio)
24364                    | Some(DialectType::Spark)
24365                    | Some(DialectType::Databricks)
24366                    | Some(DialectType::Hive)
24367                    | Some(DialectType::Snowflake)
24368                    | Some(DialectType::BigQuery)
24369                    | Some(DialectType::Presto)
24370                    | Some(DialectType::Trino)
24371                    | Some(DialectType::ClickHouse)
24372                    | Some(DialectType::Redshift) => {
24373                        // These dialects don't support BIT type, use BOOLEAN
24374                        self.write_keyword("BOOLEAN");
24375                    }
24376                    _ => {
24377                        self.write_keyword("BIT");
24378                        if let Some(n) = length {
24379                            self.write(&format!("({})", n));
24380                        }
24381                    }
24382                }
24383            }
24384            DataType::VarBit { length } => {
24385                self.write_keyword("VARBIT");
24386                if let Some(n) = length {
24387                    self.write(&format!("({})", n));
24388                }
24389            }
24390            DataType::Date => self.write_keyword("DATE"),
24391            DataType::Time {
24392                precision,
24393                timezone,
24394            } => {
24395                if *timezone {
24396                    // Dialect-specific TIME WITH TIME ZONE output
24397                    match self.config.dialect {
24398                        Some(DialectType::DuckDB) => {
24399                            // DuckDB: TIMETZ (drops precision)
24400                            self.write_keyword("TIMETZ");
24401                        }
24402                        Some(DialectType::PostgreSQL) => {
24403                            // PostgreSQL: TIMETZ or TIMETZ(p)
24404                            self.write_keyword("TIMETZ");
24405                            if let Some(p) = precision {
24406                                self.write(&format!("({})", p));
24407                            }
24408                        }
24409                        _ => {
24410                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
24411                            self.write_keyword("TIME");
24412                            if let Some(p) = precision {
24413                                self.write(&format!("({})", p));
24414                            }
24415                            self.write_keyword(" WITH TIME ZONE");
24416                        }
24417                    }
24418                } else {
24419                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
24420                    if matches!(
24421                        self.config.dialect,
24422                        Some(DialectType::Spark)
24423                            | Some(DialectType::Databricks)
24424                            | Some(DialectType::Hive)
24425                    ) {
24426                        self.write_keyword("TIMESTAMP");
24427                    } else {
24428                        self.write_keyword("TIME");
24429                        if let Some(p) = precision {
24430                            self.write(&format!("({})", p));
24431                        }
24432                    }
24433                }
24434            }
24435            DataType::Timestamp {
24436                precision,
24437                timezone,
24438            } => {
24439                // Dialect-specific timestamp type mappings
24440                match self.config.dialect {
24441                    Some(DialectType::Snowflake) if *timezone => {
24442                        self.write_keyword("TIMESTAMPTZ");
24443                        if let Some(p) = precision {
24444                            self.write(&format!("({})", p));
24445                        }
24446                    }
24447                    Some(DialectType::ClickHouse) => {
24448                        self.write("DateTime");
24449                        if let Some(p) = precision {
24450                            self.write(&format!("({})", p));
24451                        }
24452                    }
24453                    Some(DialectType::TSQL) => {
24454                        if *timezone {
24455                            self.write_keyword("DATETIMEOFFSET");
24456                        } else {
24457                            self.write_keyword("DATETIME2");
24458                        }
24459                        if let Some(p) = precision {
24460                            self.write(&format!("({})", p));
24461                        }
24462                    }
24463                    Some(DialectType::MySQL) => {
24464                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24465                        self.write_keyword("TIMESTAMP");
24466                        if let Some(p) = precision {
24467                            self.write(&format!("({})", p));
24468                        }
24469                    }
24470                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24471                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24472                        self.write_keyword("DATETIME");
24473                        if let Some(p) = precision {
24474                            self.write(&format!("({})", p));
24475                        }
24476                    }
24477                    Some(DialectType::BigQuery) => {
24478                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24479                        if *timezone {
24480                            self.write_keyword("TIMESTAMP");
24481                        } else {
24482                            self.write_keyword("DATETIME");
24483                        }
24484                    }
24485                    Some(DialectType::DuckDB) => {
24486                        // DuckDB: TIMESTAMPTZ shorthand
24487                        if *timezone {
24488                            self.write_keyword("TIMESTAMPTZ");
24489                        } else {
24490                            self.write_keyword("TIMESTAMP");
24491                            if let Some(p) = precision {
24492                                self.write(&format!("({})", p));
24493                            }
24494                        }
24495                    }
24496                    _ => {
24497                        if *timezone && !self.config.tz_to_with_time_zone {
24498                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24499                            self.write_keyword("TIMESTAMPTZ");
24500                            if let Some(p) = precision {
24501                                self.write(&format!("({})", p));
24502                            }
24503                        } else {
24504                            self.write_keyword("TIMESTAMP");
24505                            if let Some(p) = precision {
24506                                self.write(&format!("({})", p));
24507                            }
24508                            if *timezone {
24509                                self.write_space();
24510                                self.write_keyword("WITH TIME ZONE");
24511                            }
24512                        }
24513                    }
24514                }
24515            }
24516            DataType::Interval { unit, to } => {
24517                self.write_keyword("INTERVAL");
24518                if let Some(u) = unit {
24519                    self.write_space();
24520                    self.write_keyword(u);
24521                }
24522                // Handle range intervals like DAY TO HOUR
24523                if let Some(t) = to {
24524                    self.write_space();
24525                    self.write_keyword("TO");
24526                    self.write_space();
24527                    self.write_keyword(t);
24528                }
24529            }
24530            DataType::Json => {
24531                // Dialect-specific JSON type mappings
24532                match self.config.dialect {
24533                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24534                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24535                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24536                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24537                    _ => self.write_keyword("JSON"),
24538                }
24539            }
24540            DataType::JsonB => {
24541                // JSONB is PostgreSQL specific, but Doris also supports it
24542                match self.config.dialect {
24543                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24544                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24545                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24546                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24547                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24548                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24549                }
24550            }
24551            DataType::Uuid => {
24552                // Dialect-specific UUID type mappings
24553                match self.config.dialect {
24554                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24555                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24556                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24557                    Some(DialectType::BigQuery)
24558                    | Some(DialectType::Spark)
24559                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24560                    _ => self.write_keyword("UUID"),
24561                }
24562            }
24563            DataType::Array {
24564                element_type,
24565                dimension,
24566            } => {
24567                // Dialect-specific array syntax
24568                match self.config.dialect {
24569                    Some(DialectType::PostgreSQL)
24570                    | Some(DialectType::Redshift)
24571                    | Some(DialectType::DuckDB) => {
24572                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24573                        self.generate_data_type(element_type)?;
24574                        if let Some(dim) = dimension {
24575                            self.write(&format!("[{}]", dim));
24576                        } else {
24577                            self.write("[]");
24578                        }
24579                    }
24580                    Some(DialectType::BigQuery) => {
24581                        self.write_keyword("ARRAY<");
24582                        self.generate_data_type(element_type)?;
24583                        self.write(">");
24584                    }
24585                    Some(DialectType::Snowflake)
24586                    | Some(DialectType::Presto)
24587                    | Some(DialectType::Trino)
24588                    | Some(DialectType::ClickHouse) => {
24589                        // These dialects use Array(TYPE) parentheses syntax
24590                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24591                            self.write("Array(");
24592                        } else {
24593                            self.write_keyword("ARRAY(");
24594                        }
24595                        self.generate_data_type(element_type)?;
24596                        self.write(")");
24597                    }
24598                    Some(DialectType::TSQL)
24599                    | Some(DialectType::MySQL)
24600                    | Some(DialectType::Oracle) => {
24601                        // These dialects don't have native array types
24602                        // Fall back to JSON or use native workarounds
24603                        match self.config.dialect {
24604                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24605                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24606                            _ => self.write_keyword("JSON"),
24607                        }
24608                    }
24609                    _ => {
24610                        // Default: use angle bracket syntax (ARRAY<T>)
24611                        self.write_keyword("ARRAY<");
24612                        self.generate_data_type(element_type)?;
24613                        self.write(">");
24614                    }
24615                }
24616            }
24617            DataType::List { element_type } => {
24618                // Materialize: element_type LIST (postfix syntax)
24619                self.generate_data_type(element_type)?;
24620                self.write_keyword(" LIST");
24621            }
24622            DataType::Map {
24623                key_type,
24624                value_type,
24625            } => {
24626                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24627                match self.config.dialect {
24628                    Some(DialectType::Materialize) => {
24629                        // Materialize: MAP[key_type => value_type]
24630                        self.write_keyword("MAP[");
24631                        self.generate_data_type(key_type)?;
24632                        self.write(" => ");
24633                        self.generate_data_type(value_type)?;
24634                        self.write("]");
24635                    }
24636                    Some(DialectType::Snowflake)
24637                    | Some(DialectType::RisingWave)
24638                    | Some(DialectType::DuckDB)
24639                    | Some(DialectType::Presto)
24640                    | Some(DialectType::Trino)
24641                    | Some(DialectType::Athena) => {
24642                        self.write_keyword("MAP(");
24643                        self.generate_data_type(key_type)?;
24644                        self.write(", ");
24645                        self.generate_data_type(value_type)?;
24646                        self.write(")");
24647                    }
24648                    Some(DialectType::ClickHouse) => {
24649                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24650                        // Key types must NOT be wrapped in Nullable
24651                        self.write("Map(");
24652                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24653                        self.generate_data_type(key_type)?;
24654                        self.clickhouse_nullable_depth = 0;
24655                        self.write(", ");
24656                        self.generate_data_type(value_type)?;
24657                        self.write(")");
24658                    }
24659                    _ => {
24660                        self.write_keyword("MAP<");
24661                        self.generate_data_type(key_type)?;
24662                        self.write(", ");
24663                        self.generate_data_type(value_type)?;
24664                        self.write(">");
24665                    }
24666                }
24667            }
24668            DataType::Vector {
24669                element_type,
24670                dimension,
24671            } => {
24672                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24673                    // SingleStore format: VECTOR(dimension, type_alias)
24674                    self.write_keyword("VECTOR(");
24675                    if let Some(dim) = dimension {
24676                        self.write(&dim.to_string());
24677                    }
24678                    // Map type back to SingleStore alias
24679                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24680                        DataType::TinyInt { .. } => Some("I8"),
24681                        DataType::SmallInt { .. } => Some("I16"),
24682                        DataType::Int { .. } => Some("I32"),
24683                        DataType::BigInt { .. } => Some("I64"),
24684                        DataType::Float { .. } => Some("F32"),
24685                        DataType::Double { .. } => Some("F64"),
24686                        _ => None,
24687                    });
24688                    if let Some(alias) = type_alias {
24689                        if dimension.is_some() {
24690                            self.write(", ");
24691                        }
24692                        self.write(alias);
24693                    }
24694                    self.write(")");
24695                } else {
24696                    // Snowflake format: VECTOR(type, dimension)
24697                    self.write_keyword("VECTOR(");
24698                    if let Some(ref et) = element_type {
24699                        self.generate_data_type(et)?;
24700                        if dimension.is_some() {
24701                            self.write(", ");
24702                        }
24703                    }
24704                    if let Some(dim) = dimension {
24705                        self.write(&dim.to_string());
24706                    }
24707                    self.write(")");
24708                }
24709            }
24710            DataType::Object { fields, modifier } => {
24711                self.write_keyword("OBJECT(");
24712                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24713                    if i > 0 {
24714                        self.write(", ");
24715                    }
24716                    self.write(name);
24717                    self.write(" ");
24718                    self.generate_data_type(dt)?;
24719                    if *not_null {
24720                        self.write_keyword(" NOT NULL");
24721                    }
24722                }
24723                self.write(")");
24724                if let Some(mod_str) = modifier {
24725                    self.write(" ");
24726                    self.write_keyword(mod_str);
24727                }
24728            }
24729            DataType::Struct { fields, nested } => {
24730                // Dialect-specific struct type mappings
24731                match self.config.dialect {
24732                    Some(DialectType::Snowflake) => {
24733                        // Snowflake maps STRUCT to OBJECT
24734                        self.write_keyword("OBJECT(");
24735                        for (i, field) in fields.iter().enumerate() {
24736                            if i > 0 {
24737                                self.write(", ");
24738                            }
24739                            if !field.name.is_empty() {
24740                                self.write(&field.name);
24741                                self.write(" ");
24742                            }
24743                            self.generate_data_type(&field.data_type)?;
24744                        }
24745                        self.write(")");
24746                    }
24747                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24748                        // Presto/Trino use ROW(name TYPE, ...) syntax
24749                        self.write_keyword("ROW(");
24750                        for (i, field) in fields.iter().enumerate() {
24751                            if i > 0 {
24752                                self.write(", ");
24753                            }
24754                            if !field.name.is_empty() {
24755                                self.write(&field.name);
24756                                self.write(" ");
24757                            }
24758                            self.generate_data_type(&field.data_type)?;
24759                        }
24760                        self.write(")");
24761                    }
24762                    Some(DialectType::DuckDB) => {
24763                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24764                        self.write_keyword("STRUCT(");
24765                        for (i, field) in fields.iter().enumerate() {
24766                            if i > 0 {
24767                                self.write(", ");
24768                            }
24769                            if !field.name.is_empty() {
24770                                self.write(&field.name);
24771                                self.write(" ");
24772                            }
24773                            self.generate_data_type(&field.data_type)?;
24774                        }
24775                        self.write(")");
24776                    }
24777                    Some(DialectType::ClickHouse) => {
24778                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24779                        self.write("Tuple(");
24780                        for (i, field) in fields.iter().enumerate() {
24781                            if i > 0 {
24782                                self.write(", ");
24783                            }
24784                            if !field.name.is_empty() {
24785                                self.write(&field.name);
24786                                self.write(" ");
24787                            }
24788                            self.generate_data_type(&field.data_type)?;
24789                        }
24790                        self.write(")");
24791                    }
24792                    Some(DialectType::SingleStore) => {
24793                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24794                        self.write_keyword("RECORD(");
24795                        for (i, field) in fields.iter().enumerate() {
24796                            if i > 0 {
24797                                self.write(", ");
24798                            }
24799                            if !field.name.is_empty() {
24800                                self.write(&field.name);
24801                                self.write(" ");
24802                            }
24803                            self.generate_data_type(&field.data_type)?;
24804                        }
24805                        self.write(")");
24806                    }
24807                    _ => {
24808                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24809                        let force_angle_brackets = matches!(
24810                            self.config.dialect,
24811                            Some(DialectType::Hive)
24812                                | Some(DialectType::Spark)
24813                                | Some(DialectType::Databricks)
24814                        );
24815                        if *nested && !force_angle_brackets {
24816                            self.write_keyword("STRUCT(");
24817                            for (i, field) in fields.iter().enumerate() {
24818                                if i > 0 {
24819                                    self.write(", ");
24820                                }
24821                                if !field.name.is_empty() {
24822                                    self.write(&field.name);
24823                                    self.write(" ");
24824                                }
24825                                self.generate_data_type(&field.data_type)?;
24826                            }
24827                            self.write(")");
24828                        } else {
24829                            self.write_keyword("STRUCT<");
24830                            for (i, field) in fields.iter().enumerate() {
24831                                if i > 0 {
24832                                    self.write(", ");
24833                                }
24834                                if !field.name.is_empty() {
24835                                    // Named field: name TYPE (with configurable separator for Hive)
24836                                    self.write(&field.name);
24837                                    self.write(self.config.struct_field_sep);
24838                                }
24839                                // For anonymous fields, just output the type
24840                                self.generate_data_type(&field.data_type)?;
24841                                // Spark/Databricks: Output COMMENT clause if present
24842                                if let Some(comment) = &field.comment {
24843                                    self.write(" COMMENT '");
24844                                    self.write(comment);
24845                                    self.write("'");
24846                                }
24847                                // BigQuery: Output OPTIONS clause if present
24848                                if !field.options.is_empty() {
24849                                    self.write(" ");
24850                                    self.generate_options_clause(&field.options)?;
24851                                }
24852                            }
24853                            self.write(">");
24854                        }
24855                    }
24856                }
24857            }
24858            DataType::Enum {
24859                values,
24860                assignments,
24861            } => {
24862                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24863                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24864                if self.config.dialect == Some(DialectType::ClickHouse) {
24865                    self.write("Enum(");
24866                } else {
24867                    self.write_keyword("ENUM(");
24868                }
24869                for (i, val) in values.iter().enumerate() {
24870                    if i > 0 {
24871                        self.write(", ");
24872                    }
24873                    self.write("'");
24874                    self.write(val);
24875                    self.write("'");
24876                    if let Some(Some(assignment)) = assignments.get(i) {
24877                        self.write(" = ");
24878                        self.write(assignment);
24879                    }
24880                }
24881                self.write(")");
24882            }
24883            DataType::Set { values } => {
24884                // MySQL SET type: SET('a', 'b', 'c')
24885                self.write_keyword("SET(");
24886                for (i, val) in values.iter().enumerate() {
24887                    if i > 0 {
24888                        self.write(", ");
24889                    }
24890                    self.write("'");
24891                    self.write(val);
24892                    self.write("'");
24893                }
24894                self.write(")");
24895            }
24896            DataType::Union { fields } => {
24897                // DuckDB UNION type: UNION(num INT, str TEXT)
24898                self.write_keyword("UNION(");
24899                for (i, (name, dt)) in fields.iter().enumerate() {
24900                    if i > 0 {
24901                        self.write(", ");
24902                    }
24903                    if !name.is_empty() {
24904                        self.write(name);
24905                        self.write(" ");
24906                    }
24907                    self.generate_data_type(dt)?;
24908                }
24909                self.write(")");
24910            }
24911            DataType::Nullable { inner } => {
24912                // ClickHouse: Nullable(T), other dialects: just the inner type
24913                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24914                    self.write("Nullable(");
24915                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24916                    let saved_depth = self.clickhouse_nullable_depth;
24917                    self.clickhouse_nullable_depth = -1;
24918                    self.generate_data_type(inner)?;
24919                    self.clickhouse_nullable_depth = saved_depth;
24920                    self.write(")");
24921                } else {
24922                    // Map ClickHouse-specific custom type names to standard types
24923                    match inner.as_ref() {
24924                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24925                            self.generate_data_type(&DataType::Timestamp {
24926                                precision: None,
24927                                timezone: false,
24928                            })?;
24929                        }
24930                        _ => {
24931                            self.generate_data_type(inner)?;
24932                        }
24933                    }
24934                }
24935            }
24936            DataType::Custom { name } => {
24937                // Handle dialect-specific type transformations
24938                let name_upper = name.to_ascii_uppercase();
24939                match self.config.dialect {
24940                    Some(DialectType::ClickHouse) => {
24941                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24942                            (name_upper[..idx].to_string(), &name[idx..])
24943                        } else {
24944                            (name_upper.clone(), "")
24945                        };
24946                        let mapped = match base_upper.as_str() {
24947                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24948                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24949                            "DATETIME64" => "DateTime64",
24950                            "DATE32" => "Date32",
24951                            "INT" => "Int32",
24952                            "MEDIUMINT" => "Int32",
24953                            "INT8" => "Int8",
24954                            "INT16" => "Int16",
24955                            "INT32" => "Int32",
24956                            "INT64" => "Int64",
24957                            "INT128" => "Int128",
24958                            "INT256" => "Int256",
24959                            "UINT8" => "UInt8",
24960                            "UINT16" => "UInt16",
24961                            "UINT32" => "UInt32",
24962                            "UINT64" => "UInt64",
24963                            "UINT128" => "UInt128",
24964                            "UINT256" => "UInt256",
24965                            "FLOAT32" => "Float32",
24966                            "FLOAT64" => "Float64",
24967                            "DECIMAL32" => "Decimal32",
24968                            "DECIMAL64" => "Decimal64",
24969                            "DECIMAL128" => "Decimal128",
24970                            "DECIMAL256" => "Decimal256",
24971                            "ENUM" => "Enum",
24972                            "ENUM8" => "Enum8",
24973                            "ENUM16" => "Enum16",
24974                            "FIXEDSTRING" => "FixedString",
24975                            "NESTED" => "Nested",
24976                            "LOWCARDINALITY" => "LowCardinality",
24977                            "NULLABLE" => "Nullable",
24978                            "IPV4" => "IPv4",
24979                            "IPV6" => "IPv6",
24980                            "POINT" => "Point",
24981                            "RING" => "Ring",
24982                            "LINESTRING" => "LineString",
24983                            "MULTILINESTRING" => "MultiLineString",
24984                            "POLYGON" => "Polygon",
24985                            "MULTIPOLYGON" => "MultiPolygon",
24986                            "AGGREGATEFUNCTION" => "AggregateFunction",
24987                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24988                            "DYNAMIC" => "Dynamic",
24989                            _ => "",
24990                        };
24991                        if mapped.is_empty() {
24992                            self.write(name);
24993                        } else {
24994                            self.write(mapped);
24995                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
24996                                && !suffix.is_empty()
24997                            {
24998                                let escaped_suffix = suffix
24999                                    .replace('\\', "\\\\")
25000                                    .replace('\t', "\\t")
25001                                    .replace('\n', "\\n")
25002                                    .replace('\r', "\\r");
25003                                self.write(&escaped_suffix);
25004                            } else {
25005                                self.write(suffix);
25006                            }
25007                        }
25008                    }
25009                    Some(DialectType::MySQL)
25010                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
25011                    {
25012                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
25013                        self.write_keyword("TIMESTAMP");
25014                    }
25015                    Some(DialectType::Snowflake) => {
25016                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25017                            (name_upper[..idx].to_string(), &name[idx..])
25018                        } else {
25019                            (name_upper.clone(), "")
25020                        };
25021
25022                        match base_upper.as_str() {
25023                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
25024                                self.write_keyword("TIMESTAMPNTZ");
25025                                self.write(suffix);
25026                            }
25027                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
25028                                self.write_keyword("TIMESTAMPLTZ");
25029                                self.write(suffix);
25030                            }
25031                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
25032                                self.write_keyword("TIMESTAMPTZ");
25033                                self.write(suffix);
25034                            }
25035                            _ => self.write(name),
25036                        }
25037                    }
25038                    Some(DialectType::Fabric) => {
25039                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
25040                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25041                        } else {
25042                            (name_upper.clone(), None)
25043                        };
25044
25045                        match base_upper.as_str() {
25046                            "NVARCHAR" => {
25047                                self.write_keyword("VARCHAR");
25048                                if let Some(args) = args_str {
25049                                    self.write(args);
25050                                }
25051                            }
25052                            "NCHAR" => {
25053                                self.write_keyword("CHAR");
25054                                if let Some(args) = args_str {
25055                                    self.write(args);
25056                                }
25057                            }
25058                            _ => self.write(name),
25059                        }
25060                    }
25061                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
25062                        self.write_keyword("SQL_VARIANT");
25063                    }
25064                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
25065                        self.write_keyword("DECIMAL(38, 5)");
25066                    }
25067                    Some(DialectType::Exasol) => {
25068                        // Exasol type mappings for custom types
25069                        match name_upper.as_str() {
25070                            // Binary types → VARCHAR
25071                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
25072                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
25073                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
25074                            // Integer types
25075                            "MEDIUMINT" => self.write_keyword("INT"),
25076                            // Decimal types → DECIMAL
25077                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
25078                                self.write_keyword("DECIMAL")
25079                            }
25080                            // Timestamp types
25081                            "DATETIME" => self.write_keyword("TIMESTAMP"),
25082                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
25083                            _ => self.write(name),
25084                        }
25085                    }
25086                    Some(DialectType::Dremio) => {
25087                        // Dremio type mappings for custom types
25088                        match name_upper.as_str() {
25089                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
25090                            "ARRAY" => self.write_keyword("LIST"),
25091                            "NCHAR" => self.write_keyword("VARCHAR"),
25092                            _ => self.write(name),
25093                        }
25094                    }
25095                    // Map dialect-specific custom types to standard SQL types for other dialects
25096                    _ => {
25097                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
25098                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
25099                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25100                        } else {
25101                            (name_upper.clone(), None)
25102                        };
25103
25104                        match base_upper.as_str() {
25105                            "INT64"
25106                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25107                            {
25108                                self.write_keyword("BIGINT");
25109                            }
25110                            "FLOAT64"
25111                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25112                            {
25113                                self.write_keyword("DOUBLE");
25114                            }
25115                            "BOOL"
25116                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25117                            {
25118                                self.write_keyword("BOOLEAN");
25119                            }
25120                            "BYTES"
25121                                if matches!(
25122                                    self.config.dialect,
25123                                    Some(DialectType::Spark)
25124                                        | Some(DialectType::Hive)
25125                                        | Some(DialectType::Databricks)
25126                                ) =>
25127                            {
25128                                self.write_keyword("BINARY");
25129                            }
25130                            "BYTES"
25131                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25132                            {
25133                                self.write_keyword("VARBINARY");
25134                            }
25135                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
25136                            "DATETIME2" | "SMALLDATETIME"
25137                                if !matches!(
25138                                    self.config.dialect,
25139                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25140                                ) =>
25141                            {
25142                                // PostgreSQL preserves precision, others don't
25143                                if matches!(
25144                                    self.config.dialect,
25145                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25146                                ) {
25147                                    self.write_keyword("TIMESTAMP");
25148                                    if let Some(args) = _args_str {
25149                                        self.write(args);
25150                                    }
25151                                } else {
25152                                    self.write_keyword("TIMESTAMP");
25153                                }
25154                            }
25155                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
25156                            "DATETIMEOFFSET"
25157                                if !matches!(
25158                                    self.config.dialect,
25159                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25160                                ) =>
25161                            {
25162                                if matches!(
25163                                    self.config.dialect,
25164                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25165                                ) {
25166                                    self.write_keyword("TIMESTAMPTZ");
25167                                    if let Some(args) = _args_str {
25168                                        self.write(args);
25169                                    }
25170                                } else {
25171                                    self.write_keyword("TIMESTAMPTZ");
25172                                }
25173                            }
25174                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
25175                            "UNIQUEIDENTIFIER"
25176                                if !matches!(
25177                                    self.config.dialect,
25178                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25179                                ) =>
25180                            {
25181                                match self.config.dialect {
25182                                    Some(DialectType::Spark)
25183                                    | Some(DialectType::Databricks)
25184                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25185                                    _ => self.write_keyword("UUID"),
25186                                }
25187                            }
25188                            // TSQL BIT -> BOOLEAN for most dialects
25189                            "BIT"
25190                                if !matches!(
25191                                    self.config.dialect,
25192                                    Some(DialectType::TSQL)
25193                                        | Some(DialectType::Fabric)
25194                                        | Some(DialectType::PostgreSQL)
25195                                        | Some(DialectType::MySQL)
25196                                        | Some(DialectType::DuckDB)
25197                                ) =>
25198                            {
25199                                self.write_keyword("BOOLEAN");
25200                            }
25201                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
25202                            "NVARCHAR"
25203                                if !matches!(
25204                                    self.config.dialect,
25205                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25206                                ) =>
25207                            {
25208                                match self.config.dialect {
25209                                    Some(DialectType::Oracle) => {
25210                                        // Oracle: NVARCHAR -> NVARCHAR2
25211                                        self.write_keyword("NVARCHAR2");
25212                                        if let Some(args) = _args_str {
25213                                            self.write(args);
25214                                        }
25215                                    }
25216                                    Some(DialectType::BigQuery) => {
25217                                        // BigQuery: NVARCHAR -> STRING
25218                                        self.write_keyword("STRING");
25219                                    }
25220                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25221                                        self.write_keyword("TEXT");
25222                                        if let Some(args) = _args_str {
25223                                            self.write(args);
25224                                        }
25225                                    }
25226                                    Some(DialectType::Hive) => {
25227                                        // Hive: NVARCHAR -> STRING
25228                                        self.write_keyword("STRING");
25229                                    }
25230                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25231                                        if _args_str.is_some() {
25232                                            self.write_keyword("VARCHAR");
25233                                            self.write(_args_str.unwrap());
25234                                        } else {
25235                                            self.write_keyword("STRING");
25236                                        }
25237                                    }
25238                                    _ => {
25239                                        self.write_keyword("VARCHAR");
25240                                        if let Some(args) = _args_str {
25241                                            self.write(args);
25242                                        }
25243                                    }
25244                                }
25245                            }
25246                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
25247                            "NCHAR"
25248                                if !matches!(
25249                                    self.config.dialect,
25250                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25251                                ) =>
25252                            {
25253                                match self.config.dialect {
25254                                    Some(DialectType::Oracle) => {
25255                                        // Oracle natively supports NCHAR
25256                                        self.write_keyword("NCHAR");
25257                                        if let Some(args) = _args_str {
25258                                            self.write(args);
25259                                        }
25260                                    }
25261                                    Some(DialectType::BigQuery) => {
25262                                        // BigQuery: NCHAR -> STRING
25263                                        self.write_keyword("STRING");
25264                                    }
25265                                    Some(DialectType::Hive) => {
25266                                        // Hive: NCHAR -> STRING
25267                                        self.write_keyword("STRING");
25268                                    }
25269                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25270                                        self.write_keyword("TEXT");
25271                                        if let Some(args) = _args_str {
25272                                            self.write(args);
25273                                        }
25274                                    }
25275                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25276                                        if _args_str.is_some() {
25277                                            self.write_keyword("CHAR");
25278                                            self.write(_args_str.unwrap());
25279                                        } else {
25280                                            self.write_keyword("STRING");
25281                                        }
25282                                    }
25283                                    _ => {
25284                                        self.write_keyword("CHAR");
25285                                        if let Some(args) = _args_str {
25286                                            self.write(args);
25287                                        }
25288                                    }
25289                                }
25290                            }
25291                            // MySQL text variant types -> map to appropriate target type
25292                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25293                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
25294                                Some(DialectType::MySQL)
25295                                | Some(DialectType::SingleStore)
25296                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25297                                Some(DialectType::Spark)
25298                                | Some(DialectType::Databricks)
25299                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
25300                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25301                                Some(DialectType::Presto)
25302                                | Some(DialectType::Trino)
25303                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25304                                Some(DialectType::Snowflake)
25305                                | Some(DialectType::Redshift)
25306                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
25307                                _ => self.write_keyword("TEXT"),
25308                            },
25309                            // MySQL blob variant types -> map to appropriate target type
25310                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25311                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
25312                                Some(DialectType::MySQL)
25313                                | Some(DialectType::SingleStore)
25314                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25315                                Some(DialectType::Spark)
25316                                | Some(DialectType::Databricks)
25317                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
25318                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
25319                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25320                                Some(DialectType::Presto)
25321                                | Some(DialectType::Trino)
25322                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25323                                Some(DialectType::Snowflake)
25324                                | Some(DialectType::Redshift)
25325                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
25326                                _ => self.write_keyword("BLOB"),
25327                            },
25328                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
25329                            "LONGVARCHAR" => match self.config.dialect {
25330                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
25331                                _ => self.write_keyword("VARCHAR"),
25332                            },
25333                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
25334                            "DATETIME" => {
25335                                match self.config.dialect {
25336                                    Some(DialectType::MySQL)
25337                                    | Some(DialectType::Doris)
25338                                    | Some(DialectType::StarRocks)
25339                                    | Some(DialectType::TSQL)
25340                                    | Some(DialectType::Fabric)
25341                                    | Some(DialectType::BigQuery)
25342                                    | Some(DialectType::SQLite)
25343                                    | Some(DialectType::Snowflake) => {
25344                                        self.write_keyword("DATETIME");
25345                                        if let Some(args) = _args_str {
25346                                            self.write(args);
25347                                        }
25348                                    }
25349                                    Some(_) => {
25350                                        // Only map to TIMESTAMP when we have a specific target dialect
25351                                        self.write_keyword("TIMESTAMP");
25352                                        if let Some(args) = _args_str {
25353                                            self.write(args);
25354                                        }
25355                                    }
25356                                    None => {
25357                                        // No dialect - preserve original
25358                                        self.write(name);
25359                                    }
25360                                }
25361                            }
25362                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
25363                            "VARCHAR2"
25364                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25365                            {
25366                                match self.config.dialect {
25367                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25368                                        self.write_keyword("TEXT");
25369                                    }
25370                                    Some(DialectType::Hive)
25371                                    | Some(DialectType::Spark)
25372                                    | Some(DialectType::Databricks)
25373                                    | Some(DialectType::BigQuery)
25374                                    | Some(DialectType::ClickHouse)
25375                                    | Some(DialectType::StarRocks)
25376                                    | Some(DialectType::Doris) => {
25377                                        self.write_keyword("STRING");
25378                                    }
25379                                    _ => {
25380                                        self.write_keyword("VARCHAR");
25381                                        if let Some(args) = _args_str {
25382                                            self.write(args);
25383                                        }
25384                                    }
25385                                }
25386                            }
25387                            "NVARCHAR2"
25388                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25389                            {
25390                                match self.config.dialect {
25391                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25392                                        self.write_keyword("TEXT");
25393                                    }
25394                                    Some(DialectType::Hive)
25395                                    | Some(DialectType::Spark)
25396                                    | Some(DialectType::Databricks)
25397                                    | Some(DialectType::BigQuery)
25398                                    | Some(DialectType::ClickHouse)
25399                                    | Some(DialectType::StarRocks)
25400                                    | Some(DialectType::Doris) => {
25401                                        self.write_keyword("STRING");
25402                                    }
25403                                    _ => {
25404                                        self.write_keyword("VARCHAR");
25405                                        if let Some(args) = _args_str {
25406                                            self.write(args);
25407                                        }
25408                                    }
25409                                }
25410                            }
25411                            _ => self.write(name),
25412                        }
25413                    }
25414                }
25415            }
25416            DataType::Geometry { subtype, srid } => {
25417                // Dialect-specific geometry type mappings
25418                match self.config.dialect {
25419                    Some(DialectType::MySQL) => {
25420                        // MySQL uses POINT SRID 4326 syntax for specific types
25421                        if let Some(sub) = subtype {
25422                            self.write_keyword(sub);
25423                            if let Some(s) = srid {
25424                                self.write(" SRID ");
25425                                self.write(&s.to_string());
25426                            }
25427                        } else {
25428                            self.write_keyword("GEOMETRY");
25429                        }
25430                    }
25431                    Some(DialectType::BigQuery) => {
25432                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
25433                        self.write_keyword("GEOGRAPHY");
25434                    }
25435                    Some(DialectType::Teradata) => {
25436                        // Teradata uses ST_GEOMETRY
25437                        self.write_keyword("ST_GEOMETRY");
25438                        if subtype.is_some() || srid.is_some() {
25439                            self.write("(");
25440                            if let Some(sub) = subtype {
25441                                self.write_keyword(sub);
25442                            }
25443                            if let Some(s) = srid {
25444                                if subtype.is_some() {
25445                                    self.write(", ");
25446                                }
25447                                self.write(&s.to_string());
25448                            }
25449                            self.write(")");
25450                        }
25451                    }
25452                    _ => {
25453                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
25454                        self.write_keyword("GEOMETRY");
25455                        if subtype.is_some() || srid.is_some() {
25456                            self.write("(");
25457                            if let Some(sub) = subtype {
25458                                self.write_keyword(sub);
25459                            }
25460                            if let Some(s) = srid {
25461                                if subtype.is_some() {
25462                                    self.write(", ");
25463                                }
25464                                self.write(&s.to_string());
25465                            }
25466                            self.write(")");
25467                        }
25468                    }
25469                }
25470            }
25471            DataType::Geography { subtype, srid } => {
25472                // Dialect-specific geography type mappings
25473                match self.config.dialect {
25474                    Some(DialectType::MySQL) => {
25475                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
25476                        if let Some(sub) = subtype {
25477                            self.write_keyword(sub);
25478                        } else {
25479                            self.write_keyword("GEOMETRY");
25480                        }
25481                        // Geography implies SRID 4326 (WGS84)
25482                        let effective_srid = srid.unwrap_or(4326);
25483                        self.write(" SRID ");
25484                        self.write(&effective_srid.to_string());
25485                    }
25486                    Some(DialectType::BigQuery) => {
25487                        // BigQuery uses simple GEOGRAPHY without parameters
25488                        self.write_keyword("GEOGRAPHY");
25489                    }
25490                    Some(DialectType::Snowflake) => {
25491                        // Snowflake uses GEOGRAPHY without parameters
25492                        self.write_keyword("GEOGRAPHY");
25493                    }
25494                    _ => {
25495                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25496                        self.write_keyword("GEOGRAPHY");
25497                        if subtype.is_some() || srid.is_some() {
25498                            self.write("(");
25499                            if let Some(sub) = subtype {
25500                                self.write_keyword(sub);
25501                            }
25502                            if let Some(s) = srid {
25503                                if subtype.is_some() {
25504                                    self.write(", ");
25505                                }
25506                                self.write(&s.to_string());
25507                            }
25508                            self.write(")");
25509                        }
25510                    }
25511                }
25512            }
25513            DataType::CharacterSet { name } => {
25514                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25515                self.write_keyword("CHAR CHARACTER SET ");
25516                self.write(name);
25517            }
25518            _ => self.write("UNKNOWN"),
25519        }
25520        Ok(())
25521    }
25522
25523    // === Helper methods ===
25524
25525    #[inline]
25526    fn write(&mut self, s: &str) {
25527        self.output.push_str(s);
25528    }
25529
25530    #[inline]
25531    fn write_space(&mut self) {
25532        self.output.push(' ');
25533    }
25534
25535    #[inline]
25536    fn write_keyword(&mut self, keyword: &str) {
25537        if self.config.uppercase_keywords {
25538            self.output.push_str(keyword);
25539        } else {
25540            for b in keyword.bytes() {
25541                self.output.push(b.to_ascii_lowercase() as char);
25542            }
25543        }
25544    }
25545
25546    /// Write a function name respecting the normalize_functions config setting
25547    fn write_func_name(&mut self, name: &str) {
25548        let normalized = self.normalize_func_name(name);
25549        self.output.push_str(normalized.as_ref());
25550    }
25551
25552    /// Convert strptime format string to Exasol format string
25553    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25554    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25555    fn convert_strptime_to_exasol_format(format: &str) -> String {
25556        let mut result = String::new();
25557        let chars: Vec<char> = format.chars().collect();
25558        let mut i = 0;
25559        while i < chars.len() {
25560            if chars[i] == '%' && i + 1 < chars.len() {
25561                let spec = chars[i + 1];
25562                let exasol_spec = match spec {
25563                    'Y' => "YYYY",
25564                    'y' => "YY",
25565                    'm' => "MM",
25566                    'd' => "DD",
25567                    'H' => "HH",
25568                    'M' => "MI",
25569                    'S' => "SS",
25570                    'a' => "DY",    // abbreviated weekday name
25571                    'A' => "DAY",   // full weekday name
25572                    'b' => "MON",   // abbreviated month name
25573                    'B' => "MONTH", // full month name
25574                    'I' => "H12",   // 12-hour format
25575                    'u' => "ID",    // ISO weekday (1-7)
25576                    'V' => "IW",    // ISO week number
25577                    'G' => "IYYY",  // ISO year
25578                    'W' => "UW",    // Week number (Monday as first day)
25579                    'U' => "UW",    // Week number (Sunday as first day)
25580                    'z' => "Z",     // timezone offset
25581                    _ => {
25582                        // Unknown specifier, keep as-is
25583                        result.push('%');
25584                        result.push(spec);
25585                        i += 2;
25586                        continue;
25587                    }
25588                };
25589                result.push_str(exasol_spec);
25590                i += 2;
25591            } else {
25592                result.push(chars[i]);
25593                i += 1;
25594            }
25595        }
25596        result
25597    }
25598
25599    /// Convert strptime format string to PostgreSQL/Redshift format string
25600    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25601    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25602    fn convert_strptime_to_postgres_format(format: &str) -> String {
25603        let mut result = String::new();
25604        let chars: Vec<char> = format.chars().collect();
25605        let mut i = 0;
25606        while i < chars.len() {
25607            if chars[i] == '%' && i + 1 < chars.len() {
25608                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25609                if chars[i + 1] == '-' && i + 2 < chars.len() {
25610                    let spec = chars[i + 2];
25611                    let pg_spec = match spec {
25612                        'd' => "FMDD",
25613                        'm' => "FMMM",
25614                        'H' => "FMHH24",
25615                        'M' => "FMMI",
25616                        'S' => "FMSS",
25617                        _ => {
25618                            result.push('%');
25619                            result.push('-');
25620                            result.push(spec);
25621                            i += 3;
25622                            continue;
25623                        }
25624                    };
25625                    result.push_str(pg_spec);
25626                    i += 3;
25627                    continue;
25628                }
25629                let spec = chars[i + 1];
25630                let pg_spec = match spec {
25631                    'Y' => "YYYY",
25632                    'y' => "YY",
25633                    'm' => "MM",
25634                    'd' => "DD",
25635                    'H' => "HH24",
25636                    'I' => "HH12",
25637                    'M' => "MI",
25638                    'S' => "SS",
25639                    'f' => "US",      // microseconds
25640                    'u' => "D",       // day of week (1=Monday)
25641                    'j' => "DDD",     // day of year
25642                    'z' => "OF",      // UTC offset
25643                    'Z' => "TZ",      // timezone name
25644                    'A' => "TMDay",   // full weekday name
25645                    'a' => "TMDy",    // abbreviated weekday name
25646                    'b' => "TMMon",   // abbreviated month name
25647                    'B' => "TMMonth", // full month name
25648                    'U' => "WW",      // week number
25649                    _ => {
25650                        // Unknown specifier, keep as-is
25651                        result.push('%');
25652                        result.push(spec);
25653                        i += 2;
25654                        continue;
25655                    }
25656                };
25657                result.push_str(pg_spec);
25658                i += 2;
25659            } else {
25660                result.push(chars[i]);
25661                i += 1;
25662            }
25663        }
25664        result
25665    }
25666
25667    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25668    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25669        if self.config.limit_only_literals {
25670            if let Some(value) = Self::try_evaluate_constant(expr) {
25671                self.write(&value.to_string());
25672                return Ok(());
25673            }
25674        }
25675        self.generate_expression(expr)
25676    }
25677
25678    /// Format a comment with proper spacing.
25679    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25680    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25681    fn write_formatted_comment(&mut self, comment: &str) {
25682        // Normalize all comments to block comment format /* ... */
25683        // This matches Python sqlglot behavior which always outputs block comments
25684        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25685            // Already block comment - extract inner content
25686            // Preserve internal whitespace, but ensure at least one space padding
25687            &comment[2..comment.len() - 2]
25688        } else if comment.starts_with("--") {
25689            // Line comment - extract content after --
25690            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25691            &comment[2..]
25692        } else {
25693            // Raw content (no delimiters)
25694            comment
25695        };
25696        // Skip empty comments (e.g., bare "--" with no content)
25697        if content.trim().is_empty() {
25698            return;
25699        }
25700        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25701        // This matches Python sqlglot's sanitize_comment behavior.
25702        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25703        let content = &sanitized;
25704        // Ensure at least one space after /* and before */
25705        self.output.push_str("/*");
25706        if !content.starts_with(' ') {
25707            self.output.push(' ');
25708        }
25709        self.output.push_str(content);
25710        if !content.ends_with(' ') {
25711            self.output.push(' ');
25712        }
25713        self.output.push_str("*/");
25714    }
25715
25716    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25717    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25718    fn escape_block_for_single_quote(&self, block: &str) -> String {
25719        let escape_backslash = matches!(
25720            self.config.dialect,
25721            Some(crate::dialects::DialectType::Snowflake)
25722        );
25723        let mut escaped = String::with_capacity(block.len() + 4);
25724        for ch in block.chars() {
25725            if ch == '\'' {
25726                escaped.push('\\');
25727                escaped.push('\'');
25728            } else if escape_backslash && ch == '\\' {
25729                escaped.push('\\');
25730                escaped.push('\\');
25731            } else {
25732                escaped.push(ch);
25733            }
25734        }
25735        escaped
25736    }
25737
25738    fn write_newline(&mut self) {
25739        self.output.push('\n');
25740    }
25741
25742    fn write_indent(&mut self) {
25743        for _ in 0..self.indent_level {
25744            self.output.push_str(self.config.indent);
25745        }
25746    }
25747
25748    // === SQLGlot-style pretty printing helpers ===
25749
25750    /// Returns the separator string for pretty printing.
25751    /// Check if the total length of arguments exceeds max_text_width.
25752    /// Used for dynamic line breaking in expressions() formatting.
25753    fn too_wide(&self, args: &[String]) -> bool {
25754        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25755    }
25756
25757    /// Generate an expression to a string using a temporary non-pretty generator.
25758    /// Useful for width calculations before deciding on formatting.
25759    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25760        let config = GeneratorConfig {
25761            pretty: false,
25762            dialect: self.config.dialect,
25763            ..Default::default()
25764        };
25765        let mut gen = Generator::with_config(config);
25766        gen.generate_expression(expr)?;
25767        Ok(gen.output)
25768    }
25769
25770    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25771    /// In pretty mode: newline + indented keyword + newline + indented condition
25772    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25773        if self.config.pretty {
25774            self.write_newline();
25775            self.write_indent();
25776            self.write_keyword(keyword);
25777            self.write_newline();
25778            self.indent_level += 1;
25779            self.write_indent();
25780            self.generate_expression(condition)?;
25781            self.indent_level -= 1;
25782        } else {
25783            self.write_space();
25784            self.write_keyword(keyword);
25785            self.write_space();
25786            self.generate_expression(condition)?;
25787        }
25788        Ok(())
25789    }
25790
25791    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25792    /// In pretty mode: each expression on new line with indentation
25793    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25794        if exprs.is_empty() {
25795            return Ok(());
25796        }
25797
25798        if self.config.pretty {
25799            self.write_newline();
25800            self.write_indent();
25801            self.write_keyword(keyword);
25802            self.write_newline();
25803            self.indent_level += 1;
25804            for (i, expr) in exprs.iter().enumerate() {
25805                if i > 0 {
25806                    self.write(",");
25807                    self.write_newline();
25808                }
25809                self.write_indent();
25810                self.generate_expression(expr)?;
25811            }
25812            self.indent_level -= 1;
25813        } else {
25814            self.write_space();
25815            self.write_keyword(keyword);
25816            self.write_space();
25817            for (i, expr) in exprs.iter().enumerate() {
25818                if i > 0 {
25819                    self.write(", ");
25820                }
25821                self.generate_expression(expr)?;
25822            }
25823        }
25824        Ok(())
25825    }
25826
25827    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25828    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25829        if orderings.is_empty() {
25830            return Ok(());
25831        }
25832
25833        if self.config.pretty {
25834            self.write_newline();
25835            self.write_indent();
25836            self.write_keyword(keyword);
25837            self.write_newline();
25838            self.indent_level += 1;
25839            for (i, ordered) in orderings.iter().enumerate() {
25840                if i > 0 {
25841                    self.write(",");
25842                    self.write_newline();
25843                }
25844                self.write_indent();
25845                self.generate_ordered(ordered)?;
25846            }
25847            self.indent_level -= 1;
25848        } else {
25849            self.write_space();
25850            self.write_keyword(keyword);
25851            self.write_space();
25852            for (i, ordered) in orderings.iter().enumerate() {
25853                if i > 0 {
25854                    self.write(", ");
25855                }
25856                self.generate_ordered(ordered)?;
25857            }
25858        }
25859        Ok(())
25860    }
25861
25862    /// Writes WINDOW clause with named window definitions
25863    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
25864        if windows.is_empty() {
25865            return Ok(());
25866        }
25867
25868        if self.config.pretty {
25869            self.write_newline();
25870            self.write_indent();
25871            self.write_keyword("WINDOW");
25872            self.write_newline();
25873            self.indent_level += 1;
25874            for (i, named_window) in windows.iter().enumerate() {
25875                if i > 0 {
25876                    self.write(",");
25877                    self.write_newline();
25878                }
25879                self.write_indent();
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            self.indent_level -= 1;
25888        } else {
25889            self.write_space();
25890            self.write_keyword("WINDOW");
25891            self.write_space();
25892            for (i, named_window) in windows.iter().enumerate() {
25893                if i > 0 {
25894                    self.write(", ");
25895                }
25896                self.generate_identifier(&named_window.name)?;
25897                self.write_space();
25898                self.write_keyword("AS");
25899                self.write(" (");
25900                self.generate_over(&named_window.spec)?;
25901                self.write(")");
25902            }
25903        }
25904        Ok(())
25905    }
25906
25907    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25908    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25909        // AI_AGG(this, expression)
25910        self.write_keyword("AI_AGG");
25911        self.write("(");
25912        self.generate_expression(&e.this)?;
25913        self.write(", ");
25914        self.generate_expression(&e.expression)?;
25915        self.write(")");
25916        Ok(())
25917    }
25918
25919    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25920        // AI_CLASSIFY(input, [categories], [config])
25921        self.write_keyword("AI_CLASSIFY");
25922        self.write("(");
25923        self.generate_expression(&e.this)?;
25924        if let Some(categories) = &e.categories {
25925            self.write(", ");
25926            self.generate_expression(categories)?;
25927        }
25928        if let Some(config) = &e.config {
25929            self.write(", ");
25930            self.generate_expression(config)?;
25931        }
25932        self.write(")");
25933        Ok(())
25934    }
25935
25936    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25937        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25938        self.write_keyword("ADD");
25939        self.write_space();
25940        if e.exists {
25941            self.write_keyword("IF NOT EXISTS");
25942            self.write_space();
25943        }
25944        self.generate_expression(&e.this)?;
25945        if let Some(location) = &e.location {
25946            self.write_space();
25947            self.generate_expression(location)?;
25948        }
25949        Ok(())
25950    }
25951
25952    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25953        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25954        self.write_keyword("ALGORITHM");
25955        self.write("=");
25956        self.generate_expression(&e.this)?;
25957        Ok(())
25958    }
25959
25960    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25961        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25962        self.generate_expression(&e.this)?;
25963        self.write_space();
25964        self.write_keyword("AS");
25965        self.write(" (");
25966        for (i, expr) in e.expressions.iter().enumerate() {
25967            if i > 0 {
25968                self.write(", ");
25969            }
25970            self.generate_expression(expr)?;
25971        }
25972        self.write(")");
25973        Ok(())
25974    }
25975
25976    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25977        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25978        self.write_keyword("ALLOWED_VALUES");
25979        self.write_space();
25980        for (i, expr) in e.expressions.iter().enumerate() {
25981            if i > 0 {
25982                self.write(", ");
25983            }
25984            self.generate_expression(expr)?;
25985        }
25986        Ok(())
25987    }
25988
25989    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25990        // Python: complex logic based on dtype, default, comment, visible, etc.
25991        self.write_keyword("ALTER COLUMN");
25992        self.write_space();
25993        self.generate_expression(&e.this)?;
25994
25995        if let Some(dtype) = &e.dtype {
25996            self.write_space();
25997            self.write_keyword("SET DATA TYPE");
25998            self.write_space();
25999            self.generate_expression(dtype)?;
26000            if let Some(collate) = &e.collate {
26001                self.write_space();
26002                self.write_keyword("COLLATE");
26003                self.write_space();
26004                self.generate_expression(collate)?;
26005            }
26006            if let Some(using) = &e.using {
26007                self.write_space();
26008                self.write_keyword("USING");
26009                self.write_space();
26010                self.generate_expression(using)?;
26011            }
26012        } else if let Some(default) = &e.default {
26013            self.write_space();
26014            self.write_keyword("SET DEFAULT");
26015            self.write_space();
26016            self.generate_expression(default)?;
26017        } else if let Some(comment) = &e.comment {
26018            self.write_space();
26019            self.write_keyword("COMMENT");
26020            self.write_space();
26021            self.generate_expression(comment)?;
26022        } else if let Some(drop) = &e.drop {
26023            self.write_space();
26024            self.write_keyword("DROP");
26025            self.write_space();
26026            self.generate_expression(drop)?;
26027        } else if let Some(visible) = &e.visible {
26028            self.write_space();
26029            self.generate_expression(visible)?;
26030        } else if let Some(rename_to) = &e.rename_to {
26031            self.write_space();
26032            self.write_keyword("RENAME TO");
26033            self.write_space();
26034            self.generate_expression(rename_to)?;
26035        } else if let Some(allow_null) = &e.allow_null {
26036            self.write_space();
26037            self.generate_expression(allow_null)?;
26038        }
26039        Ok(())
26040    }
26041
26042    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
26043        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
26044        self.write_keyword("ALTER SESSION");
26045        self.write_space();
26046        if e.unset.is_some() {
26047            self.write_keyword("UNSET");
26048        } else {
26049            self.write_keyword("SET");
26050        }
26051        self.write_space();
26052        for (i, expr) in e.expressions.iter().enumerate() {
26053            if i > 0 {
26054                self.write(", ");
26055            }
26056            self.generate_expression(expr)?;
26057        }
26058        Ok(())
26059    }
26060
26061    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
26062        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
26063        self.write_keyword("SET");
26064
26065        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
26066        if let Some(opt) = &e.option {
26067            self.write_space();
26068            self.generate_expression(opt)?;
26069        }
26070
26071        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
26072        // Check if expressions look like property assignments
26073        if !e.expressions.is_empty() {
26074            // Check if this looks like property assignments (for SET PROPERTIES)
26075            let is_properties = e
26076                .expressions
26077                .iter()
26078                .any(|expr| matches!(expr, Expression::Eq(_)));
26079            if is_properties && e.option.is_none() {
26080                self.write_space();
26081                self.write_keyword("PROPERTIES");
26082            }
26083            self.write_space();
26084            for (i, expr) in e.expressions.iter().enumerate() {
26085                if i > 0 {
26086                    self.write(", ");
26087                }
26088                self.generate_expression(expr)?;
26089            }
26090        }
26091
26092        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
26093        if let Some(file_format) = &e.file_format {
26094            self.write(" ");
26095            self.write_keyword("STAGE_FILE_FORMAT");
26096            self.write(" = (");
26097            self.generate_space_separated_properties(file_format)?;
26098            self.write(")");
26099        }
26100
26101        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
26102        if let Some(copy_options) = &e.copy_options {
26103            self.write(" ");
26104            self.write_keyword("STAGE_COPY_OPTIONS");
26105            self.write(" = (");
26106            self.generate_space_separated_properties(copy_options)?;
26107            self.write(")");
26108        }
26109
26110        // Generate TAG ...
26111        if let Some(tag) = &e.tag {
26112            self.write(" ");
26113            self.write_keyword("TAG");
26114            self.write(" ");
26115            self.generate_expression(tag)?;
26116        }
26117
26118        Ok(())
26119    }
26120
26121    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
26122    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
26123        match expr {
26124            Expression::Tuple(t) => {
26125                for (i, prop) in t.expressions.iter().enumerate() {
26126                    if i > 0 {
26127                        self.write(" ");
26128                    }
26129                    self.generate_expression(prop)?;
26130                }
26131            }
26132            _ => {
26133                self.generate_expression(expr)?;
26134            }
26135        }
26136        Ok(())
26137    }
26138
26139    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
26140        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
26141        self.write_keyword("ALTER");
26142        if e.compound.is_some() {
26143            self.write_space();
26144            self.write_keyword("COMPOUND");
26145        }
26146        self.write_space();
26147        self.write_keyword("SORTKEY");
26148        self.write_space();
26149        if let Some(this) = &e.this {
26150            self.generate_expression(this)?;
26151        } else if !e.expressions.is_empty() {
26152            self.write("(");
26153            for (i, expr) in e.expressions.iter().enumerate() {
26154                if i > 0 {
26155                    self.write(", ");
26156                }
26157                self.generate_expression(expr)?;
26158            }
26159            self.write(")");
26160        }
26161        Ok(())
26162    }
26163
26164    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
26165        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
26166        self.write_keyword("ANALYZE");
26167        if !e.options.is_empty() {
26168            self.write_space();
26169            for (i, opt) in e.options.iter().enumerate() {
26170                if i > 0 {
26171                    self.write_space();
26172                }
26173                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
26174                if let Expression::Identifier(id) = opt {
26175                    self.write_keyword(&id.name);
26176                } else {
26177                    self.generate_expression(opt)?;
26178                }
26179            }
26180        }
26181        if let Some(kind) = &e.kind {
26182            self.write_space();
26183            self.write_keyword(kind);
26184        }
26185        if let Some(this) = &e.this {
26186            self.write_space();
26187            self.generate_expression(this)?;
26188        }
26189        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
26190        if !e.columns.is_empty() {
26191            self.write("(");
26192            for (i, col) in e.columns.iter().enumerate() {
26193                if i > 0 {
26194                    self.write(", ");
26195                }
26196                self.write(col);
26197            }
26198            self.write(")");
26199        }
26200        if let Some(partition) = &e.partition {
26201            self.write_space();
26202            self.generate_expression(partition)?;
26203        }
26204        if let Some(mode) = &e.mode {
26205            self.write_space();
26206            self.generate_expression(mode)?;
26207        }
26208        if let Some(expression) = &e.expression {
26209            self.write_space();
26210            self.generate_expression(expression)?;
26211        }
26212        if !e.properties.is_empty() {
26213            self.write_space();
26214            self.write_keyword(self.config.with_properties_prefix);
26215            self.write(" (");
26216            for (i, prop) in e.properties.iter().enumerate() {
26217                if i > 0 {
26218                    self.write(", ");
26219                }
26220                self.generate_expression(prop)?;
26221            }
26222            self.write(")");
26223        }
26224        Ok(())
26225    }
26226
26227    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
26228        // Python: return f"DELETE{kind} STATISTICS"
26229        self.write_keyword("DELETE");
26230        if let Some(kind) = &e.kind {
26231            self.write_space();
26232            self.write_keyword(kind);
26233        }
26234        self.write_space();
26235        self.write_keyword("STATISTICS");
26236        Ok(())
26237    }
26238
26239    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
26240        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
26241        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
26242        if let Expression::Identifier(id) = e.this.as_ref() {
26243            self.write_keyword(&id.name);
26244        } else {
26245            self.generate_expression(&e.this)?;
26246        }
26247        self.write_space();
26248        self.write_keyword("HISTOGRAM ON");
26249        self.write_space();
26250        for (i, expr) in e.expressions.iter().enumerate() {
26251            if i > 0 {
26252                self.write(", ");
26253            }
26254            self.generate_expression(expr)?;
26255        }
26256        if let Some(expression) = &e.expression {
26257            self.write_space();
26258            self.generate_expression(expression)?;
26259        }
26260        if let Some(update_options) = &e.update_options {
26261            self.write_space();
26262            self.generate_expression(update_options)?;
26263            self.write_space();
26264            self.write_keyword("UPDATE");
26265        }
26266        Ok(())
26267    }
26268
26269    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
26270        // Python: return f"LIST CHAINED ROWS{inner_expression}"
26271        self.write_keyword("LIST CHAINED ROWS");
26272        if let Some(expression) = &e.expression {
26273            self.write_space();
26274            self.write_keyword("INTO");
26275            self.write_space();
26276            self.generate_expression(expression)?;
26277        }
26278        Ok(())
26279    }
26280
26281    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
26282        // Python: return f"SAMPLE {sample} {kind}"
26283        self.write_keyword("SAMPLE");
26284        self.write_space();
26285        if let Some(sample) = &e.sample {
26286            self.generate_expression(sample)?;
26287            self.write_space();
26288        }
26289        self.write_keyword(&e.kind);
26290        Ok(())
26291    }
26292
26293    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
26294        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
26295        self.write_keyword(&e.kind);
26296        if let Some(option) = &e.option {
26297            self.write_space();
26298            self.generate_expression(option)?;
26299        }
26300        self.write_space();
26301        self.write_keyword("STATISTICS");
26302        if let Some(this) = &e.this {
26303            self.write_space();
26304            self.generate_expression(this)?;
26305        }
26306        if !e.expressions.is_empty() {
26307            self.write_space();
26308            for (i, expr) in e.expressions.iter().enumerate() {
26309                if i > 0 {
26310                    self.write(", ");
26311                }
26312                self.generate_expression(expr)?;
26313            }
26314        }
26315        Ok(())
26316    }
26317
26318    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
26319        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
26320        self.write_keyword("VALIDATE");
26321        self.write_space();
26322        self.write_keyword(&e.kind);
26323        if let Some(this) = &e.this {
26324            self.write_space();
26325            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
26326            if let Expression::Identifier(id) = this.as_ref() {
26327                self.write_keyword(&id.name);
26328            } else {
26329                self.generate_expression(this)?;
26330            }
26331        }
26332        if let Some(expression) = &e.expression {
26333            self.write_space();
26334            self.write_keyword("INTO");
26335            self.write_space();
26336            self.generate_expression(expression)?;
26337        }
26338        Ok(())
26339    }
26340
26341    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
26342        // Python: return f"WITH {expressions}"
26343        self.write_keyword("WITH");
26344        self.write_space();
26345        for (i, expr) in e.expressions.iter().enumerate() {
26346            if i > 0 {
26347                self.write(", ");
26348            }
26349            self.generate_expression(expr)?;
26350        }
26351        Ok(())
26352    }
26353
26354    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
26355        // Anonymous represents a generic function call: FUNC_NAME(args...)
26356        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
26357        self.generate_expression(&e.this)?;
26358        self.write("(");
26359        for (i, arg) in e.expressions.iter().enumerate() {
26360            if i > 0 {
26361                self.write(", ");
26362            }
26363            self.generate_expression(arg)?;
26364        }
26365        self.write(")");
26366        Ok(())
26367    }
26368
26369    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
26370        // Same as Anonymous but for aggregate functions
26371        self.generate_expression(&e.this)?;
26372        self.write("(");
26373        for (i, arg) in e.expressions.iter().enumerate() {
26374            if i > 0 {
26375                self.write(", ");
26376            }
26377            self.generate_expression(arg)?;
26378        }
26379        self.write(")");
26380        Ok(())
26381    }
26382
26383    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
26384        // Python: return f"{this} APPLY({expr})"
26385        self.generate_expression(&e.this)?;
26386        self.write_space();
26387        self.write_keyword("APPLY");
26388        self.write("(");
26389        self.generate_expression(&e.expression)?;
26390        self.write(")");
26391        Ok(())
26392    }
26393
26394    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
26395        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
26396        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
26397        self.write("(");
26398        self.generate_expression(&e.this)?;
26399        if let Some(percentile) = &e.percentile {
26400            self.write(", ");
26401            self.generate_expression(percentile)?;
26402        }
26403        self.write(")");
26404        Ok(())
26405    }
26406
26407    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
26408        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
26409        self.write_keyword("APPROX_QUANTILE");
26410        self.write("(");
26411        self.generate_expression(&e.this)?;
26412        if let Some(quantile) = &e.quantile {
26413            self.write(", ");
26414            self.generate_expression(quantile)?;
26415        }
26416        if let Some(accuracy) = &e.accuracy {
26417            self.write(", ");
26418            self.generate_expression(accuracy)?;
26419        }
26420        if let Some(weight) = &e.weight {
26421            self.write(", ");
26422            self.generate_expression(weight)?;
26423        }
26424        self.write(")");
26425        Ok(())
26426    }
26427
26428    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
26429        // APPROX_QUANTILES(this, expression)
26430        self.write_keyword("APPROX_QUANTILES");
26431        self.write("(");
26432        self.generate_expression(&e.this)?;
26433        if let Some(expression) = &e.expression {
26434            self.write(", ");
26435            self.generate_expression(expression)?;
26436        }
26437        self.write(")");
26438        Ok(())
26439    }
26440
26441    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
26442        // APPROX_TOP_K(this[, expression][, counters])
26443        self.write_keyword("APPROX_TOP_K");
26444        self.write("(");
26445        self.generate_expression(&e.this)?;
26446        if let Some(expression) = &e.expression {
26447            self.write(", ");
26448            self.generate_expression(expression)?;
26449        }
26450        if let Some(counters) = &e.counters {
26451            self.write(", ");
26452            self.generate_expression(counters)?;
26453        }
26454        self.write(")");
26455        Ok(())
26456    }
26457
26458    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
26459        // APPROX_TOP_K_ACCUMULATE(this[, expression])
26460        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
26461        self.write("(");
26462        self.generate_expression(&e.this)?;
26463        if let Some(expression) = &e.expression {
26464            self.write(", ");
26465            self.generate_expression(expression)?;
26466        }
26467        self.write(")");
26468        Ok(())
26469    }
26470
26471    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
26472        // APPROX_TOP_K_COMBINE(this[, expression])
26473        self.write_keyword("APPROX_TOP_K_COMBINE");
26474        self.write("(");
26475        self.generate_expression(&e.this)?;
26476        if let Some(expression) = &e.expression {
26477            self.write(", ");
26478            self.generate_expression(expression)?;
26479        }
26480        self.write(")");
26481        Ok(())
26482    }
26483
26484    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
26485        // APPROX_TOP_K_ESTIMATE(this[, expression])
26486        self.write_keyword("APPROX_TOP_K_ESTIMATE");
26487        self.write("(");
26488        self.generate_expression(&e.this)?;
26489        if let Some(expression) = &e.expression {
26490            self.write(", ");
26491            self.generate_expression(expression)?;
26492        }
26493        self.write(")");
26494        Ok(())
26495    }
26496
26497    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26498        // APPROX_TOP_SUM(this, expression[, count])
26499        self.write_keyword("APPROX_TOP_SUM");
26500        self.write("(");
26501        self.generate_expression(&e.this)?;
26502        self.write(", ");
26503        self.generate_expression(&e.expression)?;
26504        if let Some(count) = &e.count {
26505            self.write(", ");
26506            self.generate_expression(count)?;
26507        }
26508        self.write(")");
26509        Ok(())
26510    }
26511
26512    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26513        // ARG_MAX(this, expression[, count])
26514        self.write_keyword("ARG_MAX");
26515        self.write("(");
26516        self.generate_expression(&e.this)?;
26517        self.write(", ");
26518        self.generate_expression(&e.expression)?;
26519        if let Some(count) = &e.count {
26520            self.write(", ");
26521            self.generate_expression(count)?;
26522        }
26523        self.write(")");
26524        Ok(())
26525    }
26526
26527    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26528        // ARG_MIN(this, expression[, count])
26529        self.write_keyword("ARG_MIN");
26530        self.write("(");
26531        self.generate_expression(&e.this)?;
26532        self.write(", ");
26533        self.generate_expression(&e.expression)?;
26534        if let Some(count) = &e.count {
26535            self.write(", ");
26536            self.generate_expression(count)?;
26537        }
26538        self.write(")");
26539        Ok(())
26540    }
26541
26542    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26543        // ARRAY_ALL(this, expression)
26544        self.write_keyword("ARRAY_ALL");
26545        self.write("(");
26546        self.generate_expression(&e.this)?;
26547        self.write(", ");
26548        self.generate_expression(&e.expression)?;
26549        self.write(")");
26550        Ok(())
26551    }
26552
26553    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26554        // ARRAY_ANY(this, expression) - fallback implementation
26555        self.write_keyword("ARRAY_ANY");
26556        self.write("(");
26557        self.generate_expression(&e.this)?;
26558        self.write(", ");
26559        self.generate_expression(&e.expression)?;
26560        self.write(")");
26561        Ok(())
26562    }
26563
26564    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26565        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26566        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26567        self.write("(");
26568        for (i, expr) in e.expressions.iter().enumerate() {
26569            if i > 0 {
26570                self.write(", ");
26571            }
26572            self.generate_expression(expr)?;
26573        }
26574        self.write(")");
26575        Ok(())
26576    }
26577
26578    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26579        // ARRAY_SUM(this[, expression])
26580        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26581            self.write("arraySum");
26582        } else {
26583            self.write_keyword("ARRAY_SUM");
26584        }
26585        self.write("(");
26586        self.generate_expression(&e.this)?;
26587        if let Some(expression) = &e.expression {
26588            self.write(", ");
26589            self.generate_expression(expression)?;
26590        }
26591        self.write(")");
26592        Ok(())
26593    }
26594
26595    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26596        // Python: return f"{this} AT {index}"
26597        self.generate_expression(&e.this)?;
26598        self.write_space();
26599        self.write_keyword("AT");
26600        self.write_space();
26601        self.generate_expression(&e.expression)?;
26602        Ok(())
26603    }
26604
26605    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26606        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26607        self.write_keyword("ATTACH");
26608        if e.exists {
26609            self.write_space();
26610            self.write_keyword("IF NOT EXISTS");
26611        }
26612        self.write_space();
26613        self.generate_expression(&e.this)?;
26614        if !e.expressions.is_empty() {
26615            self.write(" (");
26616            for (i, expr) in e.expressions.iter().enumerate() {
26617                if i > 0 {
26618                    self.write(", ");
26619                }
26620                self.generate_expression(expr)?;
26621            }
26622            self.write(")");
26623        }
26624        Ok(())
26625    }
26626
26627    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26628        // AttachOption: this [expression]
26629        // Python sqlglot: no equals sign, just space-separated
26630        self.generate_expression(&e.this)?;
26631        if let Some(expression) = &e.expression {
26632            self.write_space();
26633            self.generate_expression(expression)?;
26634        }
26635        Ok(())
26636    }
26637
26638    /// Generate the auto_increment keyword and options for a column definition.
26639    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26640    /// GENERATED AS IDENTITY, etc.
26641    fn generate_auto_increment_keyword(
26642        &mut self,
26643        col: &crate::expressions::ColumnDef,
26644    ) -> Result<()> {
26645        use crate::dialects::DialectType;
26646        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26647            self.write_keyword("IDENTITY");
26648            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26649                self.write("(");
26650                if let Some(ref start) = col.auto_increment_start {
26651                    self.generate_expression(start)?;
26652                } else {
26653                    self.write("0");
26654                }
26655                self.write(", ");
26656                if let Some(ref inc) = col.auto_increment_increment {
26657                    self.generate_expression(inc)?;
26658                } else {
26659                    self.write("1");
26660                }
26661                self.write(")");
26662            }
26663        } else if matches!(
26664            self.config.dialect,
26665            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26666        ) {
26667            self.write_keyword("AUTOINCREMENT");
26668            if let Some(ref start) = col.auto_increment_start {
26669                self.write_space();
26670                self.write_keyword("START");
26671                self.write_space();
26672                self.generate_expression(start)?;
26673            }
26674            if let Some(ref inc) = col.auto_increment_increment {
26675                self.write_space();
26676                self.write_keyword("INCREMENT");
26677                self.write_space();
26678                self.generate_expression(inc)?;
26679            }
26680            if let Some(order) = col.auto_increment_order {
26681                self.write_space();
26682                if order {
26683                    self.write_keyword("ORDER");
26684                } else {
26685                    self.write_keyword("NOORDER");
26686                }
26687            }
26688        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26689            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26690            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26691                self.write(" (");
26692                let mut first = true;
26693                if let Some(ref start) = col.auto_increment_start {
26694                    self.write_keyword("START WITH");
26695                    self.write_space();
26696                    self.generate_expression(start)?;
26697                    first = false;
26698                }
26699                if let Some(ref inc) = col.auto_increment_increment {
26700                    if !first {
26701                        self.write_space();
26702                    }
26703                    self.write_keyword("INCREMENT BY");
26704                    self.write_space();
26705                    self.generate_expression(inc)?;
26706                }
26707                self.write(")");
26708            }
26709        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26710            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26711            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26712            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26713                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26714            } else {
26715                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26716            }
26717            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26718                self.write(" (");
26719                let mut first = true;
26720                if let Some(ref start) = col.auto_increment_start {
26721                    self.write_keyword("START WITH");
26722                    self.write_space();
26723                    self.generate_expression(start)?;
26724                    first = false;
26725                }
26726                if let Some(ref inc) = col.auto_increment_increment {
26727                    if !first {
26728                        self.write_space();
26729                    }
26730                    self.write_keyword("INCREMENT BY");
26731                    self.write_space();
26732                    self.generate_expression(inc)?;
26733                }
26734                self.write(")");
26735            }
26736        } else if matches!(
26737            self.config.dialect,
26738            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26739        ) {
26740            self.write_keyword("IDENTITY");
26741            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26742                self.write("(");
26743                if let Some(ref start) = col.auto_increment_start {
26744                    self.generate_expression(start)?;
26745                } else {
26746                    self.write("0");
26747                }
26748                self.write(", ");
26749                if let Some(ref inc) = col.auto_increment_increment {
26750                    self.generate_expression(inc)?;
26751                } else {
26752                    self.write("1");
26753                }
26754                self.write(")");
26755            }
26756        } else {
26757            self.write_keyword("AUTO_INCREMENT");
26758            if let Some(ref start) = col.auto_increment_start {
26759                self.write_space();
26760                self.write_keyword("START");
26761                self.write_space();
26762                self.generate_expression(start)?;
26763            }
26764            if let Some(ref inc) = col.auto_increment_increment {
26765                self.write_space();
26766                self.write_keyword("INCREMENT");
26767                self.write_space();
26768                self.generate_expression(inc)?;
26769            }
26770            if let Some(order) = col.auto_increment_order {
26771                self.write_space();
26772                if order {
26773                    self.write_keyword("ORDER");
26774                } else {
26775                    self.write_keyword("NOORDER");
26776                }
26777            }
26778        }
26779        Ok(())
26780    }
26781
26782    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26783        // AUTO_INCREMENT=value
26784        self.write_keyword("AUTO_INCREMENT");
26785        self.write("=");
26786        self.generate_expression(&e.this)?;
26787        Ok(())
26788    }
26789
26790    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26791        // AUTO_REFRESH=value
26792        self.write_keyword("AUTO_REFRESH");
26793        self.write("=");
26794        self.generate_expression(&e.this)?;
26795        Ok(())
26796    }
26797
26798    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26799        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26800        self.write_keyword("BACKUP");
26801        self.write_space();
26802        self.generate_expression(&e.this)?;
26803        Ok(())
26804    }
26805
26806    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26807        // BASE64_DECODE_BINARY(this[, alphabet])
26808        self.write_keyword("BASE64_DECODE_BINARY");
26809        self.write("(");
26810        self.generate_expression(&e.this)?;
26811        if let Some(alphabet) = &e.alphabet {
26812            self.write(", ");
26813            self.generate_expression(alphabet)?;
26814        }
26815        self.write(")");
26816        Ok(())
26817    }
26818
26819    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26820        // BASE64_DECODE_STRING(this[, alphabet])
26821        self.write_keyword("BASE64_DECODE_STRING");
26822        self.write("(");
26823        self.generate_expression(&e.this)?;
26824        if let Some(alphabet) = &e.alphabet {
26825            self.write(", ");
26826            self.generate_expression(alphabet)?;
26827        }
26828        self.write(")");
26829        Ok(())
26830    }
26831
26832    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26833        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26834        self.write_keyword("BASE64_ENCODE");
26835        self.write("(");
26836        self.generate_expression(&e.this)?;
26837        if let Some(max_line_length) = &e.max_line_length {
26838            self.write(", ");
26839            self.generate_expression(max_line_length)?;
26840        }
26841        if let Some(alphabet) = &e.alphabet {
26842            self.write(", ");
26843            self.generate_expression(alphabet)?;
26844        }
26845        self.write(")");
26846        Ok(())
26847    }
26848
26849    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
26850        // BLOCKCOMPRESSION=... (complex Teradata property)
26851        self.write_keyword("BLOCKCOMPRESSION");
26852        self.write("=");
26853        if let Some(autotemp) = &e.autotemp {
26854            self.write_keyword("AUTOTEMP");
26855            self.write("(");
26856            self.generate_expression(autotemp)?;
26857            self.write(")");
26858        }
26859        if let Some(always) = &e.always {
26860            self.generate_expression(always)?;
26861        }
26862        if let Some(default) = &e.default {
26863            self.generate_expression(default)?;
26864        }
26865        if let Some(manual) = &e.manual {
26866            self.generate_expression(manual)?;
26867        }
26868        if let Some(never) = &e.never {
26869            self.generate_expression(never)?;
26870        }
26871        Ok(())
26872    }
26873
26874    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
26875        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
26876        self.write("((");
26877        self.generate_expression(&e.this)?;
26878        self.write(") ");
26879        self.write_keyword("AND");
26880        self.write(" (");
26881        self.generate_expression(&e.expression)?;
26882        self.write("))");
26883        Ok(())
26884    }
26885
26886    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26887        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26888        self.write("((");
26889        self.generate_expression(&e.this)?;
26890        self.write(") ");
26891        self.write_keyword("OR");
26892        self.write(" (");
26893        self.generate_expression(&e.expression)?;
26894        self.write("))");
26895        Ok(())
26896    }
26897
26898    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26899        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26900        self.write_keyword("BUILD");
26901        self.write_space();
26902        self.generate_expression(&e.this)?;
26903        Ok(())
26904    }
26905
26906    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26907        // Byte string literal like B'...' or X'...'
26908        self.generate_expression(&e.this)?;
26909        Ok(())
26910    }
26911
26912    fn generate_case_specific_column_constraint(
26913        &mut self,
26914        e: &CaseSpecificColumnConstraint,
26915    ) -> Result<()> {
26916        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26917        if e.not_.is_some() {
26918            self.write_keyword("NOT");
26919            self.write_space();
26920        }
26921        self.write_keyword("CASESPECIFIC");
26922        Ok(())
26923    }
26924
26925    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26926        // Cast to string type (dialect-specific)
26927        self.write_keyword("CAST");
26928        self.write("(");
26929        self.generate_expression(&e.this)?;
26930        if self.config.dialect == Some(DialectType::ClickHouse) {
26931            // ClickHouse: CAST(expr, 'type_string')
26932            self.write(", ");
26933        } else {
26934            self.write_space();
26935            self.write_keyword("AS");
26936            self.write_space();
26937        }
26938        if let Some(to) = &e.to {
26939            self.generate_expression(to)?;
26940        }
26941        self.write(")");
26942        Ok(())
26943    }
26944
26945    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26946        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26947        // Python: f"CHANGES ({information}){at_before}{end}"
26948        self.write_keyword("CHANGES");
26949        self.write(" (");
26950        if let Some(information) = &e.information {
26951            self.write_keyword("INFORMATION");
26952            self.write(" => ");
26953            self.generate_expression(information)?;
26954        }
26955        self.write(")");
26956        // at_before and end are HistoricalData expressions that generate their own keywords
26957        if let Some(at_before) = &e.at_before {
26958            self.write(" ");
26959            self.generate_expression(at_before)?;
26960        }
26961        if let Some(end) = &e.end {
26962            self.write(" ");
26963            self.generate_expression(end)?;
26964        }
26965        Ok(())
26966    }
26967
26968    fn generate_character_set_column_constraint(
26969        &mut self,
26970        e: &CharacterSetColumnConstraint,
26971    ) -> Result<()> {
26972        // CHARACTER SET charset_name
26973        self.write_keyword("CHARACTER SET");
26974        self.write_space();
26975        self.generate_expression(&e.this)?;
26976        Ok(())
26977    }
26978
26979    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26980        // [DEFAULT] CHARACTER SET=value
26981        if e.default.is_some() {
26982            self.write_keyword("DEFAULT");
26983            self.write_space();
26984        }
26985        self.write_keyword("CHARACTER SET");
26986        self.write("=");
26987        self.generate_expression(&e.this)?;
26988        Ok(())
26989    }
26990
26991    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26992        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26993        self.write_keyword("CHECK");
26994        self.write(" (");
26995        self.generate_expression(&e.this)?;
26996        self.write(")");
26997        if e.enforced.is_some() {
26998            self.write_space();
26999            self.write_keyword("ENFORCED");
27000        }
27001        Ok(())
27002    }
27003
27004    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
27005        // Python: return f"ASSUME ({self.sql(e, 'this')})"
27006        self.write_keyword("ASSUME");
27007        self.write(" (");
27008        self.generate_expression(&e.this)?;
27009        self.write(")");
27010        Ok(())
27011    }
27012
27013    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
27014        // CHECK_JSON(this)
27015        self.write_keyword("CHECK_JSON");
27016        self.write("(");
27017        self.generate_expression(&e.this)?;
27018        self.write(")");
27019        Ok(())
27020    }
27021
27022    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
27023        // CHECK_XML(this)
27024        self.write_keyword("CHECK_XML");
27025        self.write("(");
27026        self.generate_expression(&e.this)?;
27027        self.write(")");
27028        Ok(())
27029    }
27030
27031    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
27032        // CHECKSUM=[ON|OFF|DEFAULT]
27033        self.write_keyword("CHECKSUM");
27034        self.write("=");
27035        if e.on.is_some() {
27036            self.write_keyword("ON");
27037        } else if e.default.is_some() {
27038            self.write_keyword("DEFAULT");
27039        } else {
27040            self.write_keyword("OFF");
27041        }
27042        Ok(())
27043    }
27044
27045    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
27046        // Python: return f"{shallow}{keyword} {this}"
27047        if e.shallow.is_some() {
27048            self.write_keyword("SHALLOW");
27049            self.write_space();
27050        }
27051        if e.copy.is_some() {
27052            self.write_keyword("COPY");
27053        } else {
27054            self.write_keyword("CLONE");
27055        }
27056        self.write_space();
27057        self.generate_expression(&e.this)?;
27058        Ok(())
27059    }
27060
27061    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
27062        // CLUSTER BY (expressions)
27063        self.write_keyword("CLUSTER BY");
27064        self.write(" (");
27065        for (i, ord) in e.expressions.iter().enumerate() {
27066            if i > 0 {
27067                self.write(", ");
27068            }
27069            self.generate_ordered(ord)?;
27070        }
27071        self.write(")");
27072        Ok(())
27073    }
27074
27075    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
27076        // BigQuery table property: CLUSTER BY col1, col2
27077        self.write_keyword("CLUSTER BY");
27078        self.write_space();
27079        for (i, col) in e.columns.iter().enumerate() {
27080            if i > 0 {
27081                self.write(", ");
27082            }
27083            self.generate_identifier(col)?;
27084        }
27085        Ok(())
27086    }
27087
27088    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
27089        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
27090        self.write_keyword("CLUSTERED BY");
27091        self.write(" (");
27092        for (i, expr) in e.expressions.iter().enumerate() {
27093            if i > 0 {
27094                self.write(", ");
27095            }
27096            self.generate_expression(expr)?;
27097        }
27098        self.write(")");
27099        if let Some(sorted_by) = &e.sorted_by {
27100            self.write_space();
27101            self.write_keyword("SORTED BY");
27102            self.write(" (");
27103            // Unwrap Tuple to avoid double parentheses
27104            if let Expression::Tuple(t) = sorted_by.as_ref() {
27105                for (i, expr) in t.expressions.iter().enumerate() {
27106                    if i > 0 {
27107                        self.write(", ");
27108                    }
27109                    self.generate_expression(expr)?;
27110                }
27111            } else {
27112                self.generate_expression(sorted_by)?;
27113            }
27114            self.write(")");
27115        }
27116        if let Some(buckets) = &e.buckets {
27117            self.write_space();
27118            self.write_keyword("INTO");
27119            self.write_space();
27120            self.generate_expression(buckets)?;
27121            self.write_space();
27122            self.write_keyword("BUCKETS");
27123        }
27124        Ok(())
27125    }
27126
27127    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
27128        // [DEFAULT] COLLATE [=] value
27129        // BigQuery uses space: DEFAULT COLLATE 'en'
27130        // Others use equals: COLLATE='en'
27131        if e.default.is_some() {
27132            self.write_keyword("DEFAULT");
27133            self.write_space();
27134        }
27135        self.write_keyword("COLLATE");
27136        // BigQuery uses space between COLLATE and value
27137        match self.config.dialect {
27138            Some(DialectType::BigQuery) => self.write_space(),
27139            _ => self.write("="),
27140        }
27141        self.generate_expression(&e.this)?;
27142        Ok(())
27143    }
27144
27145    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
27146        // ColumnConstraint is an enum
27147        match e {
27148            ColumnConstraint::NotNull => {
27149                self.write_keyword("NOT NULL");
27150            }
27151            ColumnConstraint::Null => {
27152                self.write_keyword("NULL");
27153            }
27154            ColumnConstraint::Unique => {
27155                self.write_keyword("UNIQUE");
27156            }
27157            ColumnConstraint::PrimaryKey => {
27158                self.write_keyword("PRIMARY KEY");
27159            }
27160            ColumnConstraint::Default(expr) => {
27161                self.write_keyword("DEFAULT");
27162                self.write_space();
27163                self.generate_expression(expr)?;
27164            }
27165            ColumnConstraint::Check(expr) => {
27166                self.write_keyword("CHECK");
27167                self.write(" (");
27168                self.generate_expression(expr)?;
27169                self.write(")");
27170            }
27171            ColumnConstraint::References(fk_ref) => {
27172                if fk_ref.has_foreign_key_keywords {
27173                    self.write_keyword("FOREIGN KEY");
27174                    self.write_space();
27175                }
27176                self.write_keyword("REFERENCES");
27177                self.write_space();
27178                self.generate_table(&fk_ref.table)?;
27179                if !fk_ref.columns.is_empty() {
27180                    self.write(" (");
27181                    for (i, col) in fk_ref.columns.iter().enumerate() {
27182                        if i > 0 {
27183                            self.write(", ");
27184                        }
27185                        self.generate_identifier(col)?;
27186                    }
27187                    self.write(")");
27188                }
27189            }
27190            ColumnConstraint::GeneratedAsIdentity(gen) => {
27191                self.write_keyword("GENERATED");
27192                self.write_space();
27193                if gen.always {
27194                    self.write_keyword("ALWAYS");
27195                } else {
27196                    self.write_keyword("BY DEFAULT");
27197                    if gen.on_null {
27198                        self.write_space();
27199                        self.write_keyword("ON NULL");
27200                    }
27201                }
27202                self.write_space();
27203                self.write_keyword("AS IDENTITY");
27204            }
27205            ColumnConstraint::Collate(collation) => {
27206                self.write_keyword("COLLATE");
27207                self.write_space();
27208                self.generate_identifier(collation)?;
27209            }
27210            ColumnConstraint::Comment(comment) => {
27211                self.write_keyword("COMMENT");
27212                self.write(" '");
27213                self.write(comment);
27214                self.write("'");
27215            }
27216            ColumnConstraint::ComputedColumn(cc) => {
27217                self.generate_computed_column_inline(cc)?;
27218            }
27219            ColumnConstraint::GeneratedAsRow(gar) => {
27220                self.generate_generated_as_row_inline(gar)?;
27221            }
27222            ColumnConstraint::Tags(tags) => {
27223                self.write_keyword("TAG");
27224                self.write(" (");
27225                for (i, expr) in tags.expressions.iter().enumerate() {
27226                    if i > 0 {
27227                        self.write(", ");
27228                    }
27229                    self.generate_expression(expr)?;
27230                }
27231                self.write(")");
27232            }
27233            ColumnConstraint::Path(path_expr) => {
27234                self.write_keyword("PATH");
27235                self.write_space();
27236                self.generate_expression(path_expr)?;
27237            }
27238        }
27239        Ok(())
27240    }
27241
27242    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
27243        // ColumnPosition is an enum
27244        match e {
27245            ColumnPosition::First => {
27246                self.write_keyword("FIRST");
27247            }
27248            ColumnPosition::After(ident) => {
27249                self.write_keyword("AFTER");
27250                self.write_space();
27251                self.generate_identifier(ident)?;
27252            }
27253        }
27254        Ok(())
27255    }
27256
27257    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
27258        // column(prefix)
27259        self.generate_expression(&e.this)?;
27260        self.write("(");
27261        self.generate_expression(&e.expression)?;
27262        self.write(")");
27263        Ok(())
27264    }
27265
27266    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
27267        // If unpack is true, this came from * COLUMNS(pattern)
27268        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
27269        if let Some(ref unpack) = e.unpack {
27270            if let Expression::Boolean(b) = unpack.as_ref() {
27271                if b.value {
27272                    self.write("*");
27273                }
27274            }
27275        }
27276        self.write_keyword("COLUMNS");
27277        self.write("(");
27278        self.generate_expression(&e.this)?;
27279        self.write(")");
27280        Ok(())
27281    }
27282
27283    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
27284        // Combined aggregate: FUNC(args) combined
27285        self.generate_expression(&e.this)?;
27286        self.write("(");
27287        for (i, expr) in e.expressions.iter().enumerate() {
27288            if i > 0 {
27289                self.write(", ");
27290            }
27291            self.generate_expression(expr)?;
27292        }
27293        self.write(")");
27294        Ok(())
27295    }
27296
27297    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
27298        // Combined parameterized aggregate: FUNC(params)(expressions)
27299        self.generate_expression(&e.this)?;
27300        self.write("(");
27301        for (i, param) in e.params.iter().enumerate() {
27302            if i > 0 {
27303                self.write(", ");
27304            }
27305            self.generate_expression(param)?;
27306        }
27307        self.write(")(");
27308        for (i, expr) in e.expressions.iter().enumerate() {
27309            if i > 0 {
27310                self.write(", ");
27311            }
27312            self.generate_expression(expr)?;
27313        }
27314        self.write(")");
27315        Ok(())
27316    }
27317
27318    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
27319        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
27320        self.write_keyword("COMMIT");
27321
27322        // TSQL always uses COMMIT TRANSACTION
27323        if e.this.is_none()
27324            && matches!(
27325                self.config.dialect,
27326                Some(DialectType::TSQL) | Some(DialectType::Fabric)
27327            )
27328        {
27329            self.write_space();
27330            self.write_keyword("TRANSACTION");
27331        }
27332
27333        // Check if this has TRANSACTION keyword or transaction name
27334        if let Some(this) = &e.this {
27335            // Check if it's just the "TRANSACTION" marker or an actual transaction name
27336            let is_transaction_marker = matches!(
27337                this.as_ref(),
27338                Expression::Identifier(id) if id.name == "TRANSACTION"
27339            );
27340
27341            self.write_space();
27342            self.write_keyword("TRANSACTION");
27343
27344            // If it's a real transaction name, output it
27345            if !is_transaction_marker {
27346                self.write_space();
27347                self.generate_expression(this)?;
27348            }
27349        }
27350
27351        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
27352        if let Some(durability) = &e.durability {
27353            self.write_space();
27354            self.write_keyword("WITH");
27355            self.write(" (");
27356            self.write_keyword("DELAYED_DURABILITY");
27357            self.write(" = ");
27358            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
27359                self.write_keyword("ON");
27360            } else {
27361                self.write_keyword("OFF");
27362            }
27363            self.write(")");
27364        }
27365
27366        // Output AND [NO] CHAIN
27367        if let Some(chain) = &e.chain {
27368            self.write_space();
27369            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
27370                self.write_keyword("AND NO CHAIN");
27371            } else {
27372                self.write_keyword("AND CHAIN");
27373            }
27374        }
27375        Ok(())
27376    }
27377
27378    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
27379        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
27380        self.write("[");
27381        self.generate_expression(&e.this)?;
27382        self.write_space();
27383        self.write_keyword("FOR");
27384        self.write_space();
27385        self.generate_expression(&e.expression)?;
27386        // Handle optional position variable (for enumerate-like syntax)
27387        if let Some(pos) = &e.position {
27388            self.write(", ");
27389            self.generate_expression(pos)?;
27390        }
27391        if let Some(iterator) = &e.iterator {
27392            self.write_space();
27393            self.write_keyword("IN");
27394            self.write_space();
27395            self.generate_expression(iterator)?;
27396        }
27397        if let Some(condition) = &e.condition {
27398            self.write_space();
27399            self.write_keyword("IF");
27400            self.write_space();
27401            self.generate_expression(condition)?;
27402        }
27403        self.write("]");
27404        Ok(())
27405    }
27406
27407    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
27408        // COMPRESS(this[, method])
27409        self.write_keyword("COMPRESS");
27410        self.write("(");
27411        self.generate_expression(&e.this)?;
27412        if let Some(method) = &e.method {
27413            self.write(", '");
27414            self.write(method);
27415            self.write("'");
27416        }
27417        self.write(")");
27418        Ok(())
27419    }
27420
27421    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
27422        // Python: return f"COMPRESS {this}"
27423        self.write_keyword("COMPRESS");
27424        if let Some(this) = &e.this {
27425            self.write_space();
27426            self.generate_expression(this)?;
27427        }
27428        Ok(())
27429    }
27430
27431    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
27432        // Python: return f"AS {this}{persisted}"
27433        self.write_keyword("AS");
27434        self.write_space();
27435        self.generate_expression(&e.this)?;
27436        if e.not_null.is_some() {
27437            self.write_space();
27438            self.write_keyword("PERSISTED NOT NULL");
27439        } else if e.persisted.is_some() {
27440            self.write_space();
27441            self.write_keyword("PERSISTED");
27442        }
27443        Ok(())
27444    }
27445
27446    /// Generate a ComputedColumn constraint inline within a column definition.
27447    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27448    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
27449    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
27450        let computed_expr = if matches!(
27451            self.config.dialect,
27452            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27453        ) {
27454            match &*cc.expression {
27455                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27456                {
27457                    let wrapped = Expression::Cast(Box::new(Cast {
27458                        this: y.this.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::Year(Box::new(UnaryFunc::new(wrapped)))
27467                }
27468                Expression::Function(f)
27469                    if f.name.eq_ignore_ascii_case("YEAR")
27470                        && f.args.len() == 1
27471                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27472                {
27473                    let wrapped = Expression::Cast(Box::new(Cast {
27474                        this: f.args[0].clone(),
27475                        to: DataType::Date,
27476                        trailing_comments: Vec::new(),
27477                        double_colon_syntax: false,
27478                        format: None,
27479                        default: None,
27480                        inferred_type: None,
27481                    }));
27482                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
27483                }
27484                _ => *cc.expression.clone(),
27485            }
27486        } else {
27487            *cc.expression.clone()
27488        };
27489
27490        match cc.persistence_kind.as_deref() {
27491            Some("STORED") | Some("VIRTUAL") => {
27492                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27493                self.write_keyword("GENERATED ALWAYS AS");
27494                self.write(" (");
27495                self.generate_expression(&computed_expr)?;
27496                self.write(")");
27497                self.write_space();
27498                if cc.persisted {
27499                    self.write_keyword("STORED");
27500                } else {
27501                    self.write_keyword("VIRTUAL");
27502                }
27503            }
27504            Some("PERSISTED") => {
27505                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27506                self.write_keyword("AS");
27507                self.write(" (");
27508                self.generate_expression(&computed_expr)?;
27509                self.write(")");
27510                self.write_space();
27511                self.write_keyword("PERSISTED");
27512                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27513                if let Some(ref dt) = cc.data_type {
27514                    self.write_space();
27515                    self.generate_data_type(dt)?;
27516                }
27517                if cc.not_null {
27518                    self.write_space();
27519                    self.write_keyword("NOT NULL");
27520                }
27521            }
27522            _ => {
27523                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27524                // TSQL computed column without PERSISTED: AS (expr)
27525                if matches!(
27526                    self.config.dialect,
27527                    Some(DialectType::Spark)
27528                        | Some(DialectType::Databricks)
27529                        | Some(DialectType::Hive)
27530                ) {
27531                    self.write_keyword("GENERATED ALWAYS AS");
27532                    self.write(" (");
27533                    self.generate_expression(&computed_expr)?;
27534                    self.write(")");
27535                } else if matches!(
27536                    self.config.dialect,
27537                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27538                ) {
27539                    self.write_keyword("AS");
27540                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27541                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27542                    if omit_parens {
27543                        self.write_space();
27544                        self.generate_expression(&computed_expr)?;
27545                    } else {
27546                        self.write(" (");
27547                        self.generate_expression(&computed_expr)?;
27548                        self.write(")");
27549                    }
27550                } else {
27551                    self.write_keyword("AS");
27552                    self.write(" (");
27553                    self.generate_expression(&computed_expr)?;
27554                    self.write(")");
27555                }
27556            }
27557        }
27558        Ok(())
27559    }
27560
27561    /// Generate a GeneratedAsRow constraint inline within a column definition.
27562    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27563    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27564        self.write_keyword("GENERATED ALWAYS AS ROW ");
27565        if gar.start {
27566            self.write_keyword("START");
27567        } else {
27568            self.write_keyword("END");
27569        }
27570        if gar.hidden {
27571            self.write_space();
27572            self.write_keyword("HIDDEN");
27573        }
27574        Ok(())
27575    }
27576
27577    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27578    fn generate_system_versioning_content(
27579        &mut self,
27580        e: &WithSystemVersioningProperty,
27581    ) -> Result<()> {
27582        let mut parts = Vec::new();
27583
27584        if let Some(this) = &e.this {
27585            let mut s = String::from("HISTORY_TABLE=");
27586            let mut gen = Generator::with_arc_config(self.config.clone());
27587            gen.generate_expression(this)?;
27588            s.push_str(&gen.output);
27589            parts.push(s);
27590        }
27591
27592        if let Some(data_consistency) = &e.data_consistency {
27593            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27594            let mut gen = Generator::with_arc_config(self.config.clone());
27595            gen.generate_expression(data_consistency)?;
27596            s.push_str(&gen.output);
27597            parts.push(s);
27598        }
27599
27600        if let Some(retention_period) = &e.retention_period {
27601            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27602            let mut gen = Generator::with_arc_config(self.config.clone());
27603            gen.generate_expression(retention_period)?;
27604            s.push_str(&gen.output);
27605            parts.push(s);
27606        }
27607
27608        self.write_keyword("SYSTEM_VERSIONING");
27609        self.write("=");
27610
27611        if !parts.is_empty() {
27612            self.write_keyword("ON");
27613            self.write("(");
27614            self.write(&parts.join(", "));
27615            self.write(")");
27616        } else if e.on.is_some() {
27617            self.write_keyword("ON");
27618        } else {
27619            self.write_keyword("OFF");
27620        }
27621
27622        Ok(())
27623    }
27624
27625    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27626        // Conditional INSERT for multi-table inserts
27627        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27628        if e.else_.is_some() {
27629            self.write_keyword("ELSE");
27630            self.write_space();
27631        } else if let Some(expression) = &e.expression {
27632            self.write_keyword("WHEN");
27633            self.write_space();
27634            self.generate_expression(expression)?;
27635            self.write_space();
27636            self.write_keyword("THEN");
27637            self.write_space();
27638        }
27639
27640        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27641        // without the "INSERT " prefix
27642        if let Expression::Insert(insert) = e.this.as_ref() {
27643            self.write_keyword("INTO");
27644            self.write_space();
27645            self.generate_table(&insert.table)?;
27646
27647            // Optional column list
27648            if !insert.columns.is_empty() {
27649                self.write(" (");
27650                for (i, col) in insert.columns.iter().enumerate() {
27651                    if i > 0 {
27652                        self.write(", ");
27653                    }
27654                    self.generate_identifier(col)?;
27655                }
27656                self.write(")");
27657            }
27658
27659            // Optional VALUES clause
27660            if !insert.values.is_empty() {
27661                self.write_space();
27662                self.write_keyword("VALUES");
27663                for (row_idx, row) in insert.values.iter().enumerate() {
27664                    if row_idx > 0 {
27665                        self.write(", ");
27666                    }
27667                    self.write(" (");
27668                    for (i, val) in row.iter().enumerate() {
27669                        if i > 0 {
27670                            self.write(", ");
27671                        }
27672                        self.generate_expression(val)?;
27673                    }
27674                    self.write(")");
27675                }
27676            }
27677        } else {
27678            // Fallback for non-Insert expressions
27679            self.generate_expression(&e.this)?;
27680        }
27681        Ok(())
27682    }
27683
27684    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27685        // Python: return f"CONSTRAINT {this} {expressions}"
27686        self.write_keyword("CONSTRAINT");
27687        self.write_space();
27688        self.generate_expression(&e.this)?;
27689        if !e.expressions.is_empty() {
27690            self.write_space();
27691            for (i, expr) in e.expressions.iter().enumerate() {
27692                if i > 0 {
27693                    self.write_space();
27694                }
27695                self.generate_expression(expr)?;
27696            }
27697        }
27698        Ok(())
27699    }
27700
27701    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27702        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27703        self.write_keyword("CONVERT_TIMEZONE");
27704        self.write("(");
27705        let mut first = true;
27706        if let Some(source_tz) = &e.source_tz {
27707            self.generate_expression(source_tz)?;
27708            first = false;
27709        }
27710        if let Some(target_tz) = &e.target_tz {
27711            if !first {
27712                self.write(", ");
27713            }
27714            self.generate_expression(target_tz)?;
27715            first = false;
27716        }
27717        if let Some(timestamp) = &e.timestamp {
27718            if !first {
27719                self.write(", ");
27720            }
27721            self.generate_expression(timestamp)?;
27722        }
27723        self.write(")");
27724        Ok(())
27725    }
27726
27727    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27728        // CONVERT(this USING dest)
27729        self.write_keyword("CONVERT");
27730        self.write("(");
27731        self.generate_expression(&e.this)?;
27732        if let Some(dest) = &e.dest {
27733            self.write_space();
27734            self.write_keyword("USING");
27735            self.write_space();
27736            self.generate_expression(dest)?;
27737        }
27738        self.write(")");
27739        Ok(())
27740    }
27741
27742    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27743        self.write_keyword("COPY");
27744        if e.is_into {
27745            self.write_space();
27746            self.write_keyword("INTO");
27747        }
27748        self.write_space();
27749
27750        // Generate target table or query (or stage for COPY INTO @stage)
27751        if let Expression::Literal(lit) = &e.this {
27752            if let Literal::String(s) = lit.as_ref() {
27753                if s.starts_with('@') {
27754                    self.write(s);
27755                } else {
27756                    self.generate_expression(&e.this)?;
27757                }
27758            }
27759        } else {
27760            self.generate_expression(&e.this)?;
27761        }
27762
27763        // FROM or TO based on kind
27764        if e.kind {
27765            // kind=true means FROM (loading into table)
27766            if self.config.pretty {
27767                self.write_newline();
27768            } else {
27769                self.write_space();
27770            }
27771            self.write_keyword("FROM");
27772            self.write_space();
27773        } else if !e.files.is_empty() {
27774            // kind=false means TO (exporting)
27775            if self.config.pretty {
27776                self.write_newline();
27777            } else {
27778                self.write_space();
27779            }
27780            self.write_keyword("TO");
27781            self.write_space();
27782        }
27783
27784        // Generate source/destination files
27785        for (i, file) in e.files.iter().enumerate() {
27786            if i > 0 {
27787                self.write_space();
27788            }
27789            // For stage references (strings starting with @), output without quotes
27790            if let Expression::Literal(lit) = file {
27791                if let Literal::String(s) = lit.as_ref() {
27792                    if s.starts_with('@') {
27793                        self.write(s);
27794                    } else {
27795                        self.generate_expression(file)?;
27796                    }
27797                }
27798            } else if let Expression::Identifier(id) = file {
27799                // Backtick-quoted file path (Databricks style: `s3://link`)
27800                if id.quoted {
27801                    self.write("`");
27802                    self.write(&id.name);
27803                    self.write("`");
27804                } else {
27805                    self.generate_expression(file)?;
27806                }
27807            } else {
27808                self.generate_expression(file)?;
27809            }
27810        }
27811
27812        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27813        if !e.with_wrapped {
27814            if let Some(ref creds) = e.credentials {
27815                if let Some(ref storage) = creds.storage {
27816                    if self.config.pretty {
27817                        self.write_newline();
27818                    } else {
27819                        self.write_space();
27820                    }
27821                    self.write_keyword("STORAGE_INTEGRATION");
27822                    self.write(" = ");
27823                    self.write(storage);
27824                }
27825                if creds.credentials.is_empty() {
27826                    // Empty credentials: CREDENTIALS = ()
27827                    if self.config.pretty {
27828                        self.write_newline();
27829                    } else {
27830                        self.write_space();
27831                    }
27832                    self.write_keyword("CREDENTIALS");
27833                    self.write(" = ()");
27834                } else {
27835                    if self.config.pretty {
27836                        self.write_newline();
27837                    } else {
27838                        self.write_space();
27839                    }
27840                    self.write_keyword("CREDENTIALS");
27841                    // Check if this is Redshift-style (single value with empty key)
27842                    // vs Snowflake-style (multiple key=value pairs)
27843                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27844                        // Redshift style: CREDENTIALS 'value'
27845                        self.write(" '");
27846                        self.write(&creds.credentials[0].1);
27847                        self.write("'");
27848                    } else {
27849                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
27850                        self.write(" = (");
27851                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
27852                            if i > 0 {
27853                                self.write_space();
27854                            }
27855                            self.write(k);
27856                            self.write("='");
27857                            self.write(v);
27858                            self.write("'");
27859                        }
27860                        self.write(")");
27861                    }
27862                }
27863                if let Some(ref encryption) = creds.encryption {
27864                    self.write_space();
27865                    self.write_keyword("ENCRYPTION");
27866                    self.write(" = ");
27867                    self.write(encryption);
27868                }
27869            }
27870        }
27871
27872        // Generate parameters
27873        if !e.params.is_empty() {
27874            if e.with_wrapped {
27875                // DuckDB/PostgreSQL/TSQL WITH (...) format
27876                self.write_space();
27877                self.write_keyword("WITH");
27878                self.write(" (");
27879                for (i, param) in e.params.iter().enumerate() {
27880                    if i > 0 {
27881                        self.write(", ");
27882                    }
27883                    self.generate_copy_param_with_format(param)?;
27884                }
27885                self.write(")");
27886            } else {
27887                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27888                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27889                // For Snowflake: KEY = VALUE
27890                for param in &e.params {
27891                    if self.config.pretty {
27892                        self.write_newline();
27893                    } else {
27894                        self.write_space();
27895                    }
27896                    // Preserve original case of parameter name (important for Redshift COPY options)
27897                    self.write(&param.name);
27898                    if let Some(ref value) = param.value {
27899                        // Use = only if it was present in the original (param.eq)
27900                        if param.eq {
27901                            self.write(" = ");
27902                        } else {
27903                            self.write(" ");
27904                        }
27905                        if !param.values.is_empty() {
27906                            self.write("(");
27907                            for (i, v) in param.values.iter().enumerate() {
27908                                if i > 0 {
27909                                    self.write_space();
27910                                }
27911                                self.generate_copy_nested_param(v)?;
27912                            }
27913                            self.write(")");
27914                        } else {
27915                            // For COPY parameter values, output identifiers without quoting
27916                            self.generate_copy_param_value(value)?;
27917                        }
27918                    } else if !param.values.is_empty() {
27919                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27920                        if param.eq {
27921                            self.write(" = (");
27922                        } else {
27923                            self.write(" (");
27924                        }
27925                        // Determine separator for values inside parentheses:
27926                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27927                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27928                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27929                        let is_key_value_pairs = param
27930                            .values
27931                            .first()
27932                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27933                        let sep = if is_key_value_pairs && param.eq {
27934                            " "
27935                        } else {
27936                            ", "
27937                        };
27938                        for (i, v) in param.values.iter().enumerate() {
27939                            if i > 0 {
27940                                self.write(sep);
27941                            }
27942                            self.generate_copy_nested_param(v)?;
27943                        }
27944                        self.write(")");
27945                    }
27946                }
27947            }
27948        }
27949
27950        Ok(())
27951    }
27952
27953    /// Generate a COPY parameter in WITH (...) format
27954    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27955    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27956        self.write_keyword(&param.name);
27957        if !param.values.is_empty() {
27958            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27959            self.write(" = (");
27960            for (i, v) in param.values.iter().enumerate() {
27961                if i > 0 {
27962                    self.write(", ");
27963                }
27964                self.generate_copy_nested_param(v)?;
27965            }
27966            self.write(")");
27967        } else if let Some(ref value) = param.value {
27968            if param.eq {
27969                self.write(" = ");
27970            } else {
27971                self.write(" ");
27972            }
27973            self.generate_expression(value)?;
27974        }
27975        Ok(())
27976    }
27977
27978    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27979    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27980        match expr {
27981            Expression::Eq(eq) => {
27982                // Generate key
27983                match &eq.left {
27984                    Expression::Column(c) => self.write(&c.name.name),
27985                    _ => self.generate_expression(&eq.left)?,
27986                }
27987                self.write("=");
27988                // Generate value
27989                match &eq.right {
27990                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27991                        let Literal::String(s) = lit.as_ref() else {
27992                            unreachable!()
27993                        };
27994                        self.write("'");
27995                        self.write(s);
27996                        self.write("'");
27997                    }
27998                    Expression::Tuple(t) => {
27999                        // For lists like NULL_IF=('', 'str1')
28000                        self.write("(");
28001                        if self.config.pretty {
28002                            self.write_newline();
28003                            self.indent_level += 1;
28004                            for (i, item) in t.expressions.iter().enumerate() {
28005                                if i > 0 {
28006                                    self.write(", ");
28007                                }
28008                                self.write_indent();
28009                                self.generate_expression(item)?;
28010                            }
28011                            self.write_newline();
28012                            self.indent_level -= 1;
28013                        } else {
28014                            for (i, item) in t.expressions.iter().enumerate() {
28015                                if i > 0 {
28016                                    self.write(", ");
28017                                }
28018                                self.generate_expression(item)?;
28019                            }
28020                        }
28021                        self.write(")");
28022                    }
28023                    _ => self.generate_expression(&eq.right)?,
28024                }
28025                Ok(())
28026            }
28027            Expression::Column(c) => {
28028                // Standalone keyword like COMPRESSION
28029                self.write(&c.name.name);
28030                Ok(())
28031            }
28032            _ => self.generate_expression(expr),
28033        }
28034    }
28035
28036    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
28037    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
28038    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
28039        match expr {
28040            Expression::Column(c) => {
28041                // Output identifier, preserving quotes if originally quoted
28042                if c.name.quoted {
28043                    self.write("\"");
28044                    self.write(&c.name.name);
28045                    self.write("\"");
28046                } else {
28047                    self.write(&c.name.name);
28048                }
28049                Ok(())
28050            }
28051            Expression::Identifier(id) => {
28052                // Output identifier, preserving quotes if originally quoted
28053                if id.quoted {
28054                    self.write("\"");
28055                    self.write(&id.name);
28056                    self.write("\"");
28057                } else {
28058                    self.write(&id.name);
28059                }
28060                Ok(())
28061            }
28062            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28063                let Literal::String(s) = lit.as_ref() else {
28064                    unreachable!()
28065                };
28066                // Output string with quotes
28067                self.write("'");
28068                self.write(s);
28069                self.write("'");
28070                Ok(())
28071            }
28072            _ => self.generate_expression(expr),
28073        }
28074    }
28075
28076    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
28077        self.write_keyword(&e.name);
28078        if let Some(ref value) = e.value {
28079            if e.eq {
28080                self.write(" = ");
28081            } else {
28082                self.write(" ");
28083            }
28084            self.generate_expression(value)?;
28085        }
28086        if !e.values.is_empty() {
28087            if e.eq {
28088                self.write(" = ");
28089            } else {
28090                self.write(" ");
28091            }
28092            self.write("(");
28093            for (i, v) in e.values.iter().enumerate() {
28094                if i > 0 {
28095                    self.write(", ");
28096                }
28097                self.generate_expression(v)?;
28098            }
28099            self.write(")");
28100        }
28101        Ok(())
28102    }
28103
28104    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
28105        // CORR(this, expression)
28106        self.write_keyword("CORR");
28107        self.write("(");
28108        self.generate_expression(&e.this)?;
28109        self.write(", ");
28110        self.generate_expression(&e.expression)?;
28111        self.write(")");
28112        Ok(())
28113    }
28114
28115    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
28116        // COSINE_DISTANCE(this, expression)
28117        self.write_keyword("COSINE_DISTANCE");
28118        self.write("(");
28119        self.generate_expression(&e.this)?;
28120        self.write(", ");
28121        self.generate_expression(&e.expression)?;
28122        self.write(")");
28123        Ok(())
28124    }
28125
28126    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
28127        // COVAR_POP(this, expression)
28128        self.write_keyword("COVAR_POP");
28129        self.write("(");
28130        self.generate_expression(&e.this)?;
28131        self.write(", ");
28132        self.generate_expression(&e.expression)?;
28133        self.write(")");
28134        Ok(())
28135    }
28136
28137    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
28138        // COVAR_SAMP(this, expression)
28139        self.write_keyword("COVAR_SAMP");
28140        self.write("(");
28141        self.generate_expression(&e.this)?;
28142        self.write(", ");
28143        self.generate_expression(&e.expression)?;
28144        self.write(")");
28145        Ok(())
28146    }
28147
28148    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
28149        // CREDENTIALS (key1='value1', key2='value2')
28150        self.write_keyword("CREDENTIALS");
28151        self.write(" (");
28152        for (i, (key, value)) in e.credentials.iter().enumerate() {
28153            if i > 0 {
28154                self.write(", ");
28155            }
28156            self.write(key);
28157            self.write("='");
28158            self.write(value);
28159            self.write("'");
28160        }
28161        self.write(")");
28162        Ok(())
28163    }
28164
28165    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
28166        // CREDENTIALS=(expressions)
28167        self.write_keyword("CREDENTIALS");
28168        self.write("=(");
28169        for (i, expr) in e.expressions.iter().enumerate() {
28170            if i > 0 {
28171                self.write(", ");
28172            }
28173            self.generate_expression(expr)?;
28174        }
28175        self.write(")");
28176        Ok(())
28177    }
28178
28179    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
28180        use crate::dialects::DialectType;
28181
28182        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
28183        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
28184        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
28185            self.generate_expression(&e.this)?;
28186            self.write_space();
28187            self.write_keyword("AS");
28188            self.write_space();
28189            self.generate_identifier(&e.alias)?;
28190            return Ok(());
28191        }
28192        self.write(&e.alias.name);
28193
28194        // BigQuery doesn't support column aliases in CTE definitions
28195        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
28196
28197        if !e.columns.is_empty() && !skip_cte_columns {
28198            self.write("(");
28199            for (i, col) in e.columns.iter().enumerate() {
28200                if i > 0 {
28201                    self.write(", ");
28202                }
28203                self.write(&col.name);
28204            }
28205            self.write(")");
28206        }
28207        // USING KEY (columns) for DuckDB recursive CTEs
28208        if !e.key_expressions.is_empty() {
28209            self.write_space();
28210            self.write_keyword("USING KEY");
28211            self.write(" (");
28212            for (i, key) in e.key_expressions.iter().enumerate() {
28213                if i > 0 {
28214                    self.write(", ");
28215                }
28216                self.write(&key.name);
28217            }
28218            self.write(")");
28219        }
28220        self.write_space();
28221        self.write_keyword("AS");
28222        self.write_space();
28223        if let Some(materialized) = e.materialized {
28224            if materialized {
28225                self.write_keyword("MATERIALIZED");
28226            } else {
28227                self.write_keyword("NOT MATERIALIZED");
28228            }
28229            self.write_space();
28230        }
28231        self.write("(");
28232        self.generate_expression(&e.this)?;
28233        self.write(")");
28234        Ok(())
28235    }
28236
28237    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
28238        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
28239        if e.expressions.is_empty() {
28240            self.write_keyword("WITH CUBE");
28241        } else {
28242            self.write_keyword("CUBE");
28243            self.write("(");
28244            for (i, expr) in e.expressions.iter().enumerate() {
28245                if i > 0 {
28246                    self.write(", ");
28247                }
28248                self.generate_expression(expr)?;
28249            }
28250            self.write(")");
28251        }
28252        Ok(())
28253    }
28254
28255    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
28256        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
28257        self.write_keyword("CURRENT_DATETIME");
28258        if let Some(this) = &e.this {
28259            self.write("(");
28260            self.generate_expression(this)?;
28261            self.write(")");
28262        }
28263        Ok(())
28264    }
28265
28266    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
28267        // CURRENT_SCHEMA - no arguments
28268        self.write_keyword("CURRENT_SCHEMA");
28269        Ok(())
28270    }
28271
28272    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
28273        // CURRENT_SCHEMAS(include_implicit)
28274        self.write_keyword("CURRENT_SCHEMAS");
28275        self.write("(");
28276        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
28277        if !matches!(
28278            self.config.dialect,
28279            Some(crate::dialects::DialectType::Snowflake)
28280        ) {
28281            if let Some(this) = &e.this {
28282                self.generate_expression(this)?;
28283            }
28284        }
28285        self.write(")");
28286        Ok(())
28287    }
28288
28289    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
28290        // CURRENT_USER or CURRENT_USER()
28291        self.write_keyword("CURRENT_USER");
28292        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
28293        let needs_parens = e.this.is_some()
28294            || matches!(
28295                self.config.dialect,
28296                Some(DialectType::Snowflake)
28297                    | Some(DialectType::Spark)
28298                    | Some(DialectType::Hive)
28299                    | Some(DialectType::DuckDB)
28300                    | Some(DialectType::BigQuery)
28301                    | Some(DialectType::MySQL)
28302                    | Some(DialectType::Databricks)
28303            );
28304        if needs_parens {
28305            self.write("()");
28306        }
28307        Ok(())
28308    }
28309
28310    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
28311        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
28312        if self.config.dialect == Some(DialectType::Solr) {
28313            self.generate_expression(&e.this)?;
28314            self.write(" ");
28315            self.write_keyword("OR");
28316            self.write(" ");
28317            self.generate_expression(&e.expression)?;
28318        } else if self.config.dialect == Some(DialectType::MySQL) {
28319            self.generate_mysql_concat_from_dpipe(e)?;
28320        } else {
28321            // String concatenation: this || expression
28322            self.generate_expression(&e.this)?;
28323            self.write(" || ");
28324            self.generate_expression(&e.expression)?;
28325        }
28326        Ok(())
28327    }
28328
28329    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
28330        // DATABLOCKSIZE=... (Teradata)
28331        self.write_keyword("DATABLOCKSIZE");
28332        self.write("=");
28333        if let Some(size) = e.size {
28334            self.write(&size.to_string());
28335            if let Some(units) = &e.units {
28336                self.write_space();
28337                self.generate_expression(units)?;
28338            }
28339        } else if e.minimum.is_some() {
28340            self.write_keyword("MINIMUM");
28341        } else if e.maximum.is_some() {
28342            self.write_keyword("MAXIMUM");
28343        } else if e.default.is_some() {
28344            self.write_keyword("DEFAULT");
28345        }
28346        Ok(())
28347    }
28348
28349    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
28350        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
28351        self.write_keyword("DATA_DELETION");
28352        self.write("=");
28353
28354        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
28355        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
28356
28357        if is_on {
28358            self.write_keyword("ON");
28359            if has_options {
28360                self.write("(");
28361                let mut first = true;
28362                if let Some(filter_column) = &e.filter_column {
28363                    self.write_keyword("FILTER_COLUMN");
28364                    self.write("=");
28365                    self.generate_expression(filter_column)?;
28366                    first = false;
28367                }
28368                if let Some(retention_period) = &e.retention_period {
28369                    if !first {
28370                        self.write(", ");
28371                    }
28372                    self.write_keyword("RETENTION_PERIOD");
28373                    self.write("=");
28374                    self.generate_expression(retention_period)?;
28375                }
28376                self.write(")");
28377            }
28378        } else {
28379            self.write_keyword("OFF");
28380        }
28381        Ok(())
28382    }
28383
28384    /// Generate a Date function expression
28385    /// For Exasol: {d'value'} -> TO_DATE('value')
28386    /// For other dialects: DATE('value')
28387    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
28388        use crate::dialects::DialectType;
28389        use crate::expressions::Literal;
28390
28391        match self.config.dialect {
28392            // Exasol uses TO_DATE for Date expressions
28393            Some(DialectType::Exasol) => {
28394                self.write_keyword("TO_DATE");
28395                self.write("(");
28396                // Extract the string value from the expression if it's a string literal
28397                match &e.this {
28398                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28399                        let Literal::String(s) = lit.as_ref() else {
28400                            unreachable!()
28401                        };
28402                        self.write("'");
28403                        self.write(s);
28404                        self.write("'");
28405                    }
28406                    _ => {
28407                        self.generate_expression(&e.this)?;
28408                    }
28409                }
28410                self.write(")");
28411            }
28412            // Standard: DATE(value)
28413            _ => {
28414                self.write_keyword("DATE");
28415                self.write("(");
28416                self.generate_expression(&e.this)?;
28417                self.write(")");
28418            }
28419        }
28420        Ok(())
28421    }
28422
28423    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
28424        // DATE_BIN(interval, timestamp[, origin])
28425        self.write_keyword("DATE_BIN");
28426        self.write("(");
28427        self.generate_expression(&e.this)?;
28428        self.write(", ");
28429        self.generate_expression(&e.expression)?;
28430        if let Some(origin) = &e.origin {
28431            self.write(", ");
28432            self.generate_expression(origin)?;
28433        }
28434        self.write(")");
28435        Ok(())
28436    }
28437
28438    fn generate_date_format_column_constraint(
28439        &mut self,
28440        e: &DateFormatColumnConstraint,
28441    ) -> Result<()> {
28442        // FORMAT 'format_string' (Teradata)
28443        self.write_keyword("FORMAT");
28444        self.write_space();
28445        self.generate_expression(&e.this)?;
28446        Ok(())
28447    }
28448
28449    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
28450        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
28451        self.write_keyword("DATE_FROM_PARTS");
28452        self.write("(");
28453        let mut first = true;
28454        if let Some(year) = &e.year {
28455            self.generate_expression(year)?;
28456            first = false;
28457        }
28458        if let Some(month) = &e.month {
28459            if !first {
28460                self.write(", ");
28461            }
28462            self.generate_expression(month)?;
28463            first = false;
28464        }
28465        if let Some(day) = &e.day {
28466            if !first {
28467                self.write(", ");
28468            }
28469            self.generate_expression(day)?;
28470        }
28471        self.write(")");
28472        Ok(())
28473    }
28474
28475    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
28476        // DATETIME(this) or DATETIME(this, expression)
28477        self.write_keyword("DATETIME");
28478        self.write("(");
28479        self.generate_expression(&e.this)?;
28480        if let Some(expr) = &e.expression {
28481            self.write(", ");
28482            self.generate_expression(expr)?;
28483        }
28484        self.write(")");
28485        Ok(())
28486    }
28487
28488    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
28489        // DATETIME_ADD(this, expression, unit)
28490        self.write_keyword("DATETIME_ADD");
28491        self.write("(");
28492        self.generate_expression(&e.this)?;
28493        self.write(", ");
28494        self.generate_expression(&e.expression)?;
28495        if let Some(unit) = &e.unit {
28496            self.write(", ");
28497            self.write_keyword(unit);
28498        }
28499        self.write(")");
28500        Ok(())
28501    }
28502
28503    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28504        // DATETIME_DIFF(this, expression, unit)
28505        self.write_keyword("DATETIME_DIFF");
28506        self.write("(");
28507        self.generate_expression(&e.this)?;
28508        self.write(", ");
28509        self.generate_expression(&e.expression)?;
28510        if let Some(unit) = &e.unit {
28511            self.write(", ");
28512            self.write_keyword(unit);
28513        }
28514        self.write(")");
28515        Ok(())
28516    }
28517
28518    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28519        // DATETIME_SUB(this, expression, unit)
28520        self.write_keyword("DATETIME_SUB");
28521        self.write("(");
28522        self.generate_expression(&e.this)?;
28523        self.write(", ");
28524        self.generate_expression(&e.expression)?;
28525        if let Some(unit) = &e.unit {
28526            self.write(", ");
28527            self.write_keyword(unit);
28528        }
28529        self.write(")");
28530        Ok(())
28531    }
28532
28533    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28534        // DATETIME_TRUNC(this, unit, zone)
28535        self.write_keyword("DATETIME_TRUNC");
28536        self.write("(");
28537        self.generate_expression(&e.this)?;
28538        self.write(", ");
28539        self.write_keyword(&e.unit);
28540        if let Some(zone) = &e.zone {
28541            self.write(", ");
28542            self.generate_expression(zone)?;
28543        }
28544        self.write(")");
28545        Ok(())
28546    }
28547
28548    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28549        // DAYNAME(this)
28550        self.write_keyword("DAYNAME");
28551        self.write("(");
28552        self.generate_expression(&e.this)?;
28553        self.write(")");
28554        Ok(())
28555    }
28556
28557    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28558        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28559        self.write_keyword("DECLARE");
28560        self.write_space();
28561        if e.replace {
28562            self.write_keyword("OR");
28563            self.write_space();
28564            self.write_keyword("REPLACE");
28565            self.write_space();
28566        }
28567        for (i, expr) in e.expressions.iter().enumerate() {
28568            if i > 0 {
28569                self.write(", ");
28570            }
28571            self.generate_expression(expr)?;
28572        }
28573        Ok(())
28574    }
28575
28576    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28577        use crate::dialects::DialectType;
28578
28579        // variable TYPE [DEFAULT default]
28580        self.generate_expression(&e.this)?;
28581        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28582        for name in &e.additional_names {
28583            self.write(", ");
28584            self.generate_expression(name)?;
28585        }
28586        if let Some(kind) = &e.kind {
28587            self.write_space();
28588            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28589            // TSQL: Always includes AS (normalization)
28590            // Others: Include AS if present in original
28591            match self.config.dialect {
28592                Some(DialectType::BigQuery) => {
28593                    self.write(kind);
28594                }
28595                Some(DialectType::TSQL) => {
28596                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28597                    // Normalize INT to INTEGER for simple declarations
28598                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28599                    let is_complex_table = kind.starts_with("TABLE")
28600                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28601                    if is_complex_table {
28602                        self.write(kind);
28603                    } else if kind == "INT" {
28604                        self.write("INTEGER");
28605                    } else if kind.starts_with("TABLE") {
28606                        // Normalize INT to INTEGER inside simple TABLE column definitions
28607                        let normalized = kind
28608                            .replace(" INT ", " INTEGER ")
28609                            .replace(" INT,", " INTEGER,")
28610                            .replace(" INT)", " INTEGER)")
28611                            .replace("(INT ", "(INTEGER ");
28612                        self.write(&normalized);
28613                    } else {
28614                        self.write(kind);
28615                    }
28616                }
28617                _ => {
28618                    if e.has_as {
28619                        self.write_keyword("AS");
28620                        self.write_space();
28621                    }
28622                    self.write(kind);
28623                }
28624            }
28625        }
28626        if let Some(default) = &e.default {
28627            // BigQuery uses DEFAULT, others use =
28628            match self.config.dialect {
28629                Some(DialectType::BigQuery) => {
28630                    self.write_space();
28631                    self.write_keyword("DEFAULT");
28632                    self.write_space();
28633                }
28634                _ => {
28635                    self.write(" = ");
28636                }
28637            }
28638            self.generate_expression(default)?;
28639        }
28640        Ok(())
28641    }
28642
28643    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28644        // DECODE(expr, search1, result1, search2, result2, ..., default)
28645        self.write_keyword("DECODE");
28646        self.write("(");
28647        for (i, expr) in e.expressions.iter().enumerate() {
28648            if i > 0 {
28649                self.write(", ");
28650            }
28651            self.generate_expression(expr)?;
28652        }
28653        self.write(")");
28654        Ok(())
28655    }
28656
28657    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
28658        // DECOMPRESS(expr, 'method')
28659        self.write_keyword("DECOMPRESS");
28660        self.write("(");
28661        self.generate_expression(&e.this)?;
28662        self.write(", '");
28663        self.write(&e.method);
28664        self.write("')");
28665        Ok(())
28666    }
28667
28668    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28669        // DECOMPRESS(expr, 'method')
28670        self.write_keyword("DECOMPRESS");
28671        self.write("(");
28672        self.generate_expression(&e.this)?;
28673        self.write(", '");
28674        self.write(&e.method);
28675        self.write("')");
28676        Ok(())
28677    }
28678
28679    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28680        // DECRYPT(value, passphrase [, aad [, algorithm]])
28681        self.write_keyword("DECRYPT");
28682        self.write("(");
28683        self.generate_expression(&e.this)?;
28684        if let Some(passphrase) = &e.passphrase {
28685            self.write(", ");
28686            self.generate_expression(passphrase)?;
28687        }
28688        if let Some(aad) = &e.aad {
28689            self.write(", ");
28690            self.generate_expression(aad)?;
28691        }
28692        if let Some(method) = &e.encryption_method {
28693            self.write(", ");
28694            self.generate_expression(method)?;
28695        }
28696        self.write(")");
28697        Ok(())
28698    }
28699
28700    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28701        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28702        self.write_keyword("DECRYPT_RAW");
28703        self.write("(");
28704        self.generate_expression(&e.this)?;
28705        if let Some(key) = &e.key {
28706            self.write(", ");
28707            self.generate_expression(key)?;
28708        }
28709        if let Some(iv) = &e.iv {
28710            self.write(", ");
28711            self.generate_expression(iv)?;
28712        }
28713        if let Some(aad) = &e.aad {
28714            self.write(", ");
28715            self.generate_expression(aad)?;
28716        }
28717        if let Some(method) = &e.encryption_method {
28718            self.write(", ");
28719            self.generate_expression(method)?;
28720        }
28721        self.write(")");
28722        Ok(())
28723    }
28724
28725    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28726        // DEFINER = user
28727        self.write_keyword("DEFINER");
28728        self.write(" = ");
28729        self.generate_expression(&e.this)?;
28730        Ok(())
28731    }
28732
28733    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28734        // Python: DETACH[DATABASE IF EXISTS] this
28735        self.write_keyword("DETACH");
28736        if e.exists {
28737            self.write_keyword(" DATABASE IF EXISTS");
28738        }
28739        self.write_space();
28740        self.generate_expression(&e.this)?;
28741        Ok(())
28742    }
28743
28744    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28745        let property_name = match e.this.as_ref() {
28746            Expression::Identifier(id) => id.name.as_str(),
28747            Expression::Var(v) => v.this.as_str(),
28748            _ => "DICTIONARY",
28749        };
28750        self.write_keyword(property_name);
28751        self.write("(");
28752        self.write(&e.kind);
28753        if let Some(settings) = &e.settings {
28754            self.write("(");
28755            if let Expression::Tuple(t) = settings.as_ref() {
28756                if self.config.pretty && !t.expressions.is_empty() {
28757                    self.write_newline();
28758                    self.indent_level += 1;
28759                    for (i, pair) in t.expressions.iter().enumerate() {
28760                        if i > 0 {
28761                            self.write(",");
28762                            self.write_newline();
28763                        }
28764                        self.write_indent();
28765                        if let Expression::Tuple(pair_tuple) = pair {
28766                            if let Some(k) = pair_tuple.expressions.first() {
28767                                self.generate_expression(k)?;
28768                            }
28769                            if let Some(v) = pair_tuple.expressions.get(1) {
28770                                self.write(" ");
28771                                self.generate_expression(v)?;
28772                            }
28773                        } else {
28774                            self.generate_expression(pair)?;
28775                        }
28776                    }
28777                    self.indent_level -= 1;
28778                    self.write_newline();
28779                    self.write_indent();
28780                } else {
28781                    for (i, pair) in t.expressions.iter().enumerate() {
28782                        if i > 0 {
28783                            // ClickHouse dict properties are space-separated, not comma-separated
28784                            self.write(" ");
28785                        }
28786                        if let Expression::Tuple(pair_tuple) = pair {
28787                            if let Some(k) = pair_tuple.expressions.first() {
28788                                self.generate_expression(k)?;
28789                            }
28790                            if let Some(v) = pair_tuple.expressions.get(1) {
28791                                self.write(" ");
28792                                self.generate_expression(v)?;
28793                            }
28794                        } else {
28795                            self.generate_expression(pair)?;
28796                        }
28797                    }
28798                }
28799            } else {
28800                self.generate_expression(settings)?;
28801            }
28802            self.write(")");
28803        } else {
28804            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28805            self.write("()");
28806        }
28807        self.write(")");
28808        Ok(())
28809    }
28810
28811    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28812        let property_name = match e.this.as_ref() {
28813            Expression::Identifier(id) => id.name.as_str(),
28814            Expression::Var(v) => v.this.as_str(),
28815            _ => "RANGE",
28816        };
28817        self.write_keyword(property_name);
28818        self.write("(");
28819        if let Some(min) = &e.min {
28820            self.write_keyword("MIN");
28821            self.write_space();
28822            self.generate_expression(min)?;
28823        }
28824        if let Some(max) = &e.max {
28825            self.write_space();
28826            self.write_keyword("MAX");
28827            self.write_space();
28828            self.generate_expression(max)?;
28829        }
28830        self.write(")");
28831        Ok(())
28832    }
28833
28834    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28835        // Python: {local}DIRECTORY {this}{row_format}
28836        if e.local.is_some() {
28837            self.write_keyword("LOCAL ");
28838        }
28839        self.write_keyword("DIRECTORY");
28840        self.write_space();
28841        self.generate_expression(&e.this)?;
28842        if let Some(row_format) = &e.row_format {
28843            self.write_space();
28844            self.generate_expression(row_format)?;
28845        }
28846        Ok(())
28847    }
28848
28849    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
28850        // Redshift: DISTKEY(column)
28851        self.write_keyword("DISTKEY");
28852        self.write("(");
28853        self.generate_expression(&e.this)?;
28854        self.write(")");
28855        Ok(())
28856    }
28857
28858    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
28859        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
28860        self.write_keyword("DISTSTYLE");
28861        self.write_space();
28862        self.generate_expression(&e.this)?;
28863        Ok(())
28864    }
28865
28866    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
28867        // Python: "DISTRIBUTE BY" expressions
28868        self.write_keyword("DISTRIBUTE BY");
28869        self.write_space();
28870        for (i, expr) in e.expressions.iter().enumerate() {
28871            if i > 0 {
28872                self.write(", ");
28873            }
28874            self.generate_expression(expr)?;
28875        }
28876        Ok(())
28877    }
28878
28879    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
28880        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
28881        self.write_keyword("DISTRIBUTED BY");
28882        self.write_space();
28883        self.write(&e.kind);
28884        if !e.expressions.is_empty() {
28885            self.write(" (");
28886            for (i, expr) in e.expressions.iter().enumerate() {
28887                if i > 0 {
28888                    self.write(", ");
28889                }
28890                self.generate_expression(expr)?;
28891            }
28892            self.write(")");
28893        }
28894        if let Some(buckets) = &e.buckets {
28895            self.write_space();
28896            self.write_keyword("BUCKETS");
28897            self.write_space();
28898            self.generate_expression(buckets)?;
28899        }
28900        if let Some(order) = &e.order {
28901            self.write_space();
28902            self.generate_expression(order)?;
28903        }
28904        Ok(())
28905    }
28906
28907    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28908        // DOT_PRODUCT(vector1, vector2)
28909        self.write_keyword("DOT_PRODUCT");
28910        self.write("(");
28911        self.generate_expression(&e.this)?;
28912        self.write(", ");
28913        self.generate_expression(&e.expression)?;
28914        self.write(")");
28915        Ok(())
28916    }
28917
28918    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28919        // Python: DROP{IF EXISTS }expressions
28920        self.write_keyword("DROP");
28921        if e.exists {
28922            self.write_keyword(" IF EXISTS ");
28923        } else {
28924            self.write_space();
28925        }
28926        for (i, expr) in e.expressions.iter().enumerate() {
28927            if i > 0 {
28928                self.write(", ");
28929            }
28930            self.generate_expression(expr)?;
28931        }
28932        Ok(())
28933    }
28934
28935    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28936        // Python: DUPLICATE KEY (expressions)
28937        self.write_keyword("DUPLICATE KEY");
28938        self.write(" (");
28939        for (i, expr) in e.expressions.iter().enumerate() {
28940            if i > 0 {
28941                self.write(", ");
28942            }
28943            self.generate_expression(expr)?;
28944        }
28945        self.write(")");
28946        Ok(())
28947    }
28948
28949    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28950        // ELT(index, str1, str2, ...)
28951        self.write_keyword("ELT");
28952        self.write("(");
28953        self.generate_expression(&e.this)?;
28954        for expr in &e.expressions {
28955            self.write(", ");
28956            self.generate_expression(expr)?;
28957        }
28958        self.write(")");
28959        Ok(())
28960    }
28961
28962    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28963        // ENCODE(string, charset)
28964        self.write_keyword("ENCODE");
28965        self.write("(");
28966        self.generate_expression(&e.this)?;
28967        if let Some(charset) = &e.charset {
28968            self.write(", ");
28969            self.generate_expression(charset)?;
28970        }
28971        self.write(")");
28972        Ok(())
28973    }
28974
28975    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28976        // Python: [KEY ]ENCODE this [properties]
28977        if e.key.is_some() {
28978            self.write_keyword("KEY ");
28979        }
28980        self.write_keyword("ENCODE");
28981        self.write_space();
28982        self.generate_expression(&e.this)?;
28983        if !e.properties.is_empty() {
28984            self.write(" (");
28985            for (i, prop) in e.properties.iter().enumerate() {
28986                if i > 0 {
28987                    self.write(", ");
28988                }
28989                self.generate_expression(prop)?;
28990            }
28991            self.write(")");
28992        }
28993        Ok(())
28994    }
28995
28996    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28997        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28998        self.write_keyword("ENCRYPT");
28999        self.write("(");
29000        self.generate_expression(&e.this)?;
29001        if let Some(passphrase) = &e.passphrase {
29002            self.write(", ");
29003            self.generate_expression(passphrase)?;
29004        }
29005        if let Some(aad) = &e.aad {
29006            self.write(", ");
29007            self.generate_expression(aad)?;
29008        }
29009        if let Some(method) = &e.encryption_method {
29010            self.write(", ");
29011            self.generate_expression(method)?;
29012        }
29013        self.write(")");
29014        Ok(())
29015    }
29016
29017    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
29018        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29019        self.write_keyword("ENCRYPT_RAW");
29020        self.write("(");
29021        self.generate_expression(&e.this)?;
29022        if let Some(key) = &e.key {
29023            self.write(", ");
29024            self.generate_expression(key)?;
29025        }
29026        if let Some(iv) = &e.iv {
29027            self.write(", ");
29028            self.generate_expression(iv)?;
29029        }
29030        if let Some(aad) = &e.aad {
29031            self.write(", ");
29032            self.generate_expression(aad)?;
29033        }
29034        if let Some(method) = &e.encryption_method {
29035            self.write(", ");
29036            self.generate_expression(method)?;
29037        }
29038        self.write(")");
29039        Ok(())
29040    }
29041
29042    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
29043        // MySQL: ENGINE = InnoDB
29044        self.write_keyword("ENGINE");
29045        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
29046            self.write("=");
29047        } else {
29048            self.write(" = ");
29049        }
29050        self.generate_expression(&e.this)?;
29051        Ok(())
29052    }
29053
29054    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
29055        // ENVIRONMENT (expressions)
29056        self.write_keyword("ENVIRONMENT");
29057        self.write(" (");
29058        for (i, expr) in e.expressions.iter().enumerate() {
29059            if i > 0 {
29060                self.write(", ");
29061            }
29062            self.generate_expression(expr)?;
29063        }
29064        self.write(")");
29065        Ok(())
29066    }
29067
29068    fn generate_ephemeral_column_constraint(
29069        &mut self,
29070        e: &EphemeralColumnConstraint,
29071    ) -> Result<()> {
29072        // MySQL: EPHEMERAL [expr]
29073        self.write_keyword("EPHEMERAL");
29074        if let Some(this) = &e.this {
29075            self.write_space();
29076            self.generate_expression(this)?;
29077        }
29078        Ok(())
29079    }
29080
29081    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
29082        // Snowflake: EQUAL_NULL(a, b)
29083        self.write_keyword("EQUAL_NULL");
29084        self.write("(");
29085        self.generate_expression(&e.this)?;
29086        self.write(", ");
29087        self.generate_expression(&e.expression)?;
29088        self.write(")");
29089        Ok(())
29090    }
29091
29092    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
29093        use crate::dialects::DialectType;
29094
29095        // PostgreSQL uses <-> operator syntax
29096        match self.config.dialect {
29097            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
29098                self.generate_expression(&e.this)?;
29099                self.write(" <-> ");
29100                self.generate_expression(&e.expression)?;
29101            }
29102            _ => {
29103                // Other dialects use EUCLIDEAN_DISTANCE function
29104                self.write_keyword("EUCLIDEAN_DISTANCE");
29105                self.write("(");
29106                self.generate_expression(&e.this)?;
29107                self.write(", ");
29108                self.generate_expression(&e.expression)?;
29109                self.write(")");
29110            }
29111        }
29112        Ok(())
29113    }
29114
29115    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
29116        // EXECUTE AS CALLER|OWNER|user
29117        self.write_keyword("EXECUTE AS");
29118        self.write_space();
29119        self.generate_expression(&e.this)?;
29120        Ok(())
29121    }
29122
29123    fn generate_export(&mut self, e: &Export) -> Result<()> {
29124        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
29125        self.write_keyword("EXPORT DATA");
29126        if let Some(connection) = &e.connection {
29127            self.write_space();
29128            self.write_keyword("WITH CONNECTION");
29129            self.write_space();
29130            self.generate_expression(connection)?;
29131        }
29132        if !e.options.is_empty() {
29133            self.write_space();
29134            self.generate_options_clause(&e.options)?;
29135        }
29136        self.write_space();
29137        self.write_keyword("AS");
29138        self.write_space();
29139        self.generate_expression(&e.this)?;
29140        Ok(())
29141    }
29142
29143    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
29144        // EXTERNAL [this]
29145        self.write_keyword("EXTERNAL");
29146        if let Some(this) = &e.this {
29147            self.write_space();
29148            self.generate_expression(this)?;
29149        }
29150        Ok(())
29151    }
29152
29153    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
29154        // Python: {no}FALLBACK{protection}
29155        if e.no.is_some() {
29156            self.write_keyword("NO ");
29157        }
29158        self.write_keyword("FALLBACK");
29159        if e.protection.is_some() {
29160            self.write_keyword(" PROTECTION");
29161        }
29162        Ok(())
29163    }
29164
29165    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
29166        // BigQuery: FARM_FINGERPRINT(value)
29167        self.write_keyword("FARM_FINGERPRINT");
29168        self.write("(");
29169        for (i, expr) in e.expressions.iter().enumerate() {
29170            if i > 0 {
29171                self.write(", ");
29172            }
29173            self.generate_expression(expr)?;
29174        }
29175        self.write(")");
29176        Ok(())
29177    }
29178
29179    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
29180        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
29181        self.write_keyword("FEATURES_AT_TIME");
29182        self.write("(");
29183        self.generate_expression(&e.this)?;
29184        if let Some(time) = &e.time {
29185            self.write(", ");
29186            self.generate_expression(time)?;
29187        }
29188        if let Some(num_rows) = &e.num_rows {
29189            self.write(", ");
29190            self.generate_expression(num_rows)?;
29191        }
29192        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
29193            self.write(", ");
29194            self.generate_expression(ignore_nulls)?;
29195        }
29196        self.write(")");
29197        Ok(())
29198    }
29199
29200    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
29201        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
29202        let use_limit = !e.percent
29203            && !e.with_ties
29204            && e.count.is_some()
29205            && matches!(
29206                self.config.dialect,
29207                Some(DialectType::Spark)
29208                    | Some(DialectType::Hive)
29209                    | Some(DialectType::DuckDB)
29210                    | Some(DialectType::SQLite)
29211                    | Some(DialectType::MySQL)
29212                    | Some(DialectType::BigQuery)
29213                    | Some(DialectType::Databricks)
29214                    | Some(DialectType::StarRocks)
29215                    | Some(DialectType::Doris)
29216                    | Some(DialectType::Athena)
29217                    | Some(DialectType::ClickHouse)
29218            );
29219
29220        if use_limit {
29221            self.write_keyword("LIMIT");
29222            self.write_space();
29223            self.generate_expression(e.count.as_ref().unwrap())?;
29224            return Ok(());
29225        }
29226
29227        // Python: FETCH direction count limit_options
29228        self.write_keyword("FETCH");
29229        if !e.direction.is_empty() {
29230            self.write_space();
29231            self.write_keyword(&e.direction);
29232        }
29233        if let Some(count) = &e.count {
29234            self.write_space();
29235            self.generate_expression(count)?;
29236        }
29237        // Generate PERCENT, ROWS, WITH TIES/ONLY
29238        if e.percent {
29239            self.write_keyword(" PERCENT");
29240        }
29241        if e.rows {
29242            self.write_keyword(" ROWS");
29243        }
29244        if e.with_ties {
29245            self.write_keyword(" WITH TIES");
29246        } else if e.rows {
29247            self.write_keyword(" ONLY");
29248        } else {
29249            self.write_keyword(" ROWS ONLY");
29250        }
29251        Ok(())
29252    }
29253
29254    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
29255        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
29256        // For Spark/Databricks without hive_format: USING this
29257        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
29258        if e.hive_format.is_some() {
29259            // Hive format: STORED AS ...
29260            self.write_keyword("STORED AS");
29261            self.write_space();
29262            if let Some(this) = &e.this {
29263                // Uppercase the format name (e.g., parquet -> PARQUET)
29264                if let Expression::Identifier(id) = this.as_ref() {
29265                    self.write_keyword(&id.name.to_ascii_uppercase());
29266                } else {
29267                    self.generate_expression(this)?;
29268                }
29269            }
29270        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
29271            // Hive: STORED AS format
29272            self.write_keyword("STORED AS");
29273            self.write_space();
29274            if let Some(this) = &e.this {
29275                if let Expression::Identifier(id) = this.as_ref() {
29276                    self.write_keyword(&id.name.to_ascii_uppercase());
29277                } else {
29278                    self.generate_expression(this)?;
29279                }
29280            }
29281        } else if matches!(
29282            self.config.dialect,
29283            Some(DialectType::Spark) | Some(DialectType::Databricks)
29284        ) {
29285            // Spark/Databricks: USING format (e.g., USING DELTA)
29286            self.write_keyword("USING");
29287            self.write_space();
29288            if let Some(this) = &e.this {
29289                self.generate_expression(this)?;
29290            }
29291        } else {
29292            // Snowflake/standard format
29293            self.write_keyword("FILE_FORMAT");
29294            self.write(" = ");
29295            if let Some(this) = &e.this {
29296                self.generate_expression(this)?;
29297            } else if !e.expressions.is_empty() {
29298                self.write("(");
29299                for (i, expr) in e.expressions.iter().enumerate() {
29300                    if i > 0 {
29301                        self.write(", ");
29302                    }
29303                    self.generate_expression(expr)?;
29304                }
29305                self.write(")");
29306            }
29307        }
29308        Ok(())
29309    }
29310
29311    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
29312        // agg_func FILTER(WHERE condition)
29313        self.generate_expression(&e.this)?;
29314        self.write_space();
29315        self.write_keyword("FILTER");
29316        self.write("(");
29317        self.write_keyword("WHERE");
29318        self.write_space();
29319        self.generate_expression(&e.expression)?;
29320        self.write(")");
29321        Ok(())
29322    }
29323
29324    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
29325        // FLOAT64(this) or FLOAT64(this, expression)
29326        self.write_keyword("FLOAT64");
29327        self.write("(");
29328        self.generate_expression(&e.this)?;
29329        if let Some(expr) = &e.expression {
29330            self.write(", ");
29331            self.generate_expression(expr)?;
29332        }
29333        self.write(")");
29334        Ok(())
29335    }
29336
29337    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
29338        // FOR this DO expression
29339        self.write_keyword("FOR");
29340        self.write_space();
29341        self.generate_expression(&e.this)?;
29342        self.write_space();
29343        self.write_keyword("DO");
29344        self.write_space();
29345        self.generate_expression(&e.expression)?;
29346        Ok(())
29347    }
29348
29349    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
29350        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
29351        self.write_keyword("FOREIGN KEY");
29352        if !e.expressions.is_empty() {
29353            self.write(" (");
29354            for (i, expr) in e.expressions.iter().enumerate() {
29355                if i > 0 {
29356                    self.write(", ");
29357                }
29358                self.generate_expression(expr)?;
29359            }
29360            self.write(")");
29361        }
29362        if let Some(reference) = &e.reference {
29363            self.write_space();
29364            self.generate_expression(reference)?;
29365        }
29366        if let Some(delete) = &e.delete {
29367            self.write_space();
29368            self.write_keyword("ON DELETE");
29369            self.write_space();
29370            self.generate_expression(delete)?;
29371        }
29372        if let Some(update) = &e.update {
29373            self.write_space();
29374            self.write_keyword("ON UPDATE");
29375            self.write_space();
29376            self.generate_expression(update)?;
29377        }
29378        if !e.options.is_empty() {
29379            self.write_space();
29380            for (i, opt) in e.options.iter().enumerate() {
29381                if i > 0 {
29382                    self.write_space();
29383                }
29384                self.generate_expression(opt)?;
29385            }
29386        }
29387        Ok(())
29388    }
29389
29390    fn generate_format(&mut self, e: &Format) -> Result<()> {
29391        // FORMAT(this, expressions...)
29392        self.write_keyword("FORMAT");
29393        self.write("(");
29394        self.generate_expression(&e.this)?;
29395        for expr in &e.expressions {
29396            self.write(", ");
29397            self.generate_expression(expr)?;
29398        }
29399        self.write(")");
29400        Ok(())
29401    }
29402
29403    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
29404        // Teradata: column (FORMAT 'format_string')
29405        self.generate_expression(&e.this)?;
29406        self.write(" (");
29407        self.write_keyword("FORMAT");
29408        self.write(" '");
29409        self.write(&e.format);
29410        self.write("')");
29411        Ok(())
29412    }
29413
29414    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
29415        // Python: FREESPACE=this[PERCENT]
29416        self.write_keyword("FREESPACE");
29417        self.write("=");
29418        self.generate_expression(&e.this)?;
29419        if e.percent.is_some() {
29420            self.write_keyword(" PERCENT");
29421        }
29422        Ok(())
29423    }
29424
29425    fn generate_from(&mut self, e: &From) -> Result<()> {
29426        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
29427        self.write_keyword("FROM");
29428        self.write_space();
29429
29430        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
29431        // But keep commas when TABLESAMPLE is present
29432        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
29433        use crate::dialects::DialectType;
29434        let has_tablesample = e
29435            .expressions
29436            .iter()
29437            .any(|expr| matches!(expr, Expression::TableSample(_)));
29438        let is_cross_join_dialect = matches!(
29439            self.config.dialect,
29440            Some(DialectType::BigQuery)
29441                | Some(DialectType::Hive)
29442                | Some(DialectType::Spark)
29443                | Some(DialectType::Databricks)
29444                | Some(DialectType::SQLite)
29445                | Some(DialectType::ClickHouse)
29446        );
29447        let source_is_same_as_target2 = self.config.source_dialect.is_some()
29448            && self.config.source_dialect == self.config.dialect;
29449        let source_is_cross_join_dialect2 = matches!(
29450            self.config.source_dialect,
29451            Some(DialectType::BigQuery)
29452                | Some(DialectType::Hive)
29453                | Some(DialectType::Spark)
29454                | Some(DialectType::Databricks)
29455                | Some(DialectType::SQLite)
29456                | Some(DialectType::ClickHouse)
29457        );
29458        let use_cross_join = !has_tablesample
29459            && is_cross_join_dialect
29460            && (source_is_same_as_target2
29461                || source_is_cross_join_dialect2
29462                || self.config.source_dialect.is_none());
29463
29464        // Snowflake wraps standalone VALUES in FROM clause with parentheses
29465        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
29466
29467        for (i, expr) in e.expressions.iter().enumerate() {
29468            if i > 0 {
29469                if use_cross_join {
29470                    self.write(" CROSS JOIN ");
29471                } else {
29472                    self.write(", ");
29473                }
29474            }
29475            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
29476                self.write("(");
29477                self.generate_expression(expr)?;
29478                self.write(")");
29479            } else {
29480                self.generate_expression(expr)?;
29481            }
29482            // Output leading comments that were on the table name before FROM
29483            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
29484            let leading = Self::extract_table_leading_comments(expr);
29485            for comment in &leading {
29486                self.write_space();
29487                self.write_formatted_comment(comment);
29488            }
29489        }
29490        Ok(())
29491    }
29492
29493    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
29494    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29495        match expr {
29496            Expression::Table(t) => t.leading_comments.clone(),
29497            Expression::Pivot(p) => {
29498                if let Expression::Table(t) = &p.this {
29499                    t.leading_comments.clone()
29500                } else {
29501                    Vec::new()
29502                }
29503            }
29504            _ => Vec::new(),
29505        }
29506    }
29507
29508    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29509        // FROM_BASE(this, expression) - convert from base N
29510        self.write_keyword("FROM_BASE");
29511        self.write("(");
29512        self.generate_expression(&e.this)?;
29513        self.write(", ");
29514        self.generate_expression(&e.expression)?;
29515        self.write(")");
29516        Ok(())
29517    }
29518
29519    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29520        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29521        self.generate_expression(&e.this)?;
29522        if let Some(zone) = &e.zone {
29523            self.write_space();
29524            self.write_keyword("AT TIME ZONE");
29525            self.write_space();
29526            self.generate_expression(zone)?;
29527            self.write_space();
29528            self.write_keyword("AT TIME ZONE");
29529            self.write(" 'UTC'");
29530        }
29531        Ok(())
29532    }
29533
29534    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29535        // GAP_FILL(this, ts_column, bucket_width, ...)
29536        self.write_keyword("GAP_FILL");
29537        self.write("(");
29538        self.generate_expression(&e.this)?;
29539        if let Some(ts_column) = &e.ts_column {
29540            self.write(", ");
29541            self.generate_expression(ts_column)?;
29542        }
29543        if let Some(bucket_width) = &e.bucket_width {
29544            self.write(", ");
29545            self.generate_expression(bucket_width)?;
29546        }
29547        if let Some(partitioning_columns) = &e.partitioning_columns {
29548            self.write(", ");
29549            self.generate_expression(partitioning_columns)?;
29550        }
29551        if let Some(value_columns) = &e.value_columns {
29552            self.write(", ");
29553            self.generate_expression(value_columns)?;
29554        }
29555        self.write(")");
29556        Ok(())
29557    }
29558
29559    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29560        // GENERATE_DATE_ARRAY(start, end, step)
29561        self.write_keyword("GENERATE_DATE_ARRAY");
29562        self.write("(");
29563        let mut first = true;
29564        if let Some(start) = &e.start {
29565            self.generate_expression(start)?;
29566            first = false;
29567        }
29568        if let Some(end) = &e.end {
29569            if !first {
29570                self.write(", ");
29571            }
29572            self.generate_expression(end)?;
29573            first = false;
29574        }
29575        if let Some(step) = &e.step {
29576            if !first {
29577                self.write(", ");
29578            }
29579            self.generate_expression(step)?;
29580        }
29581        self.write(")");
29582        Ok(())
29583    }
29584
29585    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29586        // ML.GENERATE_EMBEDDING(model, content, params)
29587        self.write_keyword("ML.GENERATE_EMBEDDING");
29588        self.write("(");
29589        self.generate_expression(&e.this)?;
29590        self.write(", ");
29591        self.generate_expression(&e.expression)?;
29592        if let Some(params) = &e.params_struct {
29593            self.write(", ");
29594            self.generate_expression(params)?;
29595        }
29596        self.write(")");
29597        Ok(())
29598    }
29599
29600    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29601        // Dialect-specific function name
29602        let fn_name = match self.config.dialect {
29603            Some(DialectType::Presto)
29604            | Some(DialectType::Trino)
29605            | Some(DialectType::Athena)
29606            | Some(DialectType::Spark)
29607            | Some(DialectType::Databricks)
29608            | Some(DialectType::Hive) => "SEQUENCE",
29609            _ => "GENERATE_SERIES",
29610        };
29611        self.write_keyword(fn_name);
29612        self.write("(");
29613        let mut first = true;
29614        if let Some(start) = &e.start {
29615            self.generate_expression(start)?;
29616            first = false;
29617        }
29618        if let Some(end) = &e.end {
29619            if !first {
29620                self.write(", ");
29621            }
29622            self.generate_expression(end)?;
29623            first = false;
29624        }
29625        if let Some(step) = &e.step {
29626            if !first {
29627                self.write(", ");
29628            }
29629            // For Presto/Trino: convert WEEK intervals to DAY multiples
29630            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29631            if matches!(
29632                self.config.dialect,
29633                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29634            ) {
29635                if let Some(converted) = self.convert_week_interval_to_day(step) {
29636                    self.generate_expression(&converted)?;
29637                } else {
29638                    self.generate_expression(step)?;
29639                }
29640            } else {
29641                self.generate_expression(step)?;
29642            }
29643        }
29644        self.write(")");
29645        Ok(())
29646    }
29647
29648    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29649    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29650    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29651        use crate::expressions::*;
29652        if let Expression::Interval(ref iv) = expr {
29653            // Check for structured WEEK unit
29654            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29655                unit: IntervalUnit::Week,
29656                ..
29657            }) = &iv.unit
29658            {
29659                // Value is in iv.this
29660                let count = match &iv.this {
29661                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29662                        Literal::String(s) | Literal::Number(s) => s.clone(),
29663                        _ => return None,
29664                    },
29665                    _ => return None,
29666                };
29667                (true, count)
29668            } else if iv.unit.is_none() {
29669                // Check for string-encoded interval like "1 WEEK"
29670                if let Some(Expression::Literal(lit)) = &iv.this {
29671                    if let Literal::String(s) = lit.as_ref() {
29672                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29673                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29674                            (true, parts[0].to_string())
29675                        } else {
29676                            (false, String::new())
29677                        }
29678                    } else {
29679                        (false, String::new())
29680                    }
29681                } else {
29682                    (false, String::new())
29683                }
29684            } else {
29685                (false, String::new())
29686            };
29687
29688            if is_week {
29689                // Build: (N * INTERVAL '7' DAY)
29690                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29691                let day_interval = Expression::Interval(Box::new(Interval {
29692                    this: Some(Expression::Literal(Box::new(Literal::String(
29693                        "7".to_string(),
29694                    )))),
29695                    unit: Some(IntervalUnitSpec::Simple {
29696                        unit: IntervalUnit::Day,
29697                        use_plural: false,
29698                    }),
29699                }));
29700                let mul = Expression::Mul(Box::new(BinaryOp {
29701                    left: count_expr,
29702                    right: day_interval,
29703                    left_comments: vec![],
29704                    operator_comments: vec![],
29705                    trailing_comments: vec![],
29706                    inferred_type: None,
29707                }));
29708                return Some(Expression::Paren(Box::new(Paren {
29709                    this: mul,
29710                    trailing_comments: vec![],
29711                })));
29712            }
29713        }
29714        None
29715    }
29716
29717    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29718        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29719        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29720        self.write("(");
29721        let mut first = true;
29722        if let Some(start) = &e.start {
29723            self.generate_expression(start)?;
29724            first = false;
29725        }
29726        if let Some(end) = &e.end {
29727            if !first {
29728                self.write(", ");
29729            }
29730            self.generate_expression(end)?;
29731            first = false;
29732        }
29733        if let Some(step) = &e.step {
29734            if !first {
29735                self.write(", ");
29736            }
29737            self.generate_expression(step)?;
29738        }
29739        self.write(")");
29740        Ok(())
29741    }
29742
29743    fn generate_generated_as_identity_column_constraint(
29744        &mut self,
29745        e: &GeneratedAsIdentityColumnConstraint,
29746    ) -> Result<()> {
29747        use crate::dialects::DialectType;
29748
29749        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29750        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29751            self.write_keyword("AUTOINCREMENT");
29752            if let Some(start) = &e.start {
29753                self.write_keyword(" START ");
29754                self.generate_expression(start)?;
29755            }
29756            if let Some(increment) = &e.increment {
29757                self.write_keyword(" INCREMENT ");
29758                self.generate_expression(increment)?;
29759            }
29760            return Ok(());
29761        }
29762
29763        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29764        self.write_keyword("GENERATED");
29765        if let Some(this) = &e.this {
29766            // Check if it's a truthy boolean expression
29767            if let Expression::Boolean(b) = this.as_ref() {
29768                if b.value {
29769                    self.write_keyword(" ALWAYS");
29770                } else {
29771                    self.write_keyword(" BY DEFAULT");
29772                    if e.on_null.is_some() {
29773                        self.write_keyword(" ON NULL");
29774                    }
29775                }
29776            } else {
29777                self.write_keyword(" ALWAYS");
29778            }
29779        }
29780        self.write_keyword(" AS IDENTITY");
29781        // Add sequence options if any
29782        let has_options = e.start.is_some()
29783            || e.increment.is_some()
29784            || e.minvalue.is_some()
29785            || e.maxvalue.is_some();
29786        if has_options {
29787            self.write(" (");
29788            let mut first = true;
29789            if let Some(start) = &e.start {
29790                self.write_keyword("START WITH ");
29791                self.generate_expression(start)?;
29792                first = false;
29793            }
29794            if let Some(increment) = &e.increment {
29795                if !first {
29796                    self.write(" ");
29797                }
29798                self.write_keyword("INCREMENT BY ");
29799                self.generate_expression(increment)?;
29800                first = false;
29801            }
29802            if let Some(minvalue) = &e.minvalue {
29803                if !first {
29804                    self.write(" ");
29805                }
29806                self.write_keyword("MINVALUE ");
29807                self.generate_expression(minvalue)?;
29808                first = false;
29809            }
29810            if let Some(maxvalue) = &e.maxvalue {
29811                if !first {
29812                    self.write(" ");
29813                }
29814                self.write_keyword("MAXVALUE ");
29815                self.generate_expression(maxvalue)?;
29816            }
29817            self.write(")");
29818        }
29819        Ok(())
29820    }
29821
29822    fn generate_generated_as_row_column_constraint(
29823        &mut self,
29824        e: &GeneratedAsRowColumnConstraint,
29825    ) -> Result<()> {
29826        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29827        self.write_keyword("GENERATED ALWAYS AS ROW ");
29828        if e.start.is_some() {
29829            self.write_keyword("START");
29830        } else {
29831            self.write_keyword("END");
29832        }
29833        if e.hidden.is_some() {
29834            self.write_keyword(" HIDDEN");
29835        }
29836        Ok(())
29837    }
29838
29839    fn generate_get(&mut self, e: &Get) -> Result<()> {
29840        // GET this target properties
29841        self.write_keyword("GET");
29842        self.write_space();
29843        self.generate_expression(&e.this)?;
29844        if let Some(target) = &e.target {
29845            self.write_space();
29846            self.generate_expression(target)?;
29847        }
29848        for prop in &e.properties {
29849            self.write_space();
29850            self.generate_expression(prop)?;
29851        }
29852        Ok(())
29853    }
29854
29855    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
29856        // GetExtract generates bracket access: this[expression]
29857        self.generate_expression(&e.this)?;
29858        self.write("[");
29859        self.generate_expression(&e.expression)?;
29860        self.write("]");
29861        Ok(())
29862    }
29863
29864    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
29865        // GETBIT(this, expression) or GET_BIT(this, expression)
29866        self.write_keyword("GETBIT");
29867        self.write("(");
29868        self.generate_expression(&e.this)?;
29869        self.write(", ");
29870        self.generate_expression(&e.expression)?;
29871        self.write(")");
29872        Ok(())
29873    }
29874
29875    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
29876        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
29877        if e.is_role {
29878            self.write_keyword("ROLE");
29879            self.write_space();
29880        } else if e.is_group {
29881            self.write_keyword("GROUP");
29882            self.write_space();
29883        } else if e.is_share {
29884            self.write_keyword("SHARE");
29885            self.write_space();
29886        }
29887        self.write(&e.name.name);
29888        Ok(())
29889    }
29890
29891    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29892        // privilege(columns) or just privilege
29893        self.generate_expression(&e.this)?;
29894        if !e.expressions.is_empty() {
29895            self.write("(");
29896            for (i, expr) in e.expressions.iter().enumerate() {
29897                if i > 0 {
29898                    self.write(", ");
29899                }
29900                self.generate_expression(expr)?;
29901            }
29902            self.write(")");
29903        }
29904        Ok(())
29905    }
29906
29907    fn generate_group(&mut self, e: &Group) -> Result<()> {
29908        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29909        self.write_keyword("GROUP BY");
29910        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29911        match e.all {
29912            Some(true) => {
29913                self.write_space();
29914                self.write_keyword("ALL");
29915            }
29916            Some(false) => {
29917                self.write_space();
29918                self.write_keyword("DISTINCT");
29919            }
29920            None => {}
29921        }
29922        if !e.expressions.is_empty() {
29923            self.write_space();
29924            for (i, expr) in e.expressions.iter().enumerate() {
29925                if i > 0 {
29926                    self.write(", ");
29927                }
29928                self.generate_expression(expr)?;
29929            }
29930        }
29931        // Handle CUBE, ROLLUP, GROUPING SETS
29932        if let Some(cube) = &e.cube {
29933            if !e.expressions.is_empty() {
29934                self.write(", ");
29935            } else {
29936                self.write_space();
29937            }
29938            self.generate_expression(cube)?;
29939        }
29940        if let Some(rollup) = &e.rollup {
29941            if !e.expressions.is_empty() || e.cube.is_some() {
29942                self.write(", ");
29943            } else {
29944                self.write_space();
29945            }
29946            self.generate_expression(rollup)?;
29947        }
29948        if let Some(grouping_sets) = &e.grouping_sets {
29949            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29950                self.write(", ");
29951            } else {
29952                self.write_space();
29953            }
29954            self.generate_expression(grouping_sets)?;
29955        }
29956        if let Some(totals) = &e.totals {
29957            self.write_space();
29958            self.write_keyword("WITH TOTALS");
29959            self.generate_expression(totals)?;
29960        }
29961        Ok(())
29962    }
29963
29964    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29965        // GROUP BY expressions
29966        self.write_keyword("GROUP BY");
29967        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29968        match e.all {
29969            Some(true) => {
29970                self.write_space();
29971                self.write_keyword("ALL");
29972            }
29973            Some(false) => {
29974                self.write_space();
29975                self.write_keyword("DISTINCT");
29976            }
29977            None => {}
29978        }
29979
29980        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29981        // These are represented as Cube/Rollup expressions with empty expressions at the end
29982        let mut trailing_cube = false;
29983        let mut trailing_rollup = false;
29984        let mut regular_expressions: Vec<&Expression> = Vec::new();
29985
29986        for expr in &e.expressions {
29987            match expr {
29988                Expression::Cube(c) if c.expressions.is_empty() => {
29989                    trailing_cube = true;
29990                }
29991                Expression::Rollup(r) if r.expressions.is_empty() => {
29992                    trailing_rollup = true;
29993                }
29994                _ => {
29995                    regular_expressions.push(expr);
29996                }
29997            }
29998        }
29999
30000        // In pretty mode, put columns on separate lines
30001        if self.config.pretty {
30002            self.write_newline();
30003            self.indent_level += 1;
30004            for (i, expr) in regular_expressions.iter().enumerate() {
30005                if i > 0 {
30006                    self.write(",");
30007                    self.write_newline();
30008                }
30009                self.write_indent();
30010                self.generate_expression(expr)?;
30011            }
30012            self.indent_level -= 1;
30013        } else {
30014            self.write_space();
30015            for (i, expr) in regular_expressions.iter().enumerate() {
30016                if i > 0 {
30017                    self.write(", ");
30018                }
30019                self.generate_expression(expr)?;
30020            }
30021        }
30022
30023        // Output trailing WITH CUBE or WITH ROLLUP
30024        if trailing_cube {
30025            self.write_space();
30026            self.write_keyword("WITH CUBE");
30027        } else if trailing_rollup {
30028            self.write_space();
30029            self.write_keyword("WITH ROLLUP");
30030        }
30031
30032        // ClickHouse: WITH TOTALS
30033        if e.totals {
30034            self.write_space();
30035            self.write_keyword("WITH TOTALS");
30036        }
30037
30038        Ok(())
30039    }
30040
30041    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
30042        // GROUPING(col1, col2, ...)
30043        self.write_keyword("GROUPING");
30044        self.write("(");
30045        for (i, expr) in e.expressions.iter().enumerate() {
30046            if i > 0 {
30047                self.write(", ");
30048            }
30049            self.generate_expression(expr)?;
30050        }
30051        self.write(")");
30052        Ok(())
30053    }
30054
30055    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
30056        // GROUPING_ID(col1, col2, ...)
30057        self.write_keyword("GROUPING_ID");
30058        self.write("(");
30059        for (i, expr) in e.expressions.iter().enumerate() {
30060            if i > 0 {
30061                self.write(", ");
30062            }
30063            self.generate_expression(expr)?;
30064        }
30065        self.write(")");
30066        Ok(())
30067    }
30068
30069    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
30070        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
30071        self.write_keyword("GROUPING SETS");
30072        self.write(" (");
30073        for (i, expr) in e.expressions.iter().enumerate() {
30074            if i > 0 {
30075                self.write(", ");
30076            }
30077            self.generate_expression(expr)?;
30078        }
30079        self.write(")");
30080        Ok(())
30081    }
30082
30083    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
30084        // HASH_AGG(this, expressions...)
30085        self.write_keyword("HASH_AGG");
30086        self.write("(");
30087        self.generate_expression(&e.this)?;
30088        for expr in &e.expressions {
30089            self.write(", ");
30090            self.generate_expression(expr)?;
30091        }
30092        self.write(")");
30093        Ok(())
30094    }
30095
30096    fn generate_having(&mut self, e: &Having) -> Result<()> {
30097        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
30098        self.write_keyword("HAVING");
30099        self.write_space();
30100        self.generate_expression(&e.this)?;
30101        Ok(())
30102    }
30103
30104    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
30105        // Python: this HAVING MAX|MIN expression
30106        self.generate_expression(&e.this)?;
30107        self.write_space();
30108        self.write_keyword("HAVING");
30109        self.write_space();
30110        if e.max.is_some() {
30111            self.write_keyword("MAX");
30112        } else {
30113            self.write_keyword("MIN");
30114        }
30115        self.write_space();
30116        self.generate_expression(&e.expression)?;
30117        Ok(())
30118    }
30119
30120    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
30121        use crate::dialects::DialectType;
30122        // DuckDB: convert dollar-tagged strings to single-quoted
30123        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
30124            // Extract the string content and output as single-quoted
30125            if let Expression::Literal(ref lit) = *e.this {
30126                if let Literal::String(ref s) = lit.as_ref() {
30127                    return self.generate_string_literal(s);
30128                }
30129            }
30130        }
30131        // PostgreSQL: preserve dollar-quoting
30132        if matches!(
30133            self.config.dialect,
30134            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30135        ) {
30136            self.write("$");
30137            if let Some(tag) = &e.tag {
30138                self.generate_expression(tag)?;
30139            }
30140            self.write("$");
30141            self.generate_expression(&e.this)?;
30142            self.write("$");
30143            if let Some(tag) = &e.tag {
30144                self.generate_expression(tag)?;
30145            }
30146            self.write("$");
30147            return Ok(());
30148        }
30149        // Default: output as dollar-tagged
30150        self.write("$");
30151        if let Some(tag) = &e.tag {
30152            self.generate_expression(tag)?;
30153        }
30154        self.write("$");
30155        self.generate_expression(&e.this)?;
30156        self.write("$");
30157        if let Some(tag) = &e.tag {
30158            self.generate_expression(tag)?;
30159        }
30160        self.write("$");
30161        Ok(())
30162    }
30163
30164    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
30165        // HEX_ENCODE(this)
30166        self.write_keyword("HEX_ENCODE");
30167        self.write("(");
30168        self.generate_expression(&e.this)?;
30169        self.write(")");
30170        Ok(())
30171    }
30172
30173    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
30174        // Python: this (kind => expression)
30175        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
30176        match e.this.as_ref() {
30177            Expression::Identifier(id) => self.write(&id.name),
30178            other => self.generate_expression(other)?,
30179        }
30180        self.write(" (");
30181        self.write(&e.kind);
30182        self.write(" => ");
30183        self.generate_expression(&e.expression)?;
30184        self.write(")");
30185        Ok(())
30186    }
30187
30188    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
30189        // HLL(this, expressions...)
30190        self.write_keyword("HLL");
30191        self.write("(");
30192        self.generate_expression(&e.this)?;
30193        for expr in &e.expressions {
30194            self.write(", ");
30195            self.generate_expression(expr)?;
30196        }
30197        self.write(")");
30198        Ok(())
30199    }
30200
30201    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
30202        // Python: IN|OUT|IN OUT
30203        if e.input_.is_some() && e.output.is_some() {
30204            self.write_keyword("IN OUT");
30205        } else if e.input_.is_some() {
30206            self.write_keyword("IN");
30207        } else if e.output.is_some() {
30208            self.write_keyword("OUT");
30209        }
30210        Ok(())
30211    }
30212
30213    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
30214        // Python: INCLUDE this [column_def] [AS alias]
30215        self.write_keyword("INCLUDE");
30216        self.write_space();
30217        self.generate_expression(&e.this)?;
30218        if let Some(column_def) = &e.column_def {
30219            self.write_space();
30220            self.generate_expression(column_def)?;
30221        }
30222        if let Some(alias) = &e.alias {
30223            self.write_space();
30224            self.write_keyword("AS");
30225            self.write_space();
30226            self.write(alias);
30227        }
30228        Ok(())
30229    }
30230
30231    fn generate_index(&mut self, e: &Index) -> Result<()> {
30232        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
30233        if e.unique {
30234            self.write_keyword("UNIQUE");
30235            self.write_space();
30236        }
30237        if e.primary.is_some() {
30238            self.write_keyword("PRIMARY");
30239            self.write_space();
30240        }
30241        if e.amp.is_some() {
30242            self.write_keyword("AMP");
30243            self.write_space();
30244        }
30245        if e.table.is_none() {
30246            self.write_keyword("INDEX");
30247            self.write_space();
30248        }
30249        if let Some(name) = &e.this {
30250            self.generate_expression(name)?;
30251            self.write_space();
30252        }
30253        if let Some(table) = &e.table {
30254            self.write_keyword("ON");
30255            self.write_space();
30256            self.generate_expression(table)?;
30257        }
30258        if !e.params.is_empty() {
30259            self.write("(");
30260            for (i, param) in e.params.iter().enumerate() {
30261                if i > 0 {
30262                    self.write(", ");
30263                }
30264                self.generate_expression(param)?;
30265            }
30266            self.write(")");
30267        }
30268        Ok(())
30269    }
30270
30271    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
30272        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
30273        if let Some(kind) = &e.kind {
30274            self.write(kind);
30275            self.write_space();
30276        }
30277        self.write_keyword("INDEX");
30278        if let Some(this) = &e.this {
30279            self.write_space();
30280            self.generate_expression(this)?;
30281        }
30282        if let Some(index_type) = &e.index_type {
30283            self.write_space();
30284            self.write_keyword("USING");
30285            self.write_space();
30286            self.generate_expression(index_type)?;
30287        }
30288        if !e.expressions.is_empty() {
30289            self.write(" (");
30290            for (i, expr) in e.expressions.iter().enumerate() {
30291                if i > 0 {
30292                    self.write(", ");
30293                }
30294                self.generate_expression(expr)?;
30295            }
30296            self.write(")");
30297        }
30298        for opt in &e.options {
30299            self.write_space();
30300            self.generate_expression(opt)?;
30301        }
30302        Ok(())
30303    }
30304
30305    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
30306        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
30307        if let Some(key_block_size) = &e.key_block_size {
30308            self.write_keyword("KEY_BLOCK_SIZE");
30309            self.write(" = ");
30310            self.generate_expression(key_block_size)?;
30311        } else if let Some(using) = &e.using {
30312            self.write_keyword("USING");
30313            self.write_space();
30314            self.generate_expression(using)?;
30315        } else if let Some(parser) = &e.parser {
30316            self.write_keyword("WITH PARSER");
30317            self.write_space();
30318            self.generate_expression(parser)?;
30319        } else if let Some(comment) = &e.comment {
30320            self.write_keyword("COMMENT");
30321            self.write_space();
30322            self.generate_expression(comment)?;
30323        } else if let Some(visible) = &e.visible {
30324            self.generate_expression(visible)?;
30325        } else if let Some(engine_attr) = &e.engine_attr {
30326            self.write_keyword("ENGINE_ATTRIBUTE");
30327            self.write(" = ");
30328            self.generate_expression(engine_attr)?;
30329        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
30330            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
30331            self.write(" = ");
30332            self.generate_expression(secondary_engine_attr)?;
30333        }
30334        Ok(())
30335    }
30336
30337    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
30338        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
30339        if let Some(using) = &e.using {
30340            self.write_keyword("USING");
30341            self.write_space();
30342            self.generate_expression(using)?;
30343        }
30344        if !e.columns.is_empty() {
30345            self.write("(");
30346            for (i, col) in e.columns.iter().enumerate() {
30347                if i > 0 {
30348                    self.write(", ");
30349                }
30350                self.generate_expression(col)?;
30351            }
30352            self.write(")");
30353        }
30354        if let Some(partition_by) = &e.partition_by {
30355            self.write_space();
30356            self.write_keyword("PARTITION BY");
30357            self.write_space();
30358            self.generate_expression(partition_by)?;
30359        }
30360        if let Some(where_) = &e.where_ {
30361            self.write_space();
30362            self.generate_expression(where_)?;
30363        }
30364        if let Some(include) = &e.include {
30365            self.write_space();
30366            self.write_keyword("INCLUDE");
30367            self.write(" (");
30368            self.generate_expression(include)?;
30369            self.write(")");
30370        }
30371        if let Some(with_storage) = &e.with_storage {
30372            self.write_space();
30373            self.write_keyword("WITH");
30374            self.write(" (");
30375            self.generate_expression(with_storage)?;
30376            self.write(")");
30377        }
30378        if let Some(tablespace) = &e.tablespace {
30379            self.write_space();
30380            self.write_keyword("USING INDEX TABLESPACE");
30381            self.write_space();
30382            self.generate_expression(tablespace)?;
30383        }
30384        Ok(())
30385    }
30386
30387    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
30388        // Python: this INDEX [FOR target] (expressions)
30389        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
30390        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
30391        if let Expression::Identifier(id) = &*e.this {
30392            self.write_keyword(&id.name);
30393        } else {
30394            self.generate_expression(&e.this)?;
30395        }
30396        self.write_space();
30397        self.write_keyword("INDEX");
30398        if let Some(target) = &e.target {
30399            self.write_space();
30400            self.write_keyword("FOR");
30401            self.write_space();
30402            if let Expression::Identifier(id) = &**target {
30403                self.write_keyword(&id.name);
30404            } else {
30405                self.generate_expression(target)?;
30406            }
30407        }
30408        // Always output parentheses (even if empty, e.g. USE INDEX ())
30409        self.write(" (");
30410        for (i, expr) in e.expressions.iter().enumerate() {
30411            if i > 0 {
30412                self.write(", ");
30413            }
30414            self.generate_expression(expr)?;
30415        }
30416        self.write(")");
30417        Ok(())
30418    }
30419
30420    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
30421        // INHERITS (table1, table2, ...)
30422        self.write_keyword("INHERITS");
30423        self.write(" (");
30424        for (i, expr) in e.expressions.iter().enumerate() {
30425            if i > 0 {
30426                self.write(", ");
30427            }
30428            self.generate_expression(expr)?;
30429        }
30430        self.write(")");
30431        Ok(())
30432    }
30433
30434    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
30435        // INPUT(model)
30436        self.write_keyword("INPUT");
30437        self.write("(");
30438        self.generate_expression(&e.this)?;
30439        self.write(")");
30440        Ok(())
30441    }
30442
30443    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
30444        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
30445        if let Some(input_format) = &e.input_format {
30446            self.write_keyword("INPUTFORMAT");
30447            self.write_space();
30448            self.generate_expression(input_format)?;
30449        }
30450        if let Some(output_format) = &e.output_format {
30451            if e.input_format.is_some() {
30452                self.write(" ");
30453            }
30454            self.write_keyword("OUTPUTFORMAT");
30455            self.write_space();
30456            self.generate_expression(output_format)?;
30457        }
30458        Ok(())
30459    }
30460
30461    fn generate_install(&mut self, e: &Install) -> Result<()> {
30462        // [FORCE] INSTALL extension [FROM source]
30463        if e.force.is_some() {
30464            self.write_keyword("FORCE");
30465            self.write_space();
30466        }
30467        self.write_keyword("INSTALL");
30468        self.write_space();
30469        self.generate_expression(&e.this)?;
30470        if let Some(from) = &e.from_ {
30471            self.write_space();
30472            self.write_keyword("FROM");
30473            self.write_space();
30474            self.generate_expression(from)?;
30475        }
30476        Ok(())
30477    }
30478
30479    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
30480        // INTERVAL 'expression' unit
30481        self.write_keyword("INTERVAL");
30482        self.write_space();
30483        // When a unit is specified and the expression is a number,
30484        self.generate_expression(&e.expression)?;
30485        if let Some(unit) = &e.unit {
30486            self.write_space();
30487            self.write(unit);
30488        }
30489        Ok(())
30490    }
30491
30492    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
30493        // unit TO unit (e.g., HOUR TO SECOND)
30494        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30495        self.write_space();
30496        self.write_keyword("TO");
30497        self.write_space();
30498        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30499        Ok(())
30500    }
30501
30502    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30503        // INTO [TEMPORARY|UNLOGGED] table
30504        self.write_keyword("INTO");
30505        if e.temporary {
30506            self.write_keyword(" TEMPORARY");
30507        }
30508        if e.unlogged.is_some() {
30509            self.write_keyword(" UNLOGGED");
30510        }
30511        if let Some(this) = &e.this {
30512            self.write_space();
30513            self.generate_expression(this)?;
30514        }
30515        if !e.expressions.is_empty() {
30516            self.write(" (");
30517            for (i, expr) in e.expressions.iter().enumerate() {
30518                if i > 0 {
30519                    self.write(", ");
30520                }
30521                self.generate_expression(expr)?;
30522            }
30523            self.write(")");
30524        }
30525        Ok(())
30526    }
30527
30528    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30529        // Python: this expression (e.g., _utf8 'string')
30530        self.generate_expression(&e.this)?;
30531        self.write_space();
30532        self.generate_expression(&e.expression)?;
30533        Ok(())
30534    }
30535
30536    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30537        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30538        self.write_keyword("WITH");
30539        if e.no.is_some() {
30540            self.write_keyword(" NO");
30541        }
30542        if e.concurrent.is_some() {
30543            self.write_keyword(" CONCURRENT");
30544        }
30545        self.write_keyword(" ISOLATED LOADING");
30546        if let Some(target) = &e.target {
30547            self.write_space();
30548            self.generate_expression(target)?;
30549        }
30550        Ok(())
30551    }
30552
30553    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30554        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30555        self.write_keyword("JSON");
30556        if let Some(this) = &e.this {
30557            self.write_space();
30558            self.generate_expression(this)?;
30559        }
30560        if let Some(with_) = &e.with_ {
30561            // Check if it's a truthy boolean
30562            if let Expression::Boolean(b) = with_.as_ref() {
30563                if b.value {
30564                    self.write_keyword(" WITH");
30565                } else {
30566                    self.write_keyword(" WITHOUT");
30567                }
30568            }
30569        }
30570        if e.unique {
30571            self.write_keyword(" UNIQUE KEYS");
30572        }
30573        Ok(())
30574    }
30575
30576    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30577        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30578        self.write_keyword("JSON_ARRAY");
30579        self.write("(");
30580        for (i, expr) in e.expressions.iter().enumerate() {
30581            if i > 0 {
30582                self.write(", ");
30583            }
30584            self.generate_expression(expr)?;
30585        }
30586        if let Some(null_handling) = &e.null_handling {
30587            self.write_space();
30588            self.generate_expression(null_handling)?;
30589        }
30590        if let Some(return_type) = &e.return_type {
30591            self.write_space();
30592            self.write_keyword("RETURNING");
30593            self.write_space();
30594            self.generate_expression(return_type)?;
30595        }
30596        if e.strict.is_some() {
30597            self.write_space();
30598            self.write_keyword("STRICT");
30599        }
30600        self.write(")");
30601        Ok(())
30602    }
30603
30604    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30605        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30606        self.write_keyword("JSON_ARRAYAGG");
30607        self.write("(");
30608        self.generate_expression(&e.this)?;
30609        if let Some(order) = &e.order {
30610            self.write_space();
30611            // Order is stored as an OrderBy expression
30612            if let Expression::OrderBy(ob) = order.as_ref() {
30613                self.write_keyword("ORDER BY");
30614                self.write_space();
30615                for (i, ord) in ob.expressions.iter().enumerate() {
30616                    if i > 0 {
30617                        self.write(", ");
30618                    }
30619                    self.generate_ordered(ord)?;
30620                }
30621            } else {
30622                // Fallback: generate the expression directly
30623                self.generate_expression(order)?;
30624            }
30625        }
30626        if let Some(null_handling) = &e.null_handling {
30627            self.write_space();
30628            self.generate_expression(null_handling)?;
30629        }
30630        if let Some(return_type) = &e.return_type {
30631            self.write_space();
30632            self.write_keyword("RETURNING");
30633            self.write_space();
30634            self.generate_expression(return_type)?;
30635        }
30636        if e.strict.is_some() {
30637            self.write_space();
30638            self.write_keyword("STRICT");
30639        }
30640        self.write(")");
30641        Ok(())
30642    }
30643
30644    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30645        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30646        self.write_keyword("JSON_OBJECTAGG");
30647        self.write("(");
30648        for (i, expr) in e.expressions.iter().enumerate() {
30649            if i > 0 {
30650                self.write(", ");
30651            }
30652            self.generate_expression(expr)?;
30653        }
30654        if let Some(null_handling) = &e.null_handling {
30655            self.write_space();
30656            self.generate_expression(null_handling)?;
30657        }
30658        if let Some(unique_keys) = &e.unique_keys {
30659            self.write_space();
30660            if let Expression::Boolean(b) = unique_keys.as_ref() {
30661                if b.value {
30662                    self.write_keyword("WITH UNIQUE KEYS");
30663                } else {
30664                    self.write_keyword("WITHOUT UNIQUE KEYS");
30665                }
30666            }
30667        }
30668        if let Some(return_type) = &e.return_type {
30669            self.write_space();
30670            self.write_keyword("RETURNING");
30671            self.write_space();
30672            self.generate_expression(return_type)?;
30673        }
30674        self.write(")");
30675        Ok(())
30676    }
30677
30678    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30679        // JSON_ARRAY_APPEND(this, path, value, ...)
30680        self.write_keyword("JSON_ARRAY_APPEND");
30681        self.write("(");
30682        self.generate_expression(&e.this)?;
30683        for expr in &e.expressions {
30684            self.write(", ");
30685            self.generate_expression(expr)?;
30686        }
30687        self.write(")");
30688        Ok(())
30689    }
30690
30691    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30692        // JSON_ARRAY_CONTAINS(this, expression)
30693        self.write_keyword("JSON_ARRAY_CONTAINS");
30694        self.write("(");
30695        self.generate_expression(&e.this)?;
30696        self.write(", ");
30697        self.generate_expression(&e.expression)?;
30698        self.write(")");
30699        Ok(())
30700    }
30701
30702    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30703        // JSON_ARRAY_INSERT(this, path, value, ...)
30704        self.write_keyword("JSON_ARRAY_INSERT");
30705        self.write("(");
30706        self.generate_expression(&e.this)?;
30707        for expr in &e.expressions {
30708            self.write(", ");
30709            self.generate_expression(expr)?;
30710        }
30711        self.write(")");
30712        Ok(())
30713    }
30714
30715    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30716        // JSONB_EXISTS(this, path)
30717        self.write_keyword("JSONB_EXISTS");
30718        self.write("(");
30719        self.generate_expression(&e.this)?;
30720        if let Some(path) = &e.path {
30721            self.write(", ");
30722            self.generate_expression(path)?;
30723        }
30724        self.write(")");
30725        Ok(())
30726    }
30727
30728    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30729        // JSONB_EXTRACT_SCALAR(this, expression)
30730        self.write_keyword("JSONB_EXTRACT_SCALAR");
30731        self.write("(");
30732        self.generate_expression(&e.this)?;
30733        self.write(", ");
30734        self.generate_expression(&e.expression)?;
30735        self.write(")");
30736        Ok(())
30737    }
30738
30739    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30740        // JSONB_OBJECT_AGG(this, expression)
30741        self.write_keyword("JSONB_OBJECT_AGG");
30742        self.write("(");
30743        self.generate_expression(&e.this)?;
30744        self.write(", ");
30745        self.generate_expression(&e.expression)?;
30746        self.write(")");
30747        Ok(())
30748    }
30749
30750    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30751        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30752        if let Some(nested_schema) = &e.nested_schema {
30753            self.write_keyword("NESTED");
30754            if let Some(path) = &e.path {
30755                self.write_space();
30756                self.write_keyword("PATH");
30757                self.write_space();
30758                self.generate_expression(path)?;
30759            }
30760            self.write_space();
30761            self.generate_expression(nested_schema)?;
30762        } else {
30763            if let Some(this) = &e.this {
30764                self.generate_expression(this)?;
30765            }
30766            if let Some(kind) = &e.kind {
30767                self.write_space();
30768                self.write(kind);
30769            }
30770            if e.format_json {
30771                self.write_space();
30772                self.write_keyword("FORMAT JSON");
30773            }
30774            if let Some(path) = &e.path {
30775                self.write_space();
30776                self.write_keyword("PATH");
30777                self.write_space();
30778                self.generate_expression(path)?;
30779            }
30780            if e.ordinality.is_some() {
30781                self.write_keyword(" FOR ORDINALITY");
30782            }
30783        }
30784        Ok(())
30785    }
30786
30787    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30788        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30789        self.write_keyword("JSON_EXISTS");
30790        self.write("(");
30791        self.generate_expression(&e.this)?;
30792        if let Some(path) = &e.path {
30793            self.write(", ");
30794            self.generate_expression(path)?;
30795        }
30796        if let Some(passing) = &e.passing {
30797            self.write_space();
30798            self.write_keyword("PASSING");
30799            self.write_space();
30800            self.generate_expression(passing)?;
30801        }
30802        if let Some(on_condition) = &e.on_condition {
30803            self.write_space();
30804            self.generate_expression(on_condition)?;
30805        }
30806        self.write(")");
30807        Ok(())
30808    }
30809
30810    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30811        self.generate_expression(&e.this)?;
30812        self.write(".:");
30813        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30814        // wrap the entire type string in double quotes.
30815        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30816        if Self::data_type_has_nested_expressions(&e.to) {
30817            // Generate the data type to a temporary string buffer, then wrap in quotes
30818            let saved = std::mem::take(&mut self.output);
30819            self.generate_data_type(&e.to)?;
30820            let type_sql = std::mem::replace(&mut self.output, saved);
30821            self.write("\"");
30822            self.write(&type_sql);
30823            self.write("\"");
30824        } else {
30825            self.generate_data_type(&e.to)?;
30826        }
30827        Ok(())
30828    }
30829
30830    /// Check if a DataType has nested type expressions (sub-types).
30831    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30832    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30833        matches!(
30834            dt,
30835            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30836        )
30837    }
30838
30839    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30840        // JSON_EXTRACT_ARRAY(this, expression)
30841        self.write_keyword("JSON_EXTRACT_ARRAY");
30842        self.write("(");
30843        self.generate_expression(&e.this)?;
30844        if let Some(expr) = &e.expression {
30845            self.write(", ");
30846            self.generate_expression(expr)?;
30847        }
30848        self.write(")");
30849        Ok(())
30850    }
30851
30852    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
30853        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
30854        if let Some(option) = &e.option {
30855            self.generate_expression(option)?;
30856            self.write_space();
30857        }
30858        self.write_keyword("QUOTES");
30859        if e.scalar.is_some() {
30860            self.write_keyword(" SCALAR_ONLY");
30861        }
30862        Ok(())
30863    }
30864
30865    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
30866        // JSON_EXTRACT_SCALAR(this, expression)
30867        self.write_keyword("JSON_EXTRACT_SCALAR");
30868        self.write("(");
30869        self.generate_expression(&e.this)?;
30870        self.write(", ");
30871        self.generate_expression(&e.expression)?;
30872        self.write(")");
30873        Ok(())
30874    }
30875
30876    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
30877        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
30878        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
30879        // Otherwise output JSON_EXTRACT(this, expression)
30880        if e.variant_extract.is_some() {
30881            use crate::dialects::DialectType;
30882            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
30883                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
30884                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
30885                // bracket notation: c:["x-y"] instead of c:x-y
30886                self.generate_expression(&e.this)?;
30887                self.write(":");
30888                match e.expression.as_ref() {
30889                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30890                        let Literal::String(s) = lit.as_ref() else {
30891                            unreachable!()
30892                        };
30893                        self.write_databricks_json_path(s);
30894                    }
30895                    _ => {
30896                        // Fallback: generate as-is (shouldn't happen in typical cases)
30897                        self.generate_expression(&e.expression)?;
30898                    }
30899                }
30900            } else {
30901                // Snowflake and others: use GET_PATH(col, 'path')
30902                self.write_keyword("GET_PATH");
30903                self.write("(");
30904                self.generate_expression(&e.this)?;
30905                self.write(", ");
30906                self.generate_expression(&e.expression)?;
30907                self.write(")");
30908            }
30909        } else {
30910            self.write_keyword("JSON_EXTRACT");
30911            self.write("(");
30912            self.generate_expression(&e.this)?;
30913            self.write(", ");
30914            self.generate_expression(&e.expression)?;
30915            for expr in &e.expressions {
30916                self.write(", ");
30917                self.generate_expression(expr)?;
30918            }
30919            self.write(")");
30920        }
30921        Ok(())
30922    }
30923
30924    /// Write a Databricks JSON colon-path, using bracket notation for keys
30925    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30926    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30927    fn write_databricks_json_path(&mut self, path: &str) {
30928        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30929        // it was already formatted by the parser - output as-is
30930        if path.starts_with("[\"") || path.starts_with("['") {
30931            self.write(path);
30932            return;
30933        }
30934        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30935        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30936        // e.g., "x-y" -> ["x-y"]
30937        let mut first = true;
30938        for segment in path.split('.') {
30939            if !first {
30940                self.write(".");
30941            }
30942            first = false;
30943            // Check if there's a bracket subscript in this segment: "items[0]"
30944            if let Some(bracket_pos) = segment.find('[') {
30945                let key = &segment[..bracket_pos];
30946                let subscript = &segment[bracket_pos..];
30947                if key.is_empty() {
30948                    // Bracket notation at start of segment (e.g., already formatted)
30949                    self.write(segment);
30950                } else if Self::is_safe_json_path_key(key) {
30951                    self.write(key);
30952                    self.write(subscript);
30953                } else {
30954                    self.write("[\"");
30955                    self.write(key);
30956                    self.write("\"]");
30957                    self.write(subscript);
30958                }
30959            } else if Self::is_safe_json_path_key(segment) {
30960                self.write(segment);
30961            } else {
30962                self.write("[\"");
30963                self.write(segment);
30964                self.write("\"]");
30965            }
30966        }
30967    }
30968
30969    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30970    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30971    fn is_safe_json_path_key(key: &str) -> bool {
30972        if key.is_empty() {
30973            return false;
30974        }
30975        let mut chars = key.chars();
30976        let first = chars.next().unwrap();
30977        if first != '_' && !first.is_ascii_alphabetic() {
30978            return false;
30979        }
30980        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30981    }
30982
30983    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30984        // Output: {expr} FORMAT JSON
30985        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30986        if let Some(this) = &e.this {
30987            self.generate_expression(this)?;
30988            self.write_space();
30989        }
30990        self.write_keyword("FORMAT JSON");
30991        Ok(())
30992    }
30993
30994    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30995        // key: value (for JSON objects)
30996        self.generate_expression(&e.this)?;
30997        self.write(": ");
30998        self.generate_expression(&e.expression)?;
30999        Ok(())
31000    }
31001
31002    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
31003        // JSON_KEYS(this, expression, expressions...)
31004        self.write_keyword("JSON_KEYS");
31005        self.write("(");
31006        self.generate_expression(&e.this)?;
31007        if let Some(expr) = &e.expression {
31008            self.write(", ");
31009            self.generate_expression(expr)?;
31010        }
31011        for expr in &e.expressions {
31012            self.write(", ");
31013            self.generate_expression(expr)?;
31014        }
31015        self.write(")");
31016        Ok(())
31017    }
31018
31019    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
31020        // JSON_KEYS(this, expression)
31021        self.write_keyword("JSON_KEYS");
31022        self.write("(");
31023        self.generate_expression(&e.this)?;
31024        if let Some(expr) = &e.expression {
31025            self.write(", ");
31026            self.generate_expression(expr)?;
31027        }
31028        self.write(")");
31029        Ok(())
31030    }
31031
31032    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
31033        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
31034        // The path components are concatenated without spaces
31035        let mut path_str = String::new();
31036        for expr in &e.expressions {
31037            match expr {
31038                Expression::JSONPathRoot(_) => {
31039                    path_str.push('$');
31040                }
31041                Expression::JSONPathKey(k) => {
31042                    // .key or ."key" (quote if key has special characters)
31043                    if let Expression::Literal(lit) = k.this.as_ref() {
31044                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
31045                            path_str.push('.');
31046                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
31047                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
31048                            if needs_quoting {
31049                                path_str.push('"');
31050                                path_str.push_str(s);
31051                                path_str.push('"');
31052                            } else {
31053                                path_str.push_str(s);
31054                            }
31055                        }
31056                    }
31057                }
31058                Expression::JSONPathSubscript(s) => {
31059                    // [index]
31060                    if let Expression::Literal(lit) = s.this.as_ref() {
31061                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
31062                            path_str.push('[');
31063                            path_str.push_str(n);
31064                            path_str.push(']');
31065                        }
31066                    }
31067                }
31068                _ => {
31069                    // For other path parts, try to generate them
31070                    let mut temp_gen = Self::with_arc_config(self.config.clone());
31071                    temp_gen.generate_expression(expr)?;
31072                    path_str.push_str(&temp_gen.output);
31073                }
31074            }
31075        }
31076        // Output as quoted string
31077        self.write("'");
31078        self.write(&path_str);
31079        self.write("'");
31080        Ok(())
31081    }
31082
31083    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
31084        // JSON path filter: ?(predicate)
31085        self.write("?(");
31086        self.generate_expression(&e.this)?;
31087        self.write(")");
31088        Ok(())
31089    }
31090
31091    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
31092        // JSON path key: .key or ["key"]
31093        self.write(".");
31094        self.generate_expression(&e.this)?;
31095        Ok(())
31096    }
31097
31098    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
31099        // JSON path recursive descent: ..
31100        self.write("..");
31101        if let Some(this) = &e.this {
31102            self.generate_expression(this)?;
31103        }
31104        Ok(())
31105    }
31106
31107    fn generate_json_path_root(&mut self) -> Result<()> {
31108        // JSON path root: $
31109        self.write("$");
31110        Ok(())
31111    }
31112
31113    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
31114        // JSON path script: (expression)
31115        self.write("(");
31116        self.generate_expression(&e.this)?;
31117        self.write(")");
31118        Ok(())
31119    }
31120
31121    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
31122        // JSON path selector: *
31123        self.generate_expression(&e.this)?;
31124        Ok(())
31125    }
31126
31127    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
31128        // JSON path slice: [start:end:step]
31129        self.write("[");
31130        if let Some(start) = &e.start {
31131            self.generate_expression(start)?;
31132        }
31133        self.write(":");
31134        if let Some(end) = &e.end {
31135            self.generate_expression(end)?;
31136        }
31137        if let Some(step) = &e.step {
31138            self.write(":");
31139            self.generate_expression(step)?;
31140        }
31141        self.write("]");
31142        Ok(())
31143    }
31144
31145    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
31146        // JSON path subscript: [index] or [*]
31147        self.write("[");
31148        self.generate_expression(&e.this)?;
31149        self.write("]");
31150        Ok(())
31151    }
31152
31153    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
31154        // JSON path union: [key1, key2, ...]
31155        self.write("[");
31156        for (i, expr) in e.expressions.iter().enumerate() {
31157            if i > 0 {
31158                self.write(", ");
31159            }
31160            self.generate_expression(expr)?;
31161        }
31162        self.write("]");
31163        Ok(())
31164    }
31165
31166    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
31167        // JSON_REMOVE(this, path1, path2, ...)
31168        self.write_keyword("JSON_REMOVE");
31169        self.write("(");
31170        self.generate_expression(&e.this)?;
31171        for expr in &e.expressions {
31172            self.write(", ");
31173            self.generate_expression(expr)?;
31174        }
31175        self.write(")");
31176        Ok(())
31177    }
31178
31179    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
31180        // COLUMNS(col1 type, col2 type, ...)
31181        // When pretty printing and content is too wide, format with each column on a separate line
31182        self.write_keyword("COLUMNS");
31183        self.write("(");
31184
31185        if self.config.pretty && !e.expressions.is_empty() {
31186            // First, generate all expressions into strings to check width
31187            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
31188            for expr in &e.expressions {
31189                let mut temp_gen = Generator::with_arc_config(self.config.clone());
31190                temp_gen.generate_expression(expr)?;
31191                expr_strings.push(temp_gen.output);
31192            }
31193
31194            // Check if total width exceeds max_text_width
31195            if self.too_wide(&expr_strings) {
31196                // Pretty print: each column on its own line
31197                self.write_newline();
31198                self.indent_level += 1;
31199                for (i, expr_str) in expr_strings.iter().enumerate() {
31200                    if i > 0 {
31201                        self.write(",");
31202                        self.write_newline();
31203                    }
31204                    self.write_indent();
31205                    self.write(expr_str);
31206                }
31207                self.write_newline();
31208                self.indent_level -= 1;
31209                self.write_indent();
31210            } else {
31211                // Compact: all on one line
31212                for (i, expr_str) in expr_strings.iter().enumerate() {
31213                    if i > 0 {
31214                        self.write(", ");
31215                    }
31216                    self.write(expr_str);
31217                }
31218            }
31219        } else {
31220            // Non-pretty mode: compact format
31221            for (i, expr) in e.expressions.iter().enumerate() {
31222                if i > 0 {
31223                    self.write(", ");
31224                }
31225                self.generate_expression(expr)?;
31226            }
31227        }
31228        self.write(")");
31229        Ok(())
31230    }
31231
31232    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
31233        // JSON_SET(this, path, value, ...)
31234        self.write_keyword("JSON_SET");
31235        self.write("(");
31236        self.generate_expression(&e.this)?;
31237        for expr in &e.expressions {
31238            self.write(", ");
31239            self.generate_expression(expr)?;
31240        }
31241        self.write(")");
31242        Ok(())
31243    }
31244
31245    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
31246        // JSON_STRIP_NULLS(this, expression)
31247        self.write_keyword("JSON_STRIP_NULLS");
31248        self.write("(");
31249        self.generate_expression(&e.this)?;
31250        if let Some(expr) = &e.expression {
31251            self.write(", ");
31252            self.generate_expression(expr)?;
31253        }
31254        self.write(")");
31255        Ok(())
31256    }
31257
31258    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
31259        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
31260        self.write_keyword("JSON_TABLE");
31261        self.write("(");
31262        self.generate_expression(&e.this)?;
31263        if let Some(path) = &e.path {
31264            self.write(", ");
31265            self.generate_expression(path)?;
31266        }
31267        if let Some(error_handling) = &e.error_handling {
31268            self.write_space();
31269            self.generate_expression(error_handling)?;
31270        }
31271        if let Some(empty_handling) = &e.empty_handling {
31272            self.write_space();
31273            self.generate_expression(empty_handling)?;
31274        }
31275        if let Some(schema) = &e.schema {
31276            self.write_space();
31277            self.generate_expression(schema)?;
31278        }
31279        self.write(")");
31280        Ok(())
31281    }
31282
31283    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
31284        // JSON_TYPE(this)
31285        self.write_keyword("JSON_TYPE");
31286        self.write("(");
31287        self.generate_expression(&e.this)?;
31288        self.write(")");
31289        Ok(())
31290    }
31291
31292    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
31293        // JSON_VALUE(this, path RETURNING type ON condition)
31294        self.write_keyword("JSON_VALUE");
31295        self.write("(");
31296        self.generate_expression(&e.this)?;
31297        if let Some(path) = &e.path {
31298            self.write(", ");
31299            self.generate_expression(path)?;
31300        }
31301        if let Some(returning) = &e.returning {
31302            self.write_space();
31303            self.write_keyword("RETURNING");
31304            self.write_space();
31305            self.generate_expression(returning)?;
31306        }
31307        if let Some(on_condition) = &e.on_condition {
31308            self.write_space();
31309            self.generate_expression(on_condition)?;
31310        }
31311        self.write(")");
31312        Ok(())
31313    }
31314
31315    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
31316        // JSON_VALUE_ARRAY(this)
31317        self.write_keyword("JSON_VALUE_ARRAY");
31318        self.write("(");
31319        self.generate_expression(&e.this)?;
31320        self.write(")");
31321        Ok(())
31322    }
31323
31324    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
31325        // JAROWINKLER_SIMILARITY(str1, str2)
31326        self.write_keyword("JAROWINKLER_SIMILARITY");
31327        self.write("(");
31328        self.generate_expression(&e.this)?;
31329        self.write(", ");
31330        self.generate_expression(&e.expression)?;
31331        self.write(")");
31332        Ok(())
31333    }
31334
31335    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
31336        // Python: this(expressions)
31337        self.generate_expression(&e.this)?;
31338        self.write("(");
31339        for (i, expr) in e.expressions.iter().enumerate() {
31340            if i > 0 {
31341                self.write(", ");
31342            }
31343            self.generate_expression(expr)?;
31344        }
31345        self.write(")");
31346        Ok(())
31347    }
31348
31349    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
31350        // Python: {no}{local}{dual}{before}{after}JOURNAL
31351        if e.no.is_some() {
31352            self.write_keyword("NO ");
31353        }
31354        if let Some(local) = &e.local {
31355            self.generate_expression(local)?;
31356            self.write_space();
31357        }
31358        if e.dual.is_some() {
31359            self.write_keyword("DUAL ");
31360        }
31361        if e.before.is_some() {
31362            self.write_keyword("BEFORE ");
31363        }
31364        if e.after.is_some() {
31365            self.write_keyword("AFTER ");
31366        }
31367        self.write_keyword("JOURNAL");
31368        Ok(())
31369    }
31370
31371    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
31372        // LANGUAGE language_name
31373        self.write_keyword("LANGUAGE");
31374        self.write_space();
31375        self.generate_expression(&e.this)?;
31376        Ok(())
31377    }
31378
31379    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
31380        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
31381        if e.view.is_some() {
31382            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
31383            self.write_keyword("LATERAL VIEW");
31384            if e.outer.is_some() {
31385                self.write_space();
31386                self.write_keyword("OUTER");
31387            }
31388            self.write_space();
31389            self.generate_expression(&e.this)?;
31390            if let Some(alias) = &e.alias {
31391                self.write_space();
31392                self.write(alias);
31393            }
31394        } else {
31395            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
31396            self.write_keyword("LATERAL");
31397            self.write_space();
31398            self.generate_expression(&e.this)?;
31399            if e.ordinality.is_some() {
31400                self.write_space();
31401                self.write_keyword("WITH ORDINALITY");
31402            }
31403            if let Some(alias) = &e.alias {
31404                self.write_space();
31405                self.write_keyword("AS");
31406                self.write_space();
31407                self.write(alias);
31408                if !e.column_aliases.is_empty() {
31409                    self.write("(");
31410                    for (i, col) in e.column_aliases.iter().enumerate() {
31411                        if i > 0 {
31412                            self.write(", ");
31413                        }
31414                        self.write(col);
31415                    }
31416                    self.write(")");
31417                }
31418            }
31419        }
31420        Ok(())
31421    }
31422
31423    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
31424        // Python: LIKE this [options]
31425        self.write_keyword("LIKE");
31426        self.write_space();
31427        self.generate_expression(&e.this)?;
31428        for expr in &e.expressions {
31429            self.write_space();
31430            self.generate_expression(expr)?;
31431        }
31432        Ok(())
31433    }
31434
31435    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
31436        self.write_keyword("LIMIT");
31437        self.write_space();
31438        self.write_limit_expr(&e.this)?;
31439        if e.percent {
31440            self.write_space();
31441            self.write_keyword("PERCENT");
31442        }
31443        // Emit any comments that were captured from before the LIMIT keyword
31444        for comment in &e.comments {
31445            self.write(" ");
31446            self.write_formatted_comment(comment);
31447        }
31448        Ok(())
31449    }
31450
31451    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
31452        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
31453        if e.percent.is_some() {
31454            self.write_keyword(" PERCENT");
31455        }
31456        if e.rows.is_some() {
31457            self.write_keyword(" ROWS");
31458        }
31459        if e.with_ties.is_some() {
31460            self.write_keyword(" WITH TIES");
31461        } else if e.rows.is_some() {
31462            self.write_keyword(" ONLY");
31463        }
31464        Ok(())
31465    }
31466
31467    fn generate_list(&mut self, e: &List) -> Result<()> {
31468        use crate::dialects::DialectType;
31469        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
31470
31471        // Check if this is a subquery-based list (LIST(SELECT ...))
31472        if e.expressions.len() == 1 {
31473            if let Expression::Select(_) = &e.expressions[0] {
31474                self.write_keyword("LIST");
31475                self.write("(");
31476                self.generate_expression(&e.expressions[0])?;
31477                self.write(")");
31478                return Ok(());
31479            }
31480        }
31481
31482        // For Materialize, output as LIST[expr, expr, ...]
31483        if is_materialize {
31484            self.write_keyword("LIST");
31485            self.write("[");
31486            for (i, expr) in e.expressions.iter().enumerate() {
31487                if i > 0 {
31488                    self.write(", ");
31489                }
31490                self.generate_expression(expr)?;
31491            }
31492            self.write("]");
31493        } else {
31494            // For other dialects, output as LIST(expr, expr, ...)
31495            self.write_keyword("LIST");
31496            self.write("(");
31497            for (i, expr) in e.expressions.iter().enumerate() {
31498                if i > 0 {
31499                    self.write(", ");
31500                }
31501                self.generate_expression(expr)?;
31502            }
31503            self.write(")");
31504        }
31505        Ok(())
31506    }
31507
31508    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31509        // Check if this is a subquery-based map (MAP(SELECT ...))
31510        if let Expression::Select(_) = &*e.this {
31511            self.write_keyword("MAP");
31512            self.write("(");
31513            self.generate_expression(&e.this)?;
31514            self.write(")");
31515            return Ok(());
31516        }
31517
31518        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31519
31520        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31521        self.write_keyword("MAP");
31522        if is_duckdb {
31523            self.write(" {");
31524        } else {
31525            self.write("[");
31526        }
31527        if let Expression::Struct(s) = &*e.this {
31528            for (i, (_, expr)) in s.fields.iter().enumerate() {
31529                if i > 0 {
31530                    self.write(", ");
31531                }
31532                if let Expression::PropertyEQ(op) = expr {
31533                    self.generate_expression(&op.left)?;
31534                    if is_duckdb {
31535                        self.write(": ");
31536                    } else {
31537                        self.write(" => ");
31538                    }
31539                    self.generate_expression(&op.right)?;
31540                } else {
31541                    self.generate_expression(expr)?;
31542                }
31543            }
31544        }
31545        if is_duckdb {
31546            self.write("}");
31547        } else {
31548            self.write("]");
31549        }
31550        Ok(())
31551    }
31552
31553    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31554        // Python: LOCALTIME or LOCALTIME(precision)
31555        self.write_keyword("LOCALTIME");
31556        if let Some(precision) = &e.this {
31557            self.write("(");
31558            self.generate_expression(precision)?;
31559            self.write(")");
31560        }
31561        Ok(())
31562    }
31563
31564    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31565        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31566        self.write_keyword("LOCALTIMESTAMP");
31567        if let Some(precision) = &e.this {
31568            self.write("(");
31569            self.generate_expression(precision)?;
31570            self.write(")");
31571        }
31572        Ok(())
31573    }
31574
31575    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31576        // LOCATION 'path'
31577        self.write_keyword("LOCATION");
31578        self.write_space();
31579        self.generate_expression(&e.this)?;
31580        Ok(())
31581    }
31582
31583    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31584        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31585        if e.update.is_some() {
31586            if e.key.is_some() {
31587                self.write_keyword("FOR NO KEY UPDATE");
31588            } else {
31589                self.write_keyword("FOR UPDATE");
31590            }
31591        } else {
31592            if e.key.is_some() {
31593                self.write_keyword("FOR KEY SHARE");
31594            } else {
31595                self.write_keyword("FOR SHARE");
31596            }
31597        }
31598        if !e.expressions.is_empty() {
31599            self.write_keyword(" OF ");
31600            for (i, expr) in e.expressions.iter().enumerate() {
31601                if i > 0 {
31602                    self.write(", ");
31603                }
31604                self.generate_expression(expr)?;
31605            }
31606        }
31607        // Handle wait option following Python sqlglot convention:
31608        // - Boolean(true) -> NOWAIT
31609        // - Boolean(false) -> SKIP LOCKED
31610        // - Literal (number) -> WAIT n
31611        if let Some(wait) = &e.wait {
31612            match wait.as_ref() {
31613                Expression::Boolean(b) => {
31614                    if b.value {
31615                        self.write_keyword(" NOWAIT");
31616                    } else {
31617                        self.write_keyword(" SKIP LOCKED");
31618                    }
31619                }
31620                _ => {
31621                    // It's a literal (number), output WAIT n
31622                    self.write_keyword(" WAIT ");
31623                    self.generate_expression(wait)?;
31624                }
31625            }
31626        }
31627        Ok(())
31628    }
31629
31630    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31631        // LOCK property
31632        self.write_keyword("LOCK");
31633        self.write_space();
31634        self.generate_expression(&e.this)?;
31635        Ok(())
31636    }
31637
31638    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31639        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31640        self.write_keyword("LOCKING");
31641        self.write_space();
31642        self.write(&e.kind);
31643        if let Some(this) = &e.this {
31644            self.write_space();
31645            self.generate_expression(this)?;
31646        }
31647        if let Some(for_or_in) = &e.for_or_in {
31648            self.write_space();
31649            self.generate_expression(for_or_in)?;
31650        }
31651        if let Some(lock_type) = &e.lock_type {
31652            self.write_space();
31653            self.generate_expression(lock_type)?;
31654        }
31655        if e.override_.is_some() {
31656            self.write_keyword(" OVERRIDE");
31657        }
31658        Ok(())
31659    }
31660
31661    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31662        // this expression
31663        self.generate_expression(&e.this)?;
31664        self.write_space();
31665        self.generate_expression(&e.expression)?;
31666        Ok(())
31667    }
31668
31669    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31670        // [NO] LOG
31671        if e.no.is_some() {
31672            self.write_keyword("NO ");
31673        }
31674        self.write_keyword("LOG");
31675        Ok(())
31676    }
31677
31678    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31679        // MD5(this, expressions...)
31680        self.write_keyword("MD5");
31681        self.write("(");
31682        self.generate_expression(&e.this)?;
31683        for expr in &e.expressions {
31684            self.write(", ");
31685            self.generate_expression(expr)?;
31686        }
31687        self.write(")");
31688        Ok(())
31689    }
31690
31691    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31692        // ML.FORECAST(model, [params])
31693        self.write_keyword("ML.FORECAST");
31694        self.write("(");
31695        self.generate_expression(&e.this)?;
31696        if let Some(expression) = &e.expression {
31697            self.write(", ");
31698            self.generate_expression(expression)?;
31699        }
31700        if let Some(params) = &e.params_struct {
31701            self.write(", ");
31702            self.generate_expression(params)?;
31703        }
31704        self.write(")");
31705        Ok(())
31706    }
31707
31708    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31709        // ML.TRANSLATE(model, input, [params])
31710        self.write_keyword("ML.TRANSLATE");
31711        self.write("(");
31712        self.generate_expression(&e.this)?;
31713        self.write(", ");
31714        self.generate_expression(&e.expression)?;
31715        if let Some(params) = &e.params_struct {
31716            self.write(", ");
31717            self.generate_expression(params)?;
31718        }
31719        self.write(")");
31720        Ok(())
31721    }
31722
31723    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31724        // MAKE_INTERVAL(years => x, months => y, ...)
31725        self.write_keyword("MAKE_INTERVAL");
31726        self.write("(");
31727        let mut first = true;
31728        if let Some(year) = &e.year {
31729            self.write("years => ");
31730            self.generate_expression(year)?;
31731            first = false;
31732        }
31733        if let Some(month) = &e.month {
31734            if !first {
31735                self.write(", ");
31736            }
31737            self.write("months => ");
31738            self.generate_expression(month)?;
31739            first = false;
31740        }
31741        if let Some(week) = &e.week {
31742            if !first {
31743                self.write(", ");
31744            }
31745            self.write("weeks => ");
31746            self.generate_expression(week)?;
31747            first = false;
31748        }
31749        if let Some(day) = &e.day {
31750            if !first {
31751                self.write(", ");
31752            }
31753            self.write("days => ");
31754            self.generate_expression(day)?;
31755            first = false;
31756        }
31757        if let Some(hour) = &e.hour {
31758            if !first {
31759                self.write(", ");
31760            }
31761            self.write("hours => ");
31762            self.generate_expression(hour)?;
31763            first = false;
31764        }
31765        if let Some(minute) = &e.minute {
31766            if !first {
31767                self.write(", ");
31768            }
31769            self.write("mins => ");
31770            self.generate_expression(minute)?;
31771            first = false;
31772        }
31773        if let Some(second) = &e.second {
31774            if !first {
31775                self.write(", ");
31776            }
31777            self.write("secs => ");
31778            self.generate_expression(second)?;
31779        }
31780        self.write(")");
31781        Ok(())
31782    }
31783
31784    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31785        // MANHATTAN_DISTANCE(vector1, vector2)
31786        self.write_keyword("MANHATTAN_DISTANCE");
31787        self.write("(");
31788        self.generate_expression(&e.this)?;
31789        self.write(", ");
31790        self.generate_expression(&e.expression)?;
31791        self.write(")");
31792        Ok(())
31793    }
31794
31795    fn generate_map(&mut self, e: &Map) -> Result<()> {
31796        // MAP(key1, value1, key2, value2, ...)
31797        self.write_keyword("MAP");
31798        self.write("(");
31799        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31800            if i > 0 {
31801                self.write(", ");
31802            }
31803            self.generate_expression(key)?;
31804            self.write(", ");
31805            self.generate_expression(value)?;
31806        }
31807        self.write(")");
31808        Ok(())
31809    }
31810
31811    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31812        // MAP_CAT(map1, map2)
31813        self.write_keyword("MAP_CAT");
31814        self.write("(");
31815        self.generate_expression(&e.this)?;
31816        self.write(", ");
31817        self.generate_expression(&e.expression)?;
31818        self.write(")");
31819        Ok(())
31820    }
31821
31822    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31823        // MAP_DELETE(map, key1, key2, ...)
31824        self.write_keyword("MAP_DELETE");
31825        self.write("(");
31826        self.generate_expression(&e.this)?;
31827        for expr in &e.expressions {
31828            self.write(", ");
31829            self.generate_expression(expr)?;
31830        }
31831        self.write(")");
31832        Ok(())
31833    }
31834
31835    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31836        // MAP_INSERT(map, key, value, [update_flag])
31837        self.write_keyword("MAP_INSERT");
31838        self.write("(");
31839        self.generate_expression(&e.this)?;
31840        if let Some(key) = &e.key {
31841            self.write(", ");
31842            self.generate_expression(key)?;
31843        }
31844        if let Some(value) = &e.value {
31845            self.write(", ");
31846            self.generate_expression(value)?;
31847        }
31848        if let Some(update_flag) = &e.update_flag {
31849            self.write(", ");
31850            self.generate_expression(update_flag)?;
31851        }
31852        self.write(")");
31853        Ok(())
31854    }
31855
31856    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
31857        // MAP_PICK(map, key1, key2, ...)
31858        self.write_keyword("MAP_PICK");
31859        self.write("(");
31860        self.generate_expression(&e.this)?;
31861        for expr in &e.expressions {
31862            self.write(", ");
31863            self.generate_expression(expr)?;
31864        }
31865        self.write(")");
31866        Ok(())
31867    }
31868
31869    fn generate_masking_policy_column_constraint(
31870        &mut self,
31871        e: &MaskingPolicyColumnConstraint,
31872    ) -> Result<()> {
31873        // Python: MASKING POLICY name [USING (cols)]
31874        self.write_keyword("MASKING POLICY");
31875        self.write_space();
31876        self.generate_expression(&e.this)?;
31877        if !e.expressions.is_empty() {
31878            self.write_keyword(" USING");
31879            self.write(" (");
31880            for (i, expr) in e.expressions.iter().enumerate() {
31881                if i > 0 {
31882                    self.write(", ");
31883                }
31884                self.generate_expression(expr)?;
31885            }
31886            self.write(")");
31887        }
31888        Ok(())
31889    }
31890
31891    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31892        if matches!(
31893            self.config.dialect,
31894            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31895        ) {
31896            if e.expressions.len() > 1 {
31897                self.write("(");
31898            }
31899            for (i, expr) in e.expressions.iter().enumerate() {
31900                if i > 0 {
31901                    self.write_keyword(" OR ");
31902                }
31903                self.generate_expression(expr)?;
31904                self.write_space();
31905                self.write("@@");
31906                self.write_space();
31907                self.generate_expression(&e.this)?;
31908            }
31909            if e.expressions.len() > 1 {
31910                self.write(")");
31911            }
31912            return Ok(());
31913        }
31914
31915        // MATCH(columns) AGAINST(expr [modifier])
31916        self.write_keyword("MATCH");
31917        self.write("(");
31918        for (i, expr) in e.expressions.iter().enumerate() {
31919            if i > 0 {
31920                self.write(", ");
31921            }
31922            self.generate_expression(expr)?;
31923        }
31924        self.write(")");
31925        self.write_keyword(" AGAINST");
31926        self.write("(");
31927        self.generate_expression(&e.this)?;
31928        if let Some(modifier) = &e.modifier {
31929            self.write_space();
31930            self.generate_expression(modifier)?;
31931        }
31932        self.write(")");
31933        Ok(())
31934    }
31935
31936    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31937        // Python: [window_frame] this
31938        if let Some(window_frame) = &e.window_frame {
31939            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31940            self.write_space();
31941        }
31942        self.generate_expression(&e.this)?;
31943        Ok(())
31944    }
31945
31946    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31947        // MATERIALIZED [this]
31948        self.write_keyword("MATERIALIZED");
31949        if let Some(this) = &e.this {
31950            self.write_space();
31951            self.generate_expression(this)?;
31952        }
31953        Ok(())
31954    }
31955
31956    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31957        // MERGE INTO target USING source ON condition WHEN ...
31958        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31959        if let Some(with_) = &e.with_ {
31960            if let Expression::With(with_clause) = with_.as_ref() {
31961                self.generate_with(with_clause)?;
31962                self.write_space();
31963            } else {
31964                self.generate_expression(with_)?;
31965                self.write_space();
31966            }
31967        }
31968        self.write_keyword("MERGE INTO");
31969        self.write_space();
31970        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31971            if let Expression::Alias(alias) = e.this.as_ref() {
31972                self.generate_expression(&alias.this)?;
31973                self.write_space();
31974                self.generate_identifier(&alias.alias)?;
31975            } else {
31976                self.generate_expression(&e.this)?;
31977            }
31978        } else {
31979            self.generate_expression(&e.this)?;
31980        }
31981
31982        // USING clause - newline before in pretty mode
31983        if self.config.pretty {
31984            self.write_newline();
31985            self.write_indent();
31986        } else {
31987            self.write_space();
31988        }
31989        self.write_keyword("USING");
31990        self.write_space();
31991        self.generate_expression(&e.using)?;
31992
31993        // ON clause - newline before in pretty mode
31994        if let Some(on) = &e.on {
31995            if self.config.pretty {
31996                self.write_newline();
31997                self.write_indent();
31998            } else {
31999                self.write_space();
32000            }
32001            self.write_keyword("ON");
32002            self.write_space();
32003            self.generate_expression(on)?;
32004        }
32005        // DuckDB USING (key_columns) clause
32006        if let Some(using_cond) = &e.using_cond {
32007            self.write_space();
32008            self.write_keyword("USING");
32009            self.write_space();
32010            self.write("(");
32011            // using_cond is a Tuple containing the column identifiers
32012            if let Expression::Tuple(tuple) = using_cond.as_ref() {
32013                for (i, col) in tuple.expressions.iter().enumerate() {
32014                    if i > 0 {
32015                        self.write(", ");
32016                    }
32017                    self.generate_expression(col)?;
32018                }
32019            } else {
32020                self.generate_expression(using_cond)?;
32021            }
32022            self.write(")");
32023        }
32024        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
32025        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
32026        if matches!(
32027            self.config.dialect,
32028            Some(crate::DialectType::PostgreSQL)
32029                | Some(crate::DialectType::Redshift)
32030                | Some(crate::DialectType::Trino)
32031                | Some(crate::DialectType::Presto)
32032                | Some(crate::DialectType::Athena)
32033        ) {
32034            let mut names = Vec::new();
32035            match e.this.as_ref() {
32036                Expression::Alias(a) => {
32037                    // e.g., "x AS z" -> strip both "x" and "z"
32038                    if let Expression::Table(t) = &a.this {
32039                        names.push(t.name.name.clone());
32040                    } else if let Expression::Identifier(id) = &a.this {
32041                        names.push(id.name.clone());
32042                    }
32043                    names.push(a.alias.name.clone());
32044                }
32045                Expression::Table(t) => {
32046                    names.push(t.name.name.clone());
32047                }
32048                Expression::Identifier(id) => {
32049                    names.push(id.name.clone());
32050                }
32051                _ => {}
32052            }
32053            self.merge_strip_qualifiers = names;
32054        }
32055
32056        // WHEN clauses - newline before each in pretty mode
32057        if let Some(whens) = &e.whens {
32058            if self.config.pretty {
32059                self.write_newline();
32060                self.write_indent();
32061            } else {
32062                self.write_space();
32063            }
32064            self.generate_expression(whens)?;
32065        }
32066
32067        // Restore merge_strip_qualifiers
32068        self.merge_strip_qualifiers = saved_merge_strip;
32069
32070        // OUTPUT/RETURNING clause - newline before in pretty mode
32071        if let Some(returning) = &e.returning {
32072            if self.config.pretty {
32073                self.write_newline();
32074                self.write_indent();
32075            } else {
32076                self.write_space();
32077            }
32078            self.generate_expression(returning)?;
32079        }
32080        Ok(())
32081    }
32082
32083    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
32084        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
32085        if e.no.is_some() {
32086            self.write_keyword("NO MERGEBLOCKRATIO");
32087        } else if e.default.is_some() {
32088            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
32089        } else {
32090            self.write_keyword("MERGEBLOCKRATIO");
32091            self.write("=");
32092            if let Some(this) = &e.this {
32093                self.generate_expression(this)?;
32094            }
32095            if e.percent.is_some() {
32096                self.write_keyword(" PERCENT");
32097            }
32098        }
32099        Ok(())
32100    }
32101
32102    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
32103        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
32104        self.write_keyword("TTL");
32105        let pretty_clickhouse = self.config.pretty
32106            && matches!(
32107                self.config.dialect,
32108                Some(crate::dialects::DialectType::ClickHouse)
32109            );
32110
32111        if pretty_clickhouse {
32112            self.write_newline();
32113            self.indent_level += 1;
32114            for (i, expr) in e.expressions.iter().enumerate() {
32115                if i > 0 {
32116                    self.write(",");
32117                    self.write_newline();
32118                }
32119                self.write_indent();
32120                self.generate_expression(expr)?;
32121            }
32122            self.indent_level -= 1;
32123        } else {
32124            self.write_space();
32125            for (i, expr) in e.expressions.iter().enumerate() {
32126                if i > 0 {
32127                    self.write(", ");
32128                }
32129                self.generate_expression(expr)?;
32130            }
32131        }
32132
32133        if let Some(where_) = &e.where_ {
32134            if pretty_clickhouse {
32135                self.write_newline();
32136                if let Expression::Where(w) = where_.as_ref() {
32137                    self.write_indent();
32138                    self.write_keyword("WHERE");
32139                    self.write_newline();
32140                    self.indent_level += 1;
32141                    self.write_indent();
32142                    self.generate_expression(&w.this)?;
32143                    self.indent_level -= 1;
32144                } else {
32145                    self.write_indent();
32146                    self.generate_expression(where_)?;
32147                }
32148            } else {
32149                self.write_space();
32150                self.generate_expression(where_)?;
32151            }
32152        }
32153        if let Some(group) = &e.group {
32154            if pretty_clickhouse {
32155                self.write_newline();
32156                if let Expression::Group(g) = group.as_ref() {
32157                    self.write_indent();
32158                    self.write_keyword("GROUP BY");
32159                    self.write_newline();
32160                    self.indent_level += 1;
32161                    for (i, expr) in g.expressions.iter().enumerate() {
32162                        if i > 0 {
32163                            self.write(",");
32164                            self.write_newline();
32165                        }
32166                        self.write_indent();
32167                        self.generate_expression(expr)?;
32168                    }
32169                    self.indent_level -= 1;
32170                } else {
32171                    self.write_indent();
32172                    self.generate_expression(group)?;
32173                }
32174            } else {
32175                self.write_space();
32176                self.generate_expression(group)?;
32177            }
32178        }
32179        if let Some(aggregates) = &e.aggregates {
32180            if pretty_clickhouse {
32181                self.write_newline();
32182                self.write_indent();
32183                self.write_keyword("SET");
32184                self.write_newline();
32185                self.indent_level += 1;
32186                if let Expression::Tuple(t) = aggregates.as_ref() {
32187                    for (i, agg) in t.expressions.iter().enumerate() {
32188                        if i > 0 {
32189                            self.write(",");
32190                            self.write_newline();
32191                        }
32192                        self.write_indent();
32193                        self.generate_expression(agg)?;
32194                    }
32195                } else {
32196                    self.write_indent();
32197                    self.generate_expression(aggregates)?;
32198                }
32199                self.indent_level -= 1;
32200            } else {
32201                self.write_space();
32202                self.write_keyword("SET");
32203                self.write_space();
32204                if let Expression::Tuple(t) = aggregates.as_ref() {
32205                    for (i, agg) in t.expressions.iter().enumerate() {
32206                        if i > 0 {
32207                            self.write(", ");
32208                        }
32209                        self.generate_expression(agg)?;
32210                    }
32211                } else {
32212                    self.generate_expression(aggregates)?;
32213                }
32214            }
32215        }
32216        Ok(())
32217    }
32218
32219    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
32220        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
32221        self.generate_expression(&e.this)?;
32222        if e.delete.is_some() {
32223            self.write_keyword(" DELETE");
32224        }
32225        if let Some(recompress) = &e.recompress {
32226            self.write_keyword(" RECOMPRESS ");
32227            self.generate_expression(recompress)?;
32228        }
32229        if let Some(to_disk) = &e.to_disk {
32230            self.write_keyword(" TO DISK ");
32231            self.generate_expression(to_disk)?;
32232        }
32233        if let Some(to_volume) = &e.to_volume {
32234            self.write_keyword(" TO VOLUME ");
32235            self.generate_expression(to_volume)?;
32236        }
32237        Ok(())
32238    }
32239
32240    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
32241        // MINHASH(this, expressions...)
32242        self.write_keyword("MINHASH");
32243        self.write("(");
32244        self.generate_expression(&e.this)?;
32245        for expr in &e.expressions {
32246            self.write(", ");
32247            self.generate_expression(expr)?;
32248        }
32249        self.write(")");
32250        Ok(())
32251    }
32252
32253    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
32254        // model!attribute - Snowflake syntax
32255        self.generate_expression(&e.this)?;
32256        self.write("!");
32257        self.generate_expression(&e.expression)?;
32258        Ok(())
32259    }
32260
32261    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
32262        // MONTHNAME(this)
32263        self.write_keyword("MONTHNAME");
32264        self.write("(");
32265        self.generate_expression(&e.this)?;
32266        self.write(")");
32267        Ok(())
32268    }
32269
32270    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
32271        // Output leading comments
32272        for comment in &e.leading_comments {
32273            self.write_formatted_comment(comment);
32274            if self.config.pretty {
32275                self.write_newline();
32276                self.write_indent();
32277            } else {
32278                self.write_space();
32279            }
32280        }
32281        // Python: INSERT [OVERWRITE] kind expressions source
32282        self.write_keyword("INSERT");
32283        if e.overwrite {
32284            self.write_space();
32285            self.write_keyword("OVERWRITE");
32286        }
32287        self.write_space();
32288        self.write(&e.kind);
32289        if self.config.pretty {
32290            self.indent_level += 1;
32291            for expr in &e.expressions {
32292                self.write_newline();
32293                self.write_indent();
32294                self.generate_expression(expr)?;
32295            }
32296            self.indent_level -= 1;
32297        } else {
32298            for expr in &e.expressions {
32299                self.write_space();
32300                self.generate_expression(expr)?;
32301            }
32302        }
32303        if let Some(source) = &e.source {
32304            if self.config.pretty {
32305                self.write_newline();
32306                self.write_indent();
32307            } else {
32308                self.write_space();
32309            }
32310            self.generate_expression(source)?;
32311        }
32312        Ok(())
32313    }
32314
32315    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
32316        // Python: NEXT VALUE FOR this [OVER (order)]
32317        self.write_keyword("NEXT VALUE FOR");
32318        self.write_space();
32319        self.generate_expression(&e.this)?;
32320        if let Some(order) = &e.order {
32321            self.write_space();
32322            self.write_keyword("OVER");
32323            self.write(" (");
32324            self.generate_expression(order)?;
32325            self.write(")");
32326        }
32327        Ok(())
32328    }
32329
32330    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
32331        // NORMAL(mean, stddev, gen)
32332        self.write_keyword("NORMAL");
32333        self.write("(");
32334        self.generate_expression(&e.this)?;
32335        if let Some(stddev) = &e.stddev {
32336            self.write(", ");
32337            self.generate_expression(stddev)?;
32338        }
32339        if let Some(gen) = &e.gen {
32340            self.write(", ");
32341            self.generate_expression(gen)?;
32342        }
32343        self.write(")");
32344        Ok(())
32345    }
32346
32347    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
32348        // NORMALIZE(this, form) or CASEFOLD version
32349        if e.is_casefold.is_some() {
32350            self.write_keyword("NORMALIZE_AND_CASEFOLD");
32351        } else {
32352            self.write_keyword("NORMALIZE");
32353        }
32354        self.write("(");
32355        self.generate_expression(&e.this)?;
32356        if let Some(form) = &e.form {
32357            self.write(", ");
32358            self.generate_expression(form)?;
32359        }
32360        self.write(")");
32361        Ok(())
32362    }
32363
32364    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
32365        // Python: [NOT ]NULL
32366        if e.allow_null.is_none() {
32367            self.write_keyword("NOT ");
32368        }
32369        self.write_keyword("NULL");
32370        Ok(())
32371    }
32372
32373    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
32374        // NULLIF(this, expression)
32375        self.write_keyword("NULLIF");
32376        self.write("(");
32377        self.generate_expression(&e.this)?;
32378        self.write(", ");
32379        self.generate_expression(&e.expression)?;
32380        self.write(")");
32381        Ok(())
32382    }
32383
32384    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
32385        // FORMAT(this, format, culture)
32386        self.write_keyword("FORMAT");
32387        self.write("(");
32388        self.generate_expression(&e.this)?;
32389        self.write(", '");
32390        self.write(&e.format);
32391        self.write("'");
32392        if let Some(culture) = &e.culture {
32393            self.write(", ");
32394            self.generate_expression(culture)?;
32395        }
32396        self.write(")");
32397        Ok(())
32398    }
32399
32400    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
32401        // OBJECT_AGG(key, value)
32402        self.write_keyword("OBJECT_AGG");
32403        self.write("(");
32404        self.generate_expression(&e.this)?;
32405        self.write(", ");
32406        self.generate_expression(&e.expression)?;
32407        self.write(")");
32408        Ok(())
32409    }
32410
32411    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
32412        // Python: Just returns the name
32413        self.generate_expression(&e.this)?;
32414        Ok(())
32415    }
32416
32417    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
32418        // OBJECT_INSERT(obj, key, value, [update_flag])
32419        self.write_keyword("OBJECT_INSERT");
32420        self.write("(");
32421        self.generate_expression(&e.this)?;
32422        if let Some(key) = &e.key {
32423            self.write(", ");
32424            self.generate_expression(key)?;
32425        }
32426        if let Some(value) = &e.value {
32427            self.write(", ");
32428            self.generate_expression(value)?;
32429        }
32430        if let Some(update_flag) = &e.update_flag {
32431            self.write(", ");
32432            self.generate_expression(update_flag)?;
32433        }
32434        self.write(")");
32435        Ok(())
32436    }
32437
32438    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
32439        // OFFSET value [ROW|ROWS]
32440        self.write_keyword("OFFSET");
32441        self.write_space();
32442        self.generate_expression(&e.this)?;
32443        // Output ROWS keyword only for TSQL/Oracle targets
32444        if e.rows == Some(true)
32445            && matches!(
32446                self.config.dialect,
32447                Some(crate::dialects::DialectType::TSQL)
32448                    | Some(crate::dialects::DialectType::Oracle)
32449            )
32450        {
32451            self.write_space();
32452            self.write_keyword("ROWS");
32453        }
32454        Ok(())
32455    }
32456
32457    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
32458        // QUALIFY condition (Snowflake/BigQuery)
32459        self.write_keyword("QUALIFY");
32460        self.write_space();
32461        self.generate_expression(&e.this)?;
32462        Ok(())
32463    }
32464
32465    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
32466        // ON CLUSTER cluster_name
32467        self.write_keyword("ON CLUSTER");
32468        self.write_space();
32469        self.generate_expression(&e.this)?;
32470        Ok(())
32471    }
32472
32473    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
32474        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
32475        self.write_keyword("ON COMMIT");
32476        if e.delete.is_some() {
32477            self.write_keyword(" DELETE ROWS");
32478        } else {
32479            self.write_keyword(" PRESERVE ROWS");
32480        }
32481        Ok(())
32482    }
32483
32484    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
32485        // Python: error/empty/null handling
32486        if let Some(empty) = &e.empty {
32487            self.generate_expression(empty)?;
32488            self.write_keyword(" ON EMPTY");
32489        }
32490        if let Some(error) = &e.error {
32491            if e.empty.is_some() {
32492                self.write_space();
32493            }
32494            self.generate_expression(error)?;
32495            self.write_keyword(" ON ERROR");
32496        }
32497        if let Some(null) = &e.null {
32498            if e.empty.is_some() || e.error.is_some() {
32499                self.write_space();
32500            }
32501            self.generate_expression(null)?;
32502            self.write_keyword(" ON NULL");
32503        }
32504        Ok(())
32505    }
32506
32507    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32508        // Materialize doesn't support ON CONFLICT - skip entirely
32509        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32510            return Ok(());
32511        }
32512        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32513        if e.duplicate.is_some() {
32514            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32515            self.write_keyword("ON DUPLICATE KEY UPDATE");
32516            for (i, expr) in e.expressions.iter().enumerate() {
32517                if i > 0 {
32518                    self.write(",");
32519                }
32520                self.write_space();
32521                self.generate_expression(expr)?;
32522            }
32523            return Ok(());
32524        } else {
32525            self.write_keyword("ON CONFLICT");
32526        }
32527        if let Some(constraint) = &e.constraint {
32528            self.write_keyword(" ON CONSTRAINT ");
32529            self.generate_expression(constraint)?;
32530        }
32531        if let Some(conflict_keys) = &e.conflict_keys {
32532            // conflict_keys can be a Tuple containing expressions
32533            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32534                self.write("(");
32535                for (i, expr) in t.expressions.iter().enumerate() {
32536                    if i > 0 {
32537                        self.write(", ");
32538                    }
32539                    self.generate_expression(expr)?;
32540                }
32541                self.write(")");
32542            } else {
32543                self.write("(");
32544                self.generate_expression(conflict_keys)?;
32545                self.write(")");
32546            }
32547        }
32548        if let Some(index_predicate) = &e.index_predicate {
32549            self.write_keyword(" WHERE ");
32550            self.generate_expression(index_predicate)?;
32551        }
32552        if let Some(action) = &e.action {
32553            // Check if action is "NOTHING" or an UPDATE set
32554            if let Expression::Identifier(id) = action.as_ref() {
32555                if id.name.eq_ignore_ascii_case("NOTHING") {
32556                    self.write_keyword(" DO NOTHING");
32557                } else {
32558                    self.write_keyword(" DO ");
32559                    self.generate_expression(action)?;
32560                }
32561            } else if let Expression::Tuple(t) = action.as_ref() {
32562                // DO UPDATE SET col1 = val1, col2 = val2
32563                self.write_keyword(" DO UPDATE SET ");
32564                for (i, expr) in t.expressions.iter().enumerate() {
32565                    if i > 0 {
32566                        self.write(", ");
32567                    }
32568                    self.generate_expression(expr)?;
32569                }
32570            } else {
32571                self.write_keyword(" DO ");
32572                self.generate_expression(action)?;
32573            }
32574        }
32575        // WHERE clause for the UPDATE action
32576        if let Some(where_) = &e.where_ {
32577            self.write_keyword(" WHERE ");
32578            self.generate_expression(where_)?;
32579        }
32580        Ok(())
32581    }
32582
32583    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32584        // ON property_value
32585        self.write_keyword("ON");
32586        self.write_space();
32587        self.generate_expression(&e.this)?;
32588        Ok(())
32589    }
32590
32591    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32592        // Python: this expression (e.g., column opclass)
32593        self.generate_expression(&e.this)?;
32594        self.write_space();
32595        self.generate_expression(&e.expression)?;
32596        Ok(())
32597    }
32598
32599    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32600        // Python: OPENJSON(this[, path]) [WITH (columns)]
32601        self.write_keyword("OPENJSON");
32602        self.write("(");
32603        self.generate_expression(&e.this)?;
32604        if let Some(path) = &e.path {
32605            self.write(", ");
32606            self.generate_expression(path)?;
32607        }
32608        self.write(")");
32609        if !e.expressions.is_empty() {
32610            self.write_keyword(" WITH");
32611            if self.config.pretty {
32612                self.write(" (\n");
32613                self.indent_level += 2;
32614                for (i, expr) in e.expressions.iter().enumerate() {
32615                    if i > 0 {
32616                        self.write(",\n");
32617                    }
32618                    self.write_indent();
32619                    self.generate_expression(expr)?;
32620                }
32621                self.write("\n");
32622                self.indent_level -= 2;
32623                self.write(")");
32624            } else {
32625                self.write(" (");
32626                for (i, expr) in e.expressions.iter().enumerate() {
32627                    if i > 0 {
32628                        self.write(", ");
32629                    }
32630                    self.generate_expression(expr)?;
32631                }
32632                self.write(")");
32633            }
32634        }
32635        Ok(())
32636    }
32637
32638    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32639        // Python: this kind [path] [AS JSON]
32640        self.generate_expression(&e.this)?;
32641        self.write_space();
32642        // Use parsed data_type if available, otherwise fall back to kind string
32643        if let Some(ref dt) = e.data_type {
32644            self.generate_data_type(dt)?;
32645        } else if !e.kind.is_empty() {
32646            self.write(&e.kind);
32647        }
32648        if let Some(path) = &e.path {
32649            self.write_space();
32650            self.generate_expression(path)?;
32651        }
32652        if e.as_json.is_some() {
32653            self.write_keyword(" AS JSON");
32654        }
32655        Ok(())
32656    }
32657
32658    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32659        // this OPERATOR(op) expression
32660        self.generate_expression(&e.this)?;
32661        self.write_space();
32662        if let Some(op) = &e.operator {
32663            self.write_keyword("OPERATOR");
32664            self.write("(");
32665            self.generate_expression(op)?;
32666            self.write(")");
32667        }
32668        // Emit inline comments between OPERATOR() and the RHS
32669        for comment in &e.comments {
32670            self.write_space();
32671            self.write_formatted_comment(comment);
32672        }
32673        self.write_space();
32674        self.generate_expression(&e.expression)?;
32675        Ok(())
32676    }
32677
32678    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32679        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32680        self.write_keyword("ORDER BY");
32681        let pretty_clickhouse_single_paren = self.config.pretty
32682            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32683            && e.expressions.len() == 1
32684            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32685        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32686            && e.expressions.len() == 1
32687            && matches!(e.expressions[0].this, Expression::Tuple(_))
32688            && !e.expressions[0].desc
32689            && e.expressions[0].nulls_first.is_none();
32690
32691        if pretty_clickhouse_single_paren {
32692            self.write_space();
32693            if let Expression::Paren(p) = &e.expressions[0].this {
32694                self.write("(");
32695                self.write_newline();
32696                self.indent_level += 1;
32697                self.write_indent();
32698                self.generate_expression(&p.this)?;
32699                self.indent_level -= 1;
32700                self.write_newline();
32701                self.write(")");
32702            }
32703            return Ok(());
32704        }
32705
32706        if clickhouse_single_tuple {
32707            self.write_space();
32708            if let Expression::Tuple(t) = &e.expressions[0].this {
32709                self.write("(");
32710                for (i, expr) in t.expressions.iter().enumerate() {
32711                    if i > 0 {
32712                        self.write(", ");
32713                    }
32714                    self.generate_expression(expr)?;
32715                }
32716                self.write(")");
32717            }
32718            return Ok(());
32719        }
32720
32721        self.write_space();
32722        for (i, ordered) in e.expressions.iter().enumerate() {
32723            if i > 0 {
32724                self.write(", ");
32725            }
32726            self.generate_expression(&ordered.this)?;
32727            if ordered.desc {
32728                self.write_space();
32729                self.write_keyword("DESC");
32730            } else if ordered.explicit_asc {
32731                self.write_space();
32732                self.write_keyword("ASC");
32733            }
32734            if let Some(nulls_first) = ordered.nulls_first {
32735                // In Dremio, NULLS LAST is the default, so skip generating it
32736                let skip_nulls_last =
32737                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32738                if !skip_nulls_last {
32739                    self.write_space();
32740                    self.write_keyword("NULLS");
32741                    self.write_space();
32742                    if nulls_first {
32743                        self.write_keyword("FIRST");
32744                    } else {
32745                        self.write_keyword("LAST");
32746                    }
32747                }
32748            }
32749        }
32750        Ok(())
32751    }
32752
32753    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32754        // OUTPUT(model)
32755        self.write_keyword("OUTPUT");
32756        self.write("(");
32757        if self.config.pretty {
32758            self.indent_level += 1;
32759            self.write_newline();
32760            self.write_indent();
32761            self.generate_expression(&e.this)?;
32762            self.indent_level -= 1;
32763            self.write_newline();
32764        } else {
32765            self.generate_expression(&e.this)?;
32766        }
32767        self.write(")");
32768        Ok(())
32769    }
32770
32771    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32772        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32773        self.write_keyword("TRUNCATE");
32774        if let Some(this) = &e.this {
32775            self.write_space();
32776            self.generate_expression(this)?;
32777        }
32778        if e.with_count.is_some() {
32779            self.write_keyword(" WITH COUNT");
32780        } else {
32781            self.write_keyword(" WITHOUT COUNT");
32782        }
32783        Ok(())
32784    }
32785
32786    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32787        // Python: name(expressions)(params)
32788        self.generate_expression(&e.this)?;
32789        self.write("(");
32790        for (i, expr) in e.expressions.iter().enumerate() {
32791            if i > 0 {
32792                self.write(", ");
32793            }
32794            self.generate_expression(expr)?;
32795        }
32796        self.write(")(");
32797        for (i, param) in e.params.iter().enumerate() {
32798            if i > 0 {
32799                self.write(", ");
32800            }
32801            self.generate_expression(param)?;
32802        }
32803        self.write(")");
32804        Ok(())
32805    }
32806
32807    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32808        // PARSE_DATETIME(format, this) or similar
32809        self.write_keyword("PARSE_DATETIME");
32810        self.write("(");
32811        if let Some(format) = &e.format {
32812            self.write("'");
32813            self.write(format);
32814            self.write("', ");
32815        }
32816        self.generate_expression(&e.this)?;
32817        if let Some(zone) = &e.zone {
32818            self.write(", ");
32819            self.generate_expression(zone)?;
32820        }
32821        self.write(")");
32822        Ok(())
32823    }
32824
32825    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32826        // PARSE_IP(this, type, permissive)
32827        self.write_keyword("PARSE_IP");
32828        self.write("(");
32829        self.generate_expression(&e.this)?;
32830        if let Some(type_) = &e.type_ {
32831            self.write(", ");
32832            self.generate_expression(type_)?;
32833        }
32834        if let Some(permissive) = &e.permissive {
32835            self.write(", ");
32836            self.generate_expression(permissive)?;
32837        }
32838        self.write(")");
32839        Ok(())
32840    }
32841
32842    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32843        // PARSE_JSON(this, [expression])
32844        self.write_keyword("PARSE_JSON");
32845        self.write("(");
32846        self.generate_expression(&e.this)?;
32847        if let Some(expression) = &e.expression {
32848            self.write(", ");
32849            self.generate_expression(expression)?;
32850        }
32851        self.write(")");
32852        Ok(())
32853    }
32854
32855    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
32856        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
32857        self.write_keyword("PARSE_TIME");
32858        self.write("(");
32859        self.write(&format!("'{}'", e.format));
32860        self.write(", ");
32861        self.generate_expression(&e.this)?;
32862        self.write(")");
32863        Ok(())
32864    }
32865
32866    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
32867        // PARSE_URL(this, [part_to_extract], [key], [permissive])
32868        self.write_keyword("PARSE_URL");
32869        self.write("(");
32870        self.generate_expression(&e.this)?;
32871        if let Some(part) = &e.part_to_extract {
32872            self.write(", ");
32873            self.generate_expression(part)?;
32874        }
32875        if let Some(key) = &e.key {
32876            self.write(", ");
32877            self.generate_expression(key)?;
32878        }
32879        if let Some(permissive) = &e.permissive {
32880            self.write(", ");
32881            self.generate_expression(permissive)?;
32882        }
32883        self.write(")");
32884        Ok(())
32885    }
32886
32887    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
32888        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
32889        if e.subpartition {
32890            self.write_keyword("SUBPARTITION");
32891        } else {
32892            self.write_keyword("PARTITION");
32893        }
32894        self.write("(");
32895        for (i, expr) in e.expressions.iter().enumerate() {
32896            if i > 0 {
32897                self.write(", ");
32898            }
32899            self.generate_expression(expr)?;
32900        }
32901        self.write(")");
32902        Ok(())
32903    }
32904
32905    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32906        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32907        if let Some(this) = &e.this {
32908            if let Some(expression) = &e.expression {
32909                // WITH (MODULUS this, REMAINDER expression)
32910                self.write_keyword("WITH");
32911                self.write(" (");
32912                self.write_keyword("MODULUS");
32913                self.write_space();
32914                self.generate_expression(this)?;
32915                self.write(", ");
32916                self.write_keyword("REMAINDER");
32917                self.write_space();
32918                self.generate_expression(expression)?;
32919                self.write(")");
32920            } else {
32921                // IN (this) - this could be a list
32922                self.write_keyword("IN");
32923                self.write(" (");
32924                self.generate_partition_bound_values(this)?;
32925                self.write(")");
32926            }
32927        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32928            // FROM (from_expressions) TO (to_expressions)
32929            self.write_keyword("FROM");
32930            self.write(" (");
32931            self.generate_partition_bound_values(from)?;
32932            self.write(") ");
32933            self.write_keyword("TO");
32934            self.write(" (");
32935            self.generate_partition_bound_values(to)?;
32936            self.write(")");
32937        }
32938        Ok(())
32939    }
32940
32941    /// Generate partition bound values - handles Tuple expressions by outputting
32942    /// contents without wrapping parens (since caller provides the parens)
32943    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32944        if let Expression::Tuple(t) = expr {
32945            for (i, e) in t.expressions.iter().enumerate() {
32946                if i > 0 {
32947                    self.write(", ");
32948                }
32949                self.generate_expression(e)?;
32950            }
32951            Ok(())
32952        } else {
32953            self.generate_expression(expr)
32954        }
32955    }
32956
32957    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32958        // PARTITION BY LIST (partition_expressions) (create_expressions)
32959        self.write_keyword("PARTITION BY LIST");
32960        if let Some(partition_exprs) = &e.partition_expressions {
32961            self.write(" (");
32962            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32963            self.generate_doris_partition_expressions(partition_exprs)?;
32964            self.write(")");
32965        }
32966        if let Some(create_exprs) = &e.create_expressions {
32967            self.write(" (");
32968            // Unwrap Tuple for partition definitions
32969            self.generate_doris_partition_definitions(create_exprs)?;
32970            self.write(")");
32971        }
32972        Ok(())
32973    }
32974
32975    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32976        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32977        self.write_keyword("PARTITION BY RANGE");
32978        if let Some(partition_exprs) = &e.partition_expressions {
32979            self.write(" (");
32980            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32981            self.generate_doris_partition_expressions(partition_exprs)?;
32982            self.write(")");
32983        }
32984        if let Some(create_exprs) = &e.create_expressions {
32985            self.write(" (");
32986            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32987            self.generate_doris_partition_definitions(create_exprs)?;
32988            self.write(")");
32989        }
32990        Ok(())
32991    }
32992
32993    /// Generate Doris partition column expressions (unwrap Tuple)
32994    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32995        if let Expression::Tuple(t) = expr {
32996            for (i, e) in t.expressions.iter().enumerate() {
32997                if i > 0 {
32998                    self.write(", ");
32999                }
33000                self.generate_expression(e)?;
33001            }
33002        } else {
33003            self.generate_expression(expr)?;
33004        }
33005        Ok(())
33006    }
33007
33008    /// Generate Doris partition definitions (comma-separated Partition expressions)
33009    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
33010        match expr {
33011            Expression::Tuple(t) => {
33012                // Multiple partitions, comma-separated
33013                for (i, part) in t.expressions.iter().enumerate() {
33014                    if i > 0 {
33015                        self.write(", ");
33016                    }
33017                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
33018                    if let Expression::Partition(p) = part {
33019                        for (j, inner) in p.expressions.iter().enumerate() {
33020                            if j > 0 {
33021                                self.write(", ");
33022                            }
33023                            self.generate_expression(inner)?;
33024                        }
33025                    } else {
33026                        self.generate_expression(part)?;
33027                    }
33028                }
33029            }
33030            Expression::PartitionByRangePropertyDynamic(_) => {
33031                // Dynamic partition - FROM/TO/INTERVAL
33032                self.generate_expression(expr)?;
33033            }
33034            _ => {
33035                self.generate_expression(expr)?;
33036            }
33037        }
33038        Ok(())
33039    }
33040
33041    fn generate_partition_by_range_property_dynamic(
33042        &mut self,
33043        e: &PartitionByRangePropertyDynamic,
33044    ) -> Result<()> {
33045        if e.use_start_end {
33046            // StarRocks: START ('val') END ('val') EVERY (expr)
33047            if let Some(start) = &e.start {
33048                self.write_keyword("START");
33049                self.write(" (");
33050                self.generate_expression(start)?;
33051                self.write(")");
33052            }
33053            if let Some(end) = &e.end {
33054                self.write_space();
33055                self.write_keyword("END");
33056                self.write(" (");
33057                self.generate_expression(end)?;
33058                self.write(")");
33059            }
33060            if let Some(every) = &e.every {
33061                self.write_space();
33062                self.write_keyword("EVERY");
33063                self.write(" (");
33064                // Use unquoted interval format for StarRocks
33065                self.generate_doris_interval(every)?;
33066                self.write(")");
33067            }
33068        } else {
33069            // Doris: FROM (start) TO (end) INTERVAL n UNIT
33070            if let Some(start) = &e.start {
33071                self.write_keyword("FROM");
33072                self.write(" (");
33073                self.generate_expression(start)?;
33074                self.write(")");
33075            }
33076            if let Some(end) = &e.end {
33077                self.write_space();
33078                self.write_keyword("TO");
33079                self.write(" (");
33080                self.generate_expression(end)?;
33081                self.write(")");
33082            }
33083            if let Some(every) = &e.every {
33084                self.write_space();
33085                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
33086                self.generate_doris_interval(every)?;
33087            }
33088        }
33089        Ok(())
33090    }
33091
33092    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
33093    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
33094        if let Expression::Interval(interval) = expr {
33095            self.write_keyword("INTERVAL");
33096            if let Some(ref value) = interval.this {
33097                self.write_space();
33098                // If the value is a string literal that looks like a number,
33099                // output it without quotes (matching Python sqlglot's
33100                // partitionbyrangepropertydynamic_sql which converts back to number)
33101                match value {
33102                    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()) => {
33103                        if let Literal::String(s) = lit.as_ref() {
33104                            self.write(s);
33105                        }
33106                    }
33107                    _ => {
33108                        self.generate_expression(value)?;
33109                    }
33110                }
33111            }
33112            if let Some(ref unit_spec) = interval.unit {
33113                self.write_space();
33114                self.write_interval_unit_spec(unit_spec)?;
33115            }
33116            Ok(())
33117        } else {
33118            self.generate_expression(expr)
33119        }
33120    }
33121
33122    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
33123        // TRUNCATE(expression, this)
33124        self.write_keyword("TRUNCATE");
33125        self.write("(");
33126        self.generate_expression(&e.expression)?;
33127        self.write(", ");
33128        self.generate_expression(&e.this)?;
33129        self.write(")");
33130        Ok(())
33131    }
33132
33133    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
33134        // Doris: PARTITION name VALUES IN (val1, val2)
33135        self.write_keyword("PARTITION");
33136        self.write_space();
33137        self.generate_expression(&e.this)?;
33138        self.write_space();
33139        self.write_keyword("VALUES IN");
33140        self.write(" (");
33141        for (i, expr) in e.expressions.iter().enumerate() {
33142            if i > 0 {
33143                self.write(", ");
33144            }
33145            self.generate_expression(expr)?;
33146        }
33147        self.write(")");
33148        Ok(())
33149    }
33150
33151    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
33152        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
33153        // TSQL ranges have no expressions and just use `this TO expression`
33154        if e.expressions.is_empty() && e.expression.is_some() {
33155            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
33156            self.generate_expression(&e.this)?;
33157            self.write_space();
33158            self.write_keyword("TO");
33159            self.write_space();
33160            self.generate_expression(e.expression.as_ref().unwrap())?;
33161            return Ok(());
33162        }
33163
33164        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
33165        self.write_keyword("PARTITION");
33166        self.write_space();
33167        self.generate_expression(&e.this)?;
33168        self.write_space();
33169
33170        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
33171        if e.expressions.len() == 1 {
33172            // Single value: VALUES LESS THAN (val)
33173            self.write_keyword("VALUES LESS THAN");
33174            self.write(" (");
33175            self.generate_expression(&e.expressions[0])?;
33176            self.write(")");
33177        } else if !e.expressions.is_empty() {
33178            // Multiple values with Tuple: VALUES [(val1), (val2))
33179            self.write_keyword("VALUES");
33180            self.write(" [");
33181            for (i, expr) in e.expressions.iter().enumerate() {
33182                if i > 0 {
33183                    self.write(", ");
33184                }
33185                // If the expr is a Tuple, generate its contents wrapped in parens
33186                if let Expression::Tuple(t) = expr {
33187                    self.write("(");
33188                    for (j, inner) in t.expressions.iter().enumerate() {
33189                        if j > 0 {
33190                            self.write(", ");
33191                        }
33192                        self.generate_expression(inner)?;
33193                    }
33194                    self.write(")");
33195                } else {
33196                    self.write("(");
33197                    self.generate_expression(expr)?;
33198                    self.write(")");
33199                }
33200            }
33201            self.write(")");
33202        }
33203        Ok(())
33204    }
33205
33206    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
33207        // BUCKET(this, expression)
33208        self.write_keyword("BUCKET");
33209        self.write("(");
33210        self.generate_expression(&e.this)?;
33211        self.write(", ");
33212        self.generate_expression(&e.expression)?;
33213        self.write(")");
33214        Ok(())
33215    }
33216
33217    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
33218        // BigQuery table property: PARTITION BY expression [, expression ...]
33219        self.write_keyword("PARTITION BY");
33220        self.write_space();
33221        for (i, expr) in e.expressions.iter().enumerate() {
33222            if i > 0 {
33223                self.write(", ");
33224            }
33225            self.generate_expression(expr)?;
33226        }
33227        Ok(())
33228    }
33229
33230    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
33231        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
33232        if matches!(
33233            self.config.dialect,
33234            Some(crate::dialects::DialectType::Teradata)
33235                | Some(crate::dialects::DialectType::ClickHouse)
33236        ) {
33237            self.write_keyword("PARTITION BY");
33238        } else {
33239            self.write_keyword("PARTITIONED BY");
33240        }
33241        self.write_space();
33242        // In pretty mode, always use multiline tuple format for PARTITIONED BY
33243        if self.config.pretty {
33244            if let Expression::Tuple(ref tuple) = *e.this {
33245                self.write("(");
33246                self.write_newline();
33247                self.indent_level += 1;
33248                for (i, expr) in tuple.expressions.iter().enumerate() {
33249                    if i > 0 {
33250                        self.write(",");
33251                        self.write_newline();
33252                    }
33253                    self.write_indent();
33254                    self.generate_expression(expr)?;
33255                }
33256                self.indent_level -= 1;
33257                self.write_newline();
33258                self.write(")");
33259            } else {
33260                self.generate_expression(&e.this)?;
33261            }
33262        } else {
33263            self.generate_expression(&e.this)?;
33264        }
33265        Ok(())
33266    }
33267
33268    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
33269        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
33270        self.write_keyword("PARTITION OF");
33271        self.write_space();
33272        self.generate_expression(&e.this)?;
33273        // Check if expression is a PartitionBoundSpec
33274        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
33275            self.write_space();
33276            self.write_keyword("FOR VALUES");
33277            self.write_space();
33278            self.generate_expression(&e.expression)?;
33279        } else {
33280            self.write_space();
33281            self.write_keyword("DEFAULT");
33282        }
33283        Ok(())
33284    }
33285
33286    fn generate_period_for_system_time_constraint(
33287        &mut self,
33288        e: &PeriodForSystemTimeConstraint,
33289    ) -> Result<()> {
33290        // PERIOD FOR SYSTEM_TIME (this, expression)
33291        self.write_keyword("PERIOD FOR SYSTEM_TIME");
33292        self.write(" (");
33293        self.generate_expression(&e.this)?;
33294        self.write(", ");
33295        self.generate_expression(&e.expression)?;
33296        self.write(")");
33297        Ok(())
33298    }
33299
33300    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
33301        // value AS alias
33302        // The alias can be an identifier or an expression (e.g., string concatenation)
33303        self.generate_expression(&e.this)?;
33304        self.write_space();
33305        self.write_keyword("AS");
33306        self.write_space();
33307        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
33308        if self.config.unpivot_aliases_are_identifiers {
33309            match &e.alias {
33310                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
33311                    let Literal::String(s) = lit.as_ref() else {
33312                        unreachable!()
33313                    };
33314                    // Convert string literal to identifier
33315                    self.generate_identifier(&Identifier::new(s.clone()))?;
33316                }
33317                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
33318                    let Literal::Number(n) = lit.as_ref() else {
33319                        unreachable!()
33320                    };
33321                    // Convert number literal to quoted identifier
33322                    let mut id = Identifier::new(n.clone());
33323                    id.quoted = true;
33324                    self.generate_identifier(&id)?;
33325                }
33326                other => {
33327                    self.generate_expression(other)?;
33328                }
33329            }
33330        } else {
33331            self.generate_expression(&e.alias)?;
33332        }
33333        Ok(())
33334    }
33335
33336    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
33337        // ANY or ANY [expression]
33338        self.write_keyword("ANY");
33339        if let Some(this) = &e.this {
33340            self.write_space();
33341            self.generate_expression(this)?;
33342        }
33343        Ok(())
33344    }
33345
33346    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
33347        // ML.PREDICT(MODEL this, expression, [params_struct])
33348        self.write_keyword("ML.PREDICT");
33349        self.write("(");
33350        self.write_keyword("MODEL");
33351        self.write_space();
33352        self.generate_expression(&e.this)?;
33353        self.write(", ");
33354        self.generate_expression(&e.expression)?;
33355        if let Some(params) = &e.params_struct {
33356            self.write(", ");
33357            self.generate_expression(params)?;
33358        }
33359        self.write(")");
33360        Ok(())
33361    }
33362
33363    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
33364        // PREVIOUS_DAY(this, expression)
33365        self.write_keyword("PREVIOUS_DAY");
33366        self.write("(");
33367        self.generate_expression(&e.this)?;
33368        self.write(", ");
33369        self.generate_expression(&e.expression)?;
33370        self.write(")");
33371        Ok(())
33372    }
33373
33374    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
33375        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
33376        self.write_keyword("PRIMARY KEY");
33377        if let Some(name) = &e.this {
33378            self.write_space();
33379            self.generate_expression(name)?;
33380        }
33381        if !e.expressions.is_empty() {
33382            self.write(" (");
33383            for (i, expr) in e.expressions.iter().enumerate() {
33384                if i > 0 {
33385                    self.write(", ");
33386                }
33387                self.generate_expression(expr)?;
33388            }
33389            self.write(")");
33390        }
33391        if let Some(include) = &e.include {
33392            self.write_space();
33393            self.generate_expression(include)?;
33394        }
33395        if !e.options.is_empty() {
33396            self.write_space();
33397            for (i, opt) in e.options.iter().enumerate() {
33398                if i > 0 {
33399                    self.write_space();
33400                }
33401                self.generate_expression(opt)?;
33402            }
33403        }
33404        Ok(())
33405    }
33406
33407    fn generate_primary_key_column_constraint(
33408        &mut self,
33409        _e: &PrimaryKeyColumnConstraint,
33410    ) -> Result<()> {
33411        // PRIMARY KEY constraint at column level
33412        self.write_keyword("PRIMARY KEY");
33413        Ok(())
33414    }
33415
33416    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
33417        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
33418        self.write_keyword("PATH");
33419        self.write_space();
33420        self.generate_expression(&e.this)?;
33421        Ok(())
33422    }
33423
33424    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
33425        // PROJECTION this (expression)
33426        self.write_keyword("PROJECTION");
33427        self.write_space();
33428        self.generate_expression(&e.this)?;
33429        self.write(" (");
33430        self.generate_expression(&e.expression)?;
33431        self.write(")");
33432        Ok(())
33433    }
33434
33435    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
33436        // Properties list
33437        for (i, prop) in e.expressions.iter().enumerate() {
33438            if i > 0 {
33439                self.write(", ");
33440            }
33441            self.generate_expression(prop)?;
33442        }
33443        Ok(())
33444    }
33445
33446    fn generate_property(&mut self, e: &Property) -> Result<()> {
33447        // name=value
33448        self.generate_expression(&e.this)?;
33449        if let Some(value) = &e.value {
33450            self.write("=");
33451            self.generate_expression(value)?;
33452        }
33453        Ok(())
33454    }
33455
33456    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
33457        self.write_keyword("OPTIONS");
33458        if e.entries.is_empty() {
33459            self.write(" ()");
33460            return Ok(());
33461        }
33462
33463        if self.config.pretty {
33464            self.write(" (");
33465            self.write_newline();
33466            self.indent_level += 1;
33467            for (i, entry) in e.entries.iter().enumerate() {
33468                if i > 0 {
33469                    self.write(",");
33470                    self.write_newline();
33471                }
33472                self.write_indent();
33473                self.generate_identifier(&entry.key)?;
33474                self.write("=");
33475                self.generate_expression(&entry.value)?;
33476            }
33477            self.indent_level -= 1;
33478            self.write_newline();
33479            self.write(")");
33480        } else {
33481            self.write(" (");
33482            for (i, entry) in e.entries.iter().enumerate() {
33483                if i > 0 {
33484                    self.write(", ");
33485                }
33486                self.generate_identifier(&entry.key)?;
33487                self.write("=");
33488                self.generate_expression(&entry.value)?;
33489            }
33490            self.write(")");
33491        }
33492        Ok(())
33493    }
33494
33495    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33496    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33497        self.write_keyword("OPTIONS");
33498        self.write(" (");
33499        for (i, opt) in options.iter().enumerate() {
33500            if i > 0 {
33501                self.write(", ");
33502            }
33503            self.generate_option_expression(opt)?;
33504        }
33505        self.write(")");
33506        Ok(())
33507    }
33508
33509    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33510    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33511        self.write_keyword("PROPERTIES");
33512        self.write(" (");
33513        for (i, prop) in properties.iter().enumerate() {
33514            if i > 0 {
33515                self.write(", ");
33516            }
33517            self.generate_option_expression(prop)?;
33518        }
33519        self.write(")");
33520        Ok(())
33521    }
33522
33523    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33524    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33525        self.write_keyword("ENVIRONMENT");
33526        self.write(" (");
33527        for (i, env_item) in environment.iter().enumerate() {
33528            if i > 0 {
33529                self.write(", ");
33530            }
33531            self.generate_environment_expression(env_item)?;
33532        }
33533        self.write(")");
33534        Ok(())
33535    }
33536
33537    /// Generate an environment expression with spaces around =
33538    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33539        match expr {
33540            Expression::Eq(eq) => {
33541                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33542                self.generate_expression(&eq.left)?;
33543                self.write(" = ");
33544                self.generate_expression(&eq.right)?;
33545                Ok(())
33546            }
33547            _ => self.generate_expression(expr),
33548        }
33549    }
33550
33551    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33552    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33553        self.write_keyword("TBLPROPERTIES");
33554        if self.config.pretty {
33555            self.write(" (");
33556            self.write_newline();
33557            self.indent_level += 1;
33558            for (i, opt) in options.iter().enumerate() {
33559                if i > 0 {
33560                    self.write(",");
33561                    self.write_newline();
33562                }
33563                self.write_indent();
33564                self.generate_option_expression(opt)?;
33565            }
33566            self.indent_level -= 1;
33567            self.write_newline();
33568            self.write(")");
33569        } else {
33570            self.write(" (");
33571            for (i, opt) in options.iter().enumerate() {
33572                if i > 0 {
33573                    self.write(", ");
33574                }
33575                self.generate_option_expression(opt)?;
33576            }
33577            self.write(")");
33578        }
33579        Ok(())
33580    }
33581
33582    /// Generate an option expression without spaces around =
33583    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33584        match expr {
33585            Expression::Eq(eq) => {
33586                // Generate key=value without spaces
33587                self.generate_expression(&eq.left)?;
33588                self.write("=");
33589                self.generate_expression(&eq.right)?;
33590                Ok(())
33591            }
33592            _ => self.generate_expression(expr),
33593        }
33594    }
33595
33596    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33597        // Just output the name
33598        self.generate_expression(&e.this)?;
33599        Ok(())
33600    }
33601
33602    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33603        // PUT source_file @stage [options]
33604        self.write_keyword("PUT");
33605        self.write_space();
33606
33607        // Source file path - preserve original quoting
33608        if e.source_quoted {
33609            self.write("'");
33610            self.write(&e.source);
33611            self.write("'");
33612        } else {
33613            self.write(&e.source);
33614        }
33615
33616        self.write_space();
33617
33618        // Target stage reference - output the string directly (includes @)
33619        if let Expression::Literal(lit) = &e.target {
33620            if let Literal::String(s) = lit.as_ref() {
33621                self.write(s);
33622            }
33623        } else {
33624            self.generate_expression(&e.target)?;
33625        }
33626
33627        // Optional parameters: KEY=VALUE
33628        for param in &e.params {
33629            self.write_space();
33630            self.write(&param.name);
33631            if let Some(ref value) = param.value {
33632                self.write("=");
33633                self.generate_expression(value)?;
33634            }
33635        }
33636
33637        Ok(())
33638    }
33639
33640    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33641        // QUANTILE(this, quantile)
33642        self.write_keyword("QUANTILE");
33643        self.write("(");
33644        self.generate_expression(&e.this)?;
33645        if let Some(quantile) = &e.quantile {
33646            self.write(", ");
33647            self.generate_expression(quantile)?;
33648        }
33649        self.write(")");
33650        Ok(())
33651    }
33652
33653    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33654        // QUERY_BAND = this [UPDATE] [FOR scope]
33655        if matches!(
33656            self.config.dialect,
33657            Some(crate::dialects::DialectType::Teradata)
33658        ) {
33659            self.write_keyword("SET");
33660            self.write_space();
33661        }
33662        self.write_keyword("QUERY_BAND");
33663        self.write(" = ");
33664        self.generate_expression(&e.this)?;
33665        if e.update.is_some() {
33666            self.write_space();
33667            self.write_keyword("UPDATE");
33668        }
33669        if let Some(scope) = &e.scope {
33670            self.write_space();
33671            self.write_keyword("FOR");
33672            self.write_space();
33673            self.generate_expression(scope)?;
33674        }
33675        Ok(())
33676    }
33677
33678    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33679        // this = expression
33680        self.generate_expression(&e.this)?;
33681        if let Some(expression) = &e.expression {
33682            self.write(" = ");
33683            self.generate_expression(expression)?;
33684        }
33685        Ok(())
33686    }
33687
33688    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33689        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33690        self.write_keyword("TRANSFORM");
33691        self.write("(");
33692        for (i, expr) in e.expressions.iter().enumerate() {
33693            if i > 0 {
33694                self.write(", ");
33695            }
33696            self.generate_expression(expr)?;
33697        }
33698        self.write(")");
33699        if let Some(row_format_before) = &e.row_format_before {
33700            self.write_space();
33701            self.generate_expression(row_format_before)?;
33702        }
33703        if let Some(record_writer) = &e.record_writer {
33704            self.write_space();
33705            self.write_keyword("RECORDWRITER");
33706            self.write_space();
33707            self.generate_expression(record_writer)?;
33708        }
33709        if let Some(command_script) = &e.command_script {
33710            self.write_space();
33711            self.write_keyword("USING");
33712            self.write_space();
33713            self.generate_expression(command_script)?;
33714        }
33715        if let Some(schema) = &e.schema {
33716            self.write_space();
33717            self.write_keyword("AS");
33718            self.write_space();
33719            self.generate_expression(schema)?;
33720        }
33721        if let Some(row_format_after) = &e.row_format_after {
33722            self.write_space();
33723            self.generate_expression(row_format_after)?;
33724        }
33725        if let Some(record_reader) = &e.record_reader {
33726            self.write_space();
33727            self.write_keyword("RECORDREADER");
33728            self.write_space();
33729            self.generate_expression(record_reader)?;
33730        }
33731        Ok(())
33732    }
33733
33734    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33735        // RANDN([seed])
33736        self.write_keyword("RANDN");
33737        self.write("(");
33738        if let Some(this) = &e.this {
33739            self.generate_expression(this)?;
33740        }
33741        self.write(")");
33742        Ok(())
33743    }
33744
33745    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33746        // RANDSTR(this, [generator])
33747        self.write_keyword("RANDSTR");
33748        self.write("(");
33749        self.generate_expression(&e.this)?;
33750        if let Some(generator) = &e.generator {
33751            self.write(", ");
33752            self.generate_expression(generator)?;
33753        }
33754        self.write(")");
33755        Ok(())
33756    }
33757
33758    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33759        // RANGE_BUCKET(this, expression)
33760        self.write_keyword("RANGE_BUCKET");
33761        self.write("(");
33762        self.generate_expression(&e.this)?;
33763        self.write(", ");
33764        self.generate_expression(&e.expression)?;
33765        self.write(")");
33766        Ok(())
33767    }
33768
33769    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33770        // RANGE_N(this BETWEEN expressions [EACH each])
33771        self.write_keyword("RANGE_N");
33772        self.write("(");
33773        self.generate_expression(&e.this)?;
33774        self.write_space();
33775        self.write_keyword("BETWEEN");
33776        self.write_space();
33777        for (i, expr) in e.expressions.iter().enumerate() {
33778            if i > 0 {
33779                self.write(", ");
33780            }
33781            self.generate_expression(expr)?;
33782        }
33783        if let Some(each) = &e.each {
33784            self.write_space();
33785            self.write_keyword("EACH");
33786            self.write_space();
33787            self.generate_expression(each)?;
33788        }
33789        self.write(")");
33790        Ok(())
33791    }
33792
33793    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33794        // READ_CSV(this, expressions...)
33795        self.write_keyword("READ_CSV");
33796        self.write("(");
33797        self.generate_expression(&e.this)?;
33798        for expr in &e.expressions {
33799            self.write(", ");
33800            self.generate_expression(expr)?;
33801        }
33802        self.write(")");
33803        Ok(())
33804    }
33805
33806    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33807        // READ_PARQUET(expressions...)
33808        self.write_keyword("READ_PARQUET");
33809        self.write("(");
33810        for (i, expr) in e.expressions.iter().enumerate() {
33811            if i > 0 {
33812                self.write(", ");
33813            }
33814            self.generate_expression(expr)?;
33815        }
33816        self.write(")");
33817        Ok(())
33818    }
33819
33820    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33821        // SEARCH kind FIRST BY this SET expression [USING using]
33822        // or CYCLE this SET expression [USING using]
33823        if e.kind == "CYCLE" {
33824            self.write_keyword("CYCLE");
33825        } else {
33826            self.write_keyword("SEARCH");
33827            self.write_space();
33828            self.write(&e.kind);
33829            self.write_space();
33830            self.write_keyword("FIRST BY");
33831        }
33832        self.write_space();
33833        self.generate_expression(&e.this)?;
33834        self.write_space();
33835        self.write_keyword("SET");
33836        self.write_space();
33837        self.generate_expression(&e.expression)?;
33838        if let Some(using) = &e.using {
33839            self.write_space();
33840            self.write_keyword("USING");
33841            self.write_space();
33842            self.generate_expression(using)?;
33843        }
33844        Ok(())
33845    }
33846
33847    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33848        // REDUCE(this, initial, merge, [finish])
33849        self.write_keyword("REDUCE");
33850        self.write("(");
33851        self.generate_expression(&e.this)?;
33852        if let Some(initial) = &e.initial {
33853            self.write(", ");
33854            self.generate_expression(initial)?;
33855        }
33856        if let Some(merge) = &e.merge {
33857            self.write(", ");
33858            self.generate_expression(merge)?;
33859        }
33860        if let Some(finish) = &e.finish {
33861            self.write(", ");
33862            self.generate_expression(finish)?;
33863        }
33864        self.write(")");
33865        Ok(())
33866    }
33867
33868    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
33869        // REFERENCES this (expressions) [options]
33870        self.write_keyword("REFERENCES");
33871        self.write_space();
33872        self.generate_expression(&e.this)?;
33873        if !e.expressions.is_empty() {
33874            self.write(" (");
33875            for (i, expr) in e.expressions.iter().enumerate() {
33876                if i > 0 {
33877                    self.write(", ");
33878                }
33879                self.generate_expression(expr)?;
33880            }
33881            self.write(")");
33882        }
33883        for opt in &e.options {
33884            self.write_space();
33885            self.generate_expression(opt)?;
33886        }
33887        Ok(())
33888    }
33889
33890    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
33891        // REFRESH [kind] this
33892        self.write_keyword("REFRESH");
33893        if !e.kind.is_empty() {
33894            self.write_space();
33895            self.write_keyword(&e.kind);
33896        }
33897        self.write_space();
33898        self.generate_expression(&e.this)?;
33899        Ok(())
33900    }
33901
33902    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33903        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33904        self.write_keyword("REFRESH");
33905        self.write_space();
33906        self.write_keyword(&e.method);
33907
33908        if let Some(ref kind) = e.kind {
33909            self.write_space();
33910            self.write_keyword("ON");
33911            self.write_space();
33912            self.write_keyword(kind);
33913
33914            // EVERY n UNIT
33915            if let Some(ref every) = e.every {
33916                self.write_space();
33917                self.write_keyword("EVERY");
33918                self.write_space();
33919                self.generate_expression(every)?;
33920                if let Some(ref unit) = e.unit {
33921                    self.write_space();
33922                    self.write_keyword(unit);
33923                }
33924            }
33925
33926            // STARTS 'datetime'
33927            if let Some(ref starts) = e.starts {
33928                self.write_space();
33929                self.write_keyword("STARTS");
33930                self.write_space();
33931                self.generate_expression(starts)?;
33932            }
33933        }
33934        Ok(())
33935    }
33936
33937    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33938        // REGEXP_COUNT(this, expression, position, parameters)
33939        self.write_keyword("REGEXP_COUNT");
33940        self.write("(");
33941        self.generate_expression(&e.this)?;
33942        self.write(", ");
33943        self.generate_expression(&e.expression)?;
33944        if let Some(position) = &e.position {
33945            self.write(", ");
33946            self.generate_expression(position)?;
33947        }
33948        if let Some(parameters) = &e.parameters {
33949            self.write(", ");
33950            self.generate_expression(parameters)?;
33951        }
33952        self.write(")");
33953        Ok(())
33954    }
33955
33956    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33957        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33958        self.write_keyword("REGEXP_EXTRACT_ALL");
33959        self.write("(");
33960        self.generate_expression(&e.this)?;
33961        self.write(", ");
33962        self.generate_expression(&e.expression)?;
33963        if let Some(group) = &e.group {
33964            self.write(", ");
33965            self.generate_expression(group)?;
33966        }
33967        self.write(")");
33968        Ok(())
33969    }
33970
33971    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33972        // REGEXP_FULL_MATCH(this, expression)
33973        self.write_keyword("REGEXP_FULL_MATCH");
33974        self.write("(");
33975        self.generate_expression(&e.this)?;
33976        self.write(", ");
33977        self.generate_expression(&e.expression)?;
33978        self.write(")");
33979        Ok(())
33980    }
33981
33982    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33983        use crate::dialects::DialectType;
33984        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33985        if matches!(
33986            self.config.dialect,
33987            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33988        ) && e.flag.is_none()
33989        {
33990            self.generate_expression(&e.this)?;
33991            self.write(" ~* ");
33992            self.generate_expression(&e.expression)?;
33993        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33994            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33995            self.write_keyword("REGEXP_LIKE");
33996            self.write("(");
33997            self.generate_expression(&e.this)?;
33998            self.write(", ");
33999            self.generate_expression(&e.expression)?;
34000            self.write(", ");
34001            if let Some(flag) = &e.flag {
34002                self.generate_expression(flag)?;
34003            } else {
34004                self.write("'i'");
34005            }
34006            self.write(")");
34007        } else {
34008            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
34009            self.generate_expression(&e.this)?;
34010            self.write_space();
34011            self.write_keyword("REGEXP_ILIKE");
34012            self.write_space();
34013            self.generate_expression(&e.expression)?;
34014            if let Some(flag) = &e.flag {
34015                self.write(", ");
34016                self.generate_expression(flag)?;
34017            }
34018        }
34019        Ok(())
34020    }
34021
34022    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
34023        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
34024        self.write_keyword("REGEXP_INSTR");
34025        self.write("(");
34026        self.generate_expression(&e.this)?;
34027        self.write(", ");
34028        self.generate_expression(&e.expression)?;
34029        if let Some(position) = &e.position {
34030            self.write(", ");
34031            self.generate_expression(position)?;
34032        }
34033        if let Some(occurrence) = &e.occurrence {
34034            self.write(", ");
34035            self.generate_expression(occurrence)?;
34036        }
34037        if let Some(option) = &e.option {
34038            self.write(", ");
34039            self.generate_expression(option)?;
34040        }
34041        if let Some(parameters) = &e.parameters {
34042            self.write(", ");
34043            self.generate_expression(parameters)?;
34044        }
34045        if let Some(group) = &e.group {
34046            self.write(", ");
34047            self.generate_expression(group)?;
34048        }
34049        self.write(")");
34050        Ok(())
34051    }
34052
34053    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
34054        // REGEXP_SPLIT(this, expression, limit)
34055        self.write_keyword("REGEXP_SPLIT");
34056        self.write("(");
34057        self.generate_expression(&e.this)?;
34058        self.write(", ");
34059        self.generate_expression(&e.expression)?;
34060        if let Some(limit) = &e.limit {
34061            self.write(", ");
34062            self.generate_expression(limit)?;
34063        }
34064        self.write(")");
34065        Ok(())
34066    }
34067
34068    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
34069        // REGR_AVGX(this, expression)
34070        self.write_keyword("REGR_AVGX");
34071        self.write("(");
34072        self.generate_expression(&e.this)?;
34073        self.write(", ");
34074        self.generate_expression(&e.expression)?;
34075        self.write(")");
34076        Ok(())
34077    }
34078
34079    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
34080        // REGR_AVGY(this, expression)
34081        self.write_keyword("REGR_AVGY");
34082        self.write("(");
34083        self.generate_expression(&e.this)?;
34084        self.write(", ");
34085        self.generate_expression(&e.expression)?;
34086        self.write(")");
34087        Ok(())
34088    }
34089
34090    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
34091        // REGR_COUNT(this, expression)
34092        self.write_keyword("REGR_COUNT");
34093        self.write("(");
34094        self.generate_expression(&e.this)?;
34095        self.write(", ");
34096        self.generate_expression(&e.expression)?;
34097        self.write(")");
34098        Ok(())
34099    }
34100
34101    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
34102        // REGR_INTERCEPT(this, expression)
34103        self.write_keyword("REGR_INTERCEPT");
34104        self.write("(");
34105        self.generate_expression(&e.this)?;
34106        self.write(", ");
34107        self.generate_expression(&e.expression)?;
34108        self.write(")");
34109        Ok(())
34110    }
34111
34112    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
34113        // REGR_R2(this, expression)
34114        self.write_keyword("REGR_R2");
34115        self.write("(");
34116        self.generate_expression(&e.this)?;
34117        self.write(", ");
34118        self.generate_expression(&e.expression)?;
34119        self.write(")");
34120        Ok(())
34121    }
34122
34123    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
34124        // REGR_SLOPE(this, expression)
34125        self.write_keyword("REGR_SLOPE");
34126        self.write("(");
34127        self.generate_expression(&e.this)?;
34128        self.write(", ");
34129        self.generate_expression(&e.expression)?;
34130        self.write(")");
34131        Ok(())
34132    }
34133
34134    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
34135        // REGR_SXX(this, expression)
34136        self.write_keyword("REGR_SXX");
34137        self.write("(");
34138        self.generate_expression(&e.this)?;
34139        self.write(", ");
34140        self.generate_expression(&e.expression)?;
34141        self.write(")");
34142        Ok(())
34143    }
34144
34145    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
34146        // REGR_SXY(this, expression)
34147        self.write_keyword("REGR_SXY");
34148        self.write("(");
34149        self.generate_expression(&e.this)?;
34150        self.write(", ");
34151        self.generate_expression(&e.expression)?;
34152        self.write(")");
34153        Ok(())
34154    }
34155
34156    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
34157        // REGR_SYY(this, expression)
34158        self.write_keyword("REGR_SYY");
34159        self.write("(");
34160        self.generate_expression(&e.this)?;
34161        self.write(", ");
34162        self.generate_expression(&e.expression)?;
34163        self.write(")");
34164        Ok(())
34165    }
34166
34167    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
34168        // REGR_VALX(this, expression)
34169        self.write_keyword("REGR_VALX");
34170        self.write("(");
34171        self.generate_expression(&e.this)?;
34172        self.write(", ");
34173        self.generate_expression(&e.expression)?;
34174        self.write(")");
34175        Ok(())
34176    }
34177
34178    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
34179        // REGR_VALY(this, expression)
34180        self.write_keyword("REGR_VALY");
34181        self.write("(");
34182        self.generate_expression(&e.this)?;
34183        self.write(", ");
34184        self.generate_expression(&e.expression)?;
34185        self.write(")");
34186        Ok(())
34187    }
34188
34189    fn generate_remote_with_connection_model_property(
34190        &mut self,
34191        e: &RemoteWithConnectionModelProperty,
34192    ) -> Result<()> {
34193        // REMOTE WITH CONNECTION this
34194        self.write_keyword("REMOTE WITH CONNECTION");
34195        self.write_space();
34196        self.generate_expression(&e.this)?;
34197        Ok(())
34198    }
34199
34200    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
34201        // RENAME COLUMN [IF EXISTS] this TO new_name
34202        self.write_keyword("RENAME COLUMN");
34203        if e.exists {
34204            self.write_space();
34205            self.write_keyword("IF EXISTS");
34206        }
34207        self.write_space();
34208        self.generate_expression(&e.this)?;
34209        if let Some(to) = &e.to {
34210            self.write_space();
34211            self.write_keyword("TO");
34212            self.write_space();
34213            self.generate_expression(to)?;
34214        }
34215        Ok(())
34216    }
34217
34218    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
34219        // REPLACE PARTITION expression [FROM source]
34220        self.write_keyword("REPLACE PARTITION");
34221        self.write_space();
34222        self.generate_expression(&e.expression)?;
34223        if let Some(source) = &e.source {
34224            self.write_space();
34225            self.write_keyword("FROM");
34226            self.write_space();
34227            self.generate_expression(source)?;
34228        }
34229        Ok(())
34230    }
34231
34232    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
34233        // RETURNING expressions [INTO into]
34234        // TSQL and Fabric use OUTPUT instead of RETURNING
34235        let keyword = match self.config.dialect {
34236            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
34237            _ => "RETURNING",
34238        };
34239        self.write_keyword(keyword);
34240        self.write_space();
34241        for (i, expr) in e.expressions.iter().enumerate() {
34242            if i > 0 {
34243                self.write(", ");
34244            }
34245            self.generate_expression(expr)?;
34246        }
34247        if let Some(into) = &e.into {
34248            self.write_space();
34249            self.write_keyword("INTO");
34250            self.write_space();
34251            self.generate_expression(into)?;
34252        }
34253        Ok(())
34254    }
34255
34256    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
34257        // OUTPUT expressions [INTO into_table]
34258        self.write_space();
34259        self.write_keyword("OUTPUT");
34260        self.write_space();
34261        for (i, expr) in output.columns.iter().enumerate() {
34262            if i > 0 {
34263                self.write(", ");
34264            }
34265            self.generate_expression(expr)?;
34266        }
34267        if let Some(into_table) = &output.into_table {
34268            self.write_space();
34269            self.write_keyword("INTO");
34270            self.write_space();
34271            self.generate_expression(into_table)?;
34272        }
34273        Ok(())
34274    }
34275
34276    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
34277        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
34278        self.write_keyword("RETURNS");
34279        if e.is_table.is_some() {
34280            self.write_space();
34281            self.write_keyword("TABLE");
34282        }
34283        if let Some(table) = &e.table {
34284            self.write_space();
34285            self.generate_expression(table)?;
34286        } else if let Some(this) = &e.this {
34287            self.write_space();
34288            self.generate_expression(this)?;
34289        }
34290        if e.null.is_some() {
34291            self.write_space();
34292            self.write_keyword("NULL ON NULL INPUT");
34293        }
34294        Ok(())
34295    }
34296
34297    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
34298        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
34299        self.write_keyword("ROLLBACK");
34300
34301        // TSQL always uses ROLLBACK TRANSACTION
34302        if e.this.is_none()
34303            && matches!(
34304                self.config.dialect,
34305                Some(DialectType::TSQL) | Some(DialectType::Fabric)
34306            )
34307        {
34308            self.write_space();
34309            self.write_keyword("TRANSACTION");
34310        }
34311
34312        // Check if this has TRANSACTION keyword or transaction name
34313        if let Some(this) = &e.this {
34314            // Check if it's just the "TRANSACTION" marker or an actual transaction name
34315            let is_transaction_marker = matches!(
34316                this.as_ref(),
34317                Expression::Identifier(id) if id.name == "TRANSACTION"
34318            );
34319
34320            self.write_space();
34321            self.write_keyword("TRANSACTION");
34322
34323            // If it's a real transaction name, output it
34324            if !is_transaction_marker {
34325                self.write_space();
34326                self.generate_expression(this)?;
34327            }
34328        }
34329
34330        // Output TO savepoint
34331        if let Some(savepoint) = &e.savepoint {
34332            self.write_space();
34333            self.write_keyword("TO");
34334            self.write_space();
34335            self.generate_expression(savepoint)?;
34336        }
34337        Ok(())
34338    }
34339
34340    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
34341        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
34342        if e.expressions.is_empty() {
34343            self.write_keyword("WITH ROLLUP");
34344        } else {
34345            self.write_keyword("ROLLUP");
34346            self.write("(");
34347            for (i, expr) in e.expressions.iter().enumerate() {
34348                if i > 0 {
34349                    self.write(", ");
34350                }
34351                self.generate_expression(expr)?;
34352            }
34353            self.write(")");
34354        }
34355        Ok(())
34356    }
34357
34358    fn generate_row_format_delimited_property(
34359        &mut self,
34360        e: &RowFormatDelimitedProperty,
34361    ) -> Result<()> {
34362        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
34363        self.write_keyword("ROW FORMAT DELIMITED");
34364        if let Some(fields) = &e.fields {
34365            self.write_space();
34366            self.write_keyword("FIELDS TERMINATED BY");
34367            self.write_space();
34368            self.generate_expression(fields)?;
34369        }
34370        if let Some(escaped) = &e.escaped {
34371            self.write_space();
34372            self.write_keyword("ESCAPED BY");
34373            self.write_space();
34374            self.generate_expression(escaped)?;
34375        }
34376        if let Some(items) = &e.collection_items {
34377            self.write_space();
34378            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
34379            self.write_space();
34380            self.generate_expression(items)?;
34381        }
34382        if let Some(keys) = &e.map_keys {
34383            self.write_space();
34384            self.write_keyword("MAP KEYS TERMINATED BY");
34385            self.write_space();
34386            self.generate_expression(keys)?;
34387        }
34388        if let Some(lines) = &e.lines {
34389            self.write_space();
34390            self.write_keyword("LINES TERMINATED BY");
34391            self.write_space();
34392            self.generate_expression(lines)?;
34393        }
34394        if let Some(null) = &e.null {
34395            self.write_space();
34396            self.write_keyword("NULL DEFINED AS");
34397            self.write_space();
34398            self.generate_expression(null)?;
34399        }
34400        if let Some(serde) = &e.serde {
34401            self.write_space();
34402            self.generate_expression(serde)?;
34403        }
34404        Ok(())
34405    }
34406
34407    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
34408        // ROW FORMAT this
34409        self.write_keyword("ROW FORMAT");
34410        self.write_space();
34411        self.generate_expression(&e.this)?;
34412        Ok(())
34413    }
34414
34415    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
34416        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
34417        self.write_keyword("ROW FORMAT SERDE");
34418        self.write_space();
34419        self.generate_expression(&e.this)?;
34420        if let Some(props) = &e.serde_properties {
34421            self.write_space();
34422            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
34423            self.generate_expression(props)?;
34424        }
34425        Ok(())
34426    }
34427
34428    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
34429        // SHA2(this, length)
34430        self.write_keyword("SHA2");
34431        self.write("(");
34432        self.generate_expression(&e.this)?;
34433        if let Some(length) = e.length {
34434            self.write(", ");
34435            self.write(&length.to_string());
34436        }
34437        self.write(")");
34438        Ok(())
34439    }
34440
34441    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
34442        // SHA2_DIGEST(this, length)
34443        self.write_keyword("SHA2_DIGEST");
34444        self.write("(");
34445        self.generate_expression(&e.this)?;
34446        if let Some(length) = e.length {
34447            self.write(", ");
34448            self.write(&length.to_string());
34449        }
34450        self.write(")");
34451        Ok(())
34452    }
34453
34454    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
34455        let name = if matches!(
34456            self.config.dialect,
34457            Some(crate::dialects::DialectType::Spark)
34458                | Some(crate::dialects::DialectType::Databricks)
34459        ) {
34460            "TRY_ADD"
34461        } else {
34462            "SAFE_ADD"
34463        };
34464        self.write_keyword(name);
34465        self.write("(");
34466        self.generate_expression(&e.this)?;
34467        self.write(", ");
34468        self.generate_expression(&e.expression)?;
34469        self.write(")");
34470        Ok(())
34471    }
34472
34473    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
34474        // SAFE_DIVIDE(this, expression)
34475        self.write_keyword("SAFE_DIVIDE");
34476        self.write("(");
34477        self.generate_expression(&e.this)?;
34478        self.write(", ");
34479        self.generate_expression(&e.expression)?;
34480        self.write(")");
34481        Ok(())
34482    }
34483
34484    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
34485        let name = if matches!(
34486            self.config.dialect,
34487            Some(crate::dialects::DialectType::Spark)
34488                | Some(crate::dialects::DialectType::Databricks)
34489        ) {
34490            "TRY_MULTIPLY"
34491        } else {
34492            "SAFE_MULTIPLY"
34493        };
34494        self.write_keyword(name);
34495        self.write("(");
34496        self.generate_expression(&e.this)?;
34497        self.write(", ");
34498        self.generate_expression(&e.expression)?;
34499        self.write(")");
34500        Ok(())
34501    }
34502
34503    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34504        let name = if matches!(
34505            self.config.dialect,
34506            Some(crate::dialects::DialectType::Spark)
34507                | Some(crate::dialects::DialectType::Databricks)
34508        ) {
34509            "TRY_SUBTRACT"
34510        } else {
34511            "SAFE_SUBTRACT"
34512        };
34513        self.write_keyword(name);
34514        self.write("(");
34515        self.generate_expression(&e.this)?;
34516        self.write(", ");
34517        self.generate_expression(&e.expression)?;
34518        self.write(")");
34519        Ok(())
34520    }
34521
34522    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34523    /// METHOD (size UNIT) [REPEATABLE (seed)]
34524    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34525        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34526        if matches!(sample.method, SampleMethod::Bucket) {
34527            self.write(" (");
34528            self.write_keyword("BUCKET");
34529            self.write_space();
34530            if let Some(ref num) = sample.bucket_numerator {
34531                self.generate_expression(num)?;
34532            }
34533            self.write_space();
34534            self.write_keyword("OUT OF");
34535            self.write_space();
34536            if let Some(ref denom) = sample.bucket_denominator {
34537                self.generate_expression(denom)?;
34538            }
34539            if let Some(ref field) = sample.bucket_field {
34540                self.write_space();
34541                self.write_keyword("ON");
34542                self.write_space();
34543                self.generate_expression(field)?;
34544            }
34545            self.write(")");
34546            return Ok(());
34547        }
34548
34549        // Output method name if explicitly specified, or for dialects that always require it
34550        let is_snowflake = matches!(
34551            self.config.dialect,
34552            Some(crate::dialects::DialectType::Snowflake)
34553        );
34554        let is_postgres = matches!(
34555            self.config.dialect,
34556            Some(crate::dialects::DialectType::PostgreSQL)
34557                | Some(crate::dialects::DialectType::Redshift)
34558        );
34559        // Databricks and Spark don't output method names
34560        let is_databricks = matches!(
34561            self.config.dialect,
34562            Some(crate::dialects::DialectType::Databricks)
34563        );
34564        let is_spark = matches!(
34565            self.config.dialect,
34566            Some(crate::dialects::DialectType::Spark)
34567        );
34568        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34569        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34570        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34571        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34572            self.write_space();
34573            if !sample.explicit_method && (is_snowflake || force_method) {
34574                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34575                self.write_keyword("BERNOULLI");
34576            } else {
34577                match sample.method {
34578                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34579                    SampleMethod::System => self.write_keyword("SYSTEM"),
34580                    SampleMethod::Block => self.write_keyword("BLOCK"),
34581                    SampleMethod::Row => self.write_keyword("ROW"),
34582                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34583                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34584                    SampleMethod::Bucket => {} // handled above
34585                }
34586            }
34587        }
34588
34589        // Output size, with or without parentheses depending on dialect
34590        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34591        if emit_size_no_parens {
34592            self.write_space();
34593            match &sample.size {
34594                Expression::Tuple(tuple) => {
34595                    for (i, expr) in tuple.expressions.iter().enumerate() {
34596                        if i > 0 {
34597                            self.write(", ");
34598                        }
34599                        self.generate_expression(expr)?;
34600                    }
34601                }
34602                expr => self.generate_expression(expr)?,
34603            }
34604        } else {
34605            self.write(" (");
34606            self.generate_expression(&sample.size)?;
34607        }
34608
34609        // Determine unit
34610        let is_rows_method = matches!(
34611            sample.method,
34612            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34613        );
34614        let is_percent = matches!(
34615            sample.method,
34616            SampleMethod::Percent
34617                | SampleMethod::System
34618                | SampleMethod::Bernoulli
34619                | SampleMethod::Block
34620        );
34621
34622        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34623        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34624        // For Databricks and Spark, always output PERCENT for percentage samples.
34625        let is_presto = matches!(
34626            self.config.dialect,
34627            Some(crate::dialects::DialectType::Presto)
34628                | Some(crate::dialects::DialectType::Trino)
34629                | Some(crate::dialects::DialectType::Athena)
34630        );
34631        let should_output_unit = if is_databricks || is_spark {
34632            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34633            is_percent || is_rows_method || sample.unit_after_size
34634        } else if is_snowflake || is_postgres || is_presto {
34635            sample.unit_after_size
34636        } else {
34637            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34638        };
34639
34640        if should_output_unit {
34641            self.write_space();
34642            if sample.is_percent {
34643                self.write_keyword("PERCENT");
34644            } else if is_rows_method && !sample.unit_after_size {
34645                self.write_keyword("ROWS");
34646            } else if sample.unit_after_size {
34647                match sample.method {
34648                    SampleMethod::Percent
34649                    | SampleMethod::System
34650                    | SampleMethod::Bernoulli
34651                    | SampleMethod::Block => {
34652                        self.write_keyword("PERCENT");
34653                    }
34654                    SampleMethod::Row | SampleMethod::Reservoir => {
34655                        self.write_keyword("ROWS");
34656                    }
34657                    _ => self.write_keyword("ROWS"),
34658                }
34659            } else {
34660                self.write_keyword("PERCENT");
34661            }
34662        }
34663
34664        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34665            if let Some(ref offset) = sample.offset {
34666                self.write_space();
34667                self.write_keyword("OFFSET");
34668                self.write_space();
34669                self.generate_expression(offset)?;
34670            }
34671        }
34672        if !emit_size_no_parens {
34673            self.write(")");
34674        }
34675
34676        Ok(())
34677    }
34678
34679    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34680        // SAMPLE this (ClickHouse uses SAMPLE BY)
34681        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34682            self.write_keyword("SAMPLE BY");
34683        } else {
34684            self.write_keyword("SAMPLE");
34685        }
34686        self.write_space();
34687        self.generate_expression(&e.this)?;
34688        Ok(())
34689    }
34690
34691    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34692        // this (expressions...)
34693        if let Some(this) = &e.this {
34694            self.generate_expression(this)?;
34695        }
34696        if !e.expressions.is_empty() {
34697            // Add space before column list if there's a preceding expression
34698            if e.this.is_some() {
34699                self.write_space();
34700            }
34701            self.write("(");
34702            for (i, expr) in e.expressions.iter().enumerate() {
34703                if i > 0 {
34704                    self.write(", ");
34705                }
34706                self.generate_expression(expr)?;
34707            }
34708            self.write(")");
34709        }
34710        Ok(())
34711    }
34712
34713    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34714        // COMMENT this
34715        self.write_keyword("COMMENT");
34716        self.write_space();
34717        self.generate_expression(&e.this)?;
34718        Ok(())
34719    }
34720
34721    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34722        // [this::]expression
34723        if let Some(this) = &e.this {
34724            self.generate_expression(this)?;
34725            self.write("::");
34726        }
34727        self.generate_expression(&e.expression)?;
34728        Ok(())
34729    }
34730
34731    fn generate_search(&mut self, e: &Search) -> Result<()> {
34732        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34733        self.write_keyword("SEARCH");
34734        self.write("(");
34735        self.generate_expression(&e.this)?;
34736        self.write(", ");
34737        self.generate_expression(&e.expression)?;
34738        if let Some(json_scope) = &e.json_scope {
34739            self.write(", ");
34740            self.generate_expression(json_scope)?;
34741        }
34742        if let Some(analyzer) = &e.analyzer {
34743            self.write(", ");
34744            self.generate_expression(analyzer)?;
34745        }
34746        if let Some(analyzer_options) = &e.analyzer_options {
34747            self.write(", ");
34748            self.generate_expression(analyzer_options)?;
34749        }
34750        if let Some(search_mode) = &e.search_mode {
34751            self.write(", ");
34752            self.generate_expression(search_mode)?;
34753        }
34754        self.write(")");
34755        Ok(())
34756    }
34757
34758    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34759        // SEARCH_IP(this, expression)
34760        self.write_keyword("SEARCH_IP");
34761        self.write("(");
34762        self.generate_expression(&e.this)?;
34763        self.write(", ");
34764        self.generate_expression(&e.expression)?;
34765        self.write(")");
34766        Ok(())
34767    }
34768
34769    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34770        // SECURITY this
34771        self.write_keyword("SECURITY");
34772        self.write_space();
34773        self.generate_expression(&e.this)?;
34774        Ok(())
34775    }
34776
34777    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34778        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34779        self.write("SEMANTIC_VIEW(");
34780
34781        if self.config.pretty {
34782            // Pretty print: each clause on its own line
34783            self.write_newline();
34784            self.indent_level += 1;
34785            self.write_indent();
34786            self.generate_expression(&e.this)?;
34787
34788            if let Some(metrics) = &e.metrics {
34789                self.write_newline();
34790                self.write_indent();
34791                self.write_keyword("METRICS");
34792                self.write_space();
34793                self.generate_semantic_view_tuple(metrics)?;
34794            }
34795            if let Some(dimensions) = &e.dimensions {
34796                self.write_newline();
34797                self.write_indent();
34798                self.write_keyword("DIMENSIONS");
34799                self.write_space();
34800                self.generate_semantic_view_tuple(dimensions)?;
34801            }
34802            if let Some(facts) = &e.facts {
34803                self.write_newline();
34804                self.write_indent();
34805                self.write_keyword("FACTS");
34806                self.write_space();
34807                self.generate_semantic_view_tuple(facts)?;
34808            }
34809            if let Some(where_) = &e.where_ {
34810                self.write_newline();
34811                self.write_indent();
34812                self.write_keyword("WHERE");
34813                self.write_space();
34814                self.generate_expression(where_)?;
34815            }
34816            self.write_newline();
34817            self.indent_level -= 1;
34818            self.write_indent();
34819        } else {
34820            // Compact: all on one line
34821            self.generate_expression(&e.this)?;
34822            if let Some(metrics) = &e.metrics {
34823                self.write_space();
34824                self.write_keyword("METRICS");
34825                self.write_space();
34826                self.generate_semantic_view_tuple(metrics)?;
34827            }
34828            if let Some(dimensions) = &e.dimensions {
34829                self.write_space();
34830                self.write_keyword("DIMENSIONS");
34831                self.write_space();
34832                self.generate_semantic_view_tuple(dimensions)?;
34833            }
34834            if let Some(facts) = &e.facts {
34835                self.write_space();
34836                self.write_keyword("FACTS");
34837                self.write_space();
34838                self.generate_semantic_view_tuple(facts)?;
34839            }
34840            if let Some(where_) = &e.where_ {
34841                self.write_space();
34842                self.write_keyword("WHERE");
34843                self.write_space();
34844                self.generate_expression(where_)?;
34845            }
34846        }
34847        self.write(")");
34848        Ok(())
34849    }
34850
34851    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
34852    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
34853        if let Expression::Tuple(t) = expr {
34854            for (i, e) in t.expressions.iter().enumerate() {
34855                if i > 0 {
34856                    self.write(", ");
34857                }
34858                self.generate_expression(e)?;
34859            }
34860        } else {
34861            self.generate_expression(expr)?;
34862        }
34863        Ok(())
34864    }
34865
34866    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
34867        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
34868        if let Some(start) = &e.start {
34869            self.write_keyword("START WITH");
34870            self.write_space();
34871            self.generate_expression(start)?;
34872        }
34873        if let Some(increment) = &e.increment {
34874            self.write_space();
34875            self.write_keyword("INCREMENT BY");
34876            self.write_space();
34877            self.generate_expression(increment)?;
34878        }
34879        if let Some(minvalue) = &e.minvalue {
34880            self.write_space();
34881            self.write_keyword("MINVALUE");
34882            self.write_space();
34883            self.generate_expression(minvalue)?;
34884        }
34885        if let Some(maxvalue) = &e.maxvalue {
34886            self.write_space();
34887            self.write_keyword("MAXVALUE");
34888            self.write_space();
34889            self.generate_expression(maxvalue)?;
34890        }
34891        if let Some(cache) = &e.cache {
34892            self.write_space();
34893            self.write_keyword("CACHE");
34894            self.write_space();
34895            self.generate_expression(cache)?;
34896        }
34897        if let Some(owned) = &e.owned {
34898            self.write_space();
34899            self.write_keyword("OWNED BY");
34900            self.write_space();
34901            self.generate_expression(owned)?;
34902        }
34903        for opt in &e.options {
34904            self.write_space();
34905            self.generate_expression(opt)?;
34906        }
34907        Ok(())
34908    }
34909
34910    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34911        // [WITH] SERDEPROPERTIES (expressions)
34912        if e.with_.is_some() {
34913            self.write_keyword("WITH");
34914            self.write_space();
34915        }
34916        self.write_keyword("SERDEPROPERTIES");
34917        self.write(" (");
34918        for (i, expr) in e.expressions.iter().enumerate() {
34919            if i > 0 {
34920                self.write(", ");
34921            }
34922            // Generate key=value without spaces around =
34923            match expr {
34924                Expression::Eq(eq) => {
34925                    self.generate_expression(&eq.left)?;
34926                    self.write("=");
34927                    self.generate_expression(&eq.right)?;
34928                }
34929                _ => self.generate_expression(expr)?,
34930            }
34931        }
34932        self.write(")");
34933        Ok(())
34934    }
34935
34936    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34937        // @@[kind.]this
34938        self.write("@@");
34939        if let Some(kind) = &e.kind {
34940            self.write(kind);
34941            self.write(".");
34942        }
34943        self.generate_expression(&e.this)?;
34944        Ok(())
34945    }
34946
34947    fn generate_set(&mut self, e: &Set) -> Result<()> {
34948        // SET/UNSET [TAG] expressions
34949        if e.unset.is_some() {
34950            self.write_keyword("UNSET");
34951        } else {
34952            self.write_keyword("SET");
34953        }
34954        if e.tag.is_some() {
34955            self.write_space();
34956            self.write_keyword("TAG");
34957        }
34958        if !e.expressions.is_empty() {
34959            self.write_space();
34960            for (i, expr) in e.expressions.iter().enumerate() {
34961                if i > 0 {
34962                    self.write(", ");
34963                }
34964                self.generate_expression(expr)?;
34965            }
34966        }
34967        Ok(())
34968    }
34969
34970    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34971        // SET this or SETCONFIG this
34972        self.write_keyword("SET");
34973        self.write_space();
34974        self.generate_expression(&e.this)?;
34975        Ok(())
34976    }
34977
34978    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34979        // [kind] name = value
34980        if let Some(kind) = &e.kind {
34981            self.write_keyword(kind);
34982            self.write_space();
34983        }
34984        self.generate_expression(&e.name)?;
34985        self.write(" = ");
34986        self.generate_expression(&e.value)?;
34987        Ok(())
34988    }
34989
34990    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34991        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34992        if let Some(with_) = &e.with_ {
34993            self.generate_expression(with_)?;
34994            self.write_space();
34995        }
34996        self.generate_expression(&e.this)?;
34997        self.write_space();
34998        // kind should be UNION, INTERSECT, EXCEPT, etc.
34999        if let Some(kind) = &e.kind {
35000            self.write_keyword(kind);
35001        }
35002        if e.distinct {
35003            self.write_space();
35004            self.write_keyword("DISTINCT");
35005        } else {
35006            self.write_space();
35007            self.write_keyword("ALL");
35008        }
35009        if e.by_name.is_some() {
35010            self.write_space();
35011            self.write_keyword("BY NAME");
35012        }
35013        self.write_space();
35014        self.generate_expression(&e.expression)?;
35015        Ok(())
35016    }
35017
35018    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
35019        // SET or MULTISET
35020        if e.multi.is_some() {
35021            self.write_keyword("MULTISET");
35022        } else {
35023            self.write_keyword("SET");
35024        }
35025        Ok(())
35026    }
35027
35028    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
35029        // SETTINGS expressions
35030        self.write_keyword("SETTINGS");
35031        if self.config.pretty && e.expressions.len() > 1 {
35032            // Pretty print: each setting on its own line, indented
35033            self.indent_level += 1;
35034            for (i, expr) in e.expressions.iter().enumerate() {
35035                if i > 0 {
35036                    self.write(",");
35037                }
35038                self.write_newline();
35039                self.write_indent();
35040                self.generate_expression(expr)?;
35041            }
35042            self.indent_level -= 1;
35043        } else {
35044            self.write_space();
35045            for (i, expr) in e.expressions.iter().enumerate() {
35046                if i > 0 {
35047                    self.write(", ");
35048                }
35049                self.generate_expression(expr)?;
35050            }
35051        }
35052        Ok(())
35053    }
35054
35055    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
35056        // SHARING = this
35057        self.write_keyword("SHARING");
35058        if let Some(this) = &e.this {
35059            self.write(" = ");
35060            self.generate_expression(this)?;
35061        }
35062        Ok(())
35063    }
35064
35065    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
35066        // Python array slicing: begin:end:step
35067        if let Some(begin) = &e.this {
35068            self.generate_expression(begin)?;
35069        }
35070        self.write(":");
35071        if let Some(end) = &e.expression {
35072            self.generate_expression(end)?;
35073        }
35074        if let Some(step) = &e.step {
35075            self.write(":");
35076            self.generate_expression(step)?;
35077        }
35078        Ok(())
35079    }
35080
35081    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
35082        // SORT_ARRAY(this, asc)
35083        self.write_keyword("SORT_ARRAY");
35084        self.write("(");
35085        self.generate_expression(&e.this)?;
35086        if let Some(asc) = &e.asc {
35087            self.write(", ");
35088            self.generate_expression(asc)?;
35089        }
35090        self.write(")");
35091        Ok(())
35092    }
35093
35094    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
35095        // SORT BY expressions
35096        self.write_keyword("SORT BY");
35097        self.write_space();
35098        for (i, expr) in e.expressions.iter().enumerate() {
35099            if i > 0 {
35100                self.write(", ");
35101            }
35102            self.generate_ordered(expr)?;
35103        }
35104        Ok(())
35105    }
35106
35107    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
35108        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
35109        if e.compound.is_some() {
35110            self.write_keyword("COMPOUND");
35111            self.write_space();
35112        }
35113        self.write_keyword("SORTKEY");
35114        self.write("(");
35115        // If this is a Tuple, unwrap its contents to avoid double parentheses
35116        if let Expression::Tuple(t) = e.this.as_ref() {
35117            for (i, expr) in t.expressions.iter().enumerate() {
35118                if i > 0 {
35119                    self.write(", ");
35120                }
35121                self.generate_expression(expr)?;
35122            }
35123        } else {
35124            self.generate_expression(&e.this)?;
35125        }
35126        self.write(")");
35127        Ok(())
35128    }
35129
35130    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
35131        // SPLIT_PART(this, delimiter, part_index)
35132        self.write_keyword("SPLIT_PART");
35133        self.write("(");
35134        self.generate_expression(&e.this)?;
35135        if let Some(delimiter) = &e.delimiter {
35136            self.write(", ");
35137            self.generate_expression(delimiter)?;
35138        }
35139        if let Some(part_index) = &e.part_index {
35140            self.write(", ");
35141            self.generate_expression(part_index)?;
35142        }
35143        self.write(")");
35144        Ok(())
35145    }
35146
35147    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
35148        // READS SQL DATA or MODIFIES SQL DATA, etc.
35149        self.generate_expression(&e.this)?;
35150        Ok(())
35151    }
35152
35153    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
35154        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
35155        self.write_keyword("SQL SECURITY");
35156        self.write_space();
35157        self.generate_expression(&e.this)?;
35158        Ok(())
35159    }
35160
35161    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
35162        // ST_DISTANCE(this, expression, [use_spheroid])
35163        self.write_keyword("ST_DISTANCE");
35164        self.write("(");
35165        self.generate_expression(&e.this)?;
35166        self.write(", ");
35167        self.generate_expression(&e.expression)?;
35168        if let Some(use_spheroid) = &e.use_spheroid {
35169            self.write(", ");
35170            self.generate_expression(use_spheroid)?;
35171        }
35172        self.write(")");
35173        Ok(())
35174    }
35175
35176    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
35177        // ST_POINT(this, expression)
35178        self.write_keyword("ST_POINT");
35179        self.write("(");
35180        self.generate_expression(&e.this)?;
35181        self.write(", ");
35182        self.generate_expression(&e.expression)?;
35183        self.write(")");
35184        Ok(())
35185    }
35186
35187    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
35188        // IMMUTABLE, STABLE, VOLATILE
35189        self.generate_expression(&e.this)?;
35190        Ok(())
35191    }
35192
35193    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
35194        // STANDARD_HASH(this, [expression])
35195        self.write_keyword("STANDARD_HASH");
35196        self.write("(");
35197        self.generate_expression(&e.this)?;
35198        if let Some(expression) = &e.expression {
35199            self.write(", ");
35200            self.generate_expression(expression)?;
35201        }
35202        self.write(")");
35203        Ok(())
35204    }
35205
35206    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
35207        // STORED BY this
35208        self.write_keyword("STORED BY");
35209        self.write_space();
35210        self.generate_expression(&e.this)?;
35211        Ok(())
35212    }
35213
35214    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
35215        // STRPOS(this, substr) or STRPOS(this, substr, position)
35216        // Different dialects have different function names
35217        use crate::dialects::DialectType;
35218        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35219            // Snowflake: CHARINDEX(substr, str[, position])
35220            self.write_keyword("CHARINDEX");
35221            self.write("(");
35222            if let Some(substr) = &e.substr {
35223                self.generate_expression(substr)?;
35224                self.write(", ");
35225            }
35226            self.generate_expression(&e.this)?;
35227            if let Some(position) = &e.position {
35228                self.write(", ");
35229                self.generate_expression(position)?;
35230            }
35231            self.write(")");
35232        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35233            self.write_keyword("POSITION");
35234            self.write("(");
35235            self.generate_expression(&e.this)?;
35236            if let Some(substr) = &e.substr {
35237                self.write(", ");
35238                self.generate_expression(substr)?;
35239            }
35240            if let Some(position) = &e.position {
35241                self.write(", ");
35242                self.generate_expression(position)?;
35243            }
35244            if let Some(occurrence) = &e.occurrence {
35245                self.write(", ");
35246                self.generate_expression(occurrence)?;
35247            }
35248            self.write(")");
35249        } else if matches!(
35250            self.config.dialect,
35251            Some(DialectType::SQLite)
35252                | Some(DialectType::Oracle)
35253                | Some(DialectType::BigQuery)
35254                | Some(DialectType::Teradata)
35255        ) {
35256            self.write_keyword("INSTR");
35257            self.write("(");
35258            self.generate_expression(&e.this)?;
35259            if let Some(substr) = &e.substr {
35260                self.write(", ");
35261                self.generate_expression(substr)?;
35262            }
35263            if let Some(position) = &e.position {
35264                self.write(", ");
35265                self.generate_expression(position)?;
35266            } else if e.occurrence.is_some() {
35267                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
35268                // Default start position is 1
35269                self.write(", 1");
35270            }
35271            if let Some(occurrence) = &e.occurrence {
35272                self.write(", ");
35273                self.generate_expression(occurrence)?;
35274            }
35275            self.write(")");
35276        } else if matches!(
35277            self.config.dialect,
35278            Some(DialectType::MySQL)
35279                | Some(DialectType::SingleStore)
35280                | Some(DialectType::Doris)
35281                | Some(DialectType::StarRocks)
35282                | Some(DialectType::Hive)
35283                | Some(DialectType::Spark)
35284                | Some(DialectType::Databricks)
35285        ) {
35286            // LOCATE(substr, str[, position]) - substr first
35287            self.write_keyword("LOCATE");
35288            self.write("(");
35289            if let Some(substr) = &e.substr {
35290                self.generate_expression(substr)?;
35291                self.write(", ");
35292            }
35293            self.generate_expression(&e.this)?;
35294            if let Some(position) = &e.position {
35295                self.write(", ");
35296                self.generate_expression(position)?;
35297            }
35298            self.write(")");
35299        } else if matches!(
35300            self.config.dialect,
35301            Some(DialectType::TSQL) | Some(DialectType::Fabric)
35302        ) {
35303            // CHARINDEX(substr, str[, position])
35304            self.write_keyword("CHARINDEX");
35305            self.write("(");
35306            if let Some(substr) = &e.substr {
35307                self.generate_expression(substr)?;
35308                self.write(", ");
35309            }
35310            self.generate_expression(&e.this)?;
35311            if let Some(position) = &e.position {
35312                self.write(", ");
35313                self.generate_expression(position)?;
35314            }
35315            self.write(")");
35316        } else if matches!(
35317            self.config.dialect,
35318            Some(DialectType::PostgreSQL)
35319                | Some(DialectType::Materialize)
35320                | Some(DialectType::RisingWave)
35321                | Some(DialectType::Redshift)
35322        ) {
35323            // POSITION(substr IN str) syntax
35324            self.write_keyword("POSITION");
35325            self.write("(");
35326            if let Some(substr) = &e.substr {
35327                self.generate_expression(substr)?;
35328                self.write(" IN ");
35329            }
35330            self.generate_expression(&e.this)?;
35331            self.write(")");
35332        } else {
35333            self.write_keyword("STRPOS");
35334            self.write("(");
35335            self.generate_expression(&e.this)?;
35336            if let Some(substr) = &e.substr {
35337                self.write(", ");
35338                self.generate_expression(substr)?;
35339            }
35340            if let Some(position) = &e.position {
35341                self.write(", ");
35342                self.generate_expression(position)?;
35343            }
35344            if let Some(occurrence) = &e.occurrence {
35345                self.write(", ");
35346                self.generate_expression(occurrence)?;
35347            }
35348            self.write(")");
35349        }
35350        Ok(())
35351    }
35352
35353    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
35354        match self.config.dialect {
35355            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
35356                // TO_DATE(this, java_format)
35357                self.write_keyword("TO_DATE");
35358                self.write("(");
35359                self.generate_expression(&e.this)?;
35360                if let Some(format) = &e.format {
35361                    self.write(", '");
35362                    self.write(&Self::strftime_to_java_format(format));
35363                    self.write("'");
35364                }
35365                self.write(")");
35366            }
35367            Some(DialectType::DuckDB) => {
35368                // CAST(STRPTIME(this, format) AS DATE)
35369                self.write_keyword("CAST");
35370                self.write("(");
35371                self.write_keyword("STRPTIME");
35372                self.write("(");
35373                self.generate_expression(&e.this)?;
35374                if let Some(format) = &e.format {
35375                    self.write(", '");
35376                    self.write(format);
35377                    self.write("'");
35378                }
35379                self.write(")");
35380                self.write_keyword(" AS ");
35381                self.write_keyword("DATE");
35382                self.write(")");
35383            }
35384            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
35385                // TO_DATE(this, pg_format)
35386                self.write_keyword("TO_DATE");
35387                self.write("(");
35388                self.generate_expression(&e.this)?;
35389                if let Some(format) = &e.format {
35390                    self.write(", '");
35391                    self.write(&Self::strftime_to_postgres_format(format));
35392                    self.write("'");
35393                }
35394                self.write(")");
35395            }
35396            Some(DialectType::BigQuery) => {
35397                // PARSE_DATE(format, this) - note: format comes first for BigQuery
35398                self.write_keyword("PARSE_DATE");
35399                self.write("(");
35400                if let Some(format) = &e.format {
35401                    self.write("'");
35402                    self.write(format);
35403                    self.write("'");
35404                    self.write(", ");
35405                }
35406                self.generate_expression(&e.this)?;
35407                self.write(")");
35408            }
35409            Some(DialectType::Teradata) => {
35410                // CAST(this AS DATE FORMAT 'teradata_fmt')
35411                self.write_keyword("CAST");
35412                self.write("(");
35413                self.generate_expression(&e.this)?;
35414                self.write_keyword(" AS ");
35415                self.write_keyword("DATE");
35416                if let Some(format) = &e.format {
35417                    self.write_keyword(" FORMAT ");
35418                    self.write("'");
35419                    self.write(&Self::strftime_to_teradata_format(format));
35420                    self.write("'");
35421                }
35422                self.write(")");
35423            }
35424            _ => {
35425                // STR_TO_DATE(this, format) - MySQL default
35426                self.write_keyword("STR_TO_DATE");
35427                self.write("(");
35428                self.generate_expression(&e.this)?;
35429                if let Some(format) = &e.format {
35430                    self.write(", '");
35431                    self.write(format);
35432                    self.write("'");
35433                }
35434                self.write(")");
35435            }
35436        }
35437        Ok(())
35438    }
35439
35440    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
35441    fn strftime_to_teradata_format(fmt: &str) -> String {
35442        let mut result = String::with_capacity(fmt.len() * 2);
35443        let bytes = fmt.as_bytes();
35444        let len = bytes.len();
35445        let mut i = 0;
35446        while i < len {
35447            if bytes[i] == b'%' && i + 1 < len {
35448                let replacement = match bytes[i + 1] {
35449                    b'Y' => "YYYY",
35450                    b'y' => "YY",
35451                    b'm' => "MM",
35452                    b'B' => "MMMM",
35453                    b'b' => "MMM",
35454                    b'd' => "DD",
35455                    b'j' => "DDD",
35456                    b'H' => "HH",
35457                    b'M' => "MI",
35458                    b'S' => "SS",
35459                    b'f' => "SSSSSS",
35460                    b'A' => "EEEE",
35461                    b'a' => "EEE",
35462                    _ => {
35463                        result.push('%');
35464                        i += 1;
35465                        continue;
35466                    }
35467                };
35468                result.push_str(replacement);
35469                i += 2;
35470            } else {
35471                result.push(bytes[i] as char);
35472                i += 1;
35473            }
35474        }
35475        result
35476    }
35477
35478    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35479    /// Public static version for use by other modules
35480    pub fn strftime_to_java_format_static(fmt: &str) -> String {
35481        Self::strftime_to_java_format(fmt)
35482    }
35483
35484    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35485    fn strftime_to_java_format(fmt: &str) -> String {
35486        let mut result = String::with_capacity(fmt.len() * 2);
35487        let bytes = fmt.as_bytes();
35488        let len = bytes.len();
35489        let mut i = 0;
35490        while i < len {
35491            if bytes[i] == b'%' && i + 1 < len {
35492                // Check for non-padded variants (%-X)
35493                if bytes[i + 1] == b'-' && i + 2 < len {
35494                    let replacement = match bytes[i + 2] {
35495                        b'd' => "d",
35496                        b'm' => "M",
35497                        b'H' => "H",
35498                        b'M' => "m",
35499                        b'S' => "s",
35500                        _ => {
35501                            result.push('%');
35502                            i += 1;
35503                            continue;
35504                        }
35505                    };
35506                    result.push_str(replacement);
35507                    i += 3;
35508                } else {
35509                    let replacement = match bytes[i + 1] {
35510                        b'Y' => "yyyy",
35511                        b'y' => "yy",
35512                        b'm' => "MM",
35513                        b'B' => "MMMM",
35514                        b'b' => "MMM",
35515                        b'd' => "dd",
35516                        b'j' => "DDD",
35517                        b'H' => "HH",
35518                        b'M' => "mm",
35519                        b'S' => "ss",
35520                        b'f' => "SSSSSS",
35521                        b'A' => "EEEE",
35522                        b'a' => "EEE",
35523                        _ => {
35524                            result.push('%');
35525                            i += 1;
35526                            continue;
35527                        }
35528                    };
35529                    result.push_str(replacement);
35530                    i += 2;
35531                }
35532            } else {
35533                result.push(bytes[i] as char);
35534                i += 1;
35535            }
35536        }
35537        result
35538    }
35539
35540    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35541    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35542    fn strftime_to_tsql_format(fmt: &str) -> String {
35543        let mut result = String::with_capacity(fmt.len() * 2);
35544        let bytes = fmt.as_bytes();
35545        let len = bytes.len();
35546        let mut i = 0;
35547        while i < len {
35548            if bytes[i] == b'%' && i + 1 < len {
35549                // Check for non-padded variants (%-X)
35550                if bytes[i + 1] == b'-' && i + 2 < len {
35551                    let replacement = match bytes[i + 2] {
35552                        b'd' => "d",
35553                        b'm' => "M",
35554                        b'H' => "H",
35555                        b'M' => "m",
35556                        b'S' => "s",
35557                        _ => {
35558                            result.push('%');
35559                            i += 1;
35560                            continue;
35561                        }
35562                    };
35563                    result.push_str(replacement);
35564                    i += 3;
35565                } else {
35566                    let replacement = match bytes[i + 1] {
35567                        b'Y' => "yyyy",
35568                        b'y' => "yy",
35569                        b'm' => "MM",
35570                        b'B' => "MMMM",
35571                        b'b' => "MMM",
35572                        b'd' => "dd",
35573                        b'j' => "DDD",
35574                        b'H' => "HH",
35575                        b'M' => "mm",
35576                        b'S' => "ss",
35577                        b'f' => "ffffff",
35578                        b'A' => "dddd",
35579                        b'a' => "ddd",
35580                        _ => {
35581                            result.push('%');
35582                            i += 1;
35583                            continue;
35584                        }
35585                    };
35586                    result.push_str(replacement);
35587                    i += 2;
35588                }
35589            } else {
35590                result.push(bytes[i] as char);
35591                i += 1;
35592            }
35593        }
35594        result
35595    }
35596
35597    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35598    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35599    fn decompose_json_path(path: &str) -> Vec<String> {
35600        let mut parts = Vec::new();
35601        // Strip leading $ and optional .
35602        let path = if path.starts_with("$.") {
35603            &path[2..]
35604        } else if path.starts_with('$') {
35605            &path[1..]
35606        } else {
35607            path
35608        };
35609        if path.is_empty() {
35610            return parts;
35611        }
35612        let mut current = String::new();
35613        let chars: Vec<char> = path.chars().collect();
35614        let mut i = 0;
35615        while i < chars.len() {
35616            match chars[i] {
35617                '.' => {
35618                    if !current.is_empty() {
35619                        parts.push(current.clone());
35620                        current.clear();
35621                    }
35622                    i += 1;
35623                }
35624                '[' => {
35625                    if !current.is_empty() {
35626                        parts.push(current.clone());
35627                        current.clear();
35628                    }
35629                    i += 1;
35630                    // Read the content inside brackets
35631                    let mut bracket_content = String::new();
35632                    while i < chars.len() && chars[i] != ']' {
35633                        // Skip quotes inside brackets
35634                        if chars[i] == '"' || chars[i] == '\'' {
35635                            let quote = chars[i];
35636                            i += 1;
35637                            while i < chars.len() && chars[i] != quote {
35638                                bracket_content.push(chars[i]);
35639                                i += 1;
35640                            }
35641                            if i < chars.len() {
35642                                i += 1;
35643                            } // skip closing quote
35644                        } else {
35645                            bracket_content.push(chars[i]);
35646                            i += 1;
35647                        }
35648                    }
35649                    if i < chars.len() {
35650                        i += 1;
35651                    } // skip ]
35652                      // Skip wildcard [*] - don't add as a part
35653                    if bracket_content != "*" {
35654                        parts.push(bracket_content);
35655                    }
35656                }
35657                _ => {
35658                    current.push(chars[i]);
35659                    i += 1;
35660                }
35661            }
35662        }
35663        if !current.is_empty() {
35664            parts.push(current);
35665        }
35666        parts
35667    }
35668
35669    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35670    fn strftime_to_postgres_format(fmt: &str) -> String {
35671        let mut result = String::with_capacity(fmt.len() * 2);
35672        let bytes = fmt.as_bytes();
35673        let len = bytes.len();
35674        let mut i = 0;
35675        while i < len {
35676            if bytes[i] == b'%' && i + 1 < len {
35677                // Check for non-padded variants (%-X)
35678                if bytes[i + 1] == b'-' && i + 2 < len {
35679                    let replacement = match bytes[i + 2] {
35680                        b'd' => "FMDD",
35681                        b'm' => "FMMM",
35682                        b'H' => "FMHH24",
35683                        b'M' => "FMMI",
35684                        b'S' => "FMSS",
35685                        _ => {
35686                            result.push('%');
35687                            i += 1;
35688                            continue;
35689                        }
35690                    };
35691                    result.push_str(replacement);
35692                    i += 3;
35693                } else {
35694                    let replacement = match bytes[i + 1] {
35695                        b'Y' => "YYYY",
35696                        b'y' => "YY",
35697                        b'm' => "MM",
35698                        b'B' => "Month",
35699                        b'b' => "Mon",
35700                        b'd' => "DD",
35701                        b'j' => "DDD",
35702                        b'H' => "HH24",
35703                        b'M' => "MI",
35704                        b'S' => "SS",
35705                        b'f' => "US",
35706                        b'A' => "Day",
35707                        b'a' => "Dy",
35708                        _ => {
35709                            result.push('%');
35710                            i += 1;
35711                            continue;
35712                        }
35713                    };
35714                    result.push_str(replacement);
35715                    i += 2;
35716                }
35717            } else {
35718                result.push(bytes[i] as char);
35719                i += 1;
35720            }
35721        }
35722        result
35723    }
35724
35725    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35726    fn strftime_to_snowflake_format(fmt: &str) -> String {
35727        let mut result = String::with_capacity(fmt.len() * 2);
35728        let bytes = fmt.as_bytes();
35729        let len = bytes.len();
35730        let mut i = 0;
35731        while i < len {
35732            if bytes[i] == b'%' && i + 1 < len {
35733                // Check for non-padded variants (%-X)
35734                if bytes[i + 1] == b'-' && i + 2 < len {
35735                    let replacement = match bytes[i + 2] {
35736                        b'd' => "dd",
35737                        b'm' => "mm",
35738                        _ => {
35739                            result.push('%');
35740                            i += 1;
35741                            continue;
35742                        }
35743                    };
35744                    result.push_str(replacement);
35745                    i += 3;
35746                } else {
35747                    let replacement = match bytes[i + 1] {
35748                        b'Y' => "yyyy",
35749                        b'y' => "yy",
35750                        b'm' => "mm",
35751                        b'd' => "DD",
35752                        b'H' => "hh24",
35753                        b'M' => "mi",
35754                        b'S' => "ss",
35755                        b'f' => "ff",
35756                        _ => {
35757                            result.push('%');
35758                            i += 1;
35759                            continue;
35760                        }
35761                    };
35762                    result.push_str(replacement);
35763                    i += 2;
35764                }
35765            } else {
35766                result.push(bytes[i] as char);
35767                i += 1;
35768            }
35769        }
35770        result
35771    }
35772
35773    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35774        // STR_TO_MAP(this, pair_delim, key_value_delim)
35775        self.write_keyword("STR_TO_MAP");
35776        self.write("(");
35777        self.generate_expression(&e.this)?;
35778        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35779        let needs_defaults = matches!(
35780            self.config.dialect,
35781            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35782        );
35783        if let Some(pair_delim) = &e.pair_delim {
35784            self.write(", ");
35785            self.generate_expression(pair_delim)?;
35786        } else if needs_defaults {
35787            self.write(", ','");
35788        }
35789        if let Some(key_value_delim) = &e.key_value_delim {
35790            self.write(", ");
35791            self.generate_expression(key_value_delim)?;
35792        } else if needs_defaults {
35793            self.write(", ':'");
35794        }
35795        self.write(")");
35796        Ok(())
35797    }
35798
35799    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35800        // Detect format style: strftime (starts with %) vs Snowflake/Java
35801        let is_strftime = e.format.contains('%');
35802        // Helper: get strftime format from whatever style is stored
35803        let to_strftime = |f: &str| -> String {
35804            if is_strftime {
35805                f.to_string()
35806            } else {
35807                Self::snowflake_format_to_strftime(f)
35808            }
35809        };
35810        // Helper: get Java format
35811        let to_java = |f: &str| -> String {
35812            if is_strftime {
35813                Self::strftime_to_java_format(f)
35814            } else {
35815                Self::snowflake_format_to_spark(f)
35816            }
35817        };
35818        // Helper: get PG format
35819        let to_pg = |f: &str| -> String {
35820            if is_strftime {
35821                Self::strftime_to_postgres_format(f)
35822            } else {
35823                Self::convert_strptime_to_postgres_format(f)
35824            }
35825        };
35826
35827        match self.config.dialect {
35828            Some(DialectType::Exasol) => {
35829                self.write_keyword("TO_DATE");
35830                self.write("(");
35831                self.generate_expression(&e.this)?;
35832                self.write(", '");
35833                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35834                self.write("'");
35835                self.write(")");
35836            }
35837            Some(DialectType::BigQuery) => {
35838                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35839                let fmt = to_strftime(&e.format);
35840                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35841                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35842                self.write_keyword("PARSE_TIMESTAMP");
35843                self.write("('");
35844                self.write(&fmt);
35845                self.write("', ");
35846                self.generate_expression(&e.this)?;
35847                self.write(")");
35848            }
35849            Some(DialectType::Hive) => {
35850                // Hive: CAST(x AS TIMESTAMP) for simple date formats
35851                // Check both the raw format and the converted format (in case it's already Java)
35852                let java_fmt = to_java(&e.format);
35853                if java_fmt == "yyyy-MM-dd HH:mm:ss"
35854                    || java_fmt == "yyyy-MM-dd"
35855                    || e.format == "yyyy-MM-dd HH:mm:ss"
35856                    || e.format == "yyyy-MM-dd"
35857                {
35858                    self.write_keyword("CAST");
35859                    self.write("(");
35860                    self.generate_expression(&e.this)?;
35861                    self.write(" ");
35862                    self.write_keyword("AS TIMESTAMP");
35863                    self.write(")");
35864                } else {
35865                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
35866                    self.write_keyword("CAST");
35867                    self.write("(");
35868                    self.write_keyword("FROM_UNIXTIME");
35869                    self.write("(");
35870                    self.write_keyword("UNIX_TIMESTAMP");
35871                    self.write("(");
35872                    self.generate_expression(&e.this)?;
35873                    self.write(", '");
35874                    self.write(&java_fmt);
35875                    self.write("')");
35876                    self.write(") ");
35877                    self.write_keyword("AS TIMESTAMP");
35878                    self.write(")");
35879                }
35880            }
35881            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35882                // Spark: TO_TIMESTAMP(value, java_format)
35883                let java_fmt = to_java(&e.format);
35884                self.write_keyword("TO_TIMESTAMP");
35885                self.write("(");
35886                self.generate_expression(&e.this)?;
35887                self.write(", '");
35888                self.write(&java_fmt);
35889                self.write("')");
35890            }
35891            Some(DialectType::MySQL) => {
35892                // MySQL: STR_TO_DATE(value, format)
35893                let mut fmt = to_strftime(&e.format);
35894                // MySQL uses %e for non-padded day, %T for %H:%M:%S
35895                fmt = fmt.replace("%-d", "%e");
35896                fmt = fmt.replace("%-m", "%c");
35897                fmt = fmt.replace("%H:%M:%S", "%T");
35898                self.write_keyword("STR_TO_DATE");
35899                self.write("(");
35900                self.generate_expression(&e.this)?;
35901                self.write(", '");
35902                self.write(&fmt);
35903                self.write("')");
35904            }
35905            Some(DialectType::Drill) => {
35906                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35907                let java_fmt = to_java(&e.format);
35908                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35909                let java_fmt = java_fmt.replace('T', "''T''");
35910                self.write_keyword("TO_TIMESTAMP");
35911                self.write("(");
35912                self.generate_expression(&e.this)?;
35913                self.write(", '");
35914                self.write(&java_fmt);
35915                self.write("')");
35916            }
35917            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35918                // Presto: DATE_PARSE(value, strftime_format)
35919                let mut fmt = to_strftime(&e.format);
35920                // Presto uses %e for non-padded day, %T for %H:%M:%S
35921                fmt = fmt.replace("%-d", "%e");
35922                fmt = fmt.replace("%-m", "%c");
35923                fmt = fmt.replace("%H:%M:%S", "%T");
35924                self.write_keyword("DATE_PARSE");
35925                self.write("(");
35926                self.generate_expression(&e.this)?;
35927                self.write(", '");
35928                self.write(&fmt);
35929                self.write("')");
35930            }
35931            Some(DialectType::DuckDB) => {
35932                // DuckDB: STRPTIME(value, strftime_format)
35933                let fmt = to_strftime(&e.format);
35934                self.write_keyword("STRPTIME");
35935                self.write("(");
35936                self.generate_expression(&e.this)?;
35937                self.write(", '");
35938                self.write(&fmt);
35939                self.write("')");
35940            }
35941            Some(DialectType::PostgreSQL)
35942            | Some(DialectType::Redshift)
35943            | Some(DialectType::Materialize) => {
35944                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35945                let pg_fmt = to_pg(&e.format);
35946                self.write_keyword("TO_TIMESTAMP");
35947                self.write("(");
35948                self.generate_expression(&e.this)?;
35949                self.write(", '");
35950                self.write(&pg_fmt);
35951                self.write("')");
35952            }
35953            Some(DialectType::Oracle) => {
35954                // Oracle: TO_TIMESTAMP(value, pg_format)
35955                let pg_fmt = to_pg(&e.format);
35956                self.write_keyword("TO_TIMESTAMP");
35957                self.write("(");
35958                self.generate_expression(&e.this)?;
35959                self.write(", '");
35960                self.write(&pg_fmt);
35961                self.write("')");
35962            }
35963            Some(DialectType::Snowflake) => {
35964                // Snowflake: TO_TIMESTAMP(value, format) - native format
35965                self.write_keyword("TO_TIMESTAMP");
35966                self.write("(");
35967                self.generate_expression(&e.this)?;
35968                self.write(", '");
35969                self.write(&e.format);
35970                self.write("')");
35971            }
35972            _ => {
35973                // Default: STR_TO_TIME(this, format)
35974                self.write_keyword("STR_TO_TIME");
35975                self.write("(");
35976                self.generate_expression(&e.this)?;
35977                self.write(", '");
35978                self.write(&e.format);
35979                self.write("'");
35980                self.write(")");
35981            }
35982        }
35983        Ok(())
35984    }
35985
35986    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35987    fn snowflake_format_to_strftime(format: &str) -> String {
35988        let mut result = String::new();
35989        let chars: Vec<char> = format.chars().collect();
35990        let mut i = 0;
35991        while i < chars.len() {
35992            let remaining = &format[i..];
35993            if remaining.starts_with("yyyy") {
35994                result.push_str("%Y");
35995                i += 4;
35996            } else if remaining.starts_with("yy") {
35997                result.push_str("%y");
35998                i += 2;
35999            } else if remaining.starts_with("mmmm") {
36000                result.push_str("%B"); // full month name
36001                i += 4;
36002            } else if remaining.starts_with("mon") {
36003                result.push_str("%b"); // abbreviated month
36004                i += 3;
36005            } else if remaining.starts_with("mm") {
36006                result.push_str("%m");
36007                i += 2;
36008            } else if remaining.starts_with("DD") {
36009                result.push_str("%d");
36010                i += 2;
36011            } else if remaining.starts_with("dy") {
36012                result.push_str("%a"); // abbreviated day name
36013                i += 2;
36014            } else if remaining.starts_with("hh24") {
36015                result.push_str("%H");
36016                i += 4;
36017            } else if remaining.starts_with("hh12") {
36018                result.push_str("%I");
36019                i += 4;
36020            } else if remaining.starts_with("hh") {
36021                result.push_str("%H");
36022                i += 2;
36023            } else if remaining.starts_with("mi") {
36024                result.push_str("%M");
36025                i += 2;
36026            } else if remaining.starts_with("ss") {
36027                result.push_str("%S");
36028                i += 2;
36029            } else if remaining.starts_with("ff") {
36030                // Fractional seconds
36031                result.push_str("%f");
36032                i += 2;
36033                // Skip digits after ff (ff3, ff6, ff9)
36034                while i < chars.len() && chars[i].is_ascii_digit() {
36035                    i += 1;
36036                }
36037            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36038                result.push_str("%p");
36039                i += 2;
36040            } else if remaining.starts_with("tz") {
36041                result.push_str("%Z");
36042                i += 2;
36043            } else {
36044                result.push(chars[i]);
36045                i += 1;
36046            }
36047        }
36048        result
36049    }
36050
36051    /// Convert Snowflake normalized format to Spark format (Java-style)
36052    fn snowflake_format_to_spark(format: &str) -> String {
36053        let mut result = String::new();
36054        let chars: Vec<char> = format.chars().collect();
36055        let mut i = 0;
36056        while i < chars.len() {
36057            let remaining = &format[i..];
36058            if remaining.starts_with("yyyy") {
36059                result.push_str("yyyy");
36060                i += 4;
36061            } else if remaining.starts_with("yy") {
36062                result.push_str("yy");
36063                i += 2;
36064            } else if remaining.starts_with("mmmm") {
36065                result.push_str("MMMM"); // full month name
36066                i += 4;
36067            } else if remaining.starts_with("mon") {
36068                result.push_str("MMM"); // abbreviated month
36069                i += 3;
36070            } else if remaining.starts_with("mm") {
36071                result.push_str("MM");
36072                i += 2;
36073            } else if remaining.starts_with("DD") {
36074                result.push_str("dd");
36075                i += 2;
36076            } else if remaining.starts_with("dy") {
36077                result.push_str("EEE"); // abbreviated day name
36078                i += 2;
36079            } else if remaining.starts_with("hh24") {
36080                result.push_str("HH");
36081                i += 4;
36082            } else if remaining.starts_with("hh12") {
36083                result.push_str("hh");
36084                i += 4;
36085            } else if remaining.starts_with("hh") {
36086                result.push_str("HH");
36087                i += 2;
36088            } else if remaining.starts_with("mi") {
36089                result.push_str("mm");
36090                i += 2;
36091            } else if remaining.starts_with("ss") {
36092                result.push_str("ss");
36093                i += 2;
36094            } else if remaining.starts_with("ff") {
36095                result.push_str("SSS"); // milliseconds
36096                i += 2;
36097                // Skip digits after ff
36098                while i < chars.len() && chars[i].is_ascii_digit() {
36099                    i += 1;
36100                }
36101            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36102                result.push_str("a");
36103                i += 2;
36104            } else if remaining.starts_with("tz") {
36105                result.push_str("z");
36106                i += 2;
36107            } else {
36108                result.push(chars[i]);
36109                i += 1;
36110            }
36111        }
36112        result
36113    }
36114
36115    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
36116        match self.config.dialect {
36117            Some(DialectType::DuckDB) => {
36118                // DuckDB: EPOCH(STRPTIME(value, format))
36119                self.write_keyword("EPOCH");
36120                self.write("(");
36121                self.write_keyword("STRPTIME");
36122                self.write("(");
36123                if let Some(this) = &e.this {
36124                    self.generate_expression(this)?;
36125                }
36126                if let Some(format) = &e.format {
36127                    self.write(", '");
36128                    self.write(format);
36129                    self.write("'");
36130                }
36131                self.write("))");
36132            }
36133            Some(DialectType::Hive) => {
36134                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
36135                self.write_keyword("UNIX_TIMESTAMP");
36136                self.write("(");
36137                if let Some(this) = &e.this {
36138                    self.generate_expression(this)?;
36139                }
36140                if let Some(format) = &e.format {
36141                    let java_fmt = Self::strftime_to_java_format(format);
36142                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
36143                        self.write(", '");
36144                        self.write(&java_fmt);
36145                        self.write("'");
36146                    }
36147                }
36148                self.write(")");
36149            }
36150            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36151                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
36152                self.write_keyword("UNIX_TIMESTAMP");
36153                self.write("(");
36154                if let Some(this) = &e.this {
36155                    self.generate_expression(this)?;
36156                }
36157                if let Some(format) = &e.format {
36158                    self.write(", '");
36159                    self.write(format);
36160                    self.write("'");
36161                }
36162                self.write(")");
36163            }
36164            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36165                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
36166                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
36167                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
36168                let java_fmt = Self::strftime_to_java_format(c_fmt);
36169                self.write_keyword("TO_UNIXTIME");
36170                self.write("(");
36171                self.write_keyword("COALESCE");
36172                self.write("(");
36173                self.write_keyword("TRY");
36174                self.write("(");
36175                self.write_keyword("DATE_PARSE");
36176                self.write("(");
36177                self.write_keyword("CAST");
36178                self.write("(");
36179                if let Some(this) = &e.this {
36180                    self.generate_expression(this)?;
36181                }
36182                self.write(" ");
36183                self.write_keyword("AS VARCHAR");
36184                self.write("), '");
36185                self.write(c_fmt);
36186                self.write("')), ");
36187                self.write_keyword("PARSE_DATETIME");
36188                self.write("(");
36189                self.write_keyword("DATE_FORMAT");
36190                self.write("(");
36191                self.write_keyword("CAST");
36192                self.write("(");
36193                if let Some(this) = &e.this {
36194                    self.generate_expression(this)?;
36195                }
36196                self.write(" ");
36197                self.write_keyword("AS TIMESTAMP");
36198                self.write("), '");
36199                self.write(c_fmt);
36200                self.write("'), '");
36201                self.write(&java_fmt);
36202                self.write("')))");
36203            }
36204            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36205                // Spark: UNIX_TIMESTAMP(value, java_format)
36206                self.write_keyword("UNIX_TIMESTAMP");
36207                self.write("(");
36208                if let Some(this) = &e.this {
36209                    self.generate_expression(this)?;
36210                }
36211                if let Some(format) = &e.format {
36212                    let java_fmt = Self::strftime_to_java_format(format);
36213                    self.write(", '");
36214                    self.write(&java_fmt);
36215                    self.write("'");
36216                }
36217                self.write(")");
36218            }
36219            _ => {
36220                // Default: STR_TO_UNIX(this, format)
36221                self.write_keyword("STR_TO_UNIX");
36222                self.write("(");
36223                if let Some(this) = &e.this {
36224                    self.generate_expression(this)?;
36225                }
36226                if let Some(format) = &e.format {
36227                    self.write(", '");
36228                    self.write(format);
36229                    self.write("'");
36230                }
36231                self.write(")");
36232            }
36233        }
36234        Ok(())
36235    }
36236
36237    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
36238        // STRING_TO_ARRAY(this, delimiter, null_string)
36239        self.write_keyword("STRING_TO_ARRAY");
36240        self.write("(");
36241        self.generate_expression(&e.this)?;
36242        if let Some(expression) = &e.expression {
36243            self.write(", ");
36244            self.generate_expression(expression)?;
36245        }
36246        if let Some(null_val) = &e.null {
36247            self.write(", ");
36248            self.generate_expression(null_val)?;
36249        }
36250        self.write(")");
36251        Ok(())
36252    }
36253
36254    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
36255        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36256            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
36257            self.write_keyword("OBJECT_CONSTRUCT");
36258            self.write("(");
36259            for (i, (name, expr)) in e.fields.iter().enumerate() {
36260                if i > 0 {
36261                    self.write(", ");
36262                }
36263                if let Some(name) = name {
36264                    self.write("'");
36265                    self.write(name);
36266                    self.write("'");
36267                    self.write(", ");
36268                } else {
36269                    self.write("'_");
36270                    self.write(&i.to_string());
36271                    self.write("'");
36272                    self.write(", ");
36273                }
36274                self.generate_expression(expr)?;
36275            }
36276            self.write(")");
36277        } else if self.config.struct_curly_brace_notation {
36278            // DuckDB-style: {'key': value, ...}
36279            self.write("{");
36280            for (i, (name, expr)) in e.fields.iter().enumerate() {
36281                if i > 0 {
36282                    self.write(", ");
36283                }
36284                if let Some(name) = name {
36285                    // Quote the key as a string literal
36286                    self.write("'");
36287                    self.write(name);
36288                    self.write("'");
36289                    self.write(": ");
36290                } else {
36291                    // Unnamed field: use positional key
36292                    self.write("'_");
36293                    self.write(&i.to_string());
36294                    self.write("'");
36295                    self.write(": ");
36296                }
36297                self.generate_expression(expr)?;
36298            }
36299            self.write("}");
36300        } else {
36301            // Standard SQL struct notation
36302            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
36303            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
36304            let value_as_name = matches!(
36305                self.config.dialect,
36306                Some(DialectType::BigQuery)
36307                    | Some(DialectType::Spark)
36308                    | Some(DialectType::Databricks)
36309                    | Some(DialectType::Hive)
36310            );
36311            self.write_keyword("STRUCT");
36312            self.write("(");
36313            for (i, (name, expr)) in e.fields.iter().enumerate() {
36314                if i > 0 {
36315                    self.write(", ");
36316                }
36317                if let Some(name) = name {
36318                    if value_as_name {
36319                        // STRUCT(value AS name)
36320                        self.generate_expression(expr)?;
36321                        self.write_space();
36322                        self.write_keyword("AS");
36323                        self.write_space();
36324                        // Quote name if it contains spaces or special chars
36325                        let needs_quoting = name.contains(' ') || name.contains('-');
36326                        if needs_quoting {
36327                            if matches!(
36328                                self.config.dialect,
36329                                Some(DialectType::Spark)
36330                                    | Some(DialectType::Databricks)
36331                                    | Some(DialectType::Hive)
36332                            ) {
36333                                self.write("`");
36334                                self.write(name);
36335                                self.write("`");
36336                            } else {
36337                                self.write(name);
36338                            }
36339                        } else {
36340                            self.write(name);
36341                        }
36342                    } else {
36343                        // STRUCT(name AS value)
36344                        self.write(name);
36345                        self.write_space();
36346                        self.write_keyword("AS");
36347                        self.write_space();
36348                        self.generate_expression(expr)?;
36349                    }
36350                } else {
36351                    self.generate_expression(expr)?;
36352                }
36353            }
36354            self.write(")");
36355        }
36356        Ok(())
36357    }
36358
36359    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
36360        // STUFF(this, start, length, expression)
36361        self.write_keyword("STUFF");
36362        self.write("(");
36363        self.generate_expression(&e.this)?;
36364        if let Some(start) = &e.start {
36365            self.write(", ");
36366            self.generate_expression(start)?;
36367        }
36368        if let Some(length) = e.length {
36369            self.write(", ");
36370            self.write(&length.to_string());
36371        }
36372        self.write(", ");
36373        self.generate_expression(&e.expression)?;
36374        self.write(")");
36375        Ok(())
36376    }
36377
36378    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
36379        // SUBSTRING_INDEX(this, delimiter, count)
36380        self.write_keyword("SUBSTRING_INDEX");
36381        self.write("(");
36382        self.generate_expression(&e.this)?;
36383        if let Some(delimiter) = &e.delimiter {
36384            self.write(", ");
36385            self.generate_expression(delimiter)?;
36386        }
36387        if let Some(count) = &e.count {
36388            self.write(", ");
36389            self.generate_expression(count)?;
36390        }
36391        self.write(")");
36392        Ok(())
36393    }
36394
36395    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
36396        // SUMMARIZE [TABLE] this
36397        self.write_keyword("SUMMARIZE");
36398        if e.table.is_some() {
36399            self.write_space();
36400            self.write_keyword("TABLE");
36401        }
36402        self.write_space();
36403        self.generate_expression(&e.this)?;
36404        Ok(())
36405    }
36406
36407    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
36408        // SYSTIMESTAMP
36409        self.write_keyword("SYSTIMESTAMP");
36410        Ok(())
36411    }
36412
36413    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
36414        // alias (columns...)
36415        if let Some(this) = &e.this {
36416            self.generate_expression(this)?;
36417        }
36418        if !e.columns.is_empty() {
36419            self.write("(");
36420            for (i, col) in e.columns.iter().enumerate() {
36421                if i > 0 {
36422                    self.write(", ");
36423                }
36424                self.generate_expression(col)?;
36425            }
36426            self.write(")");
36427        }
36428        Ok(())
36429    }
36430
36431    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
36432        // TABLE(this) [AS alias]
36433        self.write_keyword("TABLE");
36434        self.write("(");
36435        self.generate_expression(&e.this)?;
36436        self.write(")");
36437        if let Some(alias) = &e.alias {
36438            self.write_space();
36439            self.write_keyword("AS");
36440            self.write_space();
36441            self.write(alias);
36442        }
36443        Ok(())
36444    }
36445
36446    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
36447        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
36448        self.write_keyword("ROWS FROM");
36449        self.write(" (");
36450        for (i, expr) in e.expressions.iter().enumerate() {
36451            if i > 0 {
36452                self.write(", ");
36453            }
36454            // Each expression is either:
36455            // - A plain function (no alias)
36456            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
36457            match expr {
36458                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
36459                    // First element is the function, second is the TableAlias
36460                    self.generate_expression(&tuple.expressions[0])?;
36461                    self.write_space();
36462                    self.write_keyword("AS");
36463                    self.write_space();
36464                    self.generate_expression(&tuple.expressions[1])?;
36465                }
36466                _ => {
36467                    self.generate_expression(expr)?;
36468                }
36469            }
36470        }
36471        self.write(")");
36472        if e.ordinality {
36473            self.write_space();
36474            self.write_keyword("WITH ORDINALITY");
36475        }
36476        if let Some(alias) = &e.alias {
36477            self.write_space();
36478            self.write_keyword("AS");
36479            self.write_space();
36480            self.generate_expression(alias)?;
36481        }
36482        Ok(())
36483    }
36484
36485    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
36486        use crate::dialects::DialectType;
36487
36488        // New wrapper pattern: expression + Sample struct
36489        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
36490            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
36491            if self.config.alias_post_tablesample {
36492                // Handle Subquery with alias and Alias wrapper
36493                if let Expression::Subquery(ref s) = **this {
36494                    if let Some(ref alias) = s.alias {
36495                        // Create a clone without alias for output
36496                        let mut subquery_no_alias = (**s).clone();
36497                        subquery_no_alias.alias = None;
36498                        subquery_no_alias.column_aliases = Vec::new();
36499                        self.generate_expression(&Expression::Subquery(Box::new(
36500                            subquery_no_alias,
36501                        )))?;
36502                        self.write_space();
36503                        self.write_keyword(self.config.tablesample_keywords);
36504                        self.generate_sample_body(sample)?;
36505                        if let Some(ref seed) = sample.seed {
36506                            self.write_space();
36507                            let use_seed = sample.use_seed_keyword
36508                                && !matches!(
36509                                    self.config.dialect,
36510                                    Some(crate::dialects::DialectType::Databricks)
36511                                        | Some(crate::dialects::DialectType::Spark)
36512                                );
36513                            if use_seed {
36514                                self.write_keyword("SEED");
36515                            } else {
36516                                self.write_keyword("REPEATABLE");
36517                            }
36518                            self.write(" (");
36519                            self.generate_expression(seed)?;
36520                            self.write(")");
36521                        }
36522                        self.write_space();
36523                        self.write_keyword("AS");
36524                        self.write_space();
36525                        self.generate_identifier(alias)?;
36526                        return Ok(());
36527                    }
36528                } else if let Expression::Alias(ref a) = **this {
36529                    // Output the base expression without alias
36530                    self.generate_expression(&a.this)?;
36531                    self.write_space();
36532                    self.write_keyword(self.config.tablesample_keywords);
36533                    self.generate_sample_body(sample)?;
36534                    if let Some(ref seed) = sample.seed {
36535                        self.write_space();
36536                        let use_seed = sample.use_seed_keyword
36537                            && !matches!(
36538                                self.config.dialect,
36539                                Some(crate::dialects::DialectType::Databricks)
36540                                    | Some(crate::dialects::DialectType::Spark)
36541                            );
36542                        if use_seed {
36543                            self.write_keyword("SEED");
36544                        } else {
36545                            self.write_keyword("REPEATABLE");
36546                        }
36547                        self.write(" (");
36548                        self.generate_expression(seed)?;
36549                        self.write(")");
36550                    }
36551                    // Output alias after TABLESAMPLE
36552                    self.write_space();
36553                    self.write_keyword("AS");
36554                    self.write_space();
36555                    self.generate_identifier(&a.alias)?;
36556                    return Ok(());
36557                }
36558            }
36559            // Default: generate wrapped expression first, then TABLESAMPLE
36560            self.generate_expression(this)?;
36561            self.write_space();
36562            self.write_keyword(self.config.tablesample_keywords);
36563            self.generate_sample_body(sample)?;
36564            // Seed for table-level sample
36565            if let Some(ref seed) = sample.seed {
36566                self.write_space();
36567                // Databricks uses REPEATABLE, not SEED
36568                let use_seed = sample.use_seed_keyword
36569                    && !matches!(
36570                        self.config.dialect,
36571                        Some(crate::dialects::DialectType::Databricks)
36572                            | Some(crate::dialects::DialectType::Spark)
36573                    );
36574                if use_seed {
36575                    self.write_keyword("SEED");
36576                } else {
36577                    self.write_keyword("REPEATABLE");
36578                }
36579                self.write(" (");
36580                self.generate_expression(seed)?;
36581                self.write(")");
36582            }
36583            return Ok(());
36584        }
36585
36586        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36587        self.write_keyword(self.config.tablesample_keywords);
36588        if let Some(method) = &e.method {
36589            self.write_space();
36590            self.write_keyword(method);
36591        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36592            // Snowflake defaults to BERNOULLI when no method is specified
36593            self.write_space();
36594            self.write_keyword("BERNOULLI");
36595        }
36596        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36597            self.write_space();
36598            self.write_keyword("BUCKET");
36599            self.write_space();
36600            self.generate_expression(numerator)?;
36601            self.write_space();
36602            self.write_keyword("OUT OF");
36603            self.write_space();
36604            self.generate_expression(denominator)?;
36605            if let Some(field) = &e.bucket_field {
36606                self.write_space();
36607                self.write_keyword("ON");
36608                self.write_space();
36609                self.generate_expression(field)?;
36610            }
36611        } else if !e.expressions.is_empty() {
36612            self.write(" (");
36613            for (i, expr) in e.expressions.iter().enumerate() {
36614                if i > 0 {
36615                    self.write(", ");
36616                }
36617                self.generate_expression(expr)?;
36618            }
36619            self.write(")");
36620        } else if let Some(percent) = &e.percent {
36621            self.write(" (");
36622            self.generate_expression(percent)?;
36623            self.write_space();
36624            self.write_keyword("PERCENT");
36625            self.write(")");
36626        }
36627        Ok(())
36628    }
36629
36630    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36631        // [prefix]this[postfix]
36632        if let Some(prefix) = &e.prefix {
36633            self.generate_expression(prefix)?;
36634        }
36635        if let Some(this) = &e.this {
36636            self.generate_expression(this)?;
36637        }
36638        if let Some(postfix) = &e.postfix {
36639            self.generate_expression(postfix)?;
36640        }
36641        Ok(())
36642    }
36643
36644    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36645        // TAG (expressions)
36646        self.write_keyword("TAG");
36647        self.write(" (");
36648        for (i, expr) in e.expressions.iter().enumerate() {
36649            if i > 0 {
36650                self.write(", ");
36651            }
36652            self.generate_expression(expr)?;
36653        }
36654        self.write(")");
36655        Ok(())
36656    }
36657
36658    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36659        // TEMPORARY or TEMP or [this] TEMPORARY
36660        if let Some(this) = &e.this {
36661            self.generate_expression(this)?;
36662            self.write_space();
36663        }
36664        self.write_keyword("TEMPORARY");
36665        Ok(())
36666    }
36667
36668    /// Generate a Time function expression
36669    /// For most dialects: TIME('value')
36670    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36671        // Standard: TIME(value)
36672        self.write_keyword("TIME");
36673        self.write("(");
36674        self.generate_expression(&e.this)?;
36675        self.write(")");
36676        Ok(())
36677    }
36678
36679    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36680        // TIME_ADD(this, expression, unit)
36681        self.write_keyword("TIME_ADD");
36682        self.write("(");
36683        self.generate_expression(&e.this)?;
36684        self.write(", ");
36685        self.generate_expression(&e.expression)?;
36686        if let Some(unit) = &e.unit {
36687            self.write(", ");
36688            self.write_keyword(unit);
36689        }
36690        self.write(")");
36691        Ok(())
36692    }
36693
36694    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36695        // TIME_DIFF(this, expression, unit)
36696        self.write_keyword("TIME_DIFF");
36697        self.write("(");
36698        self.generate_expression(&e.this)?;
36699        self.write(", ");
36700        self.generate_expression(&e.expression)?;
36701        if let Some(unit) = &e.unit {
36702            self.write(", ");
36703            self.write_keyword(unit);
36704        }
36705        self.write(")");
36706        Ok(())
36707    }
36708
36709    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36710        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36711        self.write_keyword("TIME_FROM_PARTS");
36712        self.write("(");
36713        let mut first = true;
36714        if let Some(hour) = &e.hour {
36715            self.generate_expression(hour)?;
36716            first = false;
36717        }
36718        if let Some(minute) = &e.min {
36719            if !first {
36720                self.write(", ");
36721            }
36722            self.generate_expression(minute)?;
36723            first = false;
36724        }
36725        if let Some(second) = &e.sec {
36726            if !first {
36727                self.write(", ");
36728            }
36729            self.generate_expression(second)?;
36730            first = false;
36731        }
36732        if let Some(ns) = &e.nano {
36733            if !first {
36734                self.write(", ");
36735            }
36736            self.generate_expression(ns)?;
36737        }
36738        self.write(")");
36739        Ok(())
36740    }
36741
36742    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36743        // TIME_SLICE(this, expression, unit)
36744        self.write_keyword("TIME_SLICE");
36745        self.write("(");
36746        self.generate_expression(&e.this)?;
36747        self.write(", ");
36748        self.generate_expression(&e.expression)?;
36749        self.write(", ");
36750        self.write_keyword(&e.unit);
36751        self.write(")");
36752        Ok(())
36753    }
36754
36755    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36756        // TIME_STR_TO_TIME(this)
36757        self.write_keyword("TIME_STR_TO_TIME");
36758        self.write("(");
36759        self.generate_expression(&e.this)?;
36760        self.write(")");
36761        Ok(())
36762    }
36763
36764    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36765        // TIME_SUB(this, expression, unit)
36766        self.write_keyword("TIME_SUB");
36767        self.write("(");
36768        self.generate_expression(&e.this)?;
36769        self.write(", ");
36770        self.generate_expression(&e.expression)?;
36771        if let Some(unit) = &e.unit {
36772            self.write(", ");
36773            self.write_keyword(unit);
36774        }
36775        self.write(")");
36776        Ok(())
36777    }
36778
36779    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36780        match self.config.dialect {
36781            Some(DialectType::Exasol) => {
36782                // Exasol uses TO_CHAR with Exasol-specific format
36783                self.write_keyword("TO_CHAR");
36784                self.write("(");
36785                self.generate_expression(&e.this)?;
36786                self.write(", '");
36787                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36788                self.write("'");
36789                self.write(")");
36790            }
36791            Some(DialectType::PostgreSQL)
36792            | Some(DialectType::Redshift)
36793            | Some(DialectType::Materialize) => {
36794                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36795                self.write_keyword("TO_CHAR");
36796                self.write("(");
36797                self.generate_expression(&e.this)?;
36798                self.write(", '");
36799                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36800                self.write("'");
36801                self.write(")");
36802            }
36803            Some(DialectType::Oracle) => {
36804                // Oracle uses TO_CHAR with PG-like format
36805                self.write_keyword("TO_CHAR");
36806                self.write("(");
36807                self.generate_expression(&e.this)?;
36808                self.write(", '");
36809                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36810                self.write("'");
36811                self.write(")");
36812            }
36813            Some(DialectType::Drill) => {
36814                // Drill: TO_CHAR with Java format
36815                self.write_keyword("TO_CHAR");
36816                self.write("(");
36817                self.generate_expression(&e.this)?;
36818                self.write(", '");
36819                self.write(&Self::strftime_to_java_format(&e.format));
36820                self.write("'");
36821                self.write(")");
36822            }
36823            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36824                // TSQL: FORMAT(value, format) with .NET-style format
36825                self.write_keyword("FORMAT");
36826                self.write("(");
36827                self.generate_expression(&e.this)?;
36828                self.write(", '");
36829                self.write(&Self::strftime_to_tsql_format(&e.format));
36830                self.write("'");
36831                self.write(")");
36832            }
36833            Some(DialectType::DuckDB) => {
36834                // DuckDB: STRFTIME(value, format) - keeps C format
36835                self.write_keyword("STRFTIME");
36836                self.write("(");
36837                self.generate_expression(&e.this)?;
36838                self.write(", '");
36839                self.write(&e.format);
36840                self.write("'");
36841                self.write(")");
36842            }
36843            Some(DialectType::BigQuery) => {
36844                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36845                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36846                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36847                self.write_keyword("FORMAT_DATE");
36848                self.write("('");
36849                self.write(&fmt);
36850                self.write("', ");
36851                self.generate_expression(&e.this)?;
36852                self.write(")");
36853            }
36854            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36855                // Hive/Spark: DATE_FORMAT(value, java_format)
36856                self.write_keyword("DATE_FORMAT");
36857                self.write("(");
36858                self.generate_expression(&e.this)?;
36859                self.write(", '");
36860                self.write(&Self::strftime_to_java_format(&e.format));
36861                self.write("'");
36862                self.write(")");
36863            }
36864            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36865                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
36866                self.write_keyword("DATE_FORMAT");
36867                self.write("(");
36868                self.generate_expression(&e.this)?;
36869                self.write(", '");
36870                self.write(&e.format);
36871                self.write("'");
36872                self.write(")");
36873            }
36874            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36875                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
36876                self.write_keyword("DATE_FORMAT");
36877                self.write("(");
36878                self.generate_expression(&e.this)?;
36879                self.write(", '");
36880                self.write(&e.format);
36881                self.write("'");
36882                self.write(")");
36883            }
36884            _ => {
36885                // Default: TIME_TO_STR(this, format)
36886                self.write_keyword("TIME_TO_STR");
36887                self.write("(");
36888                self.generate_expression(&e.this)?;
36889                self.write(", '");
36890                self.write(&e.format);
36891                self.write("'");
36892                self.write(")");
36893            }
36894        }
36895        Ok(())
36896    }
36897
36898    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36899        match self.config.dialect {
36900            Some(DialectType::DuckDB) => {
36901                // DuckDB: EPOCH(x)
36902                self.write_keyword("EPOCH");
36903                self.write("(");
36904                self.generate_expression(&e.this)?;
36905                self.write(")");
36906            }
36907            Some(DialectType::Hive)
36908            | Some(DialectType::Spark)
36909            | Some(DialectType::Databricks)
36910            | Some(DialectType::Doris)
36911            | Some(DialectType::StarRocks)
36912            | Some(DialectType::Drill) => {
36913                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36914                self.write_keyword("UNIX_TIMESTAMP");
36915                self.write("(");
36916                self.generate_expression(&e.this)?;
36917                self.write(")");
36918            }
36919            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36920                // Presto: TO_UNIXTIME(x)
36921                self.write_keyword("TO_UNIXTIME");
36922                self.write("(");
36923                self.generate_expression(&e.this)?;
36924                self.write(")");
36925            }
36926            _ => {
36927                // Default: TIME_TO_UNIX(x)
36928                self.write_keyword("TIME_TO_UNIX");
36929                self.write("(");
36930                self.generate_expression(&e.this)?;
36931                self.write(")");
36932            }
36933        }
36934        Ok(())
36935    }
36936
36937    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36938        match self.config.dialect {
36939            Some(DialectType::Hive) => {
36940                // Hive: TO_DATE(x)
36941                self.write_keyword("TO_DATE");
36942                self.write("(");
36943                self.generate_expression(&e.this)?;
36944                self.write(")");
36945            }
36946            _ => {
36947                // Default: TIME_STR_TO_DATE(x)
36948                self.write_keyword("TIME_STR_TO_DATE");
36949                self.write("(");
36950                self.generate_expression(&e.this)?;
36951                self.write(")");
36952            }
36953        }
36954        Ok(())
36955    }
36956
36957    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36958        // TIME_TRUNC(this, unit)
36959        self.write_keyword("TIME_TRUNC");
36960        self.write("(");
36961        self.generate_expression(&e.this)?;
36962        self.write(", ");
36963        self.write_keyword(&e.unit);
36964        self.write(")");
36965        Ok(())
36966    }
36967
36968    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36969        // Just output the unit name
36970        if let Some(unit) = &e.unit {
36971            self.write_keyword(unit);
36972        }
36973        Ok(())
36974    }
36975
36976    /// Generate a Timestamp function expression
36977    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36978    /// For other dialects: TIMESTAMP('value')
36979    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36980        use crate::dialects::DialectType;
36981        use crate::expressions::Literal;
36982
36983        match self.config.dialect {
36984            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36985            Some(DialectType::Exasol) => {
36986                self.write_keyword("TO_TIMESTAMP");
36987                self.write("(");
36988                // Extract the string value from the expression if it's a string literal
36989                if let Some(this) = &e.this {
36990                    match this.as_ref() {
36991                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36992                            let Literal::String(s) = lit.as_ref() else {
36993                                unreachable!()
36994                            };
36995                            self.write("'");
36996                            self.write(s);
36997                            self.write("'");
36998                        }
36999                        _ => {
37000                            self.generate_expression(this)?;
37001                        }
37002                    }
37003                }
37004                self.write(")");
37005            }
37006            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
37007            _ => {
37008                self.write_keyword("TIMESTAMP");
37009                self.write("(");
37010                if let Some(this) = &e.this {
37011                    self.generate_expression(this)?;
37012                }
37013                if let Some(zone) = &e.zone {
37014                    self.write(", ");
37015                    self.generate_expression(zone)?;
37016                }
37017                self.write(")");
37018            }
37019        }
37020        Ok(())
37021    }
37022
37023    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
37024        // TIMESTAMP_ADD(this, expression, unit)
37025        self.write_keyword("TIMESTAMP_ADD");
37026        self.write("(");
37027        self.generate_expression(&e.this)?;
37028        self.write(", ");
37029        self.generate_expression(&e.expression)?;
37030        if let Some(unit) = &e.unit {
37031            self.write(", ");
37032            self.write_keyword(unit);
37033        }
37034        self.write(")");
37035        Ok(())
37036    }
37037
37038    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
37039        // TIMESTAMP_DIFF(this, expression, unit)
37040        self.write_keyword("TIMESTAMP_DIFF");
37041        self.write("(");
37042        self.generate_expression(&e.this)?;
37043        self.write(", ");
37044        self.generate_expression(&e.expression)?;
37045        if let Some(unit) = &e.unit {
37046            self.write(", ");
37047            self.write_keyword(unit);
37048        }
37049        self.write(")");
37050        Ok(())
37051    }
37052
37053    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
37054        // TIMESTAMP_FROM_PARTS(this, expression)
37055        self.write_keyword("TIMESTAMP_FROM_PARTS");
37056        self.write("(");
37057        if let Some(this) = &e.this {
37058            self.generate_expression(this)?;
37059        }
37060        if let Some(expression) = &e.expression {
37061            self.write(", ");
37062            self.generate_expression(expression)?;
37063        }
37064        if let Some(zone) = &e.zone {
37065            self.write(", ");
37066            self.generate_expression(zone)?;
37067        }
37068        if let Some(milli) = &e.milli {
37069            self.write(", ");
37070            self.generate_expression(milli)?;
37071        }
37072        self.write(")");
37073        Ok(())
37074    }
37075
37076    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
37077        // TIMESTAMP_SUB(this, INTERVAL expression unit)
37078        self.write_keyword("TIMESTAMP_SUB");
37079        self.write("(");
37080        self.generate_expression(&e.this)?;
37081        self.write(", ");
37082        self.write_keyword("INTERVAL");
37083        self.write_space();
37084        self.generate_expression(&e.expression)?;
37085        if let Some(unit) = &e.unit {
37086            self.write_space();
37087            self.write_keyword(unit);
37088        }
37089        self.write(")");
37090        Ok(())
37091    }
37092
37093    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
37094        // TIMESTAMP_TZ_FROM_PARTS(...)
37095        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
37096        self.write("(");
37097        if let Some(zone) = &e.zone {
37098            self.generate_expression(zone)?;
37099        }
37100        self.write(")");
37101        Ok(())
37102    }
37103
37104    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
37105        // TO_BINARY(this, [format])
37106        self.write_keyword("TO_BINARY");
37107        self.write("(");
37108        self.generate_expression(&e.this)?;
37109        if let Some(format) = &e.format {
37110            self.write(", '");
37111            self.write(format);
37112            self.write("'");
37113        }
37114        self.write(")");
37115        Ok(())
37116    }
37117
37118    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
37119        // TO_BOOLEAN(this)
37120        self.write_keyword("TO_BOOLEAN");
37121        self.write("(");
37122        self.generate_expression(&e.this)?;
37123        self.write(")");
37124        Ok(())
37125    }
37126
37127    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
37128        // TO_CHAR(this, [format], [nlsparam])
37129        self.write_keyword("TO_CHAR");
37130        self.write("(");
37131        self.generate_expression(&e.this)?;
37132        if let Some(format) = &e.format {
37133            self.write(", '");
37134            self.write(format);
37135            self.write("'");
37136        }
37137        if let Some(nlsparam) = &e.nlsparam {
37138            self.write(", ");
37139            self.generate_expression(nlsparam)?;
37140        }
37141        self.write(")");
37142        Ok(())
37143    }
37144
37145    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
37146        // TO_DECFLOAT(this, [format])
37147        self.write_keyword("TO_DECFLOAT");
37148        self.write("(");
37149        self.generate_expression(&e.this)?;
37150        if let Some(format) = &e.format {
37151            self.write(", '");
37152            self.write(format);
37153            self.write("'");
37154        }
37155        self.write(")");
37156        Ok(())
37157    }
37158
37159    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
37160        // TO_DOUBLE(this, [format])
37161        self.write_keyword("TO_DOUBLE");
37162        self.write("(");
37163        self.generate_expression(&e.this)?;
37164        if let Some(format) = &e.format {
37165            self.write(", '");
37166            self.write(format);
37167            self.write("'");
37168        }
37169        self.write(")");
37170        Ok(())
37171    }
37172
37173    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
37174        // TO_FILE(this, path)
37175        self.write_keyword("TO_FILE");
37176        self.write("(");
37177        self.generate_expression(&e.this)?;
37178        if let Some(path) = &e.path {
37179            self.write(", ");
37180            self.generate_expression(path)?;
37181        }
37182        self.write(")");
37183        Ok(())
37184    }
37185
37186    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
37187        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
37188        // If safe flag is set, output TRY_TO_NUMBER
37189        let is_safe = e.safe.is_some();
37190        if is_safe {
37191            self.write_keyword("TRY_TO_NUMBER");
37192        } else {
37193            self.write_keyword("TO_NUMBER");
37194        }
37195        self.write("(");
37196        self.generate_expression(&e.this)?;
37197        let precision_is_snowflake_default = e.precision.is_none()
37198            || matches!(
37199                e.precision.as_deref(),
37200                Some(Expression::Literal(lit))
37201                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
37202            );
37203        let is_snowflake_default_precision =
37204            matches!(self.config.dialect, Some(DialectType::Snowflake))
37205                && e.nlsparam.is_none()
37206                && e.scale.is_none()
37207                && matches!(
37208                    e.format.as_deref(),
37209                    Some(Expression::Literal(lit))
37210                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
37211                )
37212                && precision_is_snowflake_default;
37213
37214        if !is_snowflake_default_precision {
37215            if let Some(format) = &e.format {
37216                self.write(", ");
37217                self.generate_expression(format)?;
37218            }
37219            if let Some(nlsparam) = &e.nlsparam {
37220                self.write(", ");
37221                self.generate_expression(nlsparam)?;
37222            }
37223            if let Some(precision) = &e.precision {
37224                self.write(", ");
37225                self.generate_expression(precision)?;
37226            }
37227            if let Some(scale) = &e.scale {
37228                self.write(", ");
37229                self.generate_expression(scale)?;
37230            }
37231        }
37232        self.write(")");
37233        Ok(())
37234    }
37235
37236    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
37237        // TO_TABLE this
37238        self.write_keyword("TO_TABLE");
37239        self.write_space();
37240        self.generate_expression(&e.this)?;
37241        Ok(())
37242    }
37243
37244    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
37245        // Check mark to determine the format
37246        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
37247            Expression::Identifier(id) => id.name.clone(),
37248            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37249                let Literal::String(s) = lit.as_ref() else {
37250                    unreachable!()
37251                };
37252                s.clone()
37253            }
37254            _ => String::new(),
37255        });
37256
37257        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
37258        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
37259        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
37260            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37261        });
37262
37263        // For Presto/Trino: always use START TRANSACTION
37264        let use_start_transaction = matches!(
37265            self.config.dialect,
37266            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
37267        );
37268        // For most dialects: strip TRANSACTION keyword
37269        let strip_transaction = matches!(
37270            self.config.dialect,
37271            Some(DialectType::Snowflake)
37272                | Some(DialectType::PostgreSQL)
37273                | Some(DialectType::Redshift)
37274                | Some(DialectType::MySQL)
37275                | Some(DialectType::Hive)
37276                | Some(DialectType::Spark)
37277                | Some(DialectType::Databricks)
37278                | Some(DialectType::DuckDB)
37279                | Some(DialectType::Oracle)
37280                | Some(DialectType::Doris)
37281                | Some(DialectType::StarRocks)
37282                | Some(DialectType::Materialize)
37283                | Some(DialectType::ClickHouse)
37284        );
37285
37286        if is_start || use_start_transaction {
37287            // START TRANSACTION [modes]
37288            self.write_keyword("START TRANSACTION");
37289            if let Some(modes) = &e.modes {
37290                self.write_space();
37291                self.generate_expression(modes)?;
37292            }
37293        } else {
37294            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
37295            self.write_keyword("BEGIN");
37296
37297            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
37298            let is_kind = e.this.as_ref().map_or(false, |t| {
37299                if let Expression::Identifier(id) = t.as_ref() {
37300                    id.name.eq_ignore_ascii_case("DEFERRED")
37301                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
37302                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
37303                } else {
37304                    false
37305                }
37306            });
37307
37308            // Output kind before TRANSACTION keyword
37309            if is_kind {
37310                if let Some(this) = &e.this {
37311                    self.write_space();
37312                    if let Expression::Identifier(id) = this.as_ref() {
37313                        self.write_keyword(&id.name);
37314                    }
37315                }
37316            }
37317
37318            // Output TRANSACTION keyword if it was present and target supports it
37319            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
37320                self.write_space();
37321                self.write_keyword("TRANSACTION");
37322            }
37323
37324            // Output transaction name (not kind)
37325            if !is_kind {
37326                if let Some(this) = &e.this {
37327                    self.write_space();
37328                    self.generate_expression(this)?;
37329                }
37330            }
37331
37332            // Output WITH MARK 'description' for TSQL
37333            if has_with_mark {
37334                self.write_space();
37335                self.write_keyword("WITH MARK");
37336                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
37337                    if let Literal::String(desc) = lit.as_ref() {
37338                        if !desc.is_empty() {
37339                            self.write_space();
37340                            self.write(&format!("'{}'", desc));
37341                        }
37342                    }
37343                }
37344            }
37345
37346            // Output modes (isolation levels, etc.)
37347            if let Some(modes) = &e.modes {
37348                self.write_space();
37349                self.generate_expression(modes)?;
37350            }
37351        }
37352        Ok(())
37353    }
37354
37355    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
37356        // TRANSFORM(this, expression)
37357        self.write_keyword("TRANSFORM");
37358        self.write("(");
37359        self.generate_expression(&e.this)?;
37360        self.write(", ");
37361        self.generate_expression(&e.expression)?;
37362        self.write(")");
37363        Ok(())
37364    }
37365
37366    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
37367        // TRANSFORM(expressions)
37368        self.write_keyword("TRANSFORM");
37369        self.write("(");
37370        if self.config.pretty && !e.expressions.is_empty() {
37371            self.indent_level += 1;
37372            for (i, expr) in e.expressions.iter().enumerate() {
37373                if i > 0 {
37374                    self.write(",");
37375                }
37376                self.write_newline();
37377                self.write_indent();
37378                self.generate_expression(expr)?;
37379            }
37380            self.indent_level -= 1;
37381            self.write_newline();
37382            self.write(")");
37383        } else {
37384            for (i, expr) in e.expressions.iter().enumerate() {
37385                if i > 0 {
37386                    self.write(", ");
37387                }
37388                self.generate_expression(expr)?;
37389            }
37390            self.write(")");
37391        }
37392        Ok(())
37393    }
37394
37395    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
37396        use crate::dialects::DialectType;
37397        // TRANSIENT is Snowflake-specific; skip for other dialects
37398        if let Some(this) = &e.this {
37399            self.generate_expression(this)?;
37400            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37401                self.write_space();
37402            }
37403        }
37404        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37405            self.write_keyword("TRANSIENT");
37406        }
37407        Ok(())
37408    }
37409
37410    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
37411        // TRANSLATE(this, from_, to)
37412        self.write_keyword("TRANSLATE");
37413        self.write("(");
37414        self.generate_expression(&e.this)?;
37415        if let Some(from) = &e.from_ {
37416            self.write(", ");
37417            self.generate_expression(from)?;
37418        }
37419        if let Some(to) = &e.to {
37420            self.write(", ");
37421            self.generate_expression(to)?;
37422        }
37423        self.write(")");
37424        Ok(())
37425    }
37426
37427    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
37428        // TRANSLATE(this USING expression)
37429        self.write_keyword("TRANSLATE");
37430        self.write("(");
37431        self.generate_expression(&e.this)?;
37432        self.write_space();
37433        self.write_keyword("USING");
37434        self.write_space();
37435        self.generate_expression(&e.expression)?;
37436        if e.with_error.is_some() {
37437            self.write_space();
37438            self.write_keyword("WITH ERROR");
37439        }
37440        self.write(")");
37441        Ok(())
37442    }
37443
37444    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
37445        // TRUNCATE TABLE table1, table2, ...
37446        self.write_keyword("TRUNCATE TABLE");
37447        self.write_space();
37448        for (i, expr) in e.expressions.iter().enumerate() {
37449            if i > 0 {
37450                self.write(", ");
37451            }
37452            self.generate_expression(expr)?;
37453        }
37454        Ok(())
37455    }
37456
37457    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
37458        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
37459        self.write_keyword("TRY_BASE64_DECODE_BINARY");
37460        self.write("(");
37461        self.generate_expression(&e.this)?;
37462        if let Some(alphabet) = &e.alphabet {
37463            self.write(", ");
37464            self.generate_expression(alphabet)?;
37465        }
37466        self.write(")");
37467        Ok(())
37468    }
37469
37470    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
37471        // TRY_BASE64_DECODE_STRING(this, [alphabet])
37472        self.write_keyword("TRY_BASE64_DECODE_STRING");
37473        self.write("(");
37474        self.generate_expression(&e.this)?;
37475        if let Some(alphabet) = &e.alphabet {
37476            self.write(", ");
37477            self.generate_expression(alphabet)?;
37478        }
37479        self.write(")");
37480        Ok(())
37481    }
37482
37483    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
37484        // TRY_TO_DECFLOAT(this, [format])
37485        self.write_keyword("TRY_TO_DECFLOAT");
37486        self.write("(");
37487        self.generate_expression(&e.this)?;
37488        if let Some(format) = &e.format {
37489            self.write(", '");
37490            self.write(format);
37491            self.write("'");
37492        }
37493        self.write(")");
37494        Ok(())
37495    }
37496
37497    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37498        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37499        self.write_keyword("TS_OR_DS_ADD");
37500        self.write("(");
37501        self.generate_expression(&e.this)?;
37502        self.write(", ");
37503        self.generate_expression(&e.expression)?;
37504        if let Some(unit) = &e.unit {
37505            self.write(", ");
37506            self.write_keyword(unit);
37507        }
37508        if let Some(return_type) = &e.return_type {
37509            self.write(", ");
37510            self.generate_expression(return_type)?;
37511        }
37512        self.write(")");
37513        Ok(())
37514    }
37515
37516    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37517        // TS_OR_DS_DIFF(this, expression, [unit])
37518        self.write_keyword("TS_OR_DS_DIFF");
37519        self.write("(");
37520        self.generate_expression(&e.this)?;
37521        self.write(", ");
37522        self.generate_expression(&e.expression)?;
37523        if let Some(unit) = &e.unit {
37524            self.write(", ");
37525            self.write_keyword(unit);
37526        }
37527        self.write(")");
37528        Ok(())
37529    }
37530
37531    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37532        let default_time_format = "%Y-%m-%d %H:%M:%S";
37533        let default_date_format = "%Y-%m-%d";
37534        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37535            f != default_time_format && f != default_date_format
37536        });
37537
37538        if has_non_default_format {
37539            // With non-default format: dialect-specific handling
37540            let fmt = e.format.as_ref().unwrap();
37541            match self.config.dialect {
37542                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37543                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37544                    // STR_TO_DATE is the MySQL-native form of StrToTime
37545                    let str_to_time = crate::expressions::StrToTime {
37546                        this: Box::new((*e.this).clone()),
37547                        format: fmt.clone(),
37548                        zone: None,
37549                        safe: None,
37550                        target_type: None,
37551                    };
37552                    self.generate_str_to_time(&str_to_time)?;
37553                }
37554                Some(DialectType::Hive)
37555                | Some(DialectType::Spark)
37556                | Some(DialectType::Databricks) => {
37557                    // Hive/Spark: TO_DATE(x, java_fmt)
37558                    self.write_keyword("TO_DATE");
37559                    self.write("(");
37560                    self.generate_expression(&e.this)?;
37561                    self.write(", '");
37562                    self.write(&Self::strftime_to_java_format(fmt));
37563                    self.write("')");
37564                }
37565                Some(DialectType::Snowflake) => {
37566                    // Snowflake: TO_DATE(x, snowflake_fmt)
37567                    self.write_keyword("TO_DATE");
37568                    self.write("(");
37569                    self.generate_expression(&e.this)?;
37570                    self.write(", '");
37571                    self.write(&Self::strftime_to_snowflake_format(fmt));
37572                    self.write("')");
37573                }
37574                Some(DialectType::Doris) => {
37575                    // Doris: TO_DATE(x) - ignores format
37576                    self.write_keyword("TO_DATE");
37577                    self.write("(");
37578                    self.generate_expression(&e.this)?;
37579                    self.write(")");
37580                }
37581                _ => {
37582                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37583                    self.write_keyword("CAST");
37584                    self.write("(");
37585                    let str_to_time = crate::expressions::StrToTime {
37586                        this: Box::new((*e.this).clone()),
37587                        format: fmt.clone(),
37588                        zone: None,
37589                        safe: None,
37590                        target_type: None,
37591                    };
37592                    self.generate_str_to_time(&str_to_time)?;
37593                    self.write_keyword(" AS ");
37594                    self.write_keyword("DATE");
37595                    self.write(")");
37596                }
37597            }
37598        } else {
37599            // Without format (or default format): simple date conversion
37600            match self.config.dialect {
37601                Some(DialectType::MySQL)
37602                | Some(DialectType::SQLite)
37603                | Some(DialectType::StarRocks) => {
37604                    // MySQL/SQLite/StarRocks: DATE(x)
37605                    self.write_keyword("DATE");
37606                    self.write("(");
37607                    self.generate_expression(&e.this)?;
37608                    self.write(")");
37609                }
37610                Some(DialectType::Hive)
37611                | Some(DialectType::Spark)
37612                | Some(DialectType::Databricks)
37613                | Some(DialectType::Snowflake)
37614                | Some(DialectType::Doris) => {
37615                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37616                    self.write_keyword("TO_DATE");
37617                    self.write("(");
37618                    self.generate_expression(&e.this)?;
37619                    self.write(")");
37620                }
37621                Some(DialectType::Presto)
37622                | Some(DialectType::Trino)
37623                | Some(DialectType::Athena) => {
37624                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37625                    self.write_keyword("CAST");
37626                    self.write("(");
37627                    self.write_keyword("CAST");
37628                    self.write("(");
37629                    self.generate_expression(&e.this)?;
37630                    self.write_keyword(" AS ");
37631                    self.write_keyword("TIMESTAMP");
37632                    self.write(")");
37633                    self.write_keyword(" AS ");
37634                    self.write_keyword("DATE");
37635                    self.write(")");
37636                }
37637                Some(DialectType::ClickHouse) => {
37638                    // ClickHouse: CAST(x AS Nullable(DATE))
37639                    self.write_keyword("CAST");
37640                    self.write("(");
37641                    self.generate_expression(&e.this)?;
37642                    self.write_keyword(" AS ");
37643                    self.write("Nullable(DATE)");
37644                    self.write(")");
37645                }
37646                _ => {
37647                    // Default: CAST(x AS DATE)
37648                    self.write_keyword("CAST");
37649                    self.write("(");
37650                    self.generate_expression(&e.this)?;
37651                    self.write_keyword(" AS ");
37652                    self.write_keyword("DATE");
37653                    self.write(")");
37654                }
37655            }
37656        }
37657        Ok(())
37658    }
37659
37660    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37661        // TS_OR_DS_TO_TIME(this, [format])
37662        self.write_keyword("TS_OR_DS_TO_TIME");
37663        self.write("(");
37664        self.generate_expression(&e.this)?;
37665        if let Some(format) = &e.format {
37666            self.write(", '");
37667            self.write(format);
37668            self.write("'");
37669        }
37670        self.write(")");
37671        Ok(())
37672    }
37673
37674    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37675        // UNHEX(this, [expression])
37676        self.write_keyword("UNHEX");
37677        self.write("(");
37678        self.generate_expression(&e.this)?;
37679        if let Some(expression) = &e.expression {
37680            self.write(", ");
37681            self.generate_expression(expression)?;
37682        }
37683        self.write(")");
37684        Ok(())
37685    }
37686
37687    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37688        // U&this [UESCAPE escape]
37689        self.write("U&");
37690        self.generate_expression(&e.this)?;
37691        if let Some(escape) = &e.escape {
37692            self.write_space();
37693            self.write_keyword("UESCAPE");
37694            self.write_space();
37695            self.generate_expression(escape)?;
37696        }
37697        Ok(())
37698    }
37699
37700    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37701        // UNIFORM(this, expression, [gen], [seed])
37702        self.write_keyword("UNIFORM");
37703        self.write("(");
37704        self.generate_expression(&e.this)?;
37705        self.write(", ");
37706        self.generate_expression(&e.expression)?;
37707        if let Some(gen) = &e.gen {
37708            self.write(", ");
37709            self.generate_expression(gen)?;
37710        }
37711        if let Some(seed) = &e.seed {
37712            self.write(", ");
37713            self.generate_expression(seed)?;
37714        }
37715        self.write(")");
37716        Ok(())
37717    }
37718
37719    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37720        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37721        self.write_keyword("UNIQUE");
37722        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37723        if e.nulls.is_some() {
37724            self.write(" NULLS NOT DISTINCT");
37725        }
37726        if let Some(this) = &e.this {
37727            self.write_space();
37728            self.generate_expression(this)?;
37729        }
37730        if let Some(index_type) = &e.index_type {
37731            self.write(" USING ");
37732            self.generate_expression(index_type)?;
37733        }
37734        if let Some(on_conflict) = &e.on_conflict {
37735            self.write_space();
37736            self.generate_expression(on_conflict)?;
37737        }
37738        for opt in &e.options {
37739            self.write_space();
37740            self.generate_expression(opt)?;
37741        }
37742        Ok(())
37743    }
37744
37745    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37746        // UNIQUE KEY (expressions)
37747        self.write_keyword("UNIQUE KEY");
37748        self.write(" (");
37749        for (i, expr) in e.expressions.iter().enumerate() {
37750            if i > 0 {
37751                self.write(", ");
37752            }
37753            self.generate_expression(expr)?;
37754        }
37755        self.write(")");
37756        Ok(())
37757    }
37758
37759    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37760        // ROLLUP (r1(col1, col2), r2(col1))
37761        self.write_keyword("ROLLUP");
37762        self.write(" (");
37763        for (i, index) in e.expressions.iter().enumerate() {
37764            if i > 0 {
37765                self.write(", ");
37766            }
37767            self.generate_identifier(&index.name)?;
37768            self.write("(");
37769            for (j, col) in index.expressions.iter().enumerate() {
37770                if j > 0 {
37771                    self.write(", ");
37772                }
37773                self.generate_identifier(col)?;
37774            }
37775            self.write(")");
37776        }
37777        self.write(")");
37778        Ok(())
37779    }
37780
37781    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37782        match self.config.dialect {
37783            Some(DialectType::DuckDB) => {
37784                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37785                self.write_keyword("STRFTIME");
37786                self.write("(");
37787                self.write_keyword("TO_TIMESTAMP");
37788                self.write("(");
37789                self.generate_expression(&e.this)?;
37790                self.write("), '");
37791                if let Some(format) = &e.format {
37792                    self.write(format);
37793                }
37794                self.write("')");
37795            }
37796            Some(DialectType::Hive) => {
37797                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37798                self.write_keyword("FROM_UNIXTIME");
37799                self.write("(");
37800                self.generate_expression(&e.this)?;
37801                if let Some(format) = &e.format {
37802                    if format != "yyyy-MM-dd HH:mm:ss" {
37803                        self.write(", '");
37804                        self.write(format);
37805                        self.write("'");
37806                    }
37807                }
37808                self.write(")");
37809            }
37810            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37811                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37812                self.write_keyword("DATE_FORMAT");
37813                self.write("(");
37814                self.write_keyword("FROM_UNIXTIME");
37815                self.write("(");
37816                self.generate_expression(&e.this)?;
37817                self.write("), '");
37818                if let Some(format) = &e.format {
37819                    self.write(format);
37820                }
37821                self.write("')");
37822            }
37823            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37824                // Spark: FROM_UNIXTIME(value, format)
37825                self.write_keyword("FROM_UNIXTIME");
37826                self.write("(");
37827                self.generate_expression(&e.this)?;
37828                if let Some(format) = &e.format {
37829                    self.write(", '");
37830                    self.write(format);
37831                    self.write("'");
37832                }
37833                self.write(")");
37834            }
37835            _ => {
37836                // Default: UNIX_TO_STR(this, [format])
37837                self.write_keyword("UNIX_TO_STR");
37838                self.write("(");
37839                self.generate_expression(&e.this)?;
37840                if let Some(format) = &e.format {
37841                    self.write(", '");
37842                    self.write(format);
37843                    self.write("'");
37844                }
37845                self.write(")");
37846            }
37847        }
37848        Ok(())
37849    }
37850
37851    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
37852        use crate::dialects::DialectType;
37853        let scale = e.scale.unwrap_or(0); // 0 = seconds
37854
37855        match self.config.dialect {
37856            Some(DialectType::Snowflake) => {
37857                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
37858                self.write_keyword("TO_TIMESTAMP");
37859                self.write("(");
37860                self.generate_expression(&e.this)?;
37861                if let Some(s) = e.scale {
37862                    if s > 0 {
37863                        self.write(", ");
37864                        self.write(&s.to_string());
37865                    }
37866                }
37867                self.write(")");
37868            }
37869            Some(DialectType::BigQuery) => {
37870                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
37871                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
37872                match scale {
37873                    0 => {
37874                        self.write_keyword("TIMESTAMP_SECONDS");
37875                        self.write("(");
37876                        self.generate_expression(&e.this)?;
37877                        self.write(")");
37878                    }
37879                    3 => {
37880                        self.write_keyword("TIMESTAMP_MILLIS");
37881                        self.write("(");
37882                        self.generate_expression(&e.this)?;
37883                        self.write(")");
37884                    }
37885                    6 => {
37886                        self.write_keyword("TIMESTAMP_MICROS");
37887                        self.write("(");
37888                        self.generate_expression(&e.this)?;
37889                        self.write(")");
37890                    }
37891                    _ => {
37892                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
37893                        self.write_keyword("TIMESTAMP_SECONDS");
37894                        self.write("(CAST(");
37895                        self.generate_expression(&e.this)?;
37896                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
37897                    }
37898                }
37899            }
37900            Some(DialectType::Spark) => {
37901                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37902                // TIMESTAMP_MILLIS(value) for scale=3
37903                // TIMESTAMP_MICROS(value) for scale=6
37904                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37905                match scale {
37906                    0 => {
37907                        self.write_keyword("CAST");
37908                        self.write("(");
37909                        self.write_keyword("FROM_UNIXTIME");
37910                        self.write("(");
37911                        self.generate_expression(&e.this)?;
37912                        self.write(") ");
37913                        self.write_keyword("AS TIMESTAMP");
37914                        self.write(")");
37915                    }
37916                    3 => {
37917                        self.write_keyword("TIMESTAMP_MILLIS");
37918                        self.write("(");
37919                        self.generate_expression(&e.this)?;
37920                        self.write(")");
37921                    }
37922                    6 => {
37923                        self.write_keyword("TIMESTAMP_MICROS");
37924                        self.write("(");
37925                        self.generate_expression(&e.this)?;
37926                        self.write(")");
37927                    }
37928                    _ => {
37929                        self.write_keyword("TIMESTAMP_SECONDS");
37930                        self.write("(");
37931                        self.generate_expression(&e.this)?;
37932                        self.write(&format!(" / POWER(10, {}))", scale));
37933                    }
37934                }
37935            }
37936            Some(DialectType::Databricks) => {
37937                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37938                // TIMESTAMP_MILLIS(value) for scale=3
37939                // TIMESTAMP_MICROS(value) for scale=6
37940                match scale {
37941                    0 => {
37942                        self.write_keyword("CAST");
37943                        self.write("(");
37944                        self.write_keyword("FROM_UNIXTIME");
37945                        self.write("(");
37946                        self.generate_expression(&e.this)?;
37947                        self.write(") ");
37948                        self.write_keyword("AS TIMESTAMP");
37949                        self.write(")");
37950                    }
37951                    3 => {
37952                        self.write_keyword("TIMESTAMP_MILLIS");
37953                        self.write("(");
37954                        self.generate_expression(&e.this)?;
37955                        self.write(")");
37956                    }
37957                    6 => {
37958                        self.write_keyword("TIMESTAMP_MICROS");
37959                        self.write("(");
37960                        self.generate_expression(&e.this)?;
37961                        self.write(")");
37962                    }
37963                    _ => {
37964                        self.write_keyword("TIMESTAMP_SECONDS");
37965                        self.write("(");
37966                        self.generate_expression(&e.this)?;
37967                        self.write(&format!(" / POWER(10, {}))", scale));
37968                    }
37969                }
37970            }
37971            Some(DialectType::Hive) => {
37972                // Hive: FROM_UNIXTIME(value)
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("(");
37981                    self.generate_expression(&e.this)?;
37982                    self.write(&format!(" / POWER(10, {})", scale));
37983                    self.write(")");
37984                }
37985            }
37986            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37987                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37988                // FROM_UNIXTIME(value) for scale=0
37989                if scale == 0 {
37990                    self.write_keyword("FROM_UNIXTIME");
37991                    self.write("(");
37992                    self.generate_expression(&e.this)?;
37993                    self.write(")");
37994                } else {
37995                    self.write_keyword("FROM_UNIXTIME");
37996                    self.write("(CAST(");
37997                    self.generate_expression(&e.this)?;
37998                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37999                }
38000            }
38001            Some(DialectType::DuckDB) => {
38002                // DuckDB: TO_TIMESTAMP(value) for scale=0
38003                // EPOCH_MS(value) for scale=3
38004                // MAKE_TIMESTAMP(value) for scale=6
38005                match scale {
38006                    0 => {
38007                        self.write_keyword("TO_TIMESTAMP");
38008                        self.write("(");
38009                        self.generate_expression(&e.this)?;
38010                        self.write(")");
38011                    }
38012                    3 => {
38013                        self.write_keyword("EPOCH_MS");
38014                        self.write("(");
38015                        self.generate_expression(&e.this)?;
38016                        self.write(")");
38017                    }
38018                    6 => {
38019                        self.write_keyword("MAKE_TIMESTAMP");
38020                        self.write("(");
38021                        self.generate_expression(&e.this)?;
38022                        self.write(")");
38023                    }
38024                    _ => {
38025                        self.write_keyword("TO_TIMESTAMP");
38026                        self.write("(");
38027                        self.generate_expression(&e.this)?;
38028                        self.write(&format!(" / POWER(10, {}))", scale));
38029                        self.write_keyword(" AT TIME ZONE");
38030                        self.write(" 'UTC'");
38031                    }
38032                }
38033            }
38034            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38035                // Doris/StarRocks: FROM_UNIXTIME(value)
38036                self.write_keyword("FROM_UNIXTIME");
38037                self.write("(");
38038                self.generate_expression(&e.this)?;
38039                self.write(")");
38040            }
38041            Some(DialectType::Oracle) => {
38042                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
38043                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
38044                self.generate_expression(&e.this)?;
38045                self.write(" / 86400)");
38046            }
38047            Some(DialectType::Redshift) => {
38048                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
38049                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
38050                self.write("(TIMESTAMP 'epoch' + ");
38051                if scale == 0 {
38052                    self.generate_expression(&e.this)?;
38053                } else {
38054                    self.write("(");
38055                    self.generate_expression(&e.this)?;
38056                    self.write(&format!(" / POWER(10, {}))", scale));
38057                }
38058                self.write(" * INTERVAL '1 SECOND')");
38059            }
38060            Some(DialectType::Exasol) => {
38061                // Exasol: FROM_POSIX_TIME(value)
38062                self.write_keyword("FROM_POSIX_TIME");
38063                self.write("(");
38064                self.generate_expression(&e.this)?;
38065                self.write(")");
38066            }
38067            _ => {
38068                // Default: TO_TIMESTAMP(value[, scale])
38069                self.write_keyword("TO_TIMESTAMP");
38070                self.write("(");
38071                self.generate_expression(&e.this)?;
38072                if let Some(s) = e.scale {
38073                    self.write(", ");
38074                    self.write(&s.to_string());
38075                }
38076                self.write(")");
38077            }
38078        }
38079        Ok(())
38080    }
38081
38082    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
38083        // NAME col VALUE col1, col2, ...
38084        if !matches!(&*e.this, Expression::Null(_)) {
38085            self.write_keyword("NAME");
38086            self.write_space();
38087            self.generate_expression(&e.this)?;
38088        }
38089        if !e.expressions.is_empty() {
38090            self.write_space();
38091            self.write_keyword("VALUE");
38092            self.write_space();
38093            for (i, expr) in e.expressions.iter().enumerate() {
38094                if i > 0 {
38095                    self.write(", ");
38096                }
38097                self.generate_expression(expr)?;
38098            }
38099        }
38100        Ok(())
38101    }
38102
38103    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
38104        // this(expressions) or (this)(expressions)
38105        if e.wrapped.is_some() {
38106            self.write("(");
38107        }
38108        self.generate_expression(&e.this)?;
38109        if e.wrapped.is_some() {
38110            self.write(")");
38111        }
38112        self.write("(");
38113        for (i, expr) in e.expressions.iter().enumerate() {
38114            if i > 0 {
38115                self.write(", ");
38116            }
38117            self.generate_expression(expr)?;
38118        }
38119        self.write(")");
38120        Ok(())
38121    }
38122
38123    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
38124        // USING TEMPLATE this
38125        self.write_keyword("USING TEMPLATE");
38126        self.write_space();
38127        self.generate_expression(&e.this)?;
38128        Ok(())
38129    }
38130
38131    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
38132        // UTC_TIME
38133        self.write_keyword("UTC_TIME");
38134        Ok(())
38135    }
38136
38137    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
38138        if matches!(
38139            self.config.dialect,
38140            Some(crate::dialects::DialectType::ClickHouse)
38141        ) {
38142            self.write_keyword("CURRENT_TIMESTAMP");
38143            self.write("('UTC')");
38144        } else {
38145            self.write_keyword("UTC_TIMESTAMP");
38146        }
38147        Ok(())
38148    }
38149
38150    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
38151        use crate::dialects::DialectType;
38152        // Choose UUID function name based on target dialect
38153        let func_name = match self.config.dialect {
38154            Some(DialectType::Snowflake) => "UUID_STRING",
38155            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
38156            Some(DialectType::BigQuery) => "GENERATE_UUID",
38157            _ => {
38158                if let Some(name) = &e.name {
38159                    name.as_str()
38160                } else {
38161                    "UUID"
38162                }
38163            }
38164        };
38165        self.write_keyword(func_name);
38166        self.write("(");
38167        if let Some(this) = &e.this {
38168            self.generate_expression(this)?;
38169        }
38170        self.write(")");
38171        Ok(())
38172    }
38173
38174    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
38175        // MAP(key1, value1, key2, value2, ...)
38176        self.write_keyword("MAP");
38177        self.write("(");
38178        let mut first = true;
38179        for (k, v) in e.keys.iter().zip(e.values.iter()) {
38180            if !first {
38181                self.write(", ");
38182            }
38183            self.generate_expression(k)?;
38184            self.write(", ");
38185            self.generate_expression(v)?;
38186            first = false;
38187        }
38188        self.write(")");
38189        Ok(())
38190    }
38191
38192    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
38193        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
38194        self.write_keyword("VECTOR_SEARCH");
38195        self.write("(");
38196        self.generate_expression(&e.this)?;
38197        if let Some(col) = &e.column_to_search {
38198            self.write(", ");
38199            self.generate_expression(col)?;
38200        }
38201        if let Some(query_table) = &e.query_table {
38202            self.write(", ");
38203            self.generate_expression(query_table)?;
38204        }
38205        if let Some(query_col) = &e.query_column_to_search {
38206            self.write(", ");
38207            self.generate_expression(query_col)?;
38208        }
38209        if let Some(top_k) = &e.top_k {
38210            self.write(", ");
38211            self.generate_expression(top_k)?;
38212        }
38213        if let Some(dist_type) = &e.distance_type {
38214            self.write(", ");
38215            self.generate_expression(dist_type)?;
38216        }
38217        self.write(")");
38218        Ok(())
38219    }
38220
38221    fn generate_version(&mut self, e: &Version) -> Result<()> {
38222        // Python: f"FOR {expression.name} {kind} {expr}"
38223        // e.this = Identifier("TIMESTAMP" or "VERSION")
38224        // e.kind = "AS OF" (or "BETWEEN", etc.)
38225        // e.expression = the value expression
38226        // Hive does NOT use the FOR prefix for time travel
38227        use crate::dialects::DialectType;
38228        let skip_for = matches!(
38229            self.config.dialect,
38230            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
38231        );
38232        if !skip_for {
38233            self.write_keyword("FOR");
38234            self.write_space();
38235        }
38236        // Extract the name from this (which is an Identifier expression)
38237        match e.this.as_ref() {
38238            Expression::Identifier(ident) => {
38239                self.write_keyword(&ident.name);
38240            }
38241            _ => {
38242                self.generate_expression(&e.this)?;
38243            }
38244        }
38245        self.write_space();
38246        self.write_keyword(&e.kind);
38247        if let Some(expression) = &e.expression {
38248            self.write_space();
38249            self.generate_expression(expression)?;
38250        }
38251        Ok(())
38252    }
38253
38254    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
38255        // Python: return self.sql(expression, "this")
38256        self.generate_expression(&e.this)?;
38257        Ok(())
38258    }
38259
38260    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
38261        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
38262        if e.this.is_some() {
38263            self.write_keyword("NOT VOLATILE");
38264        } else {
38265            self.write_keyword("VOLATILE");
38266        }
38267        Ok(())
38268    }
38269
38270    fn generate_watermark_column_constraint(
38271        &mut self,
38272        e: &WatermarkColumnConstraint,
38273    ) -> Result<()> {
38274        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
38275        self.write_keyword("WATERMARK FOR");
38276        self.write_space();
38277        self.generate_expression(&e.this)?;
38278        self.write_space();
38279        self.write_keyword("AS");
38280        self.write_space();
38281        self.generate_expression(&e.expression)?;
38282        Ok(())
38283    }
38284
38285    fn generate_week(&mut self, e: &Week) -> Result<()> {
38286        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
38287        self.write_keyword("WEEK");
38288        self.write("(");
38289        self.generate_expression(&e.this)?;
38290        if let Some(mode) = &e.mode {
38291            self.write(", ");
38292            self.generate_expression(mode)?;
38293        }
38294        self.write(")");
38295        Ok(())
38296    }
38297
38298    fn generate_when(&mut self, e: &When) -> Result<()> {
38299        // Python: WHEN {matched}{source}{condition} THEN {then}
38300        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
38301        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
38302        self.write_keyword("WHEN");
38303        self.write_space();
38304
38305        // Check if matched
38306        if let Some(matched) = &e.matched {
38307            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
38308            match matched.as_ref() {
38309                Expression::Boolean(b) if b.value => {
38310                    self.write_keyword("MATCHED");
38311                }
38312                _ => {
38313                    self.write_keyword("NOT MATCHED");
38314                }
38315            }
38316        } else {
38317            self.write_keyword("NOT MATCHED");
38318        }
38319
38320        // BY SOURCE / BY TARGET
38321        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
38322        // BY TARGET is the default and typically omitted in output
38323        // Only emit if the dialect supports BY SOURCE syntax
38324        if self.config.matched_by_source {
38325            if let Some(source) = &e.source {
38326                if let Expression::Boolean(b) = source.as_ref() {
38327                    if b.value {
38328                        // BY SOURCE
38329                        self.write_space();
38330                        self.write_keyword("BY SOURCE");
38331                    }
38332                    // BY TARGET (b.value == false) is omitted as it's the default
38333                } else {
38334                    // For non-boolean source, output as BY SOURCE (legacy behavior)
38335                    self.write_space();
38336                    self.write_keyword("BY SOURCE");
38337                }
38338            }
38339        }
38340
38341        // Condition
38342        if let Some(condition) = &e.condition {
38343            self.write_space();
38344            self.write_keyword("AND");
38345            self.write_space();
38346            self.generate_expression(condition)?;
38347        }
38348
38349        self.write_space();
38350        self.write_keyword("THEN");
38351        self.write_space();
38352
38353        // Generate the then expression (could be INSERT, UPDATE, DELETE)
38354        // MERGE actions are stored as Tuples with the action keyword as first element
38355        self.generate_merge_action(&e.then)?;
38356
38357        Ok(())
38358    }
38359
38360    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
38361        match action {
38362            Expression::Tuple(tuple) => {
38363                let elements = &tuple.expressions;
38364                if elements.is_empty() {
38365                    return self.generate_expression(action);
38366                }
38367                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
38368                match &elements[0] {
38369                    Expression::Var(v) if v.this == "INSERT" => {
38370                        self.write_keyword("INSERT");
38371                        // Spark: INSERT * (insert all columns)
38372                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38373                            self.write(" *");
38374                            if let Some(Expression::Where(w)) = elements.get(2) {
38375                                self.write_space();
38376                                self.generate_where(w)?;
38377                            }
38378                        } else {
38379                            let mut values_idx = 1;
38380                            // Check if second element is column list (Tuple)
38381                            if elements.len() > 1 {
38382                                if let Expression::Tuple(cols) = &elements[1] {
38383                                    // Could be columns or values - if there's a third element, second is columns
38384                                    if elements.len() > 2 {
38385                                        // Second is columns, third is values
38386                                        self.write(" (");
38387                                        for (i, col) in cols.expressions.iter().enumerate() {
38388                                            if i > 0 {
38389                                                self.write(", ");
38390                                            }
38391                                            // Strip MERGE target qualifiers from INSERT column list
38392                                            if !self.merge_strip_qualifiers.is_empty() {
38393                                                let stripped = self.strip_merge_qualifier(col);
38394                                                self.generate_expression(&stripped)?;
38395                                            } else {
38396                                                self.generate_expression(col)?;
38397                                            }
38398                                        }
38399                                        self.write(")");
38400                                        values_idx = 2;
38401                                    } else {
38402                                        // Only two elements: INSERT + values (no explicit columns)
38403                                        values_idx = 1;
38404                                    }
38405                                }
38406                            }
38407                            let mut next_idx = values_idx;
38408                            // Generate VALUES clause
38409                            if values_idx < elements.len()
38410                                && !matches!(&elements[values_idx], Expression::Where(_))
38411                            {
38412                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
38413                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
38414                                if !is_row {
38415                                    self.write_space();
38416                                    self.write_keyword("VALUES");
38417                                }
38418                                self.write(" ");
38419                                if let Expression::Tuple(vals) = &elements[values_idx] {
38420                                    self.write("(");
38421                                    for (i, val) in vals.expressions.iter().enumerate() {
38422                                        if i > 0 {
38423                                            self.write(", ");
38424                                        }
38425                                        self.generate_expression(val)?;
38426                                    }
38427                                    self.write(")");
38428                                } else {
38429                                    self.generate_expression(&elements[values_idx])?;
38430                                }
38431                                next_idx += 1;
38432                            }
38433                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
38434                                self.write_space();
38435                                self.generate_where(w)?;
38436                            }
38437                        } // close else for INSERT * check
38438                    }
38439                    Expression::Var(v) if v.this == "UPDATE" => {
38440                        self.write_keyword("UPDATE");
38441                        // Spark: UPDATE * (update all columns)
38442                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38443                            self.write(" *");
38444                            if let Some(Expression::Where(w)) = elements.get(2) {
38445                                self.write_space();
38446                                self.generate_where(w)?;
38447                            }
38448                        } else if elements.len() > 1 {
38449                            self.write_space();
38450                            self.write_keyword("SET");
38451                            // In pretty mode, put assignments on next line with extra indent
38452                            if self.config.pretty {
38453                                self.write_newline();
38454                                self.indent_level += 1;
38455                                self.write_indent();
38456                            } else {
38457                                self.write_space();
38458                            }
38459                            if let Expression::Tuple(assignments) = &elements[1] {
38460                                for (i, assignment) in assignments.expressions.iter().enumerate() {
38461                                    if i > 0 {
38462                                        if self.config.pretty {
38463                                            self.write(",");
38464                                            self.write_newline();
38465                                            self.write_indent();
38466                                        } else {
38467                                            self.write(", ");
38468                                        }
38469                                    }
38470                                    // Strip MERGE target qualifiers from left side of UPDATE SET
38471                                    if !self.merge_strip_qualifiers.is_empty() {
38472                                        self.generate_merge_set_assignment(assignment)?;
38473                                    } else {
38474                                        self.generate_expression(assignment)?;
38475                                    }
38476                                }
38477                            } else {
38478                                self.generate_expression(&elements[1])?;
38479                            }
38480                            if self.config.pretty {
38481                                self.indent_level -= 1;
38482                            }
38483                            if let Some(Expression::Where(w)) = elements.get(2) {
38484                                self.write_space();
38485                                self.generate_where(w)?;
38486                            }
38487                        }
38488                    }
38489                    Expression::Var(v) if v.this == "DELETE" => {
38490                        self.write_keyword("DELETE");
38491                        if let Some(Expression::Where(w)) = elements.get(1) {
38492                            self.write_space();
38493                            self.generate_where(w)?;
38494                        }
38495                    }
38496                    _ => {
38497                        // Fallback: generic tuple generation
38498                        self.generate_expression(action)?;
38499                    }
38500                }
38501            }
38502            Expression::Var(v)
38503                if v.this == "INSERT"
38504                    || v.this == "UPDATE"
38505                    || v.this == "DELETE"
38506                    || v.this == "DO NOTHING" =>
38507            {
38508                self.write_keyword(&v.this);
38509            }
38510            _ => {
38511                self.generate_expression(action)?;
38512            }
38513        }
38514        Ok(())
38515    }
38516
38517    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38518    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38519        match assignment {
38520            Expression::Eq(eq) => {
38521                // Strip qualifier from the left side if it matches a MERGE target name
38522                let stripped_left = self.strip_merge_qualifier(&eq.left);
38523                self.generate_expression(&stripped_left)?;
38524                self.write(" = ");
38525                self.generate_expression(&eq.right)?;
38526                Ok(())
38527            }
38528            other => self.generate_expression(other),
38529        }
38530    }
38531
38532    /// Strip table qualifier from a column reference if it matches a MERGE target name
38533    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38534        match expr {
38535            Expression::Column(col) => {
38536                if let Some(ref table_ident) = col.table {
38537                    if self
38538                        .merge_strip_qualifiers
38539                        .iter()
38540                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38541                    {
38542                        // Strip the table qualifier
38543                        let mut col = col.clone();
38544                        col.table = None;
38545                        return Expression::Column(col);
38546                    }
38547                }
38548                expr.clone()
38549            }
38550            Expression::Dot(dot) => {
38551                // table.column -> column (strip qualifier)
38552                if let Expression::Identifier(id) = &dot.this {
38553                    if self
38554                        .merge_strip_qualifiers
38555                        .iter()
38556                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38557                    {
38558                        return Expression::Identifier(dot.field.clone());
38559                    }
38560                }
38561                expr.clone()
38562            }
38563            _ => expr.clone(),
38564        }
38565    }
38566
38567    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38568        // Python: return self.expressions(expression, sep=" ", indent=False)
38569        for (i, expr) in e.expressions.iter().enumerate() {
38570            if i > 0 {
38571                // In pretty mode, each WHEN clause on its own line
38572                if self.config.pretty {
38573                    self.write_newline();
38574                    self.write_indent();
38575                } else {
38576                    self.write_space();
38577                }
38578            }
38579            self.generate_expression(expr)?;
38580        }
38581        Ok(())
38582    }
38583
38584    fn generate_where(&mut self, e: &Where) -> Result<()> {
38585        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38586        self.write_keyword("WHERE");
38587        self.write_space();
38588        self.generate_expression(&e.this)?;
38589        Ok(())
38590    }
38591
38592    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38593        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38594        self.write_keyword("WIDTH_BUCKET");
38595        self.write("(");
38596        self.generate_expression(&e.this)?;
38597        if let Some(min_value) = &e.min_value {
38598            self.write(", ");
38599            self.generate_expression(min_value)?;
38600        }
38601        if let Some(max_value) = &e.max_value {
38602            self.write(", ");
38603            self.generate_expression(max_value)?;
38604        }
38605        if let Some(num_buckets) = &e.num_buckets {
38606            self.write(", ");
38607            self.generate_expression(num_buckets)?;
38608        }
38609        self.write(")");
38610        Ok(())
38611    }
38612
38613    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38614        // Window specification: PARTITION BY ... ORDER BY ... frame
38615        self.generate_window_spec(e)
38616    }
38617
38618    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38619        // Window specification: PARTITION BY ... ORDER BY ... frame
38620        let mut has_content = false;
38621
38622        // PARTITION BY
38623        if !e.partition_by.is_empty() {
38624            self.write_keyword("PARTITION BY");
38625            self.write_space();
38626            for (i, expr) in e.partition_by.iter().enumerate() {
38627                if i > 0 {
38628                    self.write(", ");
38629                }
38630                self.generate_expression(expr)?;
38631            }
38632            has_content = true;
38633        }
38634
38635        // ORDER BY
38636        if !e.order_by.is_empty() {
38637            if has_content {
38638                self.write_space();
38639            }
38640            self.write_keyword("ORDER BY");
38641            self.write_space();
38642            for (i, ordered) in e.order_by.iter().enumerate() {
38643                if i > 0 {
38644                    self.write(", ");
38645                }
38646                self.generate_expression(&ordered.this)?;
38647                if ordered.desc {
38648                    self.write_space();
38649                    self.write_keyword("DESC");
38650                } else if ordered.explicit_asc {
38651                    self.write_space();
38652                    self.write_keyword("ASC");
38653                }
38654                if let Some(nulls_first) = ordered.nulls_first {
38655                    self.write_space();
38656                    self.write_keyword("NULLS");
38657                    self.write_space();
38658                    if nulls_first {
38659                        self.write_keyword("FIRST");
38660                    } else {
38661                        self.write_keyword("LAST");
38662                    }
38663                }
38664            }
38665            has_content = true;
38666        }
38667
38668        // Frame specification
38669        if let Some(frame) = &e.frame {
38670            if has_content {
38671                self.write_space();
38672            }
38673            self.generate_window_frame(frame)?;
38674        }
38675
38676        Ok(())
38677    }
38678
38679    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38680        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38681        self.write_keyword("WITH");
38682        self.write_space();
38683        if e.no.is_some() {
38684            self.write_keyword("NO");
38685            self.write_space();
38686        }
38687        self.write_keyword("DATA");
38688
38689        // statistics
38690        if let Some(statistics) = &e.statistics {
38691            self.write_space();
38692            self.write_keyword("AND");
38693            self.write_space();
38694            // Check if statistics is true or false
38695            match statistics.as_ref() {
38696                Expression::Boolean(b) if !b.value => {
38697                    self.write_keyword("NO");
38698                    self.write_space();
38699                }
38700                _ => {}
38701            }
38702            self.write_keyword("STATISTICS");
38703        }
38704        Ok(())
38705    }
38706
38707    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38708        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38709        self.write_keyword("WITH FILL");
38710
38711        if let Some(from_) = &e.from_ {
38712            self.write_space();
38713            self.write_keyword("FROM");
38714            self.write_space();
38715            self.generate_expression(from_)?;
38716        }
38717
38718        if let Some(to) = &e.to {
38719            self.write_space();
38720            self.write_keyword("TO");
38721            self.write_space();
38722            self.generate_expression(to)?;
38723        }
38724
38725        if let Some(step) = &e.step {
38726            self.write_space();
38727            self.write_keyword("STEP");
38728            self.write_space();
38729            self.generate_expression(step)?;
38730        }
38731
38732        if let Some(staleness) = &e.staleness {
38733            self.write_space();
38734            self.write_keyword("STALENESS");
38735            self.write_space();
38736            self.generate_expression(staleness)?;
38737        }
38738
38739        if let Some(interpolate) = &e.interpolate {
38740            self.write_space();
38741            self.write_keyword("INTERPOLATE");
38742            self.write(" (");
38743            // INTERPOLATE items use reversed alias format: name AS expression
38744            self.generate_interpolate_item(interpolate)?;
38745            self.write(")");
38746        }
38747
38748        Ok(())
38749    }
38750
38751    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38752    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38753        match expr {
38754            Expression::Alias(alias) => {
38755                // Output as: alias_name AS expression
38756                self.generate_identifier(&alias.alias)?;
38757                self.write_space();
38758                self.write_keyword("AS");
38759                self.write_space();
38760                self.generate_expression(&alias.this)?;
38761            }
38762            Expression::Tuple(tuple) => {
38763                for (i, item) in tuple.expressions.iter().enumerate() {
38764                    if i > 0 {
38765                        self.write(", ");
38766                    }
38767                    self.generate_interpolate_item(item)?;
38768                }
38769            }
38770            other => {
38771                self.generate_expression(other)?;
38772            }
38773        }
38774        Ok(())
38775    }
38776
38777    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38778        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38779        self.write_keyword("WITH JOURNAL TABLE");
38780        self.write("=");
38781        self.generate_expression(&e.this)?;
38782        Ok(())
38783    }
38784
38785    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38786        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38787        self.generate_expression(&e.this)?;
38788        self.write_space();
38789        self.write_keyword("WITH");
38790        self.write_space();
38791        self.write_keyword(&e.op);
38792        Ok(())
38793    }
38794
38795    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38796        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38797        self.write_keyword("WITH");
38798        self.write_space();
38799        for (i, expr) in e.expressions.iter().enumerate() {
38800            if i > 0 {
38801                self.write(", ");
38802            }
38803            self.generate_expression(expr)?;
38804        }
38805        Ok(())
38806    }
38807
38808    fn generate_with_schema_binding_property(
38809        &mut self,
38810        e: &WithSchemaBindingProperty,
38811    ) -> Result<()> {
38812        // Python: return f"WITH {self.sql(expression, 'this')}"
38813        self.write_keyword("WITH");
38814        self.write_space();
38815        self.generate_expression(&e.this)?;
38816        Ok(())
38817    }
38818
38819    fn generate_with_system_versioning_property(
38820        &mut self,
38821        e: &WithSystemVersioningProperty,
38822    ) -> Result<()> {
38823        // Python: complex logic for SYSTEM_VERSIONING with options
38824        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38825        // or SYSTEM_VERSIONING=ON/OFF
38826        // with WITH(...) wrapper if with_ is set
38827
38828        let mut parts = Vec::new();
38829
38830        if let Some(this) = &e.this {
38831            // HISTORY_TABLE=...
38832            let mut s = String::from("HISTORY_TABLE=");
38833            let mut gen = Generator::new();
38834            gen.generate_expression(this)?;
38835            s.push_str(&gen.output);
38836            parts.push(s);
38837        }
38838
38839        if let Some(data_consistency) = &e.data_consistency {
38840            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38841            let mut gen = Generator::new();
38842            gen.generate_expression(data_consistency)?;
38843            s.push_str(&gen.output);
38844            parts.push(s);
38845        }
38846
38847        if let Some(retention_period) = &e.retention_period {
38848            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
38849            let mut gen = Generator::new();
38850            gen.generate_expression(retention_period)?;
38851            s.push_str(&gen.output);
38852            parts.push(s);
38853        }
38854
38855        self.write_keyword("SYSTEM_VERSIONING");
38856        self.write("=");
38857
38858        if !parts.is_empty() {
38859            self.write_keyword("ON");
38860            self.write("(");
38861            self.write(&parts.join(", "));
38862            self.write(")");
38863        } else if e.on.is_some() {
38864            self.write_keyword("ON");
38865        } else {
38866            self.write_keyword("OFF");
38867        }
38868
38869        // Wrap in WITH(...) if with_ is set
38870        if e.with_.is_some() {
38871            let inner = self.output.clone();
38872            self.output.clear();
38873            self.write("WITH(");
38874            self.write(&inner);
38875            self.write(")");
38876        }
38877
38878        Ok(())
38879    }
38880
38881    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
38882        // Python: f"WITH ({self.expressions(expression, flat=True)})"
38883        self.write_keyword("WITH");
38884        self.write(" (");
38885        for (i, expr) in e.expressions.iter().enumerate() {
38886            if i > 0 {
38887                self.write(", ");
38888            }
38889            self.generate_expression(expr)?;
38890        }
38891        self.write(")");
38892        Ok(())
38893    }
38894
38895    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
38896        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
38897        // return self.func("XMLELEMENT", name, *expression.expressions)
38898        self.write_keyword("XMLELEMENT");
38899        self.write("(");
38900
38901        if e.evalname.is_some() {
38902            self.write_keyword("EVALNAME");
38903        } else {
38904            self.write_keyword("NAME");
38905        }
38906        self.write_space();
38907        self.generate_expression(&e.this)?;
38908
38909        for expr in &e.expressions {
38910            self.write(", ");
38911            self.generate_expression(expr)?;
38912        }
38913        self.write(")");
38914        Ok(())
38915    }
38916
38917    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38918        // XMLGET(this, expression [, instance])
38919        self.write_keyword("XMLGET");
38920        self.write("(");
38921        self.generate_expression(&e.this)?;
38922        self.write(", ");
38923        self.generate_expression(&e.expression)?;
38924        if let Some(instance) = &e.instance {
38925            self.write(", ");
38926            self.generate_expression(instance)?;
38927        }
38928        self.write(")");
38929        Ok(())
38930    }
38931
38932    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38933        // Python: this + optional (expr)
38934        self.generate_expression(&e.this)?;
38935        if let Some(expression) = &e.expression {
38936            self.write("(");
38937            self.generate_expression(expression)?;
38938            self.write(")");
38939        }
38940        Ok(())
38941    }
38942
38943    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38944        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38945        self.write_keyword("XMLTABLE");
38946        self.write("(");
38947
38948        if self.config.pretty {
38949            self.indent_level += 1;
38950            self.write_newline();
38951            self.write_indent();
38952            self.generate_expression(&e.this)?;
38953
38954            if let Some(passing) = &e.passing {
38955                self.write_newline();
38956                self.write_indent();
38957                self.write_keyword("PASSING");
38958                if let Expression::Tuple(tuple) = passing.as_ref() {
38959                    for expr in &tuple.expressions {
38960                        self.write_newline();
38961                        self.indent_level += 1;
38962                        self.write_indent();
38963                        self.generate_expression(expr)?;
38964                        self.indent_level -= 1;
38965                    }
38966                } else {
38967                    self.write_newline();
38968                    self.indent_level += 1;
38969                    self.write_indent();
38970                    self.generate_expression(passing)?;
38971                    self.indent_level -= 1;
38972                }
38973            }
38974
38975            if e.by_ref.is_some() {
38976                self.write_newline();
38977                self.write_indent();
38978                self.write_keyword("RETURNING SEQUENCE BY REF");
38979            }
38980
38981            if !e.columns.is_empty() {
38982                self.write_newline();
38983                self.write_indent();
38984                self.write_keyword("COLUMNS");
38985                for (i, col) in e.columns.iter().enumerate() {
38986                    self.write_newline();
38987                    self.indent_level += 1;
38988                    self.write_indent();
38989                    self.generate_expression(col)?;
38990                    self.indent_level -= 1;
38991                    if i < e.columns.len() - 1 {
38992                        self.write(",");
38993                    }
38994                }
38995            }
38996
38997            self.indent_level -= 1;
38998            self.write_newline();
38999            self.write_indent();
39000            self.write(")");
39001            return Ok(());
39002        }
39003
39004        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
39005        if let Some(namespaces) = &e.namespaces {
39006            self.write_keyword("XMLNAMESPACES");
39007            self.write("(");
39008            // Unwrap Tuple if present to avoid extra parentheses
39009            if let Expression::Tuple(tuple) = namespaces.as_ref() {
39010                for (i, expr) in tuple.expressions.iter().enumerate() {
39011                    if i > 0 {
39012                        self.write(", ");
39013                    }
39014                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
39015                    // See xmlnamespace_sql in generator.py
39016                    if !matches!(expr, Expression::Alias(_)) {
39017                        self.write_keyword("DEFAULT");
39018                        self.write_space();
39019                    }
39020                    self.generate_expression(expr)?;
39021                }
39022            } else {
39023                // Single namespace - check if DEFAULT
39024                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
39025                    self.write_keyword("DEFAULT");
39026                    self.write_space();
39027                }
39028                self.generate_expression(namespaces)?;
39029            }
39030            self.write("), ");
39031        }
39032
39033        // XPath expression
39034        self.generate_expression(&e.this)?;
39035
39036        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
39037        if let Some(passing) = &e.passing {
39038            self.write_space();
39039            self.write_keyword("PASSING");
39040            self.write_space();
39041            // Unwrap Tuple if present to avoid extra parentheses
39042            if let Expression::Tuple(tuple) = passing.as_ref() {
39043                for (i, expr) in tuple.expressions.iter().enumerate() {
39044                    if i > 0 {
39045                        self.write(", ");
39046                    }
39047                    self.generate_expression(expr)?;
39048                }
39049            } else {
39050                self.generate_expression(passing)?;
39051            }
39052        }
39053
39054        // RETURNING SEQUENCE BY REF
39055        if e.by_ref.is_some() {
39056            self.write_space();
39057            self.write_keyword("RETURNING SEQUENCE BY REF");
39058        }
39059
39060        // COLUMNS clause
39061        if !e.columns.is_empty() {
39062            self.write_space();
39063            self.write_keyword("COLUMNS");
39064            self.write_space();
39065            for (i, col) in e.columns.iter().enumerate() {
39066                if i > 0 {
39067                    self.write(", ");
39068                }
39069                self.generate_expression(col)?;
39070            }
39071        }
39072
39073        self.write(")");
39074        Ok(())
39075    }
39076
39077    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
39078        // Python: return self.connector_sql(expression, "XOR", stack)
39079        // Handles: this XOR expression or expressions joined by XOR
39080        if let Some(this) = &e.this {
39081            self.generate_expression(this)?;
39082            if let Some(expression) = &e.expression {
39083                self.write_space();
39084                self.write_keyword("XOR");
39085                self.write_space();
39086                self.generate_expression(expression)?;
39087            }
39088        }
39089
39090        // Handle multiple expressions
39091        for (i, expr) in e.expressions.iter().enumerate() {
39092            if i > 0 || e.this.is_some() {
39093                self.write_space();
39094                self.write_keyword("XOR");
39095                self.write_space();
39096            }
39097            self.generate_expression(expr)?;
39098        }
39099        Ok(())
39100    }
39101
39102    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
39103        // ZIPF(this, elementcount [, gen])
39104        self.write_keyword("ZIPF");
39105        self.write("(");
39106        self.generate_expression(&e.this)?;
39107        if let Some(elementcount) = &e.elementcount {
39108            self.write(", ");
39109            self.generate_expression(elementcount)?;
39110        }
39111        if let Some(gen) = &e.gen {
39112            self.write(", ");
39113            self.generate_expression(gen)?;
39114        }
39115        self.write(")");
39116        Ok(())
39117    }
39118}
39119
39120impl Default for Generator {
39121    fn default() -> Self {
39122        Self::new()
39123    }
39124}
39125
39126#[cfg(test)]
39127mod tests {
39128    use super::*;
39129    use crate::parser::Parser;
39130
39131    fn roundtrip(sql: &str) -> String {
39132        let ast = Parser::parse_sql(sql).unwrap();
39133        Generator::sql(&ast[0]).unwrap()
39134    }
39135
39136    #[test]
39137    fn test_simple_select() {
39138        let result = roundtrip("SELECT 1");
39139        assert_eq!(result, "SELECT 1");
39140    }
39141
39142    #[test]
39143    fn test_select_from() {
39144        let result = roundtrip("SELECT a, b FROM t");
39145        assert_eq!(result, "SELECT a, b FROM t");
39146    }
39147
39148    #[test]
39149    fn test_select_where() {
39150        let result = roundtrip("SELECT * FROM t WHERE x = 1");
39151        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
39152    }
39153
39154    #[test]
39155    fn test_select_join() {
39156        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
39157        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
39158    }
39159
39160    #[test]
39161    fn test_insert() {
39162        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
39163        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
39164    }
39165
39166    #[test]
39167    fn test_pretty_print() {
39168        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
39169        let result = Generator::pretty_sql(&ast[0]).unwrap();
39170        assert!(result.contains('\n'));
39171    }
39172
39173    #[test]
39174    fn test_window_function() {
39175        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
39176        assert_eq!(
39177            result,
39178            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
39179        );
39180    }
39181
39182    #[test]
39183    fn test_window_function_with_frame() {
39184        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39185        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39186    }
39187
39188    #[test]
39189    fn test_aggregate_with_filter() {
39190        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
39191        assert_eq!(
39192            result,
39193            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
39194        );
39195    }
39196
39197    #[test]
39198    fn test_subscript() {
39199        let result = roundtrip("SELECT arr[0]");
39200        assert_eq!(result, "SELECT arr[0]");
39201    }
39202
39203    // DDL tests
39204    #[test]
39205    fn test_create_table() {
39206        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
39207        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
39208    }
39209
39210    #[test]
39211    fn test_create_table_with_constraints() {
39212        let result = roundtrip(
39213            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
39214        );
39215        assert_eq!(
39216            result,
39217            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
39218        );
39219    }
39220
39221    #[test]
39222    fn test_create_table_if_not_exists() {
39223        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
39224        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
39225    }
39226
39227    #[test]
39228    fn test_drop_table() {
39229        let result = roundtrip("DROP TABLE users");
39230        assert_eq!(result, "DROP TABLE users");
39231    }
39232
39233    #[test]
39234    fn test_drop_table_if_exists_cascade() {
39235        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
39236        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
39237    }
39238
39239    #[test]
39240    fn test_alter_table_add_column() {
39241        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39242        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39243    }
39244
39245    #[test]
39246    fn test_alter_table_drop_column() {
39247        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
39248        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
39249    }
39250
39251    #[test]
39252    fn test_create_index() {
39253        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
39254        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
39255    }
39256
39257    #[test]
39258    fn test_create_unique_index() {
39259        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
39260        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
39261    }
39262
39263    #[test]
39264    fn test_drop_index() {
39265        let result = roundtrip("DROP INDEX idx_name");
39266        assert_eq!(result, "DROP INDEX idx_name");
39267
39268        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
39269        assert_eq!(
39270            result,
39271            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
39272        );
39273
39274        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
39275        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
39276    }
39277
39278    #[test]
39279    fn test_create_view() {
39280        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
39281        assert_eq!(
39282            result,
39283            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
39284        );
39285    }
39286
39287    #[test]
39288    fn test_drop_view() {
39289        let result = roundtrip("DROP VIEW active_users");
39290        assert_eq!(result, "DROP VIEW active_users");
39291    }
39292
39293    #[test]
39294    fn test_truncate() {
39295        let result = roundtrip("TRUNCATE TABLE users");
39296        assert_eq!(result, "TRUNCATE TABLE users");
39297    }
39298
39299    #[test]
39300    fn test_string_literal_escaping_default() {
39301        // Default: double single quotes
39302        let result = roundtrip("SELECT 'hello'");
39303        assert_eq!(result, "SELECT 'hello'");
39304
39305        // Single quotes are doubled
39306        let result = roundtrip("SELECT 'it''s a test'");
39307        assert_eq!(result, "SELECT 'it''s a test'");
39308    }
39309
39310    #[test]
39311    fn test_not_in_style_prefix_default_generic() {
39312        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
39313        assert_eq!(
39314            result,
39315            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
39316        );
39317    }
39318
39319    #[test]
39320    fn test_not_in_style_infix_generic_override() {
39321        let ast =
39322            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
39323                .unwrap();
39324        let config = GeneratorConfig {
39325            not_in_style: NotInStyle::Infix,
39326            ..Default::default()
39327        };
39328        let mut gen = Generator::with_config(config);
39329        let result = gen.generate(&ast[0]).unwrap();
39330        assert_eq!(
39331            result,
39332            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
39333        );
39334    }
39335
39336    #[test]
39337    fn test_string_literal_escaping_mysql() {
39338        use crate::dialects::DialectType;
39339
39340        let config = GeneratorConfig {
39341            dialect: Some(DialectType::MySQL),
39342            ..Default::default()
39343        };
39344
39345        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39346        let mut gen = Generator::with_config(config.clone());
39347        let result = gen.generate(&ast[0]).unwrap();
39348        assert_eq!(result, "SELECT 'hello'");
39349
39350        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
39351        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39352        let mut gen = Generator::with_config(config.clone());
39353        let result = gen.generate(&ast[0]).unwrap();
39354        assert_eq!(result, "SELECT 'it''s'");
39355    }
39356
39357    #[test]
39358    fn test_string_literal_escaping_postgres() {
39359        use crate::dialects::DialectType;
39360
39361        let config = GeneratorConfig {
39362            dialect: Some(DialectType::PostgreSQL),
39363            ..Default::default()
39364        };
39365
39366        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39367        let mut gen = Generator::with_config(config.clone());
39368        let result = gen.generate(&ast[0]).unwrap();
39369        assert_eq!(result, "SELECT 'hello'");
39370
39371        // PostgreSQL uses doubled quotes for regular strings
39372        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39373        let mut gen = Generator::with_config(config.clone());
39374        let result = gen.generate(&ast[0]).unwrap();
39375        assert_eq!(result, "SELECT 'it''s'");
39376    }
39377
39378    #[test]
39379    fn test_string_literal_escaping_bigquery() {
39380        use crate::dialects::DialectType;
39381
39382        let config = GeneratorConfig {
39383            dialect: Some(DialectType::BigQuery),
39384            ..Default::default()
39385        };
39386
39387        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39388        let mut gen = Generator::with_config(config.clone());
39389        let result = gen.generate(&ast[0]).unwrap();
39390        assert_eq!(result, "SELECT 'hello'");
39391
39392        // BigQuery escapes single quotes with backslash
39393        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39394        let mut gen = Generator::with_config(config.clone());
39395        let result = gen.generate(&ast[0]).unwrap();
39396        assert_eq!(result, "SELECT 'it\\'s'");
39397    }
39398
39399    #[test]
39400    fn test_generate_deep_and_chain_without_stack_growth() {
39401        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39402            Expression::column("c0"),
39403            Expression::number(0),
39404        )));
39405
39406        for i in 1..2500 {
39407            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39408                Expression::column(format!("c{i}")),
39409                Expression::number(i as i64),
39410            )));
39411            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
39412        }
39413
39414        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
39415        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39416    }
39417
39418    #[test]
39419    fn test_generate_deep_or_chain_without_stack_growth() {
39420        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39421            Expression::column("c0"),
39422            Expression::number(0),
39423        )));
39424
39425        for i in 1..2500 {
39426            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39427                Expression::column(format!("c{i}")),
39428                Expression::number(i as i64),
39429            )));
39430            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
39431        }
39432
39433        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
39434        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39435    }
39436}