Skip to main content

polyglot_sql/
generator.rs

1//! SQL Generator -- converts an AST back into SQL strings.
2//!
3//! The central type is [`Generator`], which walks an [`Expression`] tree and
4//! emits a SQL string. Generation is controlled by a [`GeneratorConfig`] that
5//! specifies the target dialect, formatting preferences, identifier quoting
6//! style, function name casing, and many other dialect-specific flags.
7//!
8//! For one-off generation the static helpers [`Generator::sql`] and
9//! [`Generator::pretty_sql`] are the simplest entry points. For repeated
10//! generation, construct a `Generator` once with [`Generator::with_config`]
11//! and call [`Generator::generate`] for each expression.
12
13use std::borrow::Cow;
14use std::sync::Arc;
15
16use crate::error::Result;
17use crate::expressions::*;
18use crate::guard::{enforce_generate_ast, ComplexityGuardOptions};
19use crate::DialectType;
20use serde::{Deserialize, Serialize};
21
22/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
23///
24/// The generator walks the expression tree and emits dialect-specific SQL text.
25/// It supports pretty-printing with configurable indentation, identifier quoting,
26/// keyword casing, function name normalization, and more than 30 SQL dialects.
27///
28/// # Usage
29///
30/// ```rust,ignore
31/// use polyglot_sql::generator::Generator;
32/// use polyglot_sql::parser::Parser;
33///
34/// let ast = Parser::parse_sql("SELECT 1")?;
35/// // Quick one-shot generation (default config):
36/// let sql = Generator::sql(&ast[0])?;
37///
38/// // Pretty-printed output:
39/// let pretty = Generator::pretty_sql(&ast[0])?;
40///
41/// // Custom config (e.g. for a specific dialect):
42/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
43/// let mut gen = Generator::with_config(config);
44/// let sql = gen.generate(&ast[0])?;
45/// ```
46pub struct Generator {
47    config: Arc<GeneratorConfig>,
48    output: String,
49    unsupported_messages: Vec<String>,
50    indent_level: usize,
51    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
52    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
53    athena_hive_context: bool,
54    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
55    sqlite_inline_pk_columns: std::collections::HashSet<String>,
56    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
57    merge_strip_qualifiers: Vec<String>,
58    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
59    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
60    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
61    /// Negative values indicate map key context (should NOT be wrapped).
62    clickhouse_nullable_depth: i32,
63}
64
65/// Controls how SQL function names are cased in generated output.
66///
67/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
68/// - `Lower` -- `count`, `sum`, `coalesce`
69/// - `None` -- preserve the original casing from the parsed input
70#[derive(Debug, Clone, Copy, PartialEq, Default)]
71pub enum NormalizeFunctions {
72    /// Emit function names in UPPER CASE (default).
73    #[default]
74    Upper,
75    /// Emit function names in lower case.
76    Lower,
77    /// Preserve the original casing from the parsed input.
78    None,
79}
80
81/// Strategy for generating row-limiting clauses across SQL dialects.
82#[derive(Debug, Clone, Copy, PartialEq, Default)]
83pub enum LimitFetchStyle {
84    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
85    #[default]
86    Limit,
87    /// `TOP n` -- TSQL (SQL Server).
88    Top,
89    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
90    FetchFirst,
91}
92
93/// Strategy for rendering negated IN predicates.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum NotInStyle {
96    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
97    #[default]
98    Prefix,
99    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
100    Infix,
101}
102
103/// Controls how the generator reacts when it encounters unsupported output.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum UnsupportedLevel {
107    /// Ignore unsupported diagnostics and continue generation.
108    Ignore,
109    /// Collect unsupported diagnostics and continue generation.
110    #[default]
111    Warn,
112    /// Collect unsupported diagnostics and raise after generation completes.
113    Raise,
114    /// Raise immediately when the first unsupported feature is encountered.
115    Immediate,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum ConnectorOperator {
120    And,
121    Or,
122}
123
124impl ConnectorOperator {
125    fn keyword(self) -> &'static str {
126        match self {
127            Self::And => "AND",
128            Self::Or => "OR",
129        }
130    }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134enum TsqlDatePart {
135    Native(String),
136    Epoch,
137    IsoDayOfWeek,
138    Unsupported(String),
139}
140
141/// Identifier quote style (start/end characters)
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct IdentifierQuoteStyle {
144    /// Start character for quoting identifiers (e.g., '"', '`', '[')
145    pub start: char,
146    /// End character for quoting identifiers (e.g., '"', '`', ']')
147    pub end: char,
148}
149
150impl Default for IdentifierQuoteStyle {
151    fn default() -> Self {
152        Self {
153            start: '"',
154            end: '"',
155        }
156    }
157}
158
159impl IdentifierQuoteStyle {
160    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
161    pub const DOUBLE_QUOTE: Self = Self {
162        start: '"',
163        end: '"',
164    };
165    /// Backtick style (MySQL, BigQuery, Spark, Hive)
166    pub const BACKTICK: Self = Self {
167        start: '`',
168        end: '`',
169    };
170    /// Square bracket style (TSQL, SQLite)
171    pub const BRACKET: Self = Self {
172        start: '[',
173        end: ']',
174    };
175}
176
177/// Configuration for the SQL [`Generator`].
178///
179/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
180/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
181/// syntax, feature support flags, and more.
182///
183/// Most users should start from `GeneratorConfig::default()` and override only the
184/// fields they need. Dialect-specific presets are applied automatically when
185/// `dialect` is set via the higher-level transpilation API.
186///
187/// # Key fields
188///
189/// | Field | Default | Purpose |
190/// |-------|---------|---------|
191/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
192/// | `pretty` | `false` | Enable multi-line, indented output |
193/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
194/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
195/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
196/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
197/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
198#[derive(Debug, Clone)]
199pub struct GeneratorConfig {
200    // ===== Basic formatting =====
201    /// Pretty print with indentation
202    pub pretty: bool,
203    /// Indentation string (default 2 spaces)
204    pub indent: &'static str,
205    /// Maximum text width before wrapping (default 80)
206    pub max_text_width: usize,
207    /// Quote identifier style (deprecated, use identifier_quote_style instead)
208    pub identifier_quote: char,
209    /// Identifier quote style with separate start/end characters
210    pub identifier_quote_style: IdentifierQuoteStyle,
211    /// Uppercase keywords
212    pub uppercase_keywords: bool,
213    /// Normalize identifiers to lowercase when generating
214    pub normalize_identifiers: bool,
215    /// Dialect type for dialect-specific generation
216    pub dialect: Option<crate::dialects::DialectType>,
217    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
218    pub source_dialect: Option<crate::dialects::DialectType>,
219    /// How unsupported generation should be handled.
220    pub unsupported_level: UnsupportedLevel,
221    /// Maximum number of unsupported diagnostics to include in raised errors.
222    pub max_unsupported: usize,
223    /// Complexity guard limits for recursive generation paths.
224    pub complexity_guard: ComplexityGuardOptions,
225    /// How to output function names (UPPER, lower, or as-is)
226    pub normalize_functions: NormalizeFunctions,
227    /// String escape character
228    pub string_escape: char,
229    /// Whether identifiers are case-sensitive
230    pub case_sensitive_identifiers: bool,
231    /// Whether unquoted identifiers can start with a digit
232    pub identifiers_can_start_with_digit: bool,
233    /// Whether to always quote identifiers regardless of reserved keyword status
234    /// Used by dialects like Athena/Presto that prefer quoted identifiers
235    pub always_quote_identifiers: bool,
236    /// How to render negated IN predicates in generic output.
237    pub not_in_style: NotInStyle,
238
239    // ===== Null handling =====
240    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
241    /// True: Full Support, false: No support
242    pub null_ordering_supported: bool,
243    /// Whether ignore nulls is inside the agg or outside
244    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
245    pub ignore_nulls_in_func: bool,
246    /// Whether the NVL2 function is supported
247    pub nvl2_supported: bool,
248
249    // ===== Limit/Fetch =====
250    /// How to output LIMIT clauses
251    pub limit_fetch_style: LimitFetchStyle,
252    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
253    pub limit_is_top: bool,
254    /// Whether limit and fetch allows expressions or just literals
255    pub limit_only_literals: bool,
256
257    // ===== Interval =====
258    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
259    pub single_string_interval: bool,
260    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
261    pub interval_allows_plural_form: bool,
262
263    // ===== CTE =====
264    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
265    pub cte_recursive_keyword_required: bool,
266
267    // ===== VALUES =====
268    /// Whether VALUES can be used as a table source
269    pub values_as_table: bool,
270    /// Wrap derived values in parens (standard but Spark doesn't support)
271    pub wrap_derived_values: bool,
272
273    // ===== TABLESAMPLE =====
274    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
275    pub tablesample_seed_keyword: &'static str,
276    /// Whether parentheses are required around the table sample's expression
277    pub tablesample_requires_parens: bool,
278    /// Whether a table sample clause's size needs to be followed by ROWS keyword
279    pub tablesample_size_is_rows: bool,
280    /// The keyword(s) to use when generating a sample clause
281    pub tablesample_keywords: &'static str,
282    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
283    pub tablesample_with_method: bool,
284    /// Whether the table alias comes after tablesample (Oracle, Hive)
285    pub alias_post_tablesample: bool,
286
287    // ===== Aggregate =====
288    /// Whether aggregate FILTER (WHERE ...) is supported
289    pub aggregate_filter_supported: bool,
290    /// Whether DISTINCT can be followed by multiple args in an AggFunc
291    pub multi_arg_distinct: bool,
292    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
293    pub quantified_no_paren_space: bool,
294    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
295    pub supports_median: bool,
296
297    // ===== SELECT =====
298    /// Whether SELECT ... INTO is supported
299    pub supports_select_into: bool,
300    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
301    pub locking_reads_supported: bool,
302
303    // ===== Table/Join =====
304    /// Whether a table is allowed to be renamed with a db
305    pub rename_table_with_db: bool,
306    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
307    pub semi_anti_join_with_side: bool,
308    /// Whether named columns are allowed in table aliases
309    pub supports_table_alias_columns: bool,
310    /// Whether join hints should be generated
311    pub join_hints: bool,
312    /// Whether table hints should be generated
313    pub table_hints: bool,
314    /// Whether query hints should be generated
315    pub query_hints: bool,
316    /// What kind of separator to use for query hints
317    pub query_hint_sep: &'static str,
318    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
319    pub supports_column_join_marks: bool,
320
321    // ===== DDL =====
322    /// Whether CREATE INDEX USING method should have no space before column parens
323    /// true: `USING btree(col)`, false: `USING btree (col)`
324    pub index_using_no_space: bool,
325    /// Whether UNLOGGED tables can be created
326    pub supports_unlogged_tables: bool,
327    /// Whether CREATE TABLE LIKE statement is supported
328    pub supports_create_table_like: bool,
329    /// Whether the LikeProperty needs to be inside the schema clause
330    pub like_property_inside_schema: bool,
331    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
332    pub alter_table_include_column_keyword: bool,
333    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
334    pub supports_table_copy: bool,
335    /// The syntax to use when altering the type of a column
336    pub alter_set_type: &'static str,
337    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
338    pub alter_set_wrapped: bool,
339
340    // ===== Timestamp/Timezone =====
341    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
342    pub tz_to_with_time_zone: bool,
343    /// Whether CONVERT_TIMEZONE() is supported
344    pub supports_convert_timezone: bool,
345
346    // ===== JSON =====
347    /// Whether the JSON extraction operators expect a value of type JSON
348    pub json_type_required_for_extraction: bool,
349    /// Whether bracketed keys like ["foo"] are supported in JSON paths
350    pub json_path_bracketed_key_supported: bool,
351    /// Whether to escape keys using single quotes in JSON paths
352    pub json_path_single_quote_escape: bool,
353    /// Whether to quote the generated expression of JsonPath
354    pub quote_json_path: bool,
355    /// What delimiter to use for separating JSON key/value pairs
356    pub json_key_value_pair_sep: &'static str,
357
358    // ===== COPY =====
359    /// Whether parameters from COPY statement are wrapped in parentheses
360    pub copy_params_are_wrapped: bool,
361    /// Whether values of params are set with "=" token or empty space
362    pub copy_params_eq_required: bool,
363    /// Whether COPY statement has INTO keyword
364    pub copy_has_into_keyword: bool,
365
366    // ===== Window functions =====
367    /// Whether EXCLUDE in window specification is supported
368    pub supports_window_exclude: bool,
369    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
370    pub unnest_with_ordinality: bool,
371    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
372    /// Exasol uses lowercase for these specific keywords
373    pub lowercase_window_frame_keywords: bool,
374    /// Whether to normalize single-bound window frames to BETWEEN form
375    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
376    pub normalize_window_frame_between: bool,
377
378    // ===== Array =====
379    /// Whether ARRAY_CONCAT can be generated with varlen args
380    pub array_concat_is_var_len: bool,
381    /// Whether exp.ArraySize should generate the dimension arg too
382    /// None -> Doesn't support, false -> optional, true -> required
383    pub array_size_dim_required: Option<bool>,
384    /// Whether any(f(x) for x in array) can be implemented
385    pub can_implement_array_any: bool,
386    /// Function used for array size
387    pub array_size_name: &'static str,
388
389    // ===== BETWEEN =====
390    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
391    pub supports_between_flags: bool,
392
393    // ===== Boolean =====
394    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
395    pub is_bool_allowed: bool,
396    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
397    pub ensure_bools: bool,
398
399    // ===== EXTRACT =====
400    /// Whether to generate an unquoted value for EXTRACT's date part argument
401    pub extract_allows_quotes: bool,
402    /// Whether to normalize date parts in EXTRACT
403    pub normalize_extract_date_parts: bool,
404
405    // ===== Other features =====
406    /// Whether the conditional TRY(expression) function is supported
407    pub try_supported: bool,
408    /// Whether the UESCAPE syntax in unicode strings is supported
409    pub supports_uescape: bool,
410    /// Whether the function TO_NUMBER is supported
411    pub supports_to_number: bool,
412    /// Whether CONCAT requires >1 arguments
413    pub supports_single_arg_concat: bool,
414    /// Whether LAST_DAY function supports a date part argument
415    pub last_day_supports_date_part: bool,
416    /// Whether a projection can explode into multiple rows
417    pub supports_exploding_projections: bool,
418    /// Whether UNIX_SECONDS(timestamp) is supported
419    pub supports_unix_seconds: bool,
420    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
421    pub supports_like_quantifiers: bool,
422    /// Whether multi-argument DECODE(...) function is supported
423    pub supports_decode_case: bool,
424    /// Whether set op modifiers apply to the outer set op or select
425    pub set_op_modifiers: bool,
426    /// Whether FROM is supported in UPDATE statements
427    pub update_statement_supports_from: bool,
428
429    // ===== COLLATE =====
430    /// Whether COLLATE is a function instead of a binary operator
431    pub collate_is_func: bool,
432
433    // ===== INSERT =====
434    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
435    pub duplicate_key_update_with_set: bool,
436    /// INSERT OVERWRITE TABLE x override
437    pub insert_overwrite: &'static str,
438
439    // ===== RETURNING =====
440    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
441    pub returning_end: bool,
442
443    // ===== MERGE =====
444    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
445    pub matched_by_source: bool,
446
447    // ===== CREATE FUNCTION =====
448    /// Whether create function uses an AS before the RETURN
449    pub create_function_return_as: bool,
450    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
451    pub parameter_default_equals: bool,
452
453    // ===== COMPUTED COLUMN =====
454    /// Whether to include the type of a computed column in the CREATE DDL
455    pub computed_column_with_type: bool,
456
457    // ===== UNPIVOT =====
458    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
459    pub unpivot_aliases_are_identifiers: bool,
460
461    // ===== STAR =====
462    /// The keyword to use when generating a star projection with excluded columns
463    pub star_except: &'static str,
464
465    // ===== HEX =====
466    /// The HEX function name
467    pub hex_func: &'static str,
468
469    // ===== WITH =====
470    /// The keywords to use when prefixing WITH based properties
471    pub with_properties_prefix: &'static str,
472
473    // ===== PAD =====
474    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
475    pub pad_fill_pattern_is_required: bool,
476
477    // ===== INDEX =====
478    /// The string used for creating an index on a table
479    pub index_on: &'static str,
480
481    // ===== GROUPING =====
482    /// The separator for grouping sets and rollups
483    pub groupings_sep: &'static str,
484
485    // ===== STRUCT =====
486    /// Delimiters for STRUCT type
487    pub struct_delimiter: (&'static str, &'static str),
488    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
489    pub struct_curly_brace_notation: bool,
490    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
491    pub array_bracket_only: bool,
492    /// Separator between struct field name and type (": " for Hive, " " for others)
493    pub struct_field_sep: &'static str,
494
495    // ===== EXCEPT/INTERSECT =====
496    /// Whether EXCEPT and INTERSECT operations can return duplicates
497    pub except_intersect_support_all_clause: bool,
498
499    // ===== PARAMETERS/PLACEHOLDERS =====
500    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
501    pub parameter_token: &'static str,
502    /// Named placeholder token (: for most, % for PostgreSQL)
503    pub named_placeholder_token: &'static str,
504
505    // ===== DATA TYPES =====
506    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
507    pub data_type_specifiers_allowed: bool,
508
509    // ===== COMMENT =====
510    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
511    /// StarRocks and Doris use naked COMMENT syntax without `=`
512    pub schema_comment_with_eq: bool,
513}
514
515impl Default for GeneratorConfig {
516    fn default() -> Self {
517        Self {
518            // ===== Basic formatting =====
519            pretty: false,
520            indent: "  ",
521            max_text_width: 80,
522            identifier_quote: '"',
523            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
524            uppercase_keywords: true,
525            normalize_identifiers: false,
526            dialect: None,
527            source_dialect: None,
528            unsupported_level: UnsupportedLevel::Warn,
529            max_unsupported: 3,
530            complexity_guard: ComplexityGuardOptions::default(),
531            normalize_functions: NormalizeFunctions::Upper,
532            string_escape: '\'',
533            case_sensitive_identifiers: false,
534            identifiers_can_start_with_digit: false,
535            always_quote_identifiers: false,
536            not_in_style: NotInStyle::Prefix,
537
538            // ===== Null handling =====
539            null_ordering_supported: true,
540            ignore_nulls_in_func: false,
541            nvl2_supported: true,
542
543            // ===== Limit/Fetch =====
544            limit_fetch_style: LimitFetchStyle::Limit,
545            limit_is_top: false,
546            limit_only_literals: false,
547
548            // ===== Interval =====
549            single_string_interval: false,
550            interval_allows_plural_form: true,
551
552            // ===== CTE =====
553            cte_recursive_keyword_required: true,
554
555            // ===== VALUES =====
556            values_as_table: true,
557            wrap_derived_values: true,
558
559            // ===== TABLESAMPLE =====
560            tablesample_seed_keyword: "SEED",
561            tablesample_requires_parens: true,
562            tablesample_size_is_rows: true,
563            tablesample_keywords: "TABLESAMPLE",
564            tablesample_with_method: true,
565            alias_post_tablesample: false,
566
567            // ===== Aggregate =====
568            aggregate_filter_supported: true,
569            multi_arg_distinct: true,
570            quantified_no_paren_space: false,
571            supports_median: true,
572
573            // ===== SELECT =====
574            supports_select_into: false,
575            locking_reads_supported: true,
576
577            // ===== Table/Join =====
578            rename_table_with_db: true,
579            semi_anti_join_with_side: true,
580            supports_table_alias_columns: true,
581            join_hints: true,
582            table_hints: true,
583            query_hints: true,
584            query_hint_sep: ", ",
585            supports_column_join_marks: false,
586
587            // ===== DDL =====
588            index_using_no_space: false,
589            supports_unlogged_tables: false,
590            supports_create_table_like: true,
591            like_property_inside_schema: false,
592            alter_table_include_column_keyword: true,
593            supports_table_copy: true,
594            alter_set_type: "SET DATA TYPE",
595            alter_set_wrapped: false,
596
597            // ===== Timestamp/Timezone =====
598            tz_to_with_time_zone: false,
599            supports_convert_timezone: false,
600
601            // ===== JSON =====
602            json_type_required_for_extraction: false,
603            json_path_bracketed_key_supported: true,
604            json_path_single_quote_escape: false,
605            quote_json_path: true,
606            json_key_value_pair_sep: ":",
607
608            // ===== COPY =====
609            copy_params_are_wrapped: true,
610            copy_params_eq_required: false,
611            copy_has_into_keyword: true,
612
613            // ===== Window functions =====
614            supports_window_exclude: false,
615            unnest_with_ordinality: true,
616            lowercase_window_frame_keywords: false,
617            normalize_window_frame_between: false,
618
619            // ===== Array =====
620            array_concat_is_var_len: true,
621            array_size_dim_required: None,
622            can_implement_array_any: false,
623            array_size_name: "ARRAY_LENGTH",
624
625            // ===== BETWEEN =====
626            supports_between_flags: false,
627
628            // ===== Boolean =====
629            is_bool_allowed: true,
630            ensure_bools: false,
631
632            // ===== EXTRACT =====
633            extract_allows_quotes: true,
634            normalize_extract_date_parts: false,
635
636            // ===== Other features =====
637            try_supported: true,
638            supports_uescape: true,
639            supports_to_number: true,
640            supports_single_arg_concat: true,
641            last_day_supports_date_part: true,
642            supports_exploding_projections: true,
643            supports_unix_seconds: false,
644            supports_like_quantifiers: true,
645            supports_decode_case: true,
646            set_op_modifiers: true,
647            update_statement_supports_from: true,
648
649            // ===== COLLATE =====
650            collate_is_func: false,
651
652            // ===== INSERT =====
653            duplicate_key_update_with_set: true,
654            insert_overwrite: " OVERWRITE TABLE",
655
656            // ===== RETURNING =====
657            returning_end: true,
658
659            // ===== MERGE =====
660            matched_by_source: true,
661
662            // ===== CREATE FUNCTION =====
663            create_function_return_as: true,
664            parameter_default_equals: false,
665
666            // ===== COMPUTED COLUMN =====
667            computed_column_with_type: true,
668
669            // ===== UNPIVOT =====
670            unpivot_aliases_are_identifiers: true,
671
672            // ===== STAR =====
673            star_except: "EXCEPT",
674
675            // ===== HEX =====
676            hex_func: "HEX",
677
678            // ===== WITH =====
679            with_properties_prefix: "WITH",
680
681            // ===== PAD =====
682            pad_fill_pattern_is_required: false,
683
684            // ===== INDEX =====
685            index_on: "ON",
686
687            // ===== GROUPING =====
688            groupings_sep: ",",
689
690            // ===== STRUCT =====
691            struct_delimiter: ("<", ">"),
692            struct_curly_brace_notation: false,
693            array_bracket_only: false,
694            struct_field_sep: " ",
695
696            // ===== EXCEPT/INTERSECT =====
697            except_intersect_support_all_clause: true,
698
699            // ===== PARAMETERS/PLACEHOLDERS =====
700            parameter_token: "@",
701            named_placeholder_token: ":",
702
703            // ===== DATA TYPES =====
704            data_type_specifiers_allowed: false,
705
706            // ===== COMMENT =====
707            schema_comment_with_eq: true,
708        }
709    }
710}
711
712/// SQL reserved keywords that require quoting when used as identifiers
713/// Based on ANSI SQL standards and common dialect-specific reserved words
714mod reserved_keywords {
715    use std::collections::HashSet;
716    use std::sync::LazyLock;
717
718    /// Standard SQL reserved keywords (ANSI SQL:2016)
719    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
720        [
721            "all",
722            "alter",
723            "and",
724            "any",
725            "array",
726            "as",
727            "asc",
728            "at",
729            "authorization",
730            "begin",
731            "between",
732            "both",
733            "by",
734            "case",
735            "cast",
736            "check",
737            "collate",
738            "column",
739            "commit",
740            "constraint",
741            "create",
742            "cross",
743            "cube",
744            "current",
745            "current_date",
746            "current_time",
747            "current_timestamp",
748            "current_user",
749            "default",
750            "delete",
751            "desc",
752            "distinct",
753            "drop",
754            "else",
755            "end",
756            "escape",
757            "except",
758            "execute",
759            "exists",
760            "external",
761            "false",
762            "fetch",
763            "filter",
764            "for",
765            "foreign",
766            "from",
767            "full",
768            "function",
769            "grant",
770            "group",
771            "grouping",
772            "having",
773            "if",
774            "in",
775            "index",
776            "inner",
777            "insert",
778            "intersect",
779            "interval",
780            "into",
781            "is",
782            "join",
783            "key",
784            "leading",
785            "left",
786            "like",
787            "limit",
788            "local",
789            "localtime",
790            "localtimestamp",
791            "match",
792            "merge",
793            "natural",
794            "no",
795            "not",
796            "null",
797            "of",
798            "offset",
799            "on",
800            "only",
801            "or",
802            "order",
803            "outer",
804            "over",
805            "partition",
806            "primary",
807            "procedure",
808            "range",
809            "references",
810            "right",
811            "rollback",
812            "rollup",
813            "row",
814            "rows",
815            "select",
816            "session_user",
817            "set",
818            "some",
819            "table",
820            "tablesample",
821            "then",
822            "to",
823            "trailing",
824            "true",
825            "truncate",
826            "union",
827            "unique",
828            "unknown",
829            "update",
830            "user",
831            "using",
832            "values",
833            "view",
834            "when",
835            "where",
836            "window",
837            "with",
838        ]
839        .into_iter()
840        .collect()
841    });
842
843    /// BigQuery-specific reserved keywords
844    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
845    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
846        let mut set = SQL_RESERVED.clone();
847        set.extend([
848            "assert_rows_modified",
849            "at",
850            "contains",
851            "cube",
852            "current",
853            "define",
854            "enum",
855            "escape",
856            "exclude",
857            "following",
858            "for",
859            "groups",
860            "hash",
861            "ignore",
862            "lateral",
863            "lookup",
864            "new",
865            "no",
866            "nulls",
867            "of",
868            "over",
869            "preceding",
870            "proto",
871            "qualify",
872            "recursive",
873            "respect",
874            "struct",
875            "tablesample",
876            "treat",
877            "unbounded",
878            "unnest",
879            "window",
880            "within",
881        ]);
882        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
883        set.remove("grant");
884        set.remove("key");
885        set.remove("index");
886        set.remove("offset");
887        set.remove("values");
888        set.remove("table");
889        set
890    });
891
892    /// MySQL-specific reserved keywords
893    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
894        let mut set = SQL_RESERVED.clone();
895        set.extend([
896            "accessible",
897            "add",
898            "analyze",
899            "asensitive",
900            "before",
901            "bigint",
902            "binary",
903            "blob",
904            "call",
905            "cascade",
906            "change",
907            "char",
908            "character",
909            "condition",
910            "continue",
911            "convert",
912            "current_date",
913            "current_time",
914            "current_timestamp",
915            "current_user",
916            "cursor",
917            "database",
918            "databases",
919            "day_hour",
920            "day_microsecond",
921            "day_minute",
922            "day_second",
923            "dec",
924            "decimal",
925            "declare",
926            "delayed",
927            "describe",
928            "deterministic",
929            "distinctrow",
930            "div",
931            "double",
932            "dual",
933            "each",
934            "elseif",
935            "enclosed",
936            "escaped",
937            "exit",
938            "explain",
939            "float",
940            "float4",
941            "float8",
942            "force",
943            "get",
944            "high_priority",
945            "hour_microsecond",
946            "hour_minute",
947            "hour_second",
948            "ignore",
949            "infile",
950            "inout",
951            "insensitive",
952            "int",
953            "int1",
954            "int2",
955            "int3",
956            "int4",
957            "int8",
958            "integer",
959            "iterate",
960            "keys",
961            "kill",
962            "leave",
963            "linear",
964            "lines",
965            "load",
966            "lock",
967            "long",
968            "longblob",
969            "longtext",
970            "loop",
971            "low_priority",
972            "master_ssl_verify_server_cert",
973            "maxvalue",
974            "mediumblob",
975            "mediumint",
976            "mediumtext",
977            "middleint",
978            "minute_microsecond",
979            "minute_second",
980            "mod",
981            "modifies",
982            "no_write_to_binlog",
983            "numeric",
984            "optimize",
985            "option",
986            "optionally",
987            "out",
988            "outfile",
989            "precision",
990            "purge",
991            "read",
992            "reads",
993            "real",
994            "regexp",
995            "release",
996            "rename",
997            "repeat",
998            "replace",
999            "require",
1000            "resignal",
1001            "restrict",
1002            "return",
1003            "revoke",
1004            "rlike",
1005            "schema",
1006            "schemas",
1007            "second_microsecond",
1008            "sensitive",
1009            "separator",
1010            "show",
1011            "signal",
1012            "smallint",
1013            "spatial",
1014            "specific",
1015            "sql",
1016            "sql_big_result",
1017            "sql_calc_found_rows",
1018            "sql_small_result",
1019            "sqlexception",
1020            "sqlstate",
1021            "sqlwarning",
1022            "ssl",
1023            "starting",
1024            "straight_join",
1025            "terminated",
1026            "text",
1027            "tinyblob",
1028            "tinyint",
1029            "tinytext",
1030            "trigger",
1031            "undo",
1032            "unlock",
1033            "unsigned",
1034            "usage",
1035            "utc_date",
1036            "utc_time",
1037            "utc_timestamp",
1038            "varbinary",
1039            "varchar",
1040            "varcharacter",
1041            "varying",
1042            "while",
1043            "write",
1044            "xor",
1045            "year_month",
1046            "zerofill",
1047        ]);
1048        set.remove("table");
1049        set
1050    });
1051
1052    /// Doris-specific reserved keywords
1053    /// Extends MySQL reserved with additional Doris-specific words
1054    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1055        let mut set = MYSQL_RESERVED.clone();
1056        set.extend([
1057            "aggregate",
1058            "anti",
1059            "array",
1060            "backend",
1061            "backup",
1062            "begin",
1063            "bitmap",
1064            "boolean",
1065            "broker",
1066            "buckets",
1067            "cached",
1068            "cancel",
1069            "cast",
1070            "catalog",
1071            "charset",
1072            "cluster",
1073            "collation",
1074            "columns",
1075            "comment",
1076            "commit",
1077            "config",
1078            "connection",
1079            "count",
1080            "current",
1081            "data",
1082            "date",
1083            "datetime",
1084            "day",
1085            "deferred",
1086            "distributed",
1087            "dynamic",
1088            "enable",
1089            "end",
1090            "events",
1091            "export",
1092            "external",
1093            "fields",
1094            "first",
1095            "follower",
1096            "format",
1097            "free",
1098            "frontend",
1099            "full",
1100            "functions",
1101            "global",
1102            "grants",
1103            "hash",
1104            "help",
1105            "hour",
1106            "install",
1107            "intermediate",
1108            "json",
1109            "label",
1110            "last",
1111            "less",
1112            "level",
1113            "link",
1114            "local",
1115            "location",
1116            "max",
1117            "merge",
1118            "min",
1119            "minute",
1120            "modify",
1121            "month",
1122            "name",
1123            "names",
1124            "negative",
1125            "nulls",
1126            "observer",
1127            "offset",
1128            "only",
1129            "open",
1130            "overwrite",
1131            "password",
1132            "path",
1133            "plan",
1134            "plugin",
1135            "plugins",
1136            "policy",
1137            "process",
1138            "properties",
1139            "property",
1140            "query",
1141            "quota",
1142            "recover",
1143            "refresh",
1144            "repair",
1145            "replica",
1146            "repository",
1147            "resource",
1148            "restore",
1149            "resume",
1150            "role",
1151            "roles",
1152            "rollback",
1153            "rollup",
1154            "routine",
1155            "sample",
1156            "second",
1157            "semi",
1158            "session",
1159            "signed",
1160            "snapshot",
1161            "start",
1162            "stats",
1163            "status",
1164            "stop",
1165            "stream",
1166            "string",
1167            "sum",
1168            "tables",
1169            "tablet",
1170            "temporary",
1171            "text",
1172            "timestamp",
1173            "transaction",
1174            "trash",
1175            "trim",
1176            "truncate",
1177            "type",
1178            "user",
1179            "value",
1180            "variables",
1181            "verbose",
1182            "version",
1183            "view",
1184            "warnings",
1185            "week",
1186            "work",
1187            "year",
1188        ]);
1189        set
1190    });
1191
1192    /// PostgreSQL-specific reserved keywords
1193    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1194        let mut set = SQL_RESERVED.clone();
1195        set.extend([
1196            "analyse",
1197            "analyze",
1198            "asymmetric",
1199            "binary",
1200            "collation",
1201            "concurrently",
1202            "current_catalog",
1203            "current_role",
1204            "current_schema",
1205            "deferrable",
1206            "do",
1207            "freeze",
1208            "ilike",
1209            "initially",
1210            "isnull",
1211            "lateral",
1212            "notnull",
1213            "placing",
1214            "returning",
1215            "similar",
1216            "symmetric",
1217            "variadic",
1218            "verbose",
1219        ]);
1220        // PostgreSQL doesn't require quoting for these keywords
1221        set.remove("default");
1222        set.remove("interval");
1223        set.remove("match");
1224        set.remove("offset");
1225        set.remove("table");
1226        set
1227    });
1228
1229    /// Redshift-specific reserved keywords
1230    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1231    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1232    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1233        [
1234            "aes128",
1235            "aes256",
1236            "all",
1237            "allowoverwrite",
1238            "analyse",
1239            "analyze",
1240            "and",
1241            "any",
1242            "array",
1243            "as",
1244            "asc",
1245            "authorization",
1246            "az64",
1247            "backup",
1248            "between",
1249            "binary",
1250            "blanksasnull",
1251            "both",
1252            "bytedict",
1253            "bzip2",
1254            "case",
1255            "cast",
1256            "check",
1257            "collate",
1258            "column",
1259            "constraint",
1260            "create",
1261            "credentials",
1262            "cross",
1263            "current_date",
1264            "current_time",
1265            "current_timestamp",
1266            "current_user",
1267            "current_user_id",
1268            "default",
1269            "deferrable",
1270            "deflate",
1271            "defrag",
1272            "delta",
1273            "delta32k",
1274            "desc",
1275            "disable",
1276            "distinct",
1277            "do",
1278            "else",
1279            "emptyasnull",
1280            "enable",
1281            "encode",
1282            "encrypt",
1283            "encryption",
1284            "end",
1285            "except",
1286            "explicit",
1287            "false",
1288            "for",
1289            "foreign",
1290            "freeze",
1291            "from",
1292            "full",
1293            "globaldict256",
1294            "globaldict64k",
1295            "grant",
1296            "group",
1297            "gzip",
1298            "having",
1299            "identity",
1300            "ignore",
1301            "ilike",
1302            "in",
1303            "initially",
1304            "inner",
1305            "intersect",
1306            "interval",
1307            "into",
1308            "is",
1309            "isnull",
1310            "join",
1311            "leading",
1312            "left",
1313            "like",
1314            "limit",
1315            "localtime",
1316            "localtimestamp",
1317            "lun",
1318            "luns",
1319            "lzo",
1320            "lzop",
1321            "minus",
1322            "mostly16",
1323            "mostly32",
1324            "mostly8",
1325            "natural",
1326            "new",
1327            "not",
1328            "notnull",
1329            "null",
1330            "nulls",
1331            "off",
1332            "offline",
1333            "offset",
1334            "oid",
1335            "old",
1336            "on",
1337            "only",
1338            "open",
1339            "or",
1340            "order",
1341            "outer",
1342            "overlaps",
1343            "parallel",
1344            "partition",
1345            "percent",
1346            "permissions",
1347            "pivot",
1348            "placing",
1349            "primary",
1350            "raw",
1351            "readratio",
1352            "recover",
1353            "references",
1354            "rejectlog",
1355            "resort",
1356            "respect",
1357            "restore",
1358            "right",
1359            "select",
1360            "session_user",
1361            "similar",
1362            "snapshot",
1363            "some",
1364            "sysdate",
1365            "system",
1366            "table",
1367            "tag",
1368            "tdes",
1369            "text255",
1370            "text32k",
1371            "then",
1372            "timestamp",
1373            "to",
1374            "top",
1375            "trailing",
1376            "true",
1377            "truncatecolumns",
1378            "type",
1379            "union",
1380            "unique",
1381            "unnest",
1382            "unpivot",
1383            "user",
1384            "using",
1385            "verbose",
1386            "wallet",
1387            "when",
1388            "where",
1389            "with",
1390            "without",
1391        ]
1392        .into_iter()
1393        .collect()
1394    });
1395
1396    /// DuckDB-specific reserved keywords
1397    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1398        let mut set = POSTGRES_RESERVED.clone();
1399        set.extend([
1400            "anti",
1401            "asof",
1402            "columns",
1403            "describe",
1404            "groups",
1405            "macro",
1406            "pivot",
1407            "pivot_longer",
1408            "pivot_wider",
1409            "qualify",
1410            "replace",
1411            "respect",
1412            "semi",
1413            "show",
1414            "table",
1415            "unpivot",
1416        ]);
1417        set.remove("at");
1418        set.remove("key");
1419        set.remove("range");
1420        set.remove("row");
1421        set.remove("values");
1422        set
1423    });
1424
1425    /// Presto/Trino/Athena-specific reserved keywords
1426    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1427        let mut set = SQL_RESERVED.clone();
1428        set.extend([
1429            "alter",
1430            "and",
1431            "as",
1432            "between",
1433            "by",
1434            "case",
1435            "cast",
1436            "constraint",
1437            "create",
1438            "cross",
1439            "cube",
1440            "current_catalog",
1441            "current_date",
1442            "current_path",
1443            "current_role",
1444            "current_schema",
1445            "current_time",
1446            "current_timestamp",
1447            "current_user",
1448            "deallocate",
1449            "delete",
1450            "describe",
1451            "distinct",
1452            "drop",
1453            "else",
1454            "end",
1455            "escape",
1456            "except",
1457            "execute",
1458            "exists",
1459            "extract",
1460            "false",
1461            "for",
1462            "from",
1463            "full",
1464            "group",
1465            "grouping",
1466            "having",
1467            "in",
1468            "inner",
1469            "insert",
1470            "intersect",
1471            "into",
1472            "is",
1473            "join",
1474            "json_array",
1475            "json_exists",
1476            "json_object",
1477            "json_query",
1478            "json_table",
1479            "json_value",
1480            "left",
1481            "like",
1482            "listagg",
1483            "localtime",
1484            "localtimestamp",
1485            "natural",
1486            "normalize",
1487            "not",
1488            "null",
1489            "on",
1490            "or",
1491            "order",
1492            "outer",
1493            "prepare",
1494            "recursive",
1495            "right",
1496            "rollup",
1497            "select",
1498            "skip",
1499            "table",
1500            "then",
1501            "trim",
1502            "true",
1503            "uescape",
1504            "union",
1505            "unnest",
1506            "using",
1507            "values",
1508            "when",
1509            "where",
1510            "with",
1511        ]);
1512        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1513        set.remove("key");
1514        set
1515    });
1516
1517    /// StarRocks-specific reserved keywords
1518    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1519    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1520        [
1521            "add",
1522            "all",
1523            "alter",
1524            "analyze",
1525            "and",
1526            "array",
1527            "as",
1528            "asc",
1529            "between",
1530            "bigint",
1531            "bitmap",
1532            "both",
1533            "by",
1534            "case",
1535            "char",
1536            "character",
1537            "check",
1538            "collate",
1539            "column",
1540            "compaction",
1541            "convert",
1542            "create",
1543            "cross",
1544            "cube",
1545            "current_date",
1546            "current_role",
1547            "current_time",
1548            "current_timestamp",
1549            "current_user",
1550            "database",
1551            "databases",
1552            "decimal",
1553            "decimalv2",
1554            "decimal32",
1555            "decimal64",
1556            "decimal128",
1557            "default",
1558            "deferred",
1559            "delete",
1560            "dense_rank",
1561            "desc",
1562            "describe",
1563            "distinct",
1564            "double",
1565            "drop",
1566            "dual",
1567            "else",
1568            "except",
1569            "exists",
1570            "explain",
1571            "false",
1572            "first_value",
1573            "float",
1574            "for",
1575            "force",
1576            "from",
1577            "full",
1578            "function",
1579            "grant",
1580            "group",
1581            "grouping",
1582            "grouping_id",
1583            "groups",
1584            "having",
1585            "hll",
1586            "host",
1587            "if",
1588            "ignore",
1589            "immediate",
1590            "in",
1591            "index",
1592            "infile",
1593            "inner",
1594            "insert",
1595            "int",
1596            "integer",
1597            "intersect",
1598            "into",
1599            "is",
1600            "join",
1601            "json",
1602            "key",
1603            "keys",
1604            "kill",
1605            "lag",
1606            "largeint",
1607            "last_value",
1608            "lateral",
1609            "lead",
1610            "left",
1611            "like",
1612            "limit",
1613            "load",
1614            "localtime",
1615            "localtimestamp",
1616            "maxvalue",
1617            "minus",
1618            "mod",
1619            "not",
1620            "ntile",
1621            "null",
1622            "on",
1623            "or",
1624            "order",
1625            "outer",
1626            "outfile",
1627            "over",
1628            "partition",
1629            "percentile",
1630            "primary",
1631            "procedure",
1632            "qualify",
1633            "range",
1634            "rank",
1635            "read",
1636            "regexp",
1637            "release",
1638            "rename",
1639            "replace",
1640            "revoke",
1641            "right",
1642            "rlike",
1643            "row",
1644            "row_number",
1645            "rows",
1646            "schema",
1647            "schemas",
1648            "select",
1649            "set",
1650            "set_var",
1651            "show",
1652            "smallint",
1653            "system",
1654            "table",
1655            "terminated",
1656            "text",
1657            "then",
1658            "tinyint",
1659            "to",
1660            "true",
1661            "union",
1662            "unique",
1663            "unsigned",
1664            "update",
1665            "use",
1666            "using",
1667            "values",
1668            "varchar",
1669            "when",
1670            "where",
1671            "with",
1672        ]
1673        .into_iter()
1674        .collect()
1675    });
1676
1677    /// SingleStore-specific reserved keywords
1678    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1679    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1680        let mut set = MYSQL_RESERVED.clone();
1681        set.extend([
1682            // Additional SingleStore reserved keywords from Python sqlglot
1683            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1684            "abs",
1685            "account",
1686            "acos",
1687            "adddate",
1688            "addtime",
1689            "admin",
1690            "aes_decrypt",
1691            "aes_encrypt",
1692            "aggregate",
1693            "aggregates",
1694            "aggregator",
1695            "anti_join",
1696            "any_value",
1697            "approx_count_distinct",
1698            "approx_percentile",
1699            "arrange",
1700            "arrangement",
1701            "asin",
1702            "atan",
1703            "atan2",
1704            "attach",
1705            "autostats",
1706            "avro",
1707            "background",
1708            "backup",
1709            "batch",
1710            "batches",
1711            "boot_strapping",
1712            "ceil",
1713            "ceiling",
1714            "coercibility",
1715            "columnar",
1716            "columnstore",
1717            "compile",
1718            "concurrent",
1719            "connection_id",
1720            "cos",
1721            "cot",
1722            "current_security_groups",
1723            "current_security_roles",
1724            "dayname",
1725            "dayofmonth",
1726            "dayofweek",
1727            "dayofyear",
1728            "degrees",
1729            "dot_product",
1730            "dump",
1731            "durability",
1732            "earliest",
1733            "echo",
1734            "election",
1735            "euclidean_distance",
1736            "exp",
1737            "extractor",
1738            "extractors",
1739            "floor",
1740            "foreground",
1741            "found_rows",
1742            "from_base64",
1743            "from_days",
1744            "from_unixtime",
1745            "fs",
1746            "fulltext",
1747            "gc",
1748            "gcs",
1749            "geography",
1750            "geography_area",
1751            "geography_contains",
1752            "geography_distance",
1753            "geography_intersects",
1754            "geography_latitude",
1755            "geography_length",
1756            "geography_longitude",
1757            "geographypoint",
1758            "geography_point",
1759            "geography_within_distance",
1760            "geometry",
1761            "geometry_area",
1762            "geometry_contains",
1763            "geometry_distance",
1764            "geometry_filter",
1765            "geometry_intersects",
1766            "geometry_length",
1767            "geometrypoint",
1768            "geometry_point",
1769            "geometry_within_distance",
1770            "geometry_x",
1771            "geometry_y",
1772            "greatest",
1773            "groups",
1774            "group_concat",
1775            "gzip",
1776            "hdfs",
1777            "hex",
1778            "highlight",
1779            "ifnull",
1780            "ilike",
1781            "inet_aton",
1782            "inet_ntoa",
1783            "inet6_aton",
1784            "inet6_ntoa",
1785            "initcap",
1786            "instr",
1787            "interpreter_mode",
1788            "isnull",
1789            "json",
1790            "json_agg",
1791            "json_array_contains_double",
1792            "json_array_contains_json",
1793            "json_array_contains_string",
1794            "json_delete_key",
1795            "json_extract_double",
1796            "json_extract_json",
1797            "json_extract_string",
1798            "json_extract_bigint",
1799            "json_get_type",
1800            "json_length",
1801            "json_set_double",
1802            "json_set_json",
1803            "json_set_string",
1804            "kafka",
1805            "lag",
1806            "last_day",
1807            "last_insert_id",
1808            "latest",
1809            "lcase",
1810            "lead",
1811            "leaf",
1812            "least",
1813            "leaves",
1814            "length",
1815            "license",
1816            "links",
1817            "llvm",
1818            "ln",
1819            "load",
1820            "locate",
1821            "log",
1822            "log10",
1823            "log2",
1824            "lpad",
1825            "lz4",
1826            "management",
1827            "match",
1828            "mbc",
1829            "md5",
1830            "median",
1831            "memsql",
1832            "memsql_deserialize",
1833            "memsql_serialize",
1834            "metadata",
1835            "microsecond",
1836            "minute",
1837            "model",
1838            "monthname",
1839            "months_between",
1840            "mpl",
1841            "namespace",
1842            "node",
1843            "noparam",
1844            "now",
1845            "nth_value",
1846            "ntile",
1847            "nullcols",
1848            "nullif",
1849            "object",
1850            "octet_length",
1851            "offsets",
1852            "online",
1853            "optimizer",
1854            "orphan",
1855            "parquet",
1856            "partitions",
1857            "pause",
1858            "percentile_cont",
1859            "percentile_disc",
1860            "periodic",
1861            "persisted",
1862            "pi",
1863            "pipeline",
1864            "pipelines",
1865            "plancache",
1866            "plugins",
1867            "pool",
1868            "pools",
1869            "pow",
1870            "power",
1871            "process",
1872            "processlist",
1873            "profile",
1874            "profiles",
1875            "quarter",
1876            "queries",
1877            "query",
1878            "radians",
1879            "rand",
1880            "record",
1881            "reduce",
1882            "redundancy",
1883            "regexp_match",
1884            "regexp_substr",
1885            "remote",
1886            "replication",
1887            "resource",
1888            "resource_pool",
1889            "restore",
1890            "retry",
1891            "role",
1892            "roles",
1893            "round",
1894            "rpad",
1895            "rtrim",
1896            "running",
1897            "s3",
1898            "scalar",
1899            "sec_to_time",
1900            "second",
1901            "security_lists_intersect",
1902            "semi_join",
1903            "sha",
1904            "sha1",
1905            "sha2",
1906            "shard",
1907            "sharded",
1908            "sharded_id",
1909            "sigmoid",
1910            "sign",
1911            "sin",
1912            "skip",
1913            "sleep",
1914            "snapshot",
1915            "soname",
1916            "sparse",
1917            "spatial_check_index",
1918            "split",
1919            "sqrt",
1920            "standalone",
1921            "std",
1922            "stddev",
1923            "stddev_pop",
1924            "stddev_samp",
1925            "stop",
1926            "str_to_date",
1927            "subdate",
1928            "substr",
1929            "substring_index",
1930            "success",
1931            "synchronize",
1932            "table_checksum",
1933            "tan",
1934            "task",
1935            "timediff",
1936            "time_bucket",
1937            "time_format",
1938            "time_to_sec",
1939            "timestampadd",
1940            "timestampdiff",
1941            "to_base64",
1942            "to_char",
1943            "to_date",
1944            "to_days",
1945            "to_json",
1946            "to_number",
1947            "to_seconds",
1948            "to_timestamp",
1949            "tracelogs",
1950            "transform",
1951            "trim",
1952            "trunc",
1953            "truncate",
1954            "ucase",
1955            "unhex",
1956            "unix_timestamp",
1957            "utc_date",
1958            "utc_time",
1959            "utc_timestamp",
1960            "vacuum",
1961            "variance",
1962            "var_pop",
1963            "var_samp",
1964            "vector_sub",
1965            "voting",
1966            "week",
1967            "weekday",
1968            "weekofyear",
1969            "workload",
1970            "year",
1971        ]);
1972        // Remove "all" because ORDER BY ALL needs ALL unquoted
1973        set.remove("all");
1974        set
1975    });
1976
1977    /// SQLite-specific reserved keywords
1978    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1979    /// Reference: https://www.sqlite.org/lang_keywords.html
1980    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1981        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1982        [
1983            "abort",
1984            "action",
1985            "add",
1986            "after",
1987            "all",
1988            "alter",
1989            "always",
1990            "analyze",
1991            "and",
1992            "as",
1993            "asc",
1994            "attach",
1995            "autoincrement",
1996            "before",
1997            "begin",
1998            "between",
1999            "by",
2000            "cascade",
2001            "case",
2002            "cast",
2003            "check",
2004            "collate",
2005            "column",
2006            "commit",
2007            "conflict",
2008            "constraint",
2009            "create",
2010            "cross",
2011            "current",
2012            "current_date",
2013            "current_time",
2014            "current_timestamp",
2015            "database",
2016            "default",
2017            "deferrable",
2018            "deferred",
2019            "delete",
2020            "desc",
2021            "detach",
2022            "distinct",
2023            "do",
2024            "drop",
2025            "each",
2026            "else",
2027            "end",
2028            "escape",
2029            "except",
2030            "exclude",
2031            "exclusive",
2032            "exists",
2033            "explain",
2034            "fail",
2035            "filter",
2036            "first",
2037            "following",
2038            "for",
2039            "foreign",
2040            "from",
2041            "full",
2042            "generated",
2043            "glob",
2044            "group",
2045            "groups",
2046            "having",
2047            "if",
2048            "ignore",
2049            "immediate",
2050            "in",
2051            "index",
2052            "indexed",
2053            "initially",
2054            "inner",
2055            "insert",
2056            "instead",
2057            "intersect",
2058            "into",
2059            "is",
2060            "isnull",
2061            "join",
2062            "key",
2063            "last",
2064            "left",
2065            "like",
2066            "limit",
2067            "natural",
2068            "no",
2069            "not",
2070            "nothing",
2071            "notnull",
2072            "null",
2073            "nulls",
2074            "of",
2075            "offset",
2076            "on",
2077            "or",
2078            "order",
2079            "others",
2080            "outer",
2081            "partition",
2082            "plan",
2083            "pragma",
2084            "preceding",
2085            "primary",
2086            "query",
2087            "raise",
2088            "range",
2089            "recursive",
2090            "references",
2091            "regexp",
2092            "reindex",
2093            "release",
2094            "rename",
2095            "replace",
2096            "restrict",
2097            "returning",
2098            "right",
2099            "rollback",
2100            "row",
2101            "rows",
2102            "savepoint",
2103            "select",
2104            "set",
2105            "table",
2106            "temp",
2107            "temporary",
2108            "then",
2109            "ties",
2110            "to",
2111            "transaction",
2112            "trigger",
2113            "unbounded",
2114            "union",
2115            "unique",
2116            "update",
2117            "using",
2118            "vacuum",
2119            "values",
2120            "view",
2121            "virtual",
2122            "when",
2123            "where",
2124            "window",
2125            "with",
2126            "without",
2127        ]
2128        .into_iter()
2129        .collect()
2130    });
2131}
2132
2133impl Generator {
2134    /// Create a new generator with the default configuration.
2135    ///
2136    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2137    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2138    /// and no dialect-specific transformations.
2139    pub fn new() -> Self {
2140        Self::with_config(GeneratorConfig::default())
2141    }
2142
2143    /// Create a generator with a custom [`GeneratorConfig`].
2144    ///
2145    /// Use this when you need dialect-specific output, pretty-printing, or other
2146    /// non-default settings.
2147    pub fn with_config(config: GeneratorConfig) -> Self {
2148        Self::with_arc_config(Arc::new(config))
2149    }
2150
2151    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2152    ///
2153    /// This avoids cloning the configuration when multiple generators share the
2154    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2155    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2156        Self {
2157            config,
2158            output: String::new(),
2159            unsupported_messages: Vec::new(),
2160            indent_level: 0,
2161            athena_hive_context: false,
2162            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2163            merge_strip_qualifiers: Vec::new(),
2164            clickhouse_nullable_depth: 0,
2165        }
2166    }
2167
2168    /// Add column aliases to a query expression for TSQL SELECT INTO.
2169    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2170    /// Recursively processes all SELECT expressions in the query tree.
2171    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2172        match expr {
2173            Expression::Select(mut select) => {
2174                // Add aliases to all select expressions that don't already have them
2175                select.expressions = select
2176                    .expressions
2177                    .into_iter()
2178                    .map(|e| Self::add_alias_to_expression(e))
2179                    .collect();
2180
2181                // Recursively process subqueries in FROM clause
2182                if let Some(ref mut from) = select.from {
2183                    from.expressions = from
2184                        .expressions
2185                        .iter()
2186                        .cloned()
2187                        .map(|e| Self::add_column_aliases_to_query(e))
2188                        .collect();
2189                }
2190
2191                Expression::Select(select)
2192            }
2193            Expression::Subquery(mut sq) => {
2194                sq.this = Self::add_column_aliases_to_query(sq.this);
2195                Expression::Subquery(sq)
2196            }
2197            Expression::Paren(mut p) => {
2198                p.this = Self::add_column_aliases_to_query(p.this);
2199                Expression::Paren(p)
2200            }
2201            // For other expressions (Union, Intersect, etc.), pass through
2202            other => other,
2203        }
2204    }
2205
2206    /// Add an alias to a single select expression if it doesn't already have one.
2207    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2208    fn add_alias_to_expression(expr: Expression) -> Expression {
2209        use crate::expressions::Alias;
2210
2211        match &expr {
2212            // Already aliased - just return it
2213            Expression::Alias(_) => expr,
2214
2215            // Column reference: add alias from column name
2216            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2217                this: expr.clone(),
2218                alias: col.name.clone(),
2219                column_aliases: Vec::new(),
2220                alias_explicit_as: false,
2221                alias_keyword: None,
2222                pre_alias_comments: Vec::new(),
2223                trailing_comments: Vec::new(),
2224                inferred_type: None,
2225            })),
2226
2227            // Identifier: add alias from identifier name
2228            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2229                this: expr.clone(),
2230                alias: ident.clone(),
2231                column_aliases: Vec::new(),
2232                alias_explicit_as: false,
2233                alias_keyword: None,
2234                pre_alias_comments: Vec::new(),
2235                trailing_comments: Vec::new(),
2236                inferred_type: None,
2237            })),
2238
2239            // Subquery: recursively process and add alias if inner returns a named column
2240            Expression::Subquery(sq) => {
2241                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2242                // Subqueries that are already aliased keep their alias
2243                if sq.alias.is_some() {
2244                    processed
2245                } else {
2246                    // If there's no alias, keep it as-is (let TSQL handle it)
2247                    processed
2248                }
2249            }
2250
2251            // Star expressions (*) - don't alias
2252            Expression::Star(_) => expr,
2253
2254            // For other expressions, don't add an alias
2255            // (function calls, literals, etc. would need explicit aliases anyway)
2256            _ => expr,
2257        }
2258    }
2259
2260    /// Try to evaluate a constant arithmetic expression to a number literal.
2261    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2262    /// otherwise returns the original expression.
2263    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2264        match expr {
2265            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2266                let Literal::Number(n) = lit.as_ref() else {
2267                    unreachable!()
2268                };
2269                n.parse::<i64>().ok()
2270            }
2271            Expression::Add(op) => {
2272                let left = Self::try_evaluate_constant(&op.left)?;
2273                let right = Self::try_evaluate_constant(&op.right)?;
2274                Some(left + right)
2275            }
2276            Expression::Sub(op) => {
2277                let left = Self::try_evaluate_constant(&op.left)?;
2278                let right = Self::try_evaluate_constant(&op.right)?;
2279                Some(left - right)
2280            }
2281            Expression::Mul(op) => {
2282                let left = Self::try_evaluate_constant(&op.left)?;
2283                let right = Self::try_evaluate_constant(&op.right)?;
2284                Some(left * right)
2285            }
2286            Expression::Div(op) => {
2287                let left = Self::try_evaluate_constant(&op.left)?;
2288                let right = Self::try_evaluate_constant(&op.right)?;
2289                if right != 0 {
2290                    Some(left / right)
2291                } else {
2292                    None
2293                }
2294            }
2295            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2296            _ => None,
2297        }
2298    }
2299
2300    /// Check if an identifier is a reserved keyword for the current dialect
2301    fn is_reserved_keyword(&self, name: &str) -> bool {
2302        use crate::dialects::DialectType;
2303        let mut buf = [0u8; 128];
2304        let lower_ref: &str = if name.len() <= 128 {
2305            for (i, b) in name.bytes().enumerate() {
2306                buf[i] = b.to_ascii_lowercase();
2307            }
2308            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2309            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2310        } else {
2311            return false;
2312        };
2313
2314        match self.config.dialect {
2315            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2316            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2317                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2318            }
2319            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2320            Some(DialectType::SingleStore) => {
2321                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2322            }
2323            Some(DialectType::StarRocks) => {
2324                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2325            }
2326            Some(DialectType::PostgreSQL)
2327            | Some(DialectType::CockroachDB)
2328            | Some(DialectType::Materialize)
2329            | Some(DialectType::RisingWave) => {
2330                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2331            }
2332            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2333            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2334            // meaning it never quotes identifiers based on reserved word status.
2335            Some(DialectType::Snowflake) => false,
2336            // ClickHouse: don't quote reserved keywords to preserve identity output
2337            Some(DialectType::ClickHouse) => false,
2338            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2339            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2340            Some(DialectType::Teradata) => false,
2341            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2342            Some(DialectType::TSQL)
2343            | Some(DialectType::Fabric)
2344            | Some(DialectType::Oracle)
2345            | Some(DialectType::Spark)
2346            | Some(DialectType::Databricks)
2347            | Some(DialectType::Hive)
2348            | Some(DialectType::Solr) => false,
2349            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2350                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2351            }
2352            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2353            // For Generic dialect or None, don't add extra quoting to preserve identity
2354            Some(DialectType::Generic) | None => false,
2355            // For other dialects, use standard SQL reserved keywords
2356            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2357        }
2358    }
2359
2360    /// Normalize function name based on dialect settings
2361    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2362        match self.config.normalize_functions {
2363            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2364            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2365            NormalizeFunctions::None => Cow::Borrowed(name),
2366        }
2367    }
2368
2369    /// Generate a SQL string from an AST expression.
2370    ///
2371    /// This is the primary generation method. It clears any previous internal state,
2372    /// walks the expression tree, and returns the resulting SQL text. The output
2373    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2374    ///
2375    /// The generator can be reused across multiple calls; each call to `generate`
2376    /// resets the internal buffer.
2377    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2378        self.output.clear();
2379        self.unsupported_messages.clear();
2380        enforce_generate_ast(expr, &self.config.complexity_guard)?;
2381        self.generate_expression(expr)?;
2382        if self.config.unsupported_level == UnsupportedLevel::Raise
2383            && !self.unsupported_messages.is_empty()
2384        {
2385            return Err(crate::error::Error::generate(
2386                self.format_unsupported_messages(),
2387            ));
2388        }
2389        Ok(std::mem::take(&mut self.output))
2390    }
2391
2392    /// Returns the unsupported diagnostics collected during the most recent generate call.
2393    pub fn unsupported_messages(&self) -> &[String] {
2394        &self.unsupported_messages
2395    }
2396
2397    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2398        let message = message.into();
2399        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2400            return Err(crate::error::Error::generate(message));
2401        }
2402        self.unsupported_messages.push(message);
2403        Ok(())
2404    }
2405
2406    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2407        self.unsupported(message.to_string())?;
2408        self.write("/* ");
2409        self.write(message);
2410        self.write(" */");
2411        Ok(())
2412    }
2413
2414    fn format_unsupported_messages(&self) -> String {
2415        let limit = self.config.max_unsupported.max(1);
2416        if self.unsupported_messages.len() <= limit {
2417            return self.unsupported_messages.join("; ");
2418        }
2419
2420        let mut messages = self
2421            .unsupported_messages
2422            .iter()
2423            .take(limit)
2424            .cloned()
2425            .collect::<Vec<_>>();
2426        messages.push(format!(
2427            "... and {} more",
2428            self.unsupported_messages.len() - limit
2429        ));
2430        messages.join("; ")
2431    }
2432
2433    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2434    ///
2435    /// This is a static helper that creates a throwaway `Generator` internally.
2436    /// For repeated generation, prefer constructing a `Generator` once and calling
2437    /// [`generate`](Self::generate) on it.
2438    pub fn sql(expr: &Expression) -> Result<String> {
2439        let mut gen = Generator::new();
2440        gen.generate(expr)
2441    }
2442
2443    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2444    ///
2445    /// Produces human-readable output with newlines and indentation. A trailing
2446    /// semicolon is appended automatically if not already present.
2447    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2448        let config = GeneratorConfig {
2449            pretty: true,
2450            ..Default::default()
2451        };
2452        let mut gen = Generator::with_config(config);
2453        let mut sql = gen.generate(expr)?;
2454        // Add semicolon for pretty output
2455        if !sql.ends_with(';') {
2456            sql.push(';');
2457        }
2458        Ok(sql)
2459    }
2460
2461    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2462        #[cfg(feature = "stacker")]
2463        {
2464            let red_zone = if cfg!(debug_assertions) {
2465                4 * 1024 * 1024
2466            } else {
2467                1024 * 1024
2468            };
2469            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2470                self.generate_expression_inner(expr)
2471            })
2472        }
2473        #[cfg(not(feature = "stacker"))]
2474        {
2475            self.generate_expression_inner(expr)
2476        }
2477    }
2478
2479    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2480        match expr {
2481            Expression::Select(select) => self.generate_select(select),
2482            Expression::Union(union) => self.generate_union(union),
2483            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2484            Expression::Except(except) => self.generate_except(except),
2485            Expression::Insert(insert) => self.generate_insert(insert),
2486            Expression::Update(update) => self.generate_update(update),
2487            Expression::Delete(delete) => self.generate_delete(delete),
2488            Expression::Literal(lit) => self.generate_literal(lit),
2489            Expression::Boolean(b) => self.generate_boolean(b),
2490            Expression::Null(_) => {
2491                self.write_keyword("NULL");
2492                Ok(())
2493            }
2494            Expression::Identifier(id) => self.generate_identifier(id),
2495            Expression::Column(col) => self.generate_column(col),
2496            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2497            Expression::Connect(c) => self.generate_connect_expr(c),
2498            Expression::Prior(p) => self.generate_prior(p),
2499            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2500            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2501            Expression::Table(table) => self.generate_table(table),
2502            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2503            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2504            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2505            Expression::Star(star) => self.generate_star(star),
2506            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2507            Expression::Alias(alias) => self.generate_alias(alias),
2508            Expression::Cast(cast) => self.generate_cast(cast),
2509            Expression::Collation(coll) => self.generate_collation(coll),
2510            Expression::Case(case) => self.generate_case(case),
2511            Expression::Function(func) => self.generate_function(func),
2512            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2513            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2514            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2515            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2516            Expression::Interval(interval) => self.generate_interval(interval),
2517
2518            // String functions
2519            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2520            Expression::Substring(f) => self.generate_substring(f),
2521            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2522            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2523            Expression::Length(f) => {
2524                let name = match self.config.dialect {
2525                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "LEN",
2526                    _ => "LENGTH",
2527                };
2528                self.generate_unary_func(name, f)
2529            }
2530            Expression::Trim(f) => self.generate_trim(f),
2531            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2532            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2533            Expression::Replace(f) => self.generate_replace(f),
2534            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2535            Expression::Left(f) => self.generate_left_right("LEFT", f),
2536            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2537            Expression::Repeat(f) => self.generate_repeat(f),
2538            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2539            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2540            Expression::Split(f) => self.generate_split(f),
2541            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2542            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2543            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2544            Expression::Overlay(f) => self.generate_overlay(f),
2545
2546            // Math functions
2547            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2548            Expression::Round(f) => self.generate_round(f),
2549            Expression::Floor(f) => self.generate_floor(f),
2550            Expression::Ceil(f) => self.generate_ceil(f),
2551            Expression::Power(f) => self.generate_power(f),
2552            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2553            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2554            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2555            Expression::Log(f) => self.generate_log(f),
2556            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2557            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2558            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2559            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2560
2561            // Date/time functions
2562            Expression::CurrentDate(_) => {
2563                self.write_keyword("CURRENT_DATE");
2564                Ok(())
2565            }
2566            Expression::CurrentTime(f) => self.generate_current_time(f),
2567            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2568            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2569            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2570            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2571            Expression::DateDiff(f) => self.generate_datediff(f),
2572            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2573            Expression::Extract(f) => self.generate_extract(f),
2574            Expression::ToDate(f) => self.generate_to_date(f),
2575            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2576
2577            // Control flow functions
2578            Expression::Coalesce(f) => {
2579                // Use original function name if preserved (COALESCE, IFNULL)
2580                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2581                self.generate_vararg_func(func_name, &f.expressions)
2582            }
2583            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2584            Expression::IfFunc(f) => self.generate_if_func(f),
2585            Expression::IfNull(f) => self.generate_ifnull(f),
2586            Expression::Nvl(f) => self.generate_nvl(f),
2587            Expression::Nvl2(f) => self.generate_nvl2(f),
2588
2589            // Type conversion
2590            Expression::TryCast(cast) => self.generate_try_cast(cast),
2591            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2592
2593            // Typed aggregate functions
2594            Expression::Count(f) => self.generate_count(f),
2595            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2596            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2597            Expression::Min(f) => self.generate_agg_func("MIN", f),
2598            Expression::Max(f) => self.generate_agg_func("MAX", f),
2599            Expression::GroupConcat(f) => self.generate_group_concat(f),
2600            Expression::StringAgg(f) => self.generate_string_agg(f),
2601            Expression::ListAgg(f) => self.generate_listagg(f),
2602            Expression::ArrayAgg(f) => {
2603                // Allow cross-dialect transforms to override the function name
2604                // (e.g., COLLECT_LIST for Spark)
2605                let override_name = f
2606                    .name
2607                    .as_ref()
2608                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2609                    .map(|n| n.to_ascii_uppercase());
2610                match override_name {
2611                    Some(name) => self.generate_agg_func(&name, f),
2612                    None => self.generate_agg_func("ARRAY_AGG", f),
2613                }
2614            }
2615            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2616            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2617            Expression::SumIf(f) => self.generate_sum_if(f),
2618            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2619            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2620            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2621            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2622            Expression::VarPop(f) => {
2623                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2624                    "VARIANCE_POP"
2625                } else {
2626                    "VAR_POP"
2627                };
2628                self.generate_agg_func(name, f)
2629            }
2630            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2631            Expression::Skewness(f) => {
2632                let name = match self.config.dialect {
2633                    Some(DialectType::Snowflake) => "SKEW",
2634                    _ => "SKEWNESS",
2635                };
2636                self.generate_agg_func(name, f)
2637            }
2638            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2639            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2640            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2641            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2642            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2643            Expression::ApproxDistinct(f) => {
2644                match self.config.dialect {
2645                    Some(DialectType::Hive)
2646                    | Some(DialectType::Spark)
2647                    | Some(DialectType::Databricks)
2648                    | Some(DialectType::BigQuery) => {
2649                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2650                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2651                    }
2652                    Some(DialectType::Redshift) => {
2653                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2654                        self.write_keyword("APPROXIMATE COUNT");
2655                        self.write("(");
2656                        self.write_keyword("DISTINCT");
2657                        self.write(" ");
2658                        self.generate_expression(&f.this)?;
2659                        self.write(")");
2660                        Ok(())
2661                    }
2662                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2663                }
2664            }
2665            Expression::ApproxCountDistinct(f) => {
2666                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2667            }
2668            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2669            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2670            Expression::LogicalAnd(f) => {
2671                let name = match self.config.dialect {
2672                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2673                    Some(DialectType::Spark)
2674                    | Some(DialectType::Databricks)
2675                    | Some(DialectType::PostgreSQL)
2676                    | Some(DialectType::DuckDB)
2677                    | Some(DialectType::Redshift) => "BOOL_AND",
2678                    Some(DialectType::Oracle)
2679                    | Some(DialectType::SQLite)
2680                    | Some(DialectType::MySQL) => "MIN",
2681                    _ => "BOOL_AND",
2682                };
2683                self.generate_agg_func(name, f)
2684            }
2685            Expression::LogicalOr(f) => {
2686                let name = match self.config.dialect {
2687                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2688                    Some(DialectType::Spark)
2689                    | Some(DialectType::Databricks)
2690                    | Some(DialectType::PostgreSQL)
2691                    | Some(DialectType::DuckDB)
2692                    | Some(DialectType::Redshift) => "BOOL_OR",
2693                    Some(DialectType::Oracle)
2694                    | Some(DialectType::SQLite)
2695                    | Some(DialectType::MySQL) => "MAX",
2696                    _ => "BOOL_OR",
2697                };
2698                self.generate_agg_func(name, f)
2699            }
2700
2701            // Typed window functions
2702            Expression::RowNumber(_) => {
2703                if self.config.dialect == Some(DialectType::ClickHouse) {
2704                    self.write("row_number");
2705                } else {
2706                    self.write_keyword("ROW_NUMBER");
2707                }
2708                self.write("()");
2709                Ok(())
2710            }
2711            Expression::Rank(r) => {
2712                self.write_keyword("RANK");
2713                self.write("(");
2714                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2715                if !r.args.is_empty() {
2716                    for (i, arg) in r.args.iter().enumerate() {
2717                        if i > 0 {
2718                            self.write(", ");
2719                        }
2720                        self.generate_expression(arg)?;
2721                    }
2722                } else if let Some(order_by) = &r.order_by {
2723                    // DuckDB: RANK(ORDER BY col)
2724                    self.write_keyword(" ORDER BY ");
2725                    for (i, ob) in order_by.iter().enumerate() {
2726                        if i > 0 {
2727                            self.write(", ");
2728                        }
2729                        self.generate_ordered(ob)?;
2730                    }
2731                }
2732                self.write(")");
2733                Ok(())
2734            }
2735            Expression::DenseRank(dr) => {
2736                self.write_keyword("DENSE_RANK");
2737                self.write("(");
2738                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2739                for (i, arg) in dr.args.iter().enumerate() {
2740                    if i > 0 {
2741                        self.write(", ");
2742                    }
2743                    self.generate_expression(arg)?;
2744                }
2745                self.write(")");
2746                Ok(())
2747            }
2748            Expression::NTile(f) => self.generate_ntile(f),
2749            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2750            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2751            Expression::FirstValue(f) => {
2752                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2753            }
2754            Expression::LastValue(f) => {
2755                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2756            }
2757            Expression::NthValue(f) => self.generate_nth_value(f),
2758            Expression::PercentRank(pr) => {
2759                self.write_keyword("PERCENT_RANK");
2760                self.write("(");
2761                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2762                if !pr.args.is_empty() {
2763                    for (i, arg) in pr.args.iter().enumerate() {
2764                        if i > 0 {
2765                            self.write(", ");
2766                        }
2767                        self.generate_expression(arg)?;
2768                    }
2769                } else if let Some(order_by) = &pr.order_by {
2770                    // DuckDB: PERCENT_RANK(ORDER BY col)
2771                    self.write_keyword(" ORDER BY ");
2772                    for (i, ob) in order_by.iter().enumerate() {
2773                        if i > 0 {
2774                            self.write(", ");
2775                        }
2776                        self.generate_ordered(ob)?;
2777                    }
2778                }
2779                self.write(")");
2780                Ok(())
2781            }
2782            Expression::CumeDist(cd) => {
2783                self.write_keyword("CUME_DIST");
2784                self.write("(");
2785                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2786                if !cd.args.is_empty() {
2787                    for (i, arg) in cd.args.iter().enumerate() {
2788                        if i > 0 {
2789                            self.write(", ");
2790                        }
2791                        self.generate_expression(arg)?;
2792                    }
2793                } else if let Some(order_by) = &cd.order_by {
2794                    // DuckDB: CUME_DIST(ORDER BY col)
2795                    self.write_keyword(" ORDER BY ");
2796                    for (i, ob) in order_by.iter().enumerate() {
2797                        if i > 0 {
2798                            self.write(", ");
2799                        }
2800                        self.generate_ordered(ob)?;
2801                    }
2802                }
2803                self.write(")");
2804                Ok(())
2805            }
2806            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2807            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2808
2809            // Additional string functions
2810            Expression::Contains(f) => {
2811                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2812            }
2813            Expression::StartsWith(f) => {
2814                let name = match self.config.dialect {
2815                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2816                    _ => "STARTS_WITH",
2817                };
2818                self.generate_binary_func(name, &f.this, &f.expression)
2819            }
2820            Expression::EndsWith(f) => {
2821                let name = match self.config.dialect {
2822                    Some(DialectType::Snowflake) => "ENDSWITH",
2823                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2824                    Some(DialectType::ClickHouse) => "endsWith",
2825                    _ => "ENDS_WITH",
2826                };
2827                self.generate_binary_func(name, &f.this, &f.expression)
2828            }
2829            Expression::Position(f) => self.generate_position(f),
2830            Expression::Initcap(f) => match self.config.dialect {
2831                Some(DialectType::Presto)
2832                | Some(DialectType::Trino)
2833                | Some(DialectType::Athena) => {
2834                    self.write_keyword("REGEXP_REPLACE");
2835                    self.write("(");
2836                    self.generate_expression(&f.this)?;
2837                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2838                    Ok(())
2839                }
2840                _ => self.generate_simple_func("INITCAP", &f.this),
2841            },
2842            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2843            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2844            Expression::CharFunc(f) => self.generate_char_func(f),
2845            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2846            Expression::Levenshtein(f) => {
2847                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2848            }
2849
2850            // Additional math functions
2851            Expression::ModFunc(f) => self.generate_mod_func(f),
2852            Expression::Random(_) => {
2853                self.write_keyword("RANDOM");
2854                self.write("()");
2855                Ok(())
2856            }
2857            Expression::Rand(f) => self.generate_rand(f),
2858            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2859            Expression::Pi(_) => {
2860                self.write_keyword("PI");
2861                self.write("()");
2862                Ok(())
2863            }
2864            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2865            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2866            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2867            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2868            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2869            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2870            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2871            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2872            Expression::Atan2(f) => {
2873                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2874                self.generate_binary_func(name, &f.this, &f.expression)
2875            }
2876
2877            // Control flow
2878            Expression::Decode(f) => self.generate_decode(f),
2879
2880            // Additional date/time functions
2881            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2882            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2883            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2884            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2885            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2886            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2887            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2888            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2889            Expression::DayOfWeek(f) => {
2890                let name = match self.config.dialect {
2891                    Some(DialectType::Presto)
2892                    | Some(DialectType::Trino)
2893                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2894                    Some(DialectType::DuckDB) => "ISODOW",
2895                    _ => "DAYOFWEEK",
2896                };
2897                self.generate_simple_func(name, &f.this)
2898            }
2899            Expression::DayOfMonth(f) => {
2900                let name = match self.config.dialect {
2901                    Some(DialectType::Presto)
2902                    | Some(DialectType::Trino)
2903                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2904                    _ => "DAYOFMONTH",
2905                };
2906                self.generate_simple_func(name, &f.this)
2907            }
2908            Expression::DayOfYear(f) => {
2909                let name = match self.config.dialect {
2910                    Some(DialectType::Presto)
2911                    | Some(DialectType::Trino)
2912                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2913                    _ => "DAYOFYEAR",
2914                };
2915                self.generate_simple_func(name, &f.this)
2916            }
2917            Expression::WeekOfYear(f) => {
2918                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2919                let name = match self.config.dialect {
2920                    Some(DialectType::Hive)
2921                    | Some(DialectType::DuckDB)
2922                    | Some(DialectType::Spark)
2923                    | Some(DialectType::Databricks)
2924                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2925                    _ => "WEEK_OF_YEAR",
2926                };
2927                self.generate_simple_func(name, &f.this)
2928            }
2929            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2930            Expression::AddMonths(f) => {
2931                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2932            }
2933            Expression::MonthsBetween(f) => {
2934                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2935            }
2936            Expression::LastDay(f) => self.generate_last_day(f),
2937            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2938            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2939            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2940            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2941            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2942            Expression::MakeDate(f) => self.generate_make_date(f),
2943            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2944            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2945
2946            // Array functions
2947            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2948            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2949            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2950            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2951            Expression::ArrayContains(f) => {
2952                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2953            }
2954            Expression::ArrayPosition(f) => {
2955                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2956            }
2957            Expression::ArrayAppend(f) => {
2958                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2959            }
2960            Expression::ArrayPrepend(f) => {
2961                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2962            }
2963            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2964            Expression::ArraySort(f) => self.generate_array_sort(f),
2965            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2966            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2967            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2968            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2969            Expression::Unnest(f) => self.generate_unnest(f),
2970            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2971            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2972            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2973            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2974            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2975            Expression::ArrayCompact(f) => {
2976                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2977                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2978                    self.write("LIST_FILTER(");
2979                    self.generate_expression(&f.this)?;
2980                    self.write(", _u -> NOT _u IS NULL)");
2981                    Ok(())
2982                } else {
2983                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2984                }
2985            }
2986            Expression::ArrayIntersect(f) => {
2987                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2988                self.generate_vararg_func(func_name, &f.expressions)
2989            }
2990            Expression::ArrayUnion(f) => {
2991                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2992            }
2993            Expression::ArrayExcept(f) => {
2994                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2995            }
2996            Expression::ArrayRemove(f) => {
2997                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2998            }
2999            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
3000            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
3001            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
3002
3003            // Struct functions
3004            Expression::StructFunc(f) => self.generate_struct_constructor(f),
3005            Expression::StructExtract(f) => self.generate_struct_extract(f),
3006            Expression::NamedStruct(f) => self.generate_named_struct(f),
3007
3008            // Map functions
3009            Expression::MapFunc(f) => self.generate_map_constructor(f),
3010            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
3011            Expression::MapFromArrays(f) => {
3012                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
3013            }
3014            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
3015            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
3016            Expression::MapContainsKey(f) => {
3017                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
3018            }
3019            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
3020            Expression::ElementAt(f) => {
3021                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
3022            }
3023            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
3024            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3025
3026            // JSON functions
3027            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3028            Expression::JsonExtractScalar(f) => {
3029                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3030            }
3031            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3032            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3033            Expression::JsonObject(f) => self.generate_json_object(f),
3034            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3035            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3036            Expression::JsonArrayLength(f) => {
3037                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3038            }
3039            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3040            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3041            Expression::ParseJson(f) => {
3042                let name = match self.config.dialect {
3043                    Some(DialectType::Presto)
3044                    | Some(DialectType::Trino)
3045                    | Some(DialectType::Athena) => "JSON_PARSE",
3046                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3047                        // PostgreSQL: CAST(x AS JSON)
3048                        self.write_keyword("CAST");
3049                        self.write("(");
3050                        self.generate_expression(&f.this)?;
3051                        self.write_keyword(" AS ");
3052                        self.write_keyword("JSON");
3053                        self.write(")");
3054                        return Ok(());
3055                    }
3056                    Some(DialectType::Spark)
3057                    | Some(DialectType::MySQL)
3058                    | Some(DialectType::SingleStore)
3059                    | Some(DialectType::TiDB)
3060                    | Some(DialectType::TSQL) => {
3061                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3062                        self.generate_expression(&f.this)?;
3063                        return Ok(());
3064                    }
3065                    Some(DialectType::DuckDB) => "JSON",
3066                    _ => "PARSE_JSON",
3067                };
3068                self.generate_simple_func(name, &f.this)
3069            }
3070            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3071            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3072            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3073            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3074            Expression::JsonMergePatch(f) => {
3075                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3076            }
3077            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3078            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3079
3080            // Type casting/conversion
3081            Expression::Convert(f) => self.generate_convert(f),
3082            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3083
3084            // Additional expressions
3085            Expression::Lambda(f) => self.generate_lambda(f),
3086            Expression::Parameter(f) => self.generate_parameter(f),
3087            Expression::Placeholder(f) => self.generate_placeholder(f),
3088            Expression::NamedArgument(f) => self.generate_named_argument(f),
3089            Expression::TableArgument(f) => self.generate_table_argument(f),
3090            Expression::SqlComment(f) => self.generate_sql_comment(f),
3091
3092            // Additional predicates
3093            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3094            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3095            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3096            Expression::SimilarTo(f) => self.generate_similar_to(f),
3097            Expression::Any(f) => self.generate_quantified("ANY", f),
3098            Expression::All(f) => self.generate_quantified("ALL", f),
3099            Expression::Overlaps(f) => self.generate_overlaps(f),
3100
3101            // Bitwise operations
3102            Expression::BitwiseLeftShift(op) => {
3103                if matches!(
3104                    self.config.dialect,
3105                    Some(DialectType::Presto) | Some(DialectType::Trino)
3106                ) {
3107                    self.write_keyword("BITWISE_LEFT_SHIFT");
3108                    self.write("(");
3109                    self.generate_expression(&op.left)?;
3110                    self.write(", ");
3111                    self.generate_expression(&op.right)?;
3112                    self.write(")");
3113                    Ok(())
3114                } else if matches!(
3115                    self.config.dialect,
3116                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3117                ) {
3118                    self.write_keyword("SHIFTLEFT");
3119                    self.write("(");
3120                    self.generate_expression(&op.left)?;
3121                    self.write(", ");
3122                    self.generate_expression(&op.right)?;
3123                    self.write(")");
3124                    Ok(())
3125                } else {
3126                    self.generate_binary_op(op, "<<")
3127                }
3128            }
3129            Expression::BitwiseRightShift(op) => {
3130                if matches!(
3131                    self.config.dialect,
3132                    Some(DialectType::Presto) | Some(DialectType::Trino)
3133                ) {
3134                    self.write_keyword("BITWISE_RIGHT_SHIFT");
3135                    self.write("(");
3136                    self.generate_expression(&op.left)?;
3137                    self.write(", ");
3138                    self.generate_expression(&op.right)?;
3139                    self.write(")");
3140                    Ok(())
3141                } else if matches!(
3142                    self.config.dialect,
3143                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3144                ) {
3145                    self.write_keyword("SHIFTRIGHT");
3146                    self.write("(");
3147                    self.generate_expression(&op.left)?;
3148                    self.write(", ");
3149                    self.generate_expression(&op.right)?;
3150                    self.write(")");
3151                    Ok(())
3152                } else {
3153                    self.generate_binary_op(op, ">>")
3154                }
3155            }
3156            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3157            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3158            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3159
3160            // Array/struct/map access
3161            Expression::Subscript(s) => self.generate_subscript(s),
3162            Expression::Dot(d) => self.generate_dot_access(d),
3163            Expression::MethodCall(m) => self.generate_method_call(m),
3164            Expression::ArraySlice(s) => self.generate_array_slice(s),
3165
3166            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3167            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3168            Expression::Add(op) => self.generate_binary_op(op, "+"),
3169            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3170            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3171            Expression::Div(op) => self.generate_binary_op(op, "/"),
3172            Expression::IntDiv(f) => {
3173                use crate::dialects::DialectType;
3174                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3175                    // DuckDB uses // operator for integer division
3176                    self.generate_expression(&f.this)?;
3177                    self.write(" // ");
3178                    self.generate_expression(&f.expression)?;
3179                    Ok(())
3180                } else if matches!(
3181                    self.config.dialect,
3182                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3183                ) {
3184                    // Hive/Spark use DIV as an infix operator
3185                    self.generate_expression(&f.this)?;
3186                    self.write(" ");
3187                    self.write_keyword("DIV");
3188                    self.write(" ");
3189                    self.generate_expression(&f.expression)?;
3190                    Ok(())
3191                } else {
3192                    // Other dialects use DIV function
3193                    self.write_keyword("DIV");
3194                    self.write("(");
3195                    self.generate_expression(&f.this)?;
3196                    self.write(", ");
3197                    self.generate_expression(&f.expression)?;
3198                    self.write(")");
3199                    Ok(())
3200                }
3201            }
3202            Expression::Mod(op) => {
3203                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3204                    self.generate_binary_op(op, "MOD")
3205                } else {
3206                    self.generate_binary_op(op, "%")
3207                }
3208            }
3209            Expression::Eq(op) => self.generate_binary_op(op, "="),
3210            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3211            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3212            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3213            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3214            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3215            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3216            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3217            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3218            Expression::Concat(op) => {
3219                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3220                if self.config.dialect == Some(DialectType::Solr) {
3221                    self.generate_binary_op(op, "OR")
3222                } else if self.config.dialect == Some(DialectType::MySQL) {
3223                    self.generate_mysql_concat_from_concat(op)
3224                } else {
3225                    self.generate_binary_op(op, "||")
3226                }
3227            }
3228            Expression::BitwiseAnd(op) => {
3229                // Presto/Trino use BITWISE_AND function
3230                if matches!(
3231                    self.config.dialect,
3232                    Some(DialectType::Presto) | Some(DialectType::Trino)
3233                ) {
3234                    self.write_keyword("BITWISE_AND");
3235                    self.write("(");
3236                    self.generate_expression(&op.left)?;
3237                    self.write(", ");
3238                    self.generate_expression(&op.right)?;
3239                    self.write(")");
3240                    Ok(())
3241                } else {
3242                    self.generate_binary_op(op, "&")
3243                }
3244            }
3245            Expression::BitwiseOr(op) => {
3246                // Presto/Trino use BITWISE_OR function
3247                if matches!(
3248                    self.config.dialect,
3249                    Some(DialectType::Presto) | Some(DialectType::Trino)
3250                ) {
3251                    self.write_keyword("BITWISE_OR");
3252                    self.write("(");
3253                    self.generate_expression(&op.left)?;
3254                    self.write(", ");
3255                    self.generate_expression(&op.right)?;
3256                    self.write(")");
3257                    Ok(())
3258                } else {
3259                    self.generate_binary_op(op, "|")
3260                }
3261            }
3262            Expression::BitwiseXor(op) => {
3263                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3264                if matches!(
3265                    self.config.dialect,
3266                    Some(DialectType::Presto) | Some(DialectType::Trino)
3267                ) {
3268                    self.write_keyword("BITWISE_XOR");
3269                    self.write("(");
3270                    self.generate_expression(&op.left)?;
3271                    self.write(", ");
3272                    self.generate_expression(&op.right)?;
3273                    self.write(")");
3274                    Ok(())
3275                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3276                    self.generate_binary_op(op, "#")
3277                } else {
3278                    self.generate_binary_op(op, "^")
3279                }
3280            }
3281            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3282            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3283            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3284            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3285            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3286            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3287            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3288            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3289            Expression::JSONBContains(f) => {
3290                // PostgreSQL JSONB contains key/path operators: a ? b, a @? b
3291                self.generate_expression(&f.this)?;
3292                self.write_space();
3293                self.write(f.original_name.as_deref().unwrap_or("?"));
3294                self.write_space();
3295                self.generate_expression(&f.expression)
3296            }
3297            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3298            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3299            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3300            Expression::Not(op) => match &op.this {
3301                Expression::Like(like) => self.generate_like_op_negated(like, "LIKE"),
3302                Expression::ILike(like) => self.generate_like_op_negated(like, "ILIKE"),
3303                _ => self.generate_unary_op(op, "NOT"),
3304            },
3305            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3306            Expression::BitwiseNot(op) => {
3307                // Presto/Trino use BITWISE_NOT function
3308                if matches!(
3309                    self.config.dialect,
3310                    Some(DialectType::Presto) | Some(DialectType::Trino)
3311                ) {
3312                    self.write_keyword("BITWISE_NOT");
3313                    self.write("(");
3314                    self.generate_expression(&op.this)?;
3315                    self.write(")");
3316                    Ok(())
3317                } else {
3318                    self.generate_unary_op(op, "~")
3319                }
3320            }
3321            Expression::In(in_expr) => self.generate_in(in_expr),
3322            Expression::Between(between) => self.generate_between(between),
3323            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3324            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3325            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3326            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3327            Expression::Is(is_expr) => self.generate_is(is_expr),
3328            Expression::Exists(exists) => self.generate_exists(exists),
3329            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3330            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3331            Expression::Paren(paren) => {
3332                // JoinedTable already outputs its own parentheses, so don't double-wrap
3333                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3334
3335                if !skip_parens {
3336                    self.write("(");
3337                    if self.config.pretty {
3338                        self.write_newline();
3339                        self.indent_level += 1;
3340                        self.write_indent();
3341                    }
3342                }
3343                self.generate_expression(&paren.this)?;
3344                if !skip_parens {
3345                    if self.config.pretty {
3346                        self.write_newline();
3347                        self.indent_level -= 1;
3348                        self.write_indent();
3349                    }
3350                    self.write(")");
3351                }
3352                // Output trailing comments after closing paren
3353                for comment in &paren.trailing_comments {
3354                    self.write(" ");
3355                    self.write_formatted_comment(comment);
3356                }
3357                Ok(())
3358            }
3359            Expression::Array(arr) => self.generate_array(arr),
3360            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3361            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3362            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3363            Expression::DataType(dt) => self.generate_data_type(dt),
3364            Expression::Raw(raw) => {
3365                self.write(&raw.sql);
3366                Ok(())
3367            }
3368            Expression::CreateTask(task) => self.generate_create_task(task),
3369            Expression::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3370            Expression::Command(cmd) => {
3371                self.write(&cmd.this);
3372                if matches!(self.config.dialect, Some(DialectType::ClickHouse))
3373                    && cmd
3374                        .this
3375                        .trim_start()
3376                        .get(..7)
3377                        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("EXPLAIN"))
3378                {
3379                    for _ in 0..Self::missing_closing_parens_outside_quotes(&cmd.this) {
3380                        self.write(")");
3381                    }
3382                }
3383                Ok(())
3384            }
3385            Expression::Kill(kill) => {
3386                self.write_keyword("KILL");
3387                if let Some(kind) = &kill.kind {
3388                    self.write_space();
3389                    self.write_keyword(kind);
3390                }
3391                self.write_space();
3392                self.generate_expression(&kill.this)?;
3393                Ok(())
3394            }
3395            Expression::Prepare(prepare) => self.generate_prepare(prepare),
3396            Expression::Execute(exec) => {
3397                self.write_keyword("EXECUTE");
3398                self.write_space();
3399                if let Some(return_status) = &exec.return_status {
3400                    self.write(return_status);
3401                    self.write(" = ");
3402                }
3403                self.generate_expression(&exec.this)?;
3404                if exec.prepared {
3405                    if !exec.arguments.is_empty() {
3406                        self.write("(");
3407                        for (i, argument) in exec.arguments.iter().enumerate() {
3408                            if i > 0 {
3409                                self.write(", ");
3410                            }
3411                            self.generate_expression(argument)?;
3412                        }
3413                        self.write(")");
3414                    }
3415                    return Ok(());
3416                }
3417                for (i, param) in exec.parameters.iter().enumerate() {
3418                    if i == 0 {
3419                        self.write_space();
3420                    } else {
3421                        self.write(", ");
3422                    }
3423                    self.write(&param.name);
3424                    // Only write = value for named parameters (not positional)
3425                    if !param.positional {
3426                        self.write(" = ");
3427                        self.generate_expression(&param.value)?;
3428                    }
3429                    if param.output {
3430                        self.write_space();
3431                        self.write_keyword("OUTPUT");
3432                    }
3433                }
3434                if let Some(ref suffix) = exec.suffix {
3435                    self.write_space();
3436                    self.write(suffix);
3437                }
3438                Ok(())
3439            }
3440            Expression::Annotated(annotated) => {
3441                self.generate_expression(&annotated.this)?;
3442                for comment in &annotated.trailing_comments {
3443                    self.write(" ");
3444                    self.write_formatted_comment(comment);
3445                }
3446                Ok(())
3447            }
3448
3449            // DDL statements
3450            Expression::CreateTable(ct) => self.generate_create_table(ct),
3451            Expression::DropTable(dt) => self.generate_drop_table(dt),
3452            Expression::Undrop(u) => self.generate_undrop(u),
3453            Expression::AlterTable(at) => self.generate_alter_table(at),
3454            Expression::SplitTable(st) => self.generate_split_table(st),
3455            Expression::FlashbackTable(ft) => self.generate_flashback_table(ft),
3456            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3457            Expression::DropIndex(di) => self.generate_drop_index(di),
3458            Expression::CreateView(cv) => self.generate_create_view(cv),
3459            Expression::DropView(dv) => self.generate_drop_view(dv),
3460            Expression::AlterView(av) => self.generate_alter_view(av),
3461            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3462            Expression::Truncate(tr) => self.generate_truncate(tr),
3463            Expression::Use(u) => self.generate_use(u),
3464            // Phase 4: Additional DDL statements
3465            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3466            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3467            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3468            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3469            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3470            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3471            Expression::DropFunction(df) => self.generate_drop_function(df),
3472            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3473            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3474            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3475            Expression::CreateSynonym(cs) => {
3476                self.write_keyword("CREATE SYNONYM");
3477                self.write_space();
3478                self.generate_table(&cs.name)?;
3479                self.write_space();
3480                self.write_keyword("FOR");
3481                self.write_space();
3482                self.generate_table(&cs.target)?;
3483                Ok(())
3484            }
3485            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3486            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3487            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3488            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3489            Expression::CreateType(ct) => self.generate_create_type(ct),
3490            Expression::DropType(dt) => self.generate_drop_type(dt),
3491            Expression::Describe(d) => self.generate_describe(d),
3492            Expression::Show(s) => self.generate_show(s),
3493
3494            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3495            Expression::Cache(c) => self.generate_cache(c),
3496            Expression::Uncache(u) => self.generate_uncache(u),
3497            Expression::LoadData(l) => self.generate_load_data(l),
3498            Expression::Pragma(p) => self.generate_pragma(p),
3499            Expression::Grant(g) => self.generate_grant(g),
3500            Expression::Revoke(r) => self.generate_revoke(r),
3501            Expression::Comment(c) => self.generate_comment(c),
3502            Expression::SetStatement(s) => self.generate_set_statement(s),
3503
3504            // PIVOT/UNPIVOT
3505            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3506            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3507
3508            // VALUES table constructor
3509            Expression::Values(values) => self.generate_values(values),
3510
3511            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3512            Expression::AIAgg(e) => self.generate_ai_agg(e),
3513            Expression::AIClassify(e) => self.generate_ai_classify(e),
3514            Expression::AddPartition(e) => self.generate_add_partition(e),
3515            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3516            Expression::Aliases(e) => self.generate_aliases(e),
3517            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3518            Expression::AlterColumn(e) => self.generate_alter_column(e),
3519            Expression::AlterSession(e) => self.generate_alter_session(e),
3520            Expression::AlterSet(e) => self.generate_alter_set(e),
3521            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3522            Expression::Analyze(e) => self.generate_analyze(e),
3523            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3524            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3525            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3526            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3527            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3528            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3529            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3530            Expression::Anonymous(e) => self.generate_anonymous(e),
3531            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3532            Expression::Apply(e) => self.generate_apply(e),
3533            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3534            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3535            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3536            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3537            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3538            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3539            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3540            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3541            Expression::ArgMax(e) => self.generate_arg_max(e),
3542            Expression::ArgMin(e) => self.generate_arg_min(e),
3543            Expression::ArrayAll(e) => self.generate_array_all(e),
3544            Expression::ArrayAny(e) => self.generate_array_any(e),
3545            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3546            Expression::ArraySum(e) => self.generate_array_sum(e),
3547            Expression::AtIndex(e) => self.generate_at_index(e),
3548            Expression::Attach(e) => self.generate_attach(e),
3549            Expression::AttachOption(e) => self.generate_attach_option(e),
3550            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3551            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3552            Expression::BackupProperty(e) => self.generate_backup_property(e),
3553            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3554            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3555            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3556            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3557            Expression::Booland(e) => self.generate_booland(e),
3558            Expression::Boolor(e) => self.generate_boolor(e),
3559            Expression::BuildProperty(e) => self.generate_build_property(e),
3560            Expression::ByteString(e) => self.generate_byte_string(e),
3561            Expression::CaseSpecificColumnConstraint(e) => {
3562                self.generate_case_specific_column_constraint(e)
3563            }
3564            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3565            Expression::Changes(e) => self.generate_changes(e),
3566            Expression::CharacterSetColumnConstraint(e) => {
3567                self.generate_character_set_column_constraint(e)
3568            }
3569            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3570            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3571            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3572            Expression::CheckJson(e) => self.generate_check_json(e),
3573            Expression::CheckXml(e) => self.generate_check_xml(e),
3574            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3575            Expression::Clone(e) => self.generate_clone(e),
3576            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3577            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3578            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3579            Expression::CollateProperty(e) => self.generate_collate_property(e),
3580            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3581            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3582            Expression::ColumnPosition(e) => self.generate_column_position(e),
3583            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3584            Expression::Columns(e) => self.generate_columns(e),
3585            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3586            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3587            Expression::Commit(e) => self.generate_commit(e),
3588            Expression::Comprehension(e) => self.generate_comprehension(e),
3589            Expression::Compress(e) => self.generate_compress(e),
3590            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3591            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3592            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3593            Expression::Constraint(e) => self.generate_constraint(e),
3594            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3595            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3596            Expression::Copy(e) => self.generate_copy(e),
3597            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3598            Expression::Corr(e) => self.generate_corr(e),
3599            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3600            Expression::CovarPop(e) => self.generate_covar_pop(e),
3601            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3602            Expression::Credentials(e) => self.generate_credentials(e),
3603            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3604            Expression::Cte(e) => self.generate_cte(e),
3605            Expression::Cube(e) => self.generate_cube(e),
3606            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3607            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3608            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3609            Expression::CurrentUser(e) => self.generate_current_user(e),
3610            Expression::DPipe(e) => self.generate_d_pipe(e),
3611            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3612            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3613            Expression::Date(e) => self.generate_date_func(e),
3614            Expression::DateBin(e) => self.generate_date_bin(e),
3615            Expression::DateFormatColumnConstraint(e) => {
3616                self.generate_date_format_column_constraint(e)
3617            }
3618            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3619            Expression::Datetime(e) => self.generate_datetime(e),
3620            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3621            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3622            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3623            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3624            Expression::Dayname(e) => self.generate_dayname(e),
3625            Expression::Declare(e) => self.generate_declare(e),
3626            Expression::DeclareItem(e) => self.generate_declare_item(e),
3627            Expression::DecodeCase(e) => self.generate_decode_case(e),
3628            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3629            Expression::DecompressString(e) => self.generate_decompress_string(e),
3630            Expression::Decrypt(e) => self.generate_decrypt(e),
3631            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3632            Expression::DefaultColumnConstraint(e) => {
3633                self.write_keyword("DEFAULT");
3634                self.write_space();
3635                self.generate_expression(&e.this)?;
3636                if let Some(ref col) = e.for_column {
3637                    self.write_space();
3638                    self.write_keyword("FOR");
3639                    self.write_space();
3640                    self.generate_identifier(col)?;
3641                }
3642                Ok(())
3643            }
3644            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3645            Expression::Detach(e) => self.generate_detach(e),
3646            Expression::DictProperty(e) => self.generate_dict_property(e),
3647            Expression::DictRange(e) => self.generate_dict_range(e),
3648            Expression::Directory(e) => self.generate_directory(e),
3649            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3650            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3651            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3652            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3653            Expression::DotProduct(e) => self.generate_dot_product(e),
3654            Expression::DropPartition(e) => self.generate_drop_partition(e),
3655            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3656            Expression::Elt(e) => self.generate_elt(e),
3657            Expression::Encode(e) => self.generate_encode(e),
3658            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3659            Expression::Encrypt(e) => self.generate_encrypt(e),
3660            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3661            Expression::EngineProperty(e) => self.generate_engine_property(e),
3662            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3663            Expression::EphemeralColumnConstraint(e) => {
3664                self.generate_ephemeral_column_constraint(e)
3665            }
3666            Expression::EqualNull(e) => self.generate_equal_null(e),
3667            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3668            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3669            Expression::Export(e) => self.generate_export(e),
3670            Expression::ExternalProperty(e) => self.generate_external_property(e),
3671            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3672            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3673            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3674            Expression::Fetch(e) => self.generate_fetch(e),
3675            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3676            Expression::Filter(e) => self.generate_filter(e),
3677            Expression::Float64(e) => self.generate_float64(e),
3678            Expression::ForIn(e) => self.generate_for_in(e),
3679            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3680            Expression::Format(e) => self.generate_format(e),
3681            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3682            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3683            Expression::From(e) => self.generate_from(e),
3684            Expression::FromBase(e) => self.generate_from_base(e),
3685            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3686            Expression::GapFill(e) => self.generate_gap_fill(e),
3687            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3688            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3689            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3690            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3691            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3692                self.generate_generated_as_identity_column_constraint(e)
3693            }
3694            Expression::GeneratedAsRowColumnConstraint(e) => {
3695                self.generate_generated_as_row_column_constraint(e)
3696            }
3697            Expression::Get(e) => self.generate_get(e),
3698            Expression::GetExtract(e) => self.generate_get_extract(e),
3699            Expression::Getbit(e) => self.generate_getbit(e),
3700            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3701            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3702            Expression::Group(e) => self.generate_group(e),
3703            Expression::GroupBy(e) => self.generate_group_by(e),
3704            Expression::Grouping(e) => self.generate_grouping(e),
3705            Expression::GroupingId(e) => self.generate_grouping_id(e),
3706            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3707            Expression::HashAgg(e) => self.generate_hash_agg(e),
3708            Expression::Having(e) => self.generate_having(e),
3709            Expression::HavingMax(e) => self.generate_having_max(e),
3710            Expression::Heredoc(e) => self.generate_heredoc(e),
3711            Expression::HexEncode(e) => self.generate_hex_encode(e),
3712            Expression::Hll(e) => self.generate_hll(e),
3713            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3714            Expression::IncludeProperty(e) => self.generate_include_property(e),
3715            Expression::Index(e) => self.generate_index(e),
3716            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3717            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3718            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3719            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3720            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3721            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3722            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3723            Expression::Install(e) => self.generate_install(e),
3724            Expression::IntervalOp(e) => self.generate_interval_op(e),
3725            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3726            Expression::IntoClause(e) => self.generate_into_clause(e),
3727            Expression::Introducer(e) => self.generate_introducer(e),
3728            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3729            Expression::JSON(e) => self.generate_json(e),
3730            Expression::JSONArray(e) => self.generate_json_array(e),
3731            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3732            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3733            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3734            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3735            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3736            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3737            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3738            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3739            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3740            Expression::JSONExists(e) => self.generate_json_exists(e),
3741            Expression::JSONCast(e) => self.generate_json_cast(e),
3742            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3743            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3744            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3745            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3746            Expression::JSONFormat(e) => self.generate_json_format(e),
3747            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3748            Expression::JSONKeys(e) => self.generate_json_keys(e),
3749            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3750            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3751            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3752            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3753            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3754            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3755            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3756            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3757            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3758            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3759            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3760            Expression::JSONRemove(e) => self.generate_json_remove(e),
3761            Expression::JSONSchema(e) => self.generate_json_schema(e),
3762            Expression::JSONSet(e) => self.generate_json_set(e),
3763            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3764            Expression::JSONTable(e) => self.generate_json_table(e),
3765            Expression::JSONType(e) => self.generate_json_type(e),
3766            Expression::JSONValue(e) => self.generate_json_value(e),
3767            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3768            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3769            Expression::JoinHint(e) => self.generate_join_hint(e),
3770            Expression::JournalProperty(e) => self.generate_journal_property(e),
3771            Expression::LanguageProperty(e) => self.generate_language_property(e),
3772            Expression::Lateral(e) => self.generate_lateral(e),
3773            Expression::LikeProperty(e) => self.generate_like_property(e),
3774            Expression::Limit(e) => self.generate_limit(e),
3775            Expression::LimitOptions(e) => self.generate_limit_options(e),
3776            Expression::List(e) => self.generate_list(e),
3777            Expression::ToMap(e) => self.generate_tomap(e),
3778            Expression::Localtime(e) => self.generate_localtime(e),
3779            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3780            Expression::LocationProperty(e) => self.generate_location_property(e),
3781            Expression::Lock(e) => self.generate_lock(e),
3782            Expression::LockProperty(e) => self.generate_lock_property(e),
3783            Expression::LockingProperty(e) => self.generate_locking_property(e),
3784            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3785            Expression::LogProperty(e) => self.generate_log_property(e),
3786            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3787            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3788            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3789            Expression::MakeInterval(e) => self.generate_make_interval(e),
3790            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3791            Expression::Map(e) => self.generate_map(e),
3792            Expression::MapCat(e) => self.generate_map_cat(e),
3793            Expression::MapDelete(e) => self.generate_map_delete(e),
3794            Expression::MapInsert(e) => self.generate_map_insert(e),
3795            Expression::MapPick(e) => self.generate_map_pick(e),
3796            Expression::MaskingPolicyColumnConstraint(e) => {
3797                self.generate_masking_policy_column_constraint(e)
3798            }
3799            Expression::MatchAgainst(e) => self.generate_match_against(e),
3800            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3801            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3802            Expression::Merge(e) => self.generate_merge(e),
3803            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3804            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3805            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3806            Expression::Minhash(e) => self.generate_minhash(e),
3807            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3808            Expression::Monthname(e) => self.generate_monthname(e),
3809            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3810            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3811            Expression::Normal(e) => self.generate_normal(e),
3812            Expression::Normalize(e) => self.generate_normalize(e),
3813            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3814            Expression::Nullif(e) => self.generate_nullif(e),
3815            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3816            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3817            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3818            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3819            Expression::Offset(e) => self.generate_offset(e),
3820            Expression::Qualify(e) => self.generate_qualify(e),
3821            Expression::OnCluster(e) => self.generate_on_cluster(e),
3822            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3823            Expression::OnCondition(e) => self.generate_on_condition(e),
3824            Expression::OnConflict(e) => self.generate_on_conflict(e),
3825            Expression::OnProperty(e) => self.generate_on_property(e),
3826            Expression::Opclass(e) => self.generate_opclass(e),
3827            Expression::OpenJSON(e) => self.generate_open_json(e),
3828            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3829            Expression::Operator(e) => self.generate_operator(e),
3830            Expression::OrderBy(e) => self.generate_order_by(e),
3831            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3832            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3833            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3834            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3835            Expression::ParseIp(e) => self.generate_parse_ip(e),
3836            Expression::ParseJSON(e) => self.generate_parse_json(e),
3837            Expression::ParseTime(e) => self.generate_parse_time(e),
3838            Expression::ParseUrl(e) => self.generate_parse_url(e),
3839            Expression::Partition(e) => self.generate_partition_expr(e),
3840            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3841            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3842            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3843            Expression::PartitionByRangePropertyDynamic(e) => {
3844                self.generate_partition_by_range_property_dynamic(e)
3845            }
3846            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3847            Expression::PartitionList(e) => self.generate_partition_list(e),
3848            Expression::PartitionRange(e) => self.generate_partition_range(e),
3849            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3850            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3851            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3852            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3853            Expression::PeriodForSystemTimeConstraint(e) => {
3854                self.generate_period_for_system_time_constraint(e)
3855            }
3856            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3857            Expression::PivotAny(e) => self.generate_pivot_any(e),
3858            Expression::Predict(e) => self.generate_predict(e),
3859            Expression::PreviousDay(e) => self.generate_previous_day(e),
3860            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3861            Expression::PrimaryKeyColumnConstraint(e) => {
3862                self.generate_primary_key_column_constraint(e)
3863            }
3864            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3865            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3866            Expression::OptionsProperty(e) => self.generate_options_property(e),
3867            Expression::Properties(e) => self.generate_properties(e),
3868            Expression::Property(e) => self.generate_property(e),
3869            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3870            Expression::Put(e) => self.generate_put(e),
3871            Expression::Quantile(e) => self.generate_quantile(e),
3872            Expression::QueryBand(e) => self.generate_query_band(e),
3873            Expression::QueryOption(e) => self.generate_query_option(e),
3874            Expression::QueryTransform(e) => self.generate_query_transform(e),
3875            Expression::Randn(e) => self.generate_randn(e),
3876            Expression::Randstr(e) => self.generate_randstr(e),
3877            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3878            Expression::RangeN(e) => self.generate_range_n(e),
3879            Expression::ReadCSV(e) => self.generate_read_csv(e),
3880            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3881            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3882            Expression::Reduce(e) => self.generate_reduce(e),
3883            Expression::Reference(e) => self.generate_reference(e),
3884            Expression::Refresh(e) => self.generate_refresh(e),
3885            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3886            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3887            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3888            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3889            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3890            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3891            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3892            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3893            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3894            Expression::RegrCount(e) => self.generate_regr_count(e),
3895            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3896            Expression::RegrR2(e) => self.generate_regr_r2(e),
3897            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3898            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3899            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3900            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3901            Expression::RegrValx(e) => self.generate_regr_valx(e),
3902            Expression::RegrValy(e) => self.generate_regr_valy(e),
3903            Expression::RemoteWithConnectionModelProperty(e) => {
3904                self.generate_remote_with_connection_model_property(e)
3905            }
3906            Expression::RenameColumn(e) => self.generate_rename_column(e),
3907            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3908            Expression::Returning(e) => self.generate_returning(e),
3909            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3910            Expression::Rollback(e) => self.generate_rollback(e),
3911            Expression::Rollup(e) => self.generate_rollup(e),
3912            Expression::RowFormatDelimitedProperty(e) => {
3913                self.generate_row_format_delimited_property(e)
3914            }
3915            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3916            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3917            Expression::SHA2(e) => self.generate_sha2(e),
3918            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3919            Expression::SafeAdd(e) => self.generate_safe_add(e),
3920            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3921            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3922            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3923            Expression::SampleProperty(e) => self.generate_sample_property(e),
3924            Expression::Schema(e) => self.generate_schema(e),
3925            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3926            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3927            Expression::Search(e) => self.generate_search(e),
3928            Expression::SearchIp(e) => self.generate_search_ip(e),
3929            Expression::SecurityProperty(e) => self.generate_security_property(e),
3930            Expression::SemanticView(e) => self.generate_semantic_view(e),
3931            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3932            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3933            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3934            Expression::Set(e) => self.generate_set(e),
3935            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3936            Expression::SetItem(e) => self.generate_set_item(e),
3937            Expression::SetOperation(e) => self.generate_set_operation(e),
3938            Expression::SetProperty(e) => self.generate_set_property(e),
3939            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3940            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3941            Expression::Slice(e) => self.generate_slice(e),
3942            Expression::SortArray(e) => self.generate_sort_array(e),
3943            Expression::SortBy(e) => self.generate_sort_by(e),
3944            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3945            Expression::SplitPart(e) => self.generate_split_part(e),
3946            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3947            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3948            Expression::StDistance(e) => self.generate_st_distance(e),
3949            Expression::StPoint(e) => self.generate_st_point(e),
3950            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3951            Expression::StandardHash(e) => self.generate_standard_hash(e),
3952            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3953            Expression::StrPosition(e) => self.generate_str_position(e),
3954            Expression::StrToDate(e) => self.generate_str_to_date(e),
3955            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3956            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3957            Expression::StrToMap(e) => self.generate_str_to_map(e),
3958            Expression::StrToTime(e) => self.generate_str_to_time(e),
3959            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3960            Expression::StringToArray(e) => self.generate_string_to_array(e),
3961            Expression::Struct(e) => self.generate_struct(e),
3962            Expression::Stuff(e) => self.generate_stuff(e),
3963            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3964            Expression::Summarize(e) => self.generate_summarize(e),
3965            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3966            Expression::TableAlias(e) => self.generate_table_alias(e),
3967            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3968            Expression::RowsFrom(e) => self.generate_rows_from(e),
3969            Expression::TableSample(e) => self.generate_table_sample(e),
3970            Expression::Tag(e) => self.generate_tag(e),
3971            Expression::Tags(e) => self.generate_tags(e),
3972            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3973            Expression::Time(e) => self.generate_time_func(e),
3974            Expression::TimeAdd(e) => self.generate_time_add(e),
3975            Expression::TimeDiff(e) => self.generate_time_diff(e),
3976            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3977            Expression::TimeSlice(e) => self.generate_time_slice(e),
3978            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3979            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3980            Expression::TimeSub(e) => self.generate_time_sub(e),
3981            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3982            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3983            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3984            Expression::TimeUnit(e) => self.generate_time_unit(e),
3985            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3986            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3987            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3988            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3989            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3990            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3991            Expression::ToBinary(e) => self.generate_to_binary(e),
3992            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3993            Expression::ToChar(e) => self.generate_to_char(e),
3994            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3995            Expression::ToDouble(e) => self.generate_to_double(e),
3996            Expression::ToFile(e) => self.generate_to_file(e),
3997            Expression::ToNumber(e) => self.generate_to_number(e),
3998            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3999            Expression::Transaction(e) => self.generate_transaction(e),
4000            Expression::Transform(e) => self.generate_transform(e),
4001            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
4002            Expression::TransientProperty(e) => self.generate_transient_property(e),
4003            Expression::Translate(e) => self.generate_translate(e),
4004            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
4005            Expression::TruncateTable(e) => self.generate_truncate_table(e),
4006            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
4007            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
4008            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
4009            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
4010            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
4011            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
4012            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
4013            Expression::Unhex(e) => self.generate_unhex(e),
4014            Expression::UnicodeString(e) => self.generate_unicode_string(e),
4015            Expression::Uniform(e) => self.generate_uniform(e),
4016            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
4017            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
4018            Expression::RollupProperty(e) => self.generate_rollup_property(e),
4019            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
4020            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
4021            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
4022            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
4023            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
4024            Expression::UtcTime(e) => self.generate_utc_time(e),
4025            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
4026            Expression::Uuid(e) => self.generate_uuid(e),
4027            Expression::Var(v) => {
4028                if matches!(self.config.dialect, Some(DialectType::MySQL))
4029                    && v.this.len() > 2
4030                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
4031                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
4032                {
4033                    return self.generate_identifier(&Identifier {
4034                        name: v.this.clone(),
4035                        quoted: true,
4036                        trailing_comments: Vec::new(),
4037                        span: None,
4038                    });
4039                }
4040                self.write(&v.this);
4041                Ok(())
4042            }
4043            Expression::Variadic(e) => {
4044                self.write_keyword("VARIADIC");
4045                self.write_space();
4046                self.generate_expression(&e.this)?;
4047                Ok(())
4048            }
4049            Expression::VarMap(e) => self.generate_var_map(e),
4050            Expression::VectorSearch(e) => self.generate_vector_search(e),
4051            Expression::Version(e) => self.generate_version(e),
4052            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
4053            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
4054            Expression::WatermarkColumnConstraint(e) => {
4055                self.generate_watermark_column_constraint(e)
4056            }
4057            Expression::Week(e) => self.generate_week(e),
4058            Expression::When(e) => self.generate_when(e),
4059            Expression::Whens(e) => self.generate_whens(e),
4060            Expression::Where(e) => self.generate_where(e),
4061            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4062            Expression::Window(e) => self.generate_window(e),
4063            Expression::WindowSpec(e) => self.generate_window_spec(e),
4064            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4065            Expression::WithFill(e) => self.generate_with_fill(e),
4066            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4067            Expression::WithOperator(e) => self.generate_with_operator(e),
4068            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4069            Expression::WithSchemaBindingProperty(e) => {
4070                self.generate_with_schema_binding_property(e)
4071            }
4072            Expression::WithSystemVersioningProperty(e) => {
4073                self.generate_with_system_versioning_property(e)
4074            }
4075            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4076            Expression::XMLElement(e) => self.generate_xml_element(e),
4077            Expression::XMLGet(e) => self.generate_xml_get(e),
4078            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4079            Expression::XMLTable(e) => self.generate_xml_table(e),
4080            Expression::Xor(e) => self.generate_xor(e),
4081            Expression::Zipf(e) => self.generate_zipf(e),
4082            _ => self.write_unsupported_comment("unsupported expression"),
4083        }
4084    }
4085
4086    fn should_handle_tsql_distinct_null_ordering(&self, select: &Select) -> bool {
4087        matches!(
4088            self.config.dialect,
4089            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4090        ) && !self.config.null_ordering_supported
4091            && select.distinct
4092            && select.order_by.as_ref().is_some_and(|order_by| {
4093                order_by
4094                    .expressions
4095                    .iter()
4096                    .any(Self::ordered_requires_tsql_null_ordering_emulation)
4097            })
4098    }
4099
4100    fn ordered_requires_tsql_null_ordering_emulation(ordered: &Ordered) -> bool {
4101        let Some(nulls_first) = ordered.nulls_first else {
4102            return false;
4103        };
4104
4105        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
4106        let target_default_nulls_first = !ordered.desc;
4107
4108        nulls_first != target_default_nulls_first && !random_ordering
4109    }
4110
4111    fn projection_output_identifier(expression: &Expression) -> Option<Identifier> {
4112        match expression {
4113            Expression::Alias(alias) if !alias.alias.name.is_empty() => Some(alias.alias.clone()),
4114            Expression::Column(column) => Some(column.name.clone()),
4115            Expression::Identifier(identifier) => Some(identifier.clone()),
4116            _ => None,
4117        }
4118    }
4119
4120    fn identifier_names_match(left: &Identifier, right: &Identifier) -> bool {
4121        if left.quoted || right.quoted {
4122            left.name == right.name
4123        } else {
4124            left.name.eq_ignore_ascii_case(&right.name)
4125        }
4126    }
4127
4128    fn expression_matches_identifier(expression: &Expression, identifier: &Identifier) -> bool {
4129        match expression {
4130            Expression::Column(column) if column.table.is_none() => {
4131                Self::identifier_names_match(&column.name, identifier)
4132            }
4133            Expression::Identifier(other) => Self::identifier_names_match(other, identifier),
4134            _ => false,
4135        }
4136    }
4137
4138    fn column_expression(identifier: Identifier) -> Expression {
4139        Expression::Column(Box::new(Column {
4140            name: identifier,
4141            table: None,
4142            join_mark: false,
4143            trailing_comments: Vec::new(),
4144            span: None,
4145            inferred_type: None,
4146        }))
4147    }
4148
4149    fn positional_ordering_index(expression: &Expression) -> Option<usize> {
4150        match expression {
4151            Expression::Literal(lit) => match lit.as_ref() {
4152                Literal::Number(n) => {
4153                    let value = n.parse::<usize>().ok()?;
4154                    value.checked_sub(1)
4155                }
4156                _ => None,
4157            },
4158            _ => None,
4159        }
4160    }
4161
4162    fn projection_sort_expression(expression: &Expression) -> Option<Expression> {
4163        let expression = match expression {
4164            Expression::Alias(alias) => &alias.this,
4165            other => other,
4166        };
4167
4168        match expression {
4169            Expression::Star(_)
4170            | Expression::Literal(_)
4171            | Expression::Null(_)
4172            | Expression::Boolean(_) => None,
4173            _ => Some(expression.clone()),
4174        }
4175    }
4176
4177    fn resolve_positional_order_projection(
4178        projections: &[Expression],
4179        index: usize,
4180    ) -> Option<Expression> {
4181        Self::projection_sort_expression(projections.get(index)?)
4182    }
4183
4184    fn is_direct_grouping_expression(expression: &Expression) -> bool {
4185        match expression {
4186            Expression::Paren(paren) => Self::is_direct_grouping_expression(&paren.this),
4187            Expression::Grouping(_) | Expression::GroupingId(_) => true,
4188            Expression::Function(function) => {
4189                function.name.eq_ignore_ascii_case("GROUPING")
4190                    || function.name.eq_ignore_ascii_case("GROUPING_ID")
4191            }
4192            _ => false,
4193        }
4194    }
4195
4196    fn group_by_has_multiple_grouping_levels(group_by: &GroupBy) -> bool {
4197        group_by
4198            .expressions
4199            .iter()
4200            .any(|expression| match expression {
4201                Expression::Cube(_) | Expression::Rollup(_) => true,
4202                Expression::GroupingSets(grouping_sets) => grouping_sets.expressions.len() > 1,
4203                Expression::Function(function)
4204                    if function.name.eq_ignore_ascii_case("CUBE")
4205                        || function.name.eq_ignore_ascii_case("ROLLUP") =>
4206                {
4207                    !function.args.is_empty()
4208                }
4209                Expression::Function(function)
4210                    if function.name.eq_ignore_ascii_case("GROUPING SETS") =>
4211                {
4212                    function.args.len() > 1
4213                        || function.args.first().is_some_and(|expression| {
4214                            matches!(expression, Expression::Cube(_) | Expression::Rollup(_))
4215                                || matches!(expression,
4216                                    Expression::Function(nested)
4217                                        if nested.name.eq_ignore_ascii_case("CUBE")
4218                                            || nested.name.eq_ignore_ascii_case("ROLLUP")
4219                                )
4220                        })
4221                }
4222                _ => false,
4223            })
4224    }
4225
4226    fn set_output_identifier_at(expression: &Expression, index: usize) -> Option<Identifier> {
4227        match expression {
4228            Expression::Select(select) => {
4229                Self::projection_output_identifier(select.expressions.get(index)?)
4230            }
4231            Expression::Union(union) => Self::set_output_identifier_at(&union.left, index),
4232            Expression::Intersect(intersect) => {
4233                Self::set_output_identifier_at(&intersect.left, index)
4234            }
4235            Expression::Except(except) => Self::set_output_identifier_at(&except.left, index),
4236            Expression::Subquery(subquery) => subquery
4237                .column_aliases
4238                .get(index)
4239                .cloned()
4240                .or_else(|| Self::set_output_identifier_at(&subquery.this, index)),
4241            _ => None,
4242        }
4243    }
4244
4245    fn resolve_single_subquery_star_projection(
4246        select: &Select,
4247        index: usize,
4248    ) -> Option<Expression> {
4249        if select.expressions.len() != 1
4250            || !matches!(select.expressions.first(), Some(Expression::Star(_)))
4251        {
4252            return None;
4253        }
4254
4255        let from = select.from.as_ref()?;
4256        if from.expressions.len() != 1 {
4257            return None;
4258        }
4259
4260        let Expression::Subquery(subquery) = from.expressions.first()? else {
4261            return None;
4262        };
4263
4264        subquery
4265            .column_aliases
4266            .get(index)
4267            .cloned()
4268            .or_else(|| Self::set_output_identifier_at(&subquery.this, index))
4269            .map(Self::column_expression)
4270    }
4271
4272    fn sole_tsql_ordering_source_qualifier(select: &Select) -> Option<Identifier> {
4273        if !select.joins.is_empty() {
4274            return None;
4275        }
4276
4277        let from = select.from.as_ref()?;
4278        if from.expressions.len() != 1 {
4279            return None;
4280        }
4281
4282        match from.expressions.first()? {
4283            Expression::Table(table) => {
4284                Some(table.alias.clone().unwrap_or_else(|| table.name.clone()))
4285            }
4286            Expression::Subquery(subquery) => subquery.alias.clone(),
4287            _ => None,
4288        }
4289    }
4290
4291    fn resolve_duplicate_tsql_ordering_column(
4292        select: &Select,
4293        expression: &Expression,
4294    ) -> Option<Expression> {
4295        let Expression::Column(order_column) = expression else {
4296            return None;
4297        };
4298        if order_column.table.is_some() {
4299            return None;
4300        }
4301
4302        let matching_projections: Vec<_> = select
4303            .expressions
4304            .iter()
4305            .filter(|projection| {
4306                Self::projection_output_identifier(projection).is_some_and(|identifier| {
4307                    Self::identifier_names_match(&identifier, &order_column.name)
4308                })
4309            })
4310            .filter_map(Self::projection_sort_expression)
4311            .collect();
4312
4313        if matching_projections.len() < 2
4314            || matching_projections[1..]
4315                .iter()
4316                .any(|projection| projection != &matching_projections[0])
4317        {
4318            return None;
4319        }
4320
4321        let Expression::Column(projected_column) = &matching_projections[0] else {
4322            return None;
4323        };
4324        if !Self::identifier_names_match(&projected_column.name, &order_column.name) {
4325            return None;
4326        }
4327
4328        let qualifier = projected_column
4329            .table
4330            .clone()
4331            .or_else(|| Self::sole_tsql_ordering_source_qualifier(select))?;
4332        let mut resolved = order_column.as_ref().clone();
4333        resolved.table = Some(qualifier);
4334        Some(Expression::Column(Box::new(resolved)))
4335    }
4336
4337    fn resolve_tsql_null_ordering_for_select(&self, select: &Select) -> Option<Select> {
4338        if !matches!(
4339            self.config.dialect,
4340            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4341        ) || self.config.null_ordering_supported
4342        {
4343            return None;
4344        }
4345
4346        let order_by = select.order_by.as_ref()?;
4347        let grouping_order_is_constant = matches!(self.config.dialect, Some(DialectType::Fabric))
4348            && select
4349                .group_by
4350                .as_ref()
4351                .is_some_and(|group_by| !Self::group_by_has_multiple_grouping_levels(group_by));
4352        let mut resolved_expressions = Vec::with_capacity(order_by.expressions.len());
4353        let mut changed = false;
4354
4355        for mut ordered in order_by.expressions.iter().cloned() {
4356            if Self::ordered_requires_tsql_null_ordering_emulation(&ordered) {
4357                if let Some(index) = Self::positional_ordering_index(&ordered.this) {
4358                    if let Some(resolved) =
4359                        Self::resolve_positional_order_projection(&select.expressions, index)
4360                            .or_else(|| {
4361                                Self::resolve_single_subquery_star_projection(select, index)
4362                            })
4363                    {
4364                        if resolved != ordered.this {
4365                            ordered.this = resolved;
4366                            changed = true;
4367                        }
4368                    }
4369                }
4370
4371                if let Some(resolved) =
4372                    Self::resolve_duplicate_tsql_ordering_column(select, &ordered.this)
4373                {
4374                    ordered.this = resolved;
4375                    changed = true;
4376                }
4377            }
4378
4379            // Fabric rejects ORDER BY expressions that fold to constants (Msg 408).
4380            // With one effective grouping level, a direct GROUPING/GROUPING_ID
4381            // result is constant and contributes no ordering, so omit it.
4382            if grouping_order_is_constant && Self::is_direct_grouping_expression(&ordered.this) {
4383                changed = true;
4384                continue;
4385            }
4386
4387            resolved_expressions.push(ordered);
4388        }
4389
4390        if changed {
4391            let mut resolved_select = select.clone();
4392            resolved_select.order_by = if resolved_expressions.is_empty() {
4393                None
4394            } else {
4395                Some(OrderBy {
4396                    expressions: resolved_expressions,
4397                    siblings: order_by.siblings,
4398                    comments: order_by.comments.clone(),
4399                })
4400            };
4401            Some(resolved_select)
4402        } else {
4403            None
4404        }
4405    }
4406
4407    fn resolve_distinct_order_projection(
4408        projections: &[Expression],
4409        order_expression: &Expression,
4410    ) -> Option<Expression> {
4411        for projection in projections {
4412            if let Expression::Alias(alias) = projection {
4413                if Self::expression_matches_identifier(order_expression, &alias.alias)
4414                    || &alias.this == order_expression
4415                {
4416                    return Some(alias.this.clone());
4417                }
4418            } else if projection == order_expression {
4419                return Some(projection.clone());
4420            } else if let Some(identifier) = Self::projection_output_identifier(projection) {
4421                if Self::expression_matches_identifier(order_expression, &identifier) {
4422                    return Some(projection.clone());
4423                }
4424            }
4425        }
4426
4427        None
4428    }
4429
4430    fn fresh_polyglot_alias(base: &str, index: usize, used_names: &mut Vec<String>) -> String {
4431        let mut suffix = 0;
4432        loop {
4433            let candidate = if suffix == 0 {
4434                format!("{base}_{index}")
4435            } else {
4436                format!("{base}_{index}_{suffix}")
4437            };
4438
4439            if !used_names
4440                .iter()
4441                .any(|name| name.eq_ignore_ascii_case(&candidate))
4442            {
4443                used_names.push(candidate.clone());
4444                return candidate;
4445            }
4446
4447            suffix += 1;
4448        }
4449    }
4450
4451    fn tsql_null_ordering_case(expression: Expression) -> Expression {
4452        Expression::Case(Box::new(Case {
4453            operand: None,
4454            whens: vec![(
4455                Expression::IsNull(Box::new(IsNull {
4456                    this: expression,
4457                    not: false,
4458                    postfix_form: false,
4459                })),
4460                Expression::number(1),
4461            )],
4462            else_: Some(Expression::number(0)),
4463            comments: Vec::new(),
4464            inferred_type: None,
4465        }))
4466    }
4467
4468    fn try_build_tsql_distinct_null_ordering_wrapper(&self, select: &Select) -> Option<Select> {
4469        let order_by = select.order_by.as_ref()?;
4470
4471        if order_by.siblings
4472            || select.distinct_on.is_some()
4473            || select.distribute_by.is_some()
4474            || select.cluster_by.is_some()
4475            || select.sort_by.is_some()
4476            || select.limit.is_some()
4477            || select.offset.is_some()
4478            || select.limit_by.is_some()
4479            || select.fetch.is_some()
4480            || select.top.is_some()
4481            || select.settings.is_some()
4482            || select.format.is_some()
4483            || select.kind.is_some()
4484            || select.hint.is_some()
4485            || select.into.is_some()
4486            || !select.locks.is_empty()
4487            || !select.for_xml.is_empty()
4488            || !select.for_json.is_empty()
4489            || !select.operation_modifiers.is_empty()
4490            || select.option.is_some()
4491            || select.exclude.is_some()
4492        {
4493            return None;
4494        }
4495
4496        let projection_identifiers: Vec<_> = select
4497            .expressions
4498            .iter()
4499            .map(Self::projection_output_identifier)
4500            .collect::<Option<_>>()?;
4501        let mut used_names: Vec<_> = projection_identifiers
4502            .iter()
4503            .map(|identifier| identifier.name.clone())
4504            .collect();
4505
4506        let mut inner = select.clone();
4507        inner.order_by = None;
4508
4509        let outer_with = inner.with.take();
4510        let outer_leading_comments = std::mem::take(&mut inner.leading_comments);
4511        let outer_post_select_comments = std::mem::take(&mut inner.post_select_comments);
4512
4513        let mut outer_order_expressions = Vec::with_capacity(order_by.expressions.len() * 2);
4514        for (index, ordered) in order_by.expressions.iter().enumerate() {
4515            if ordered.with_fill.is_some() {
4516                return None;
4517            }
4518
4519            let sort_expression =
4520                Self::resolve_distinct_order_projection(&select.expressions, &ordered.this)?;
4521
4522            if Self::ordered_requires_tsql_null_ordering_emulation(ordered) {
4523                let null_alias =
4524                    Self::fresh_polyglot_alias("_polyglot_order_null", index, &mut used_names);
4525                inner.expressions.push(
4526                    Self::tsql_null_ordering_case(sort_expression.clone())
4527                        .alias(null_alias.clone()),
4528                );
4529                outer_order_expressions.push(Ordered {
4530                    this: Expression::column(null_alias),
4531                    desc: ordered.nulls_first == Some(true),
4532                    nulls_first: None,
4533                    explicit_asc: false,
4534                    with_fill: None,
4535                });
4536            }
4537
4538            let key_alias =
4539                Self::fresh_polyglot_alias("_polyglot_order_key", index, &mut used_names);
4540            inner
4541                .expressions
4542                .push(sort_expression.alias(key_alias.clone()));
4543            outer_order_expressions.push(Ordered {
4544                this: Expression::column(key_alias),
4545                desc: ordered.desc,
4546                nulls_first: None,
4547                explicit_asc: ordered.explicit_asc,
4548                with_fill: None,
4549            });
4550        }
4551
4552        let subquery = Subquery {
4553            this: Expression::Select(Box::new(inner)),
4554            alias: Some(Identifier::new("_polyglot_distinct_order")),
4555            column_aliases: Vec::new(),
4556            alias_explicit_as: true,
4557            alias_keyword: None,
4558            order_by: None,
4559            limit: None,
4560            offset: None,
4561            distribute_by: None,
4562            sort_by: None,
4563            cluster_by: None,
4564            lateral: false,
4565            modifiers_inside: false,
4566            trailing_comments: Vec::new(),
4567            inferred_type: None,
4568        };
4569
4570        let mut outer = Select::new();
4571        outer.with = outer_with;
4572        outer.leading_comments = outer_leading_comments;
4573        outer.post_select_comments = outer_post_select_comments;
4574        outer.expressions = projection_identifiers
4575            .into_iter()
4576            .map(Self::column_expression)
4577            .collect();
4578        outer.from = Some(From {
4579            expressions: vec![Expression::Subquery(Box::new(subquery))],
4580        });
4581        outer.order_by = Some(OrderBy {
4582            expressions: outer_order_expressions,
4583            siblings: false,
4584            comments: order_by.comments.clone(),
4585        });
4586
4587        Some(outer)
4588    }
4589
4590    fn generate_select(&mut self, select: &Select) -> Result<()> {
4591        use crate::dialects::DialectType;
4592
4593        if let Some(resolved_select) = self.resolve_tsql_null_ordering_for_select(select) {
4594            return self.generate_select(&resolved_select);
4595        }
4596
4597        if self.should_handle_tsql_distinct_null_ordering(select) {
4598            if let Some(wrapped_select) = self.try_build_tsql_distinct_null_ordering_wrapper(select)
4599            {
4600                return self.generate_select(&wrapped_select);
4601            }
4602
4603            self.unsupported(
4604                "SELECT DISTINCT with emulated NULL ordering is not supported for TSQL/Fabric",
4605            )?;
4606        }
4607
4608        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4609        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4610        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4611        if let Some(exclude) = &select.exclude {
4612            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4613                // Build the inner select (same as original but without exclude)
4614                let mut inner_select = select.clone();
4615                inner_select.exclude = None;
4616                let inner_expr = Expression::Select(Box::new(inner_select));
4617
4618                // Build the subquery
4619                let subquery = crate::expressions::Subquery {
4620                    this: inner_expr,
4621                    alias: None,
4622                    column_aliases: Vec::new(),
4623                    alias_explicit_as: false,
4624                    alias_keyword: None,
4625                    order_by: None,
4626                    limit: None,
4627                    offset: None,
4628                    distribute_by: None,
4629                    sort_by: None,
4630                    cluster_by: None,
4631                    lateral: false,
4632                    modifiers_inside: false,
4633                    trailing_comments: Vec::new(),
4634                    inferred_type: None,
4635                };
4636
4637                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4638                let star = Expression::Star(crate::expressions::Star {
4639                    table: None,
4640                    except: Some(
4641                        exclude
4642                            .iter()
4643                            .map(|e| match e {
4644                                Expression::Column(col) => col.name.clone(),
4645                                Expression::Identifier(id) => id.clone(),
4646                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4647                            })
4648                            .collect(),
4649                    ),
4650                    replace: None,
4651                    rename: None,
4652                    trailing_comments: Vec::new(),
4653                    span: None,
4654                });
4655
4656                let outer_select = Select {
4657                    expressions: vec![star],
4658                    from: Some(crate::expressions::From {
4659                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4660                    }),
4661                    ..Select::new()
4662                };
4663
4664                return self.generate_select(&outer_select);
4665            }
4666        }
4667
4668        // Output leading comments before SELECT
4669        for comment in &select.leading_comments {
4670            self.write_formatted_comment(comment);
4671            self.write(" ");
4672        }
4673
4674        // WITH clause
4675        if let Some(with) = &select.with {
4676            self.generate_with(with)?;
4677            if self.config.pretty {
4678                self.write_newline();
4679                self.write_indent();
4680            } else {
4681                self.write_space();
4682            }
4683        }
4684
4685        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4686        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4687        for comment in &select.post_select_comments {
4688            self.write_formatted_comment(comment);
4689            self.write(" ");
4690        }
4691
4692        self.write_keyword("SELECT");
4693
4694        // Generate query hint if present /*+ ... */
4695        if let Some(hint) = &select.hint {
4696            self.generate_hint(hint)?;
4697        }
4698
4699        // For SQL Server, convert LIMIT to TOP (structural transformation)
4700        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4701        // TOP clause (SQL Server style - before DISTINCT)
4702        let use_top_from_limit = matches!(
4703            self.config.dialect,
4704            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4705        ) && select.top.is_none()
4706            && select
4707                .limit
4708                .as_ref()
4709                .is_some_and(|limit| !Self::is_noop_limit_expr(&limit.this))
4710            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4711
4712        // For TOP-supporting dialects: DISTINCT before TOP
4713        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4714        let is_top_dialect = matches!(
4715            self.config.dialect,
4716            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4717        );
4718        let keep_top_verbatim = !is_top_dialect
4719            && select.limit.is_none()
4720            && select
4721                .top
4722                .as_ref()
4723                .map_or(false, |top| top.percent || top.with_ties);
4724
4725        if select.distinct && (is_top_dialect || select.top.is_some()) {
4726            self.write_space();
4727            self.write_keyword("DISTINCT");
4728        }
4729
4730        if is_top_dialect || keep_top_verbatim {
4731            if let Some(top) = &select.top {
4732                self.write_space();
4733                self.write_keyword("TOP");
4734                if top.parenthesized {
4735                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4736                        self.write_space();
4737                        self.generate_expression(&top.this)?;
4738                    } else {
4739                        self.write(" (");
4740                        self.generate_expression(&top.this)?;
4741                        self.write(")");
4742                    }
4743                } else {
4744                    self.write_space();
4745                    self.generate_expression(&top.this)?;
4746                }
4747                if top.percent {
4748                    self.write_space();
4749                    self.write_keyword("PERCENT");
4750                }
4751                if top.with_ties {
4752                    self.write_space();
4753                    self.write_keyword("WITH TIES");
4754                }
4755            } else if use_top_from_limit {
4756                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4757                if let Some(limit) = &select.limit {
4758                    self.write_space();
4759                    self.write_keyword("TOP");
4760                    // Use parentheses for complex expressions, but not for simple literals
4761                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4762                    if is_simple_literal {
4763                        self.write_space();
4764                        self.generate_expression(&limit.this)?;
4765                    } else {
4766                        self.write(" (");
4767                        self.generate_expression(&limit.this)?;
4768                        self.write(")");
4769                    }
4770                }
4771            }
4772        }
4773
4774        if select.distinct && !is_top_dialect && select.top.is_none() {
4775            self.write_space();
4776            self.write_keyword("DISTINCT");
4777        }
4778
4779        // DISTINCT ON clause (PostgreSQL)
4780        if let Some(distinct_on) = &select.distinct_on {
4781            self.write_space();
4782            self.write_keyword("ON");
4783            self.write(" (");
4784            for (i, expr) in distinct_on.iter().enumerate() {
4785                if i > 0 {
4786                    self.write(", ");
4787                }
4788                self.generate_expression(expr)?;
4789            }
4790            self.write(")");
4791        }
4792
4793        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4794        for modifier in &select.operation_modifiers {
4795            self.write_space();
4796            self.write_keyword(modifier);
4797        }
4798
4799        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4800        if let Some(kind) = &select.kind {
4801            self.write_space();
4802            self.write_keyword("AS");
4803            self.write_space();
4804            self.write_keyword(kind);
4805        }
4806
4807        // Expressions (only if there are any)
4808        if !select.expressions.is_empty() {
4809            if self.config.pretty {
4810                self.write_newline();
4811                self.indent_level += 1;
4812            } else {
4813                self.write_space();
4814            }
4815        }
4816
4817        for (i, expr) in select.expressions.iter().enumerate() {
4818            if i > 0 {
4819                self.write(",");
4820                if self.config.pretty {
4821                    self.write_newline();
4822                } else {
4823                    self.write_space();
4824                }
4825            }
4826            if self.config.pretty {
4827                self.write_indent();
4828            }
4829            self.generate_expression(expr)?;
4830        }
4831
4832        if self.config.pretty && !select.expressions.is_empty() {
4833            self.indent_level -= 1;
4834        }
4835
4836        // Redshift-style EXCLUDE clause at the end of the projection list
4837        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4838        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4839        // (done after the full select is generated below)
4840        if let Some(exclude) = &select.exclude {
4841            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4842                self.write_space();
4843                self.write_keyword("EXCLUDE");
4844                self.write(" (");
4845                for (i, col) in exclude.iter().enumerate() {
4846                    if i > 0 {
4847                        self.write(", ");
4848                    }
4849                    self.generate_expression(col)?;
4850                }
4851                self.write(")");
4852            }
4853        }
4854
4855        // INTO clause (SELECT ... INTO table_name)
4856        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4857        if let Some(into) = &select.into {
4858            if self.config.pretty {
4859                self.write_newline();
4860                self.write_indent();
4861            } else {
4862                self.write_space();
4863            }
4864            if into.bulk_collect {
4865                self.write_keyword("BULK COLLECT INTO");
4866            } else {
4867                self.write_keyword("INTO");
4868            }
4869            if into.temporary {
4870                self.write_space();
4871                self.write_keyword("TEMPORARY");
4872            }
4873            if into.unlogged {
4874                self.write_space();
4875                self.write_keyword("UNLOGGED");
4876            }
4877            self.write_space();
4878            // If we have multiple expressions, output them comma-separated
4879            if !into.expressions.is_empty() {
4880                for (i, expr) in into.expressions.iter().enumerate() {
4881                    if i > 0 {
4882                        self.write(", ");
4883                    }
4884                    self.generate_expression(expr)?;
4885                }
4886            } else {
4887                self.generate_expression(&into.this)?;
4888            }
4889        }
4890
4891        // FROM clause
4892        if let Some(from) = &select.from {
4893            if self.config.pretty {
4894                self.write_newline();
4895                self.write_indent();
4896            } else {
4897                self.write_space();
4898            }
4899            self.write_keyword("FROM");
4900            self.write_space();
4901
4902            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4903            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4904            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4905            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4906            let has_tablesample = from
4907                .expressions
4908                .iter()
4909                .any(|e| matches!(e, Expression::TableSample(_)));
4910            let is_cross_join_dialect = matches!(
4911                self.config.dialect,
4912                Some(DialectType::BigQuery)
4913                    | Some(DialectType::Hive)
4914                    | Some(DialectType::Spark)
4915                    | Some(DialectType::Databricks)
4916                    | Some(DialectType::SQLite)
4917                    | Some(DialectType::ClickHouse)
4918            );
4919            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4920            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4921            let source_is_same_as_target = self.config.source_dialect.is_some()
4922                && self.config.source_dialect == self.config.dialect;
4923            let source_is_cross_join_dialect = matches!(
4924                self.config.source_dialect,
4925                Some(DialectType::BigQuery)
4926                    | Some(DialectType::Hive)
4927                    | Some(DialectType::Spark)
4928                    | Some(DialectType::Databricks)
4929                    | Some(DialectType::SQLite)
4930                    | Some(DialectType::ClickHouse)
4931            );
4932            let use_cross_join = !has_tablesample
4933                && is_cross_join_dialect
4934                && (source_is_same_as_target
4935                    || source_is_cross_join_dialect
4936                    || self.config.source_dialect.is_none());
4937
4938            // Generic SQL and VALUES-as-derived-table dialects wrap standalone VALUES
4939            // table constructors in parentheses.
4940            let wrap_values_in_parens = self.config.dialect.is_none()
4941                || matches!(
4942                    self.config.dialect,
4943                    Some(
4944                        DialectType::Generic
4945                            | DialectType::Snowflake
4946                            | DialectType::Presto
4947                            | DialectType::Trino
4948                            | DialectType::Athena
4949                    )
4950                );
4951
4952            for (i, expr) in from.expressions.iter().enumerate() {
4953                if i > 0 {
4954                    if use_cross_join {
4955                        self.write(" CROSS JOIN ");
4956                    } else {
4957                        self.write(", ");
4958                    }
4959                }
4960                self.generate_from_source(expr, wrap_values_in_parens)?;
4961                // Output leading comments that were on the table name before FROM
4962                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4963                let leading = Self::extract_table_leading_comments(expr);
4964                for comment in &leading {
4965                    self.write_space();
4966                    self.write_formatted_comment(comment);
4967                }
4968            }
4969        }
4970
4971        // JOINs - handle nested join structure for pretty printing
4972        // Deferred-condition joins "own" the non-deferred joins that follow them
4973        // until the next deferred join or end of list
4974        if self.config.pretty {
4975            self.generate_joins_with_nesting(&select.joins)?;
4976        } else {
4977            for join in &select.joins {
4978                self.generate_join(join)?;
4979            }
4980            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4981            for join in select.joins.iter().rev() {
4982                if join.deferred_condition {
4983                    self.generate_join_condition(join)?;
4984                }
4985            }
4986        }
4987
4988        // LATERAL VIEW clauses (Hive/Spark)
4989        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4990            self.generate_lateral_view(lateral_view, lv_idx)?;
4991        }
4992
4993        // PREWHERE (ClickHouse)
4994        if let Some(prewhere) = &select.prewhere {
4995            self.write_clause_condition("PREWHERE", prewhere)?;
4996        }
4997
4998        // WHERE
4999        if let Some(where_clause) = &select.where_clause {
5000            self.write_clause_condition("WHERE", &where_clause.this)?;
5001        }
5002
5003        // CONNECT BY (Oracle hierarchical queries)
5004        if let Some(connect) = &select.connect {
5005            self.generate_connect(connect)?;
5006        }
5007
5008        // GROUP BY
5009        if let Some(group_by) = &select.group_by {
5010            if self.config.pretty {
5011                // Output leading comments on their own lines before GROUP BY
5012                for comment in &group_by.comments {
5013                    self.write_newline();
5014                    self.write_indent();
5015                    self.write_formatted_comment(comment);
5016                }
5017                self.write_newline();
5018                self.write_indent();
5019            } else {
5020                self.write_space();
5021                // In non-pretty mode, output comments inline
5022                for comment in &group_by.comments {
5023                    self.write_formatted_comment(comment);
5024                    self.write_space();
5025                }
5026            }
5027            let clickhouse_bare_modifiers =
5028                matches!(self.config.dialect, Some(DialectType::ClickHouse))
5029                    && group_by.all.is_none()
5030                    && (group_by.totals || !group_by.expressions.is_empty())
5031                    && group_by.expressions.iter().all(|expr| match expr {
5032                        Expression::Cube(c) => c.expressions.is_empty(),
5033                        Expression::Rollup(r) => r.expressions.is_empty(),
5034                        _ => false,
5035                    });
5036
5037            if clickhouse_bare_modifiers {
5038                let trailing_cube = group_by
5039                    .expressions
5040                    .iter()
5041                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
5042                let trailing_rollup = group_by
5043                    .expressions
5044                    .iter()
5045                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
5046
5047                if trailing_cube {
5048                    self.write_keyword("WITH CUBE");
5049                } else if trailing_rollup {
5050                    self.write_keyword("WITH ROLLUP");
5051                }
5052
5053                if group_by.totals {
5054                    if trailing_cube || trailing_rollup {
5055                        self.write_space();
5056                    }
5057                    self.write_keyword("WITH TOTALS");
5058                }
5059            } else {
5060                self.write_keyword("GROUP BY");
5061                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
5062                match group_by.all {
5063                    Some(true) => {
5064                        self.write_space();
5065                        self.write_keyword("ALL");
5066                    }
5067                    Some(false) => {
5068                        self.write_space();
5069                        self.write_keyword("DISTINCT");
5070                    }
5071                    None => {}
5072                }
5073                if !group_by.expressions.is_empty() {
5074                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
5075                    // These are represented as Cube/Rollup expressions with empty expressions at the end
5076                    let mut trailing_cube = false;
5077                    let mut trailing_rollup = false;
5078                    let mut plain_expressions: Vec<&Expression> = Vec::new();
5079                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
5080                    let mut cube_expressions: Vec<&Expression> = Vec::new();
5081                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
5082
5083                    for expr in &group_by.expressions {
5084                        match expr {
5085                            Expression::Cube(c) if c.expressions.is_empty() => {
5086                                trailing_cube = true;
5087                            }
5088                            Expression::Rollup(r) if r.expressions.is_empty() => {
5089                                trailing_rollup = true;
5090                            }
5091                            Expression::Function(f) if f.name == "CUBE" => {
5092                                cube_expressions.push(expr);
5093                            }
5094                            Expression::Function(f) if f.name == "ROLLUP" => {
5095                                rollup_expressions.push(expr);
5096                            }
5097                            Expression::Function(f) if f.name == "GROUPING SETS" => {
5098                                grouping_sets_expressions.push(expr);
5099                            }
5100                            _ => {
5101                                plain_expressions.push(expr);
5102                            }
5103                        }
5104                    }
5105
5106                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
5107                    let mut regular_expressions: Vec<&Expression> = Vec::new();
5108                    regular_expressions.extend(plain_expressions);
5109                    regular_expressions.extend(grouping_sets_expressions);
5110                    regular_expressions.extend(cube_expressions);
5111                    regular_expressions.extend(rollup_expressions);
5112
5113                    if self.config.pretty {
5114                        self.write_newline();
5115                        self.indent_level += 1;
5116                        self.write_indent();
5117                    } else {
5118                        self.write_space();
5119                    }
5120
5121                    for (i, expr) in regular_expressions.iter().enumerate() {
5122                        if i > 0 {
5123                            if self.config.pretty {
5124                                self.write(",");
5125                                self.write_newline();
5126                                self.write_indent();
5127                            } else {
5128                                self.write(", ");
5129                            }
5130                        }
5131                        self.generate_expression(expr)?;
5132                    }
5133
5134                    if self.config.pretty {
5135                        self.indent_level -= 1;
5136                    }
5137
5138                    // Output trailing WITH CUBE or WITH ROLLUP
5139                    if trailing_cube {
5140                        self.write_space();
5141                        self.write_keyword("WITH CUBE");
5142                    } else if trailing_rollup {
5143                        self.write_space();
5144                        self.write_keyword("WITH ROLLUP");
5145                    }
5146                }
5147
5148                // ClickHouse: WITH TOTALS
5149                if group_by.totals {
5150                    self.write_space();
5151                    self.write_keyword("WITH TOTALS");
5152                }
5153            }
5154        }
5155
5156        // HAVING
5157        if let Some(having) = &select.having {
5158            if self.config.pretty {
5159                // Output leading comments on their own lines before HAVING
5160                for comment in &having.comments {
5161                    self.write_newline();
5162                    self.write_indent();
5163                    self.write_formatted_comment(comment);
5164                }
5165            } else {
5166                for comment in &having.comments {
5167                    self.write_space();
5168                    self.write_formatted_comment(comment);
5169                }
5170            }
5171            self.write_clause_condition("HAVING", &having.this)?;
5172        }
5173
5174        // QUALIFY and WINDOW clause ordering depends on input SQL
5175        if select.qualify_after_window {
5176            // WINDOW before QUALIFY (DuckDB style)
5177            if let Some(windows) = &select.windows {
5178                self.write_window_clause(windows)?;
5179            }
5180            if let Some(qualify) = &select.qualify {
5181                self.write_clause_condition("QUALIFY", &qualify.this)?;
5182            }
5183        } else {
5184            // QUALIFY before WINDOW (Snowflake/BigQuery default)
5185            if let Some(qualify) = &select.qualify {
5186                self.write_clause_condition("QUALIFY", &qualify.this)?;
5187            }
5188            if let Some(windows) = &select.windows {
5189                self.write_window_clause(windows)?;
5190            }
5191        }
5192
5193        // DISTRIBUTE BY (Hive/Spark)
5194        if let Some(distribute_by) = &select.distribute_by {
5195            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
5196        }
5197
5198        // CLUSTER BY (Hive/Spark)
5199        if let Some(cluster_by) = &select.cluster_by {
5200            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
5201        }
5202
5203        // SORT BY (Hive/Spark - comes before ORDER BY)
5204        if let Some(sort_by) = &select.sort_by {
5205            self.write_order_clause("SORT BY", &sort_by.expressions)?;
5206        }
5207
5208        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
5209        if let Some(order_by) = &select.order_by {
5210            if self.config.pretty {
5211                // Output leading comments on their own lines before ORDER BY
5212                for comment in &order_by.comments {
5213                    self.write_newline();
5214                    self.write_indent();
5215                    self.write_formatted_comment(comment);
5216                }
5217            } else {
5218                for comment in &order_by.comments {
5219                    self.write_space();
5220                    self.write_formatted_comment(comment);
5221                }
5222            }
5223            let keyword = if order_by.siblings {
5224                "ORDER SIBLINGS BY"
5225            } else {
5226                "ORDER BY"
5227            };
5228            self.write_order_clause(keyword, &order_by.expressions)?;
5229        }
5230
5231        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
5232        if select.order_by.is_none()
5233            && select.fetch.is_some()
5234            && matches!(
5235                self.config.dialect,
5236                Some(DialectType::TSQL) | Some(DialectType::Fabric)
5237            )
5238        {
5239            if self.config.pretty {
5240                self.write_newline();
5241                self.write_indent();
5242            } else {
5243                self.write_space();
5244            }
5245            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
5246        }
5247
5248        // LIMIT and OFFSET
5249        // PostgreSQL and others use: LIMIT count OFFSET offset
5250        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
5251        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
5252        let is_presto_like = matches!(
5253            self.config.dialect,
5254            Some(DialectType::Presto) | Some(DialectType::Trino)
5255        );
5256
5257        if is_presto_like && select.offset.is_some() {
5258            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
5259            if let Some(offset) = &select.offset {
5260                if self.config.pretty {
5261                    self.write_newline();
5262                    self.write_indent();
5263                } else {
5264                    self.write_space();
5265                }
5266                self.write_keyword("OFFSET");
5267                self.write_space();
5268                self.write_limit_expr(&offset.this)?;
5269                if offset.rows == Some(true) {
5270                    self.write_space();
5271                    self.write_keyword("ROWS");
5272                }
5273            }
5274            if let Some(limit) = &select.limit {
5275                if self.config.pretty {
5276                    self.write_newline();
5277                    self.write_indent();
5278                } else {
5279                    self.write_space();
5280                }
5281                self.write_keyword("LIMIT");
5282                self.write_space();
5283                self.write_limit_expr(&limit.this)?;
5284                if limit.percent {
5285                    self.write_space();
5286                    self.write_keyword("PERCENT");
5287                }
5288                // Emit any comments that were captured from before the LIMIT keyword
5289                for comment in &limit.comments {
5290                    self.write(" ");
5291                    self.write_formatted_comment(comment);
5292                }
5293            }
5294        } else {
5295            // Check if FETCH will be converted to LIMIT (used for ordering)
5296            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
5297                !fetch.percent
5298                    && !fetch.with_ties
5299                    && fetch.count.is_some()
5300                    && matches!(
5301                        self.config.dialect,
5302                        Some(DialectType::Spark)
5303                            | Some(DialectType::Hive)
5304                            | Some(DialectType::DuckDB)
5305                            | Some(DialectType::SQLite)
5306                            | Some(DialectType::MySQL)
5307                            | Some(DialectType::BigQuery)
5308                            | Some(DialectType::Databricks)
5309                            | Some(DialectType::StarRocks)
5310                            | Some(DialectType::Doris)
5311                            | Some(DialectType::Athena)
5312                            | Some(DialectType::ClickHouse)
5313                            | Some(DialectType::Redshift)
5314                    )
5315            });
5316
5317            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
5318            if let Some(limit) = &select.limit {
5319                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
5320                if !matches!(
5321                    self.config.dialect,
5322                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5323                ) {
5324                    if self.config.pretty {
5325                        self.write_newline();
5326                        self.write_indent();
5327                    } else {
5328                        self.write_space();
5329                    }
5330                    self.write_keyword("LIMIT");
5331                    self.write_space();
5332                    self.write_limit_expr(&limit.this)?;
5333                    if limit.percent {
5334                        self.write_space();
5335                        self.write_keyword("PERCENT");
5336                    }
5337                    // Emit any comments that were captured from before the LIMIT keyword
5338                    for comment in &limit.comments {
5339                        self.write(" ");
5340                        self.write_formatted_comment(comment);
5341                    }
5342                }
5343            }
5344
5345            // Convert TOP to LIMIT for non-TOP dialects
5346            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
5347                if let Some(top) = &select.top {
5348                    if !top.percent && !top.with_ties {
5349                        if self.config.pretty {
5350                            self.write_newline();
5351                            self.write_indent();
5352                        } else {
5353                            self.write_space();
5354                        }
5355                        self.write_keyword("LIMIT");
5356                        self.write_space();
5357                        self.generate_expression(&top.this)?;
5358                    }
5359                }
5360            }
5361
5362            // If FETCH will be converted to LIMIT and there's also OFFSET,
5363            // emit LIMIT from FETCH BEFORE the OFFSET
5364            if fetch_as_limit && select.offset.is_some() {
5365                if let Some(fetch) = &select.fetch {
5366                    if self.config.pretty {
5367                        self.write_newline();
5368                        self.write_indent();
5369                    } else {
5370                        self.write_space();
5371                    }
5372                    self.write_keyword("LIMIT");
5373                    self.write_space();
5374                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5375                }
5376            }
5377
5378            // OFFSET
5379            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
5380            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
5381            if let Some(offset) = &select.offset {
5382                if self.config.pretty {
5383                    self.write_newline();
5384                    self.write_indent();
5385                } else {
5386                    self.write_space();
5387                }
5388                if matches!(
5389                    self.config.dialect,
5390                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5391                ) {
5392                    // SQL Server 2012+ OFFSET ... FETCH syntax
5393                    self.write_keyword("OFFSET");
5394                    self.write_space();
5395                    self.write_limit_expr(&offset.this)?;
5396                    self.write_space();
5397                    self.write_keyword("ROWS");
5398                    // If there was a real LIMIT, use FETCH NEXT ... ROWS ONLY.
5399                    // PostgreSQL LIMIT NULL / LIMIT ALL mean "no limit", so
5400                    // T-SQL/Fabric should keep the OFFSET without FETCH.
5401                    if let Some(limit) = &select.limit {
5402                        if !Self::is_noop_limit_expr(&limit.this) {
5403                            self.write_space();
5404                            self.write_keyword("FETCH NEXT");
5405                            self.write_space();
5406                            self.write_limit_expr(&limit.this)?;
5407                            self.write_space();
5408                            self.write_keyword("ROWS ONLY");
5409                        }
5410                    }
5411                } else {
5412                    self.write_keyword("OFFSET");
5413                    self.write_space();
5414                    self.write_limit_expr(&offset.this)?;
5415                    // Output ROWS keyword if it was in the original SQL
5416                    if offset.rows == Some(true) {
5417                        self.write_space();
5418                        self.write_keyword("ROWS");
5419                    }
5420                }
5421            }
5422        }
5423
5424        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
5425        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5426            if let Some(limit_by) = &select.limit_by {
5427                if !limit_by.is_empty() {
5428                    self.write_space();
5429                    self.write_keyword("BY");
5430                    self.write_space();
5431                    for (i, expr) in limit_by.iter().enumerate() {
5432                        if i > 0 {
5433                            self.write(", ");
5434                        }
5435                        self.generate_expression(expr)?;
5436                    }
5437                }
5438            }
5439        }
5440
5441        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
5442        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5443            if let Some(settings) = &select.settings {
5444                if self.config.pretty {
5445                    self.write_newline();
5446                    self.write_indent();
5447                } else {
5448                    self.write_space();
5449                }
5450                self.write_keyword("SETTINGS");
5451                self.write_space();
5452                for (i, expr) in settings.iter().enumerate() {
5453                    if i > 0 {
5454                        self.write(", ");
5455                    }
5456                    self.generate_expression(expr)?;
5457                }
5458            }
5459
5460            if let Some(format_expr) = &select.format {
5461                if self.config.pretty {
5462                    self.write_newline();
5463                    self.write_indent();
5464                } else {
5465                    self.write_space();
5466                }
5467                self.write_keyword("FORMAT");
5468                self.write_space();
5469                self.generate_expression(format_expr)?;
5470            }
5471        }
5472
5473        // FETCH FIRST/NEXT
5474        if let Some(fetch) = &select.fetch {
5475            // Check if we already emitted LIMIT from FETCH before OFFSET
5476            let fetch_already_as_limit = select.offset.is_some()
5477                && !fetch.percent
5478                && !fetch.with_ties
5479                && fetch.count.is_some()
5480                && matches!(
5481                    self.config.dialect,
5482                    Some(DialectType::Spark)
5483                        | Some(DialectType::Hive)
5484                        | Some(DialectType::DuckDB)
5485                        | Some(DialectType::SQLite)
5486                        | Some(DialectType::MySQL)
5487                        | Some(DialectType::BigQuery)
5488                        | Some(DialectType::Databricks)
5489                        | Some(DialectType::StarRocks)
5490                        | Some(DialectType::Doris)
5491                        | Some(DialectType::Athena)
5492                        | Some(DialectType::ClickHouse)
5493                        | Some(DialectType::Redshift)
5494                );
5495
5496            if fetch_already_as_limit {
5497                // Already emitted as LIMIT before OFFSET, skip
5498            } else {
5499                if self.config.pretty {
5500                    self.write_newline();
5501                    self.write_indent();
5502                } else {
5503                    self.write_space();
5504                }
5505
5506                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
5507                let use_limit = !fetch.percent
5508                    && !fetch.with_ties
5509                    && matches!(
5510                        self.config.dialect,
5511                        Some(DialectType::Spark)
5512                            | Some(DialectType::Hive)
5513                            | Some(DialectType::DuckDB)
5514                            | Some(DialectType::SQLite)
5515                            | Some(DialectType::MySQL)
5516                            | Some(DialectType::BigQuery)
5517                            | Some(DialectType::Databricks)
5518                            | Some(DialectType::StarRocks)
5519                            | Some(DialectType::Doris)
5520                            | Some(DialectType::Athena)
5521                            | Some(DialectType::ClickHouse)
5522                            | Some(DialectType::Redshift)
5523                    );
5524
5525                if use_limit {
5526                    self.write_keyword("LIMIT");
5527                    self.write_space();
5528                    if let Some(count) = &fetch.count {
5529                        self.generate_expression(count)?;
5530                    } else {
5531                        self.write("1");
5532                    }
5533                } else {
5534                    self.write_keyword("FETCH");
5535                    self.write_space();
5536                    self.write_keyword(&fetch.direction);
5537                    if let Some(ref count) = fetch.count {
5538                        self.write_space();
5539                        self.generate_expression(count)?;
5540                    }
5541                    if fetch.percent {
5542                        self.write_space();
5543                        self.write_keyword("PERCENT");
5544                    }
5545                    if fetch.rows {
5546                        self.write_space();
5547                        self.write_keyword("ROWS");
5548                    }
5549                    if fetch.with_ties {
5550                        self.write_space();
5551                        self.write_keyword("WITH TIES");
5552                    } else {
5553                        self.write_space();
5554                        self.write_keyword("ONLY");
5555                    }
5556                }
5557            } // close fetch_already_as_limit else
5558        }
5559
5560        // SAMPLE / TABLESAMPLE
5561        if let Some(sample) = &select.sample {
5562            use crate::dialects::DialectType;
5563            if self.config.pretty {
5564                self.write_newline();
5565            } else {
5566                self.write_space();
5567            }
5568
5569            if sample.is_using_sample {
5570                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5571                self.write_keyword("USING SAMPLE");
5572                self.generate_sample_body(sample)?;
5573            } else {
5574                self.write_keyword("TABLESAMPLE");
5575
5576                // Snowflake defaults to BERNOULLI when no explicit method is given
5577                let snowflake_bernoulli =
5578                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5579                        && !sample.explicit_method;
5580                if snowflake_bernoulli {
5581                    self.write_space();
5582                    self.write_keyword("BERNOULLI");
5583                }
5584
5585                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5586                if matches!(sample.method, SampleMethod::Bucket) {
5587                    self.write_space();
5588                    self.write("(");
5589                    self.write_keyword("BUCKET");
5590                    self.write_space();
5591                    if let Some(ref num) = sample.bucket_numerator {
5592                        self.generate_expression(num)?;
5593                    }
5594                    self.write_space();
5595                    self.write_keyword("OUT OF");
5596                    self.write_space();
5597                    if let Some(ref denom) = sample.bucket_denominator {
5598                        self.generate_expression(denom)?;
5599                    }
5600                    if let Some(ref field) = sample.bucket_field {
5601                        self.write_space();
5602                        self.write_keyword("ON");
5603                        self.write_space();
5604                        self.generate_expression(field)?;
5605                    }
5606                    self.write(")");
5607                } else if sample.unit_after_size {
5608                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5609                    if sample.explicit_method && sample.method_before_size {
5610                        self.write_space();
5611                        match sample.method {
5612                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5613                            SampleMethod::System => self.write_keyword("SYSTEM"),
5614                            SampleMethod::Block => self.write_keyword("BLOCK"),
5615                            SampleMethod::Row => self.write_keyword("ROW"),
5616                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5617                            _ => {}
5618                        }
5619                    }
5620                    self.write(" (");
5621                    self.generate_expression(&sample.size)?;
5622                    self.write_space();
5623                    match sample.method {
5624                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5625                        SampleMethod::Row => self.write_keyword("ROWS"),
5626                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5627                        _ => {
5628                            self.write_keyword("PERCENT");
5629                        }
5630                    }
5631                    self.write(")");
5632                } else {
5633                    // Syntax: TABLESAMPLE METHOD (size)
5634                    self.write_space();
5635                    match sample.method {
5636                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5637                        SampleMethod::System => self.write_keyword("SYSTEM"),
5638                        SampleMethod::Block => self.write_keyword("BLOCK"),
5639                        SampleMethod::Row => self.write_keyword("ROW"),
5640                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5641                        SampleMethod::Bucket => {}
5642                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5643                    }
5644                    self.write(" (");
5645                    self.generate_expression(&sample.size)?;
5646                    if matches!(sample.method, SampleMethod::Percent) {
5647                        self.write_space();
5648                        self.write_keyword("PERCENT");
5649                    }
5650                    self.write(")");
5651                }
5652            }
5653
5654            if let Some(seed) = &sample.seed {
5655                self.write_space();
5656                // Databricks/Spark use REPEATABLE, not SEED
5657                let use_seed = sample.use_seed_keyword
5658                    && !matches!(
5659                        self.config.dialect,
5660                        Some(crate::dialects::DialectType::Databricks)
5661                            | Some(crate::dialects::DialectType::Spark)
5662                    );
5663                if use_seed {
5664                    self.write_keyword("SEED");
5665                } else {
5666                    self.write_keyword("REPEATABLE");
5667                }
5668                self.write(" (");
5669                self.generate_expression(seed)?;
5670                self.write(")");
5671            }
5672        }
5673
5674        // FOR UPDATE/SHARE locks
5675        // Skip locking clauses for dialects that don't support them
5676        if self.config.locking_reads_supported {
5677            for lock in &select.locks {
5678                if self.config.pretty {
5679                    self.write_newline();
5680                    self.write_indent();
5681                } else {
5682                    self.write_space();
5683                }
5684                self.generate_lock(lock)?;
5685            }
5686        }
5687
5688        // FOR XML clause (T-SQL)
5689        if !select.for_xml.is_empty() {
5690            if self.config.pretty {
5691                self.write_newline();
5692                self.write_indent();
5693            } else {
5694                self.write_space();
5695            }
5696            self.write_keyword("FOR XML");
5697            for (i, opt) in select.for_xml.iter().enumerate() {
5698                if self.config.pretty {
5699                    if i > 0 {
5700                        self.write(",");
5701                    }
5702                    self.write_newline();
5703                    self.write_indent();
5704                    self.write("  "); // extra indent for options
5705                } else {
5706                    if i > 0 {
5707                        self.write(",");
5708                    }
5709                    self.write_space();
5710                }
5711                self.generate_for_xml_option(opt)?;
5712            }
5713        }
5714
5715        // FOR JSON clause (T-SQL)
5716        if !select.for_json.is_empty()
5717            && matches!(
5718                self.config.dialect,
5719                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5720            )
5721        {
5722            if self.config.pretty {
5723                self.write_newline();
5724                self.write_indent();
5725            } else {
5726                self.write_space();
5727            }
5728            self.write_keyword("FOR JSON");
5729            for (i, opt) in select.for_json.iter().enumerate() {
5730                if self.config.pretty {
5731                    if i > 0 {
5732                        self.write(",");
5733                    }
5734                    self.write_newline();
5735                    self.write_indent();
5736                    self.write("  "); // extra indent for options
5737                } else {
5738                    if i > 0 {
5739                        self.write(",");
5740                    }
5741                    self.write_space();
5742                }
5743                self.generate_for_xml_option(opt)?;
5744            }
5745        }
5746
5747        // TSQL: OPTION clause
5748        if let Some(ref option) = select.option {
5749            if matches!(
5750                self.config.dialect,
5751                Some(crate::dialects::DialectType::TSQL)
5752                    | Some(crate::dialects::DialectType::Fabric)
5753            ) {
5754                self.write_space();
5755                self.write(option);
5756            }
5757        }
5758
5759        Ok(())
5760    }
5761
5762    /// Generate a single FOR XML option
5763    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5764        match opt {
5765            Expression::QueryOption(qo) => {
5766                // Extract the option name from Var
5767                if let Expression::Var(var) = &*qo.this {
5768                    self.write(&var.this);
5769                } else {
5770                    self.generate_expression(&qo.this)?;
5771                }
5772                // If there's an expression (like PATH('element')), output it in parens
5773                if let Some(expr) = &qo.expression {
5774                    self.write("(");
5775                    self.generate_expression(expr)?;
5776                    self.write(")");
5777                }
5778            }
5779            _ => {
5780                self.generate_expression(opt)?;
5781            }
5782        }
5783        Ok(())
5784    }
5785
5786    fn generate_with(&mut self, with: &With) -> Result<()> {
5787        use crate::dialects::DialectType;
5788
5789        // Output leading comments before WITH
5790        for comment in &with.leading_comments {
5791            self.write_formatted_comment(comment);
5792            self.write(" ");
5793        }
5794        self.write_keyword("WITH");
5795        if with.recursive && self.config.cte_recursive_keyword_required {
5796            self.write_space();
5797            self.write_keyword("RECURSIVE");
5798        }
5799        self.write_space();
5800
5801        // BigQuery doesn't support column aliases in CTE definitions
5802        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5803
5804        for (i, cte) in with.ctes.iter().enumerate() {
5805            if i > 0 {
5806                self.write(",");
5807                if self.config.pretty {
5808                    self.write_space();
5809                } else {
5810                    self.write(" ");
5811                }
5812            }
5813            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5814                self.generate_expression(&cte.this)?;
5815                self.write_space();
5816                self.write_keyword("AS");
5817                self.write_space();
5818                self.generate_identifier(&cte.alias)?;
5819                continue;
5820            }
5821            self.generate_identifier(&cte.alias)?;
5822            // Output CTE comments after alias name, before AS
5823            for comment in &cte.comments {
5824                self.write_space();
5825                self.write_formatted_comment(comment);
5826            }
5827            if !cte.columns.is_empty() && !skip_cte_columns {
5828                self.write("(");
5829                for (j, col) in cte.columns.iter().enumerate() {
5830                    if j > 0 {
5831                        self.write(", ");
5832                    }
5833                    self.generate_identifier(col)?;
5834                }
5835                self.write(")");
5836            }
5837            // USING KEY (columns) for DuckDB recursive CTEs
5838            if !cte.key_expressions.is_empty() {
5839                self.write_space();
5840                self.write_keyword("USING KEY");
5841                self.write(" (");
5842                for (i, key) in cte.key_expressions.iter().enumerate() {
5843                    if i > 0 {
5844                        self.write(", ");
5845                    }
5846                    self.generate_identifier(key)?;
5847                }
5848                self.write(")");
5849            }
5850            self.write_space();
5851            self.write_keyword("AS");
5852            // MATERIALIZED / NOT MATERIALIZED
5853            if let Some(materialized) = cte.materialized {
5854                self.write_space();
5855                if materialized {
5856                    self.write_keyword("MATERIALIZED");
5857                } else {
5858                    self.write_keyword("NOT MATERIALIZED");
5859                }
5860            }
5861            self.write(" (");
5862            if self.config.pretty {
5863                self.write_newline();
5864                self.indent_level += 1;
5865                self.write_indent();
5866            }
5867            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5868            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5869            let wrap_values_in_select = matches!(
5870                self.config.dialect,
5871                Some(DialectType::Spark) | Some(DialectType::Databricks)
5872            ) && matches!(&cte.this, Expression::Values(_));
5873
5874            if wrap_values_in_select {
5875                self.write_keyword("SELECT");
5876                self.write(" * ");
5877                self.write_keyword("FROM");
5878                self.write_space();
5879            }
5880            self.generate_expression(&cte.this)?;
5881            if self.config.pretty {
5882                self.write_newline();
5883                self.indent_level -= 1;
5884                self.write_indent();
5885            }
5886            self.write(")");
5887        }
5888
5889        // Generate SEARCH/CYCLE clause if present
5890        if let Some(search) = &with.search {
5891            self.write_space();
5892            self.generate_expression(search)?;
5893        }
5894
5895        Ok(())
5896    }
5897
5898    /// Generate joins with proper nesting structure for pretty printing.
5899    /// Deferred-condition joins "own" the non-deferred joins that follow them
5900    /// within the same nesting_group.
5901    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5902        let mut i = 0;
5903        while i < joins.len() {
5904            if joins[i].deferred_condition {
5905                let parent_group = joins[i].nesting_group;
5906
5907                // This join owns the following non-deferred joins in the same nesting_group
5908                // First output the join keyword and table (without condition)
5909                self.generate_join_without_condition(&joins[i])?;
5910
5911                // Find the range of child joins: same nesting_group and not deferred
5912                let child_start = i + 1;
5913                let mut child_end = child_start;
5914                while child_end < joins.len()
5915                    && !joins[child_end].deferred_condition
5916                    && joins[child_end].nesting_group == parent_group
5917                {
5918                    child_end += 1;
5919                }
5920
5921                // Output child joins with extra indentation
5922                if child_start < child_end {
5923                    self.indent_level += 1;
5924                    for j in child_start..child_end {
5925                        self.generate_join(&joins[j])?;
5926                    }
5927                    self.indent_level -= 1;
5928                }
5929
5930                // Output the deferred condition at the parent level
5931                self.generate_join_condition(&joins[i])?;
5932
5933                i = child_end;
5934            } else {
5935                // Regular join (no nesting)
5936                self.generate_join(&joins[i])?;
5937                i += 1;
5938            }
5939        }
5940        Ok(())
5941    }
5942
5943    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5944    /// Used for deferred-condition joins where the condition is output after child joins.
5945    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5946        // Save and temporarily clear the condition to prevent generate_join from outputting it
5947        // We achieve this by creating a modified copy
5948        let mut join_copy = join.clone();
5949        join_copy.on = None;
5950        join_copy.using = Vec::new();
5951        join_copy.deferred_condition = false;
5952        self.generate_join(&join_copy)
5953    }
5954
5955    fn generate_join(&mut self, join: &Join) -> Result<()> {
5956        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5957        if join.kind == JoinKind::Implicit {
5958            self.write(",");
5959            if self.config.pretty {
5960                self.write_newline();
5961                self.write_indent();
5962            } else {
5963                self.write_space();
5964            }
5965            self.generate_expression(&join.this)?;
5966            return Ok(());
5967        }
5968
5969        if self.config.pretty {
5970            self.write_newline();
5971            self.write_indent();
5972        } else {
5973            self.write_space();
5974        }
5975
5976        // Helper: format hint suffix (e.g., " LOOP" or "")
5977        // Only include join hints for dialects that support them
5978        let hint_str = if self.config.join_hints {
5979            join.join_hint
5980                .as_ref()
5981                .map(|h| format!(" {}", h))
5982                .unwrap_or_default()
5983        } else {
5984            String::new()
5985        };
5986
5987        let clickhouse_join_keyword =
5988            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5989                if let Some(hint) = &join.join_hint {
5990                    let mut global = false;
5991                    let mut strictness: Option<&'static str> = None;
5992                    for part in hint.split_whitespace() {
5993                        if part.eq_ignore_ascii_case("GLOBAL") {
5994                            global = true;
5995                        } else if part.eq_ignore_ascii_case("ALL") {
5996                            strictness = Some("ALL");
5997                        } else if part.eq_ignore_ascii_case("ANY") {
5998                            strictness = Some("ANY");
5999                        } else if part.eq_ignore_ascii_case("ASOF") {
6000                            strictness = Some("ASOF");
6001                        } else if part.eq_ignore_ascii_case("SEMI") {
6002                            strictness = Some("SEMI");
6003                        } else if part.eq_ignore_ascii_case("ANTI") {
6004                            strictness = Some("ANTI");
6005                        }
6006                    }
6007
6008                    if global || strictness.is_some() {
6009                        let join_type = match join.kind {
6010                            JoinKind::Left => {
6011                                if join.use_outer_keyword {
6012                                    "LEFT OUTER"
6013                                } else if join.use_inner_keyword {
6014                                    "LEFT INNER"
6015                                } else {
6016                                    "LEFT"
6017                                }
6018                            }
6019                            JoinKind::Right => {
6020                                if join.use_outer_keyword {
6021                                    "RIGHT OUTER"
6022                                } else if join.use_inner_keyword {
6023                                    "RIGHT INNER"
6024                                } else {
6025                                    "RIGHT"
6026                                }
6027                            }
6028                            JoinKind::Full => {
6029                                if join.use_outer_keyword {
6030                                    "FULL OUTER"
6031                                } else {
6032                                    "FULL"
6033                                }
6034                            }
6035                            JoinKind::Inner => {
6036                                if join.use_inner_keyword {
6037                                    "INNER"
6038                                } else {
6039                                    ""
6040                                }
6041                            }
6042                            _ => "",
6043                        };
6044
6045                        let mut parts = Vec::new();
6046                        if global {
6047                            parts.push("GLOBAL");
6048                        }
6049                        if !join_type.is_empty() {
6050                            parts.push(join_type);
6051                        }
6052                        if let Some(strict) = strictness {
6053                            parts.push(strict);
6054                        }
6055                        parts.push("JOIN");
6056                        Some(parts.join(" "))
6057                    } else {
6058                        None
6059                    }
6060                } else {
6061                    None
6062                }
6063            } else {
6064                None
6065            };
6066
6067        // Output any comments associated with this join
6068        // In pretty mode, comments go on their own line before the join keyword
6069        // In non-pretty mode, comments go inline before the join keyword
6070        if !join.comments.is_empty() {
6071            if self.config.pretty {
6072                // In pretty mode, go back before the newline+indent we just wrote
6073                // and output comments on their own lines
6074                // We need to output comments BEFORE the join keyword on separate lines
6075                // Trim the trailing newline+indent we already wrote
6076                let trimmed = self.output.trim_end().len();
6077                self.output.truncate(trimmed);
6078                for comment in &join.comments {
6079                    self.write_newline();
6080                    self.write_indent();
6081                    self.write_formatted_comment(comment);
6082                }
6083                self.write_newline();
6084                self.write_indent();
6085            } else {
6086                for comment in &join.comments {
6087                    self.write_formatted_comment(comment);
6088                    self.write_space();
6089                }
6090            }
6091        }
6092
6093        let directed_str = if join.directed { " DIRECTED" } else { "" };
6094
6095        if let Some(keyword) = clickhouse_join_keyword {
6096            self.write_keyword(&keyword);
6097        } else {
6098            match join.kind {
6099                JoinKind::Inner => {
6100                    if join.use_inner_keyword {
6101                        if hint_str.is_empty() && directed_str.is_empty() {
6102                            self.write_keyword("INNER JOIN");
6103                        } else {
6104                            self.write_keyword("INNER");
6105                            if !hint_str.is_empty() {
6106                                self.write_keyword(&hint_str);
6107                            }
6108                            if !directed_str.is_empty() {
6109                                self.write_keyword(directed_str);
6110                            }
6111                            self.write_keyword(" JOIN");
6112                        }
6113                    } else {
6114                        if !hint_str.is_empty() {
6115                            self.write_keyword(hint_str.trim());
6116                            self.write_keyword(" ");
6117                        }
6118                        if !directed_str.is_empty() {
6119                            self.write_keyword("DIRECTED ");
6120                        }
6121                        self.write_keyword("JOIN");
6122                    }
6123                }
6124                JoinKind::Left => {
6125                    if join.use_outer_keyword {
6126                        if hint_str.is_empty() && directed_str.is_empty() {
6127                            self.write_keyword("LEFT OUTER JOIN");
6128                        } else {
6129                            self.write_keyword("LEFT OUTER");
6130                            if !hint_str.is_empty() {
6131                                self.write_keyword(&hint_str);
6132                            }
6133                            if !directed_str.is_empty() {
6134                                self.write_keyword(directed_str);
6135                            }
6136                            self.write_keyword(" JOIN");
6137                        }
6138                    } else if join.use_inner_keyword {
6139                        if hint_str.is_empty() && directed_str.is_empty() {
6140                            self.write_keyword("LEFT INNER JOIN");
6141                        } else {
6142                            self.write_keyword("LEFT INNER");
6143                            if !hint_str.is_empty() {
6144                                self.write_keyword(&hint_str);
6145                            }
6146                            if !directed_str.is_empty() {
6147                                self.write_keyword(directed_str);
6148                            }
6149                            self.write_keyword(" JOIN");
6150                        }
6151                    } else {
6152                        if hint_str.is_empty() && directed_str.is_empty() {
6153                            self.write_keyword("LEFT JOIN");
6154                        } else {
6155                            self.write_keyword("LEFT");
6156                            if !hint_str.is_empty() {
6157                                self.write_keyword(&hint_str);
6158                            }
6159                            if !directed_str.is_empty() {
6160                                self.write_keyword(directed_str);
6161                            }
6162                            self.write_keyword(" JOIN");
6163                        }
6164                    }
6165                }
6166                JoinKind::Right => {
6167                    if join.use_outer_keyword {
6168                        if hint_str.is_empty() && directed_str.is_empty() {
6169                            self.write_keyword("RIGHT OUTER JOIN");
6170                        } else {
6171                            self.write_keyword("RIGHT OUTER");
6172                            if !hint_str.is_empty() {
6173                                self.write_keyword(&hint_str);
6174                            }
6175                            if !directed_str.is_empty() {
6176                                self.write_keyword(directed_str);
6177                            }
6178                            self.write_keyword(" JOIN");
6179                        }
6180                    } else if join.use_inner_keyword {
6181                        if hint_str.is_empty() && directed_str.is_empty() {
6182                            self.write_keyword("RIGHT INNER JOIN");
6183                        } else {
6184                            self.write_keyword("RIGHT INNER");
6185                            if !hint_str.is_empty() {
6186                                self.write_keyword(&hint_str);
6187                            }
6188                            if !directed_str.is_empty() {
6189                                self.write_keyword(directed_str);
6190                            }
6191                            self.write_keyword(" JOIN");
6192                        }
6193                    } else {
6194                        if hint_str.is_empty() && directed_str.is_empty() {
6195                            self.write_keyword("RIGHT JOIN");
6196                        } else {
6197                            self.write_keyword("RIGHT");
6198                            if !hint_str.is_empty() {
6199                                self.write_keyword(&hint_str);
6200                            }
6201                            if !directed_str.is_empty() {
6202                                self.write_keyword(directed_str);
6203                            }
6204                            self.write_keyword(" JOIN");
6205                        }
6206                    }
6207                }
6208                JoinKind::Full => {
6209                    if join.use_outer_keyword {
6210                        if hint_str.is_empty() && directed_str.is_empty() {
6211                            self.write_keyword("FULL OUTER JOIN");
6212                        } else {
6213                            self.write_keyword("FULL OUTER");
6214                            if !hint_str.is_empty() {
6215                                self.write_keyword(&hint_str);
6216                            }
6217                            if !directed_str.is_empty() {
6218                                self.write_keyword(directed_str);
6219                            }
6220                            self.write_keyword(" JOIN");
6221                        }
6222                    } else {
6223                        if hint_str.is_empty() && directed_str.is_empty() {
6224                            self.write_keyword("FULL JOIN");
6225                        } else {
6226                            self.write_keyword("FULL");
6227                            if !hint_str.is_empty() {
6228                                self.write_keyword(&hint_str);
6229                            }
6230                            if !directed_str.is_empty() {
6231                                self.write_keyword(directed_str);
6232                            }
6233                            self.write_keyword(" JOIN");
6234                        }
6235                    }
6236                }
6237                JoinKind::Outer => {
6238                    if directed_str.is_empty() {
6239                        self.write_keyword("OUTER JOIN");
6240                    } else {
6241                        self.write_keyword("OUTER");
6242                        self.write_keyword(directed_str);
6243                        self.write_keyword(" JOIN");
6244                    }
6245                }
6246                JoinKind::Cross => {
6247                    if directed_str.is_empty() {
6248                        self.write_keyword("CROSS JOIN");
6249                    } else {
6250                        self.write_keyword("CROSS");
6251                        self.write_keyword(directed_str);
6252                        self.write_keyword(" JOIN");
6253                    }
6254                }
6255                JoinKind::Natural => {
6256                    if join.use_inner_keyword {
6257                        if directed_str.is_empty() {
6258                            self.write_keyword("NATURAL INNER JOIN");
6259                        } else {
6260                            self.write_keyword("NATURAL INNER");
6261                            self.write_keyword(directed_str);
6262                            self.write_keyword(" JOIN");
6263                        }
6264                    } else {
6265                        if directed_str.is_empty() {
6266                            self.write_keyword("NATURAL JOIN");
6267                        } else {
6268                            self.write_keyword("NATURAL");
6269                            self.write_keyword(directed_str);
6270                            self.write_keyword(" JOIN");
6271                        }
6272                    }
6273                }
6274                JoinKind::NaturalLeft => {
6275                    if join.use_outer_keyword {
6276                        if directed_str.is_empty() {
6277                            self.write_keyword("NATURAL LEFT OUTER JOIN");
6278                        } else {
6279                            self.write_keyword("NATURAL LEFT OUTER");
6280                            self.write_keyword(directed_str);
6281                            self.write_keyword(" JOIN");
6282                        }
6283                    } else {
6284                        if directed_str.is_empty() {
6285                            self.write_keyword("NATURAL LEFT JOIN");
6286                        } else {
6287                            self.write_keyword("NATURAL LEFT");
6288                            self.write_keyword(directed_str);
6289                            self.write_keyword(" JOIN");
6290                        }
6291                    }
6292                }
6293                JoinKind::NaturalRight => {
6294                    if join.use_outer_keyword {
6295                        if directed_str.is_empty() {
6296                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
6297                        } else {
6298                            self.write_keyword("NATURAL RIGHT OUTER");
6299                            self.write_keyword(directed_str);
6300                            self.write_keyword(" JOIN");
6301                        }
6302                    } else {
6303                        if directed_str.is_empty() {
6304                            self.write_keyword("NATURAL RIGHT JOIN");
6305                        } else {
6306                            self.write_keyword("NATURAL RIGHT");
6307                            self.write_keyword(directed_str);
6308                            self.write_keyword(" JOIN");
6309                        }
6310                    }
6311                }
6312                JoinKind::NaturalFull => {
6313                    if join.use_outer_keyword {
6314                        if directed_str.is_empty() {
6315                            self.write_keyword("NATURAL FULL OUTER JOIN");
6316                        } else {
6317                            self.write_keyword("NATURAL FULL OUTER");
6318                            self.write_keyword(directed_str);
6319                            self.write_keyword(" JOIN");
6320                        }
6321                    } else {
6322                        if directed_str.is_empty() {
6323                            self.write_keyword("NATURAL FULL JOIN");
6324                        } else {
6325                            self.write_keyword("NATURAL FULL");
6326                            self.write_keyword(directed_str);
6327                            self.write_keyword(" JOIN");
6328                        }
6329                    }
6330                }
6331                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
6332                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
6333                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
6334                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
6335                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
6336                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
6337                JoinKind::CrossApply => {
6338                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
6339                    if matches!(
6340                        self.config.dialect,
6341                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6342                    ) {
6343                        self.write_keyword("CROSS APPLY");
6344                    } else {
6345                        self.write_keyword("INNER JOIN LATERAL");
6346                    }
6347                }
6348                JoinKind::OuterApply => {
6349                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL-like dialects
6350                    if matches!(
6351                        self.config.dialect,
6352                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6353                    ) {
6354                        self.write_keyword("OUTER APPLY");
6355                    } else {
6356                        self.write_keyword("LEFT JOIN LATERAL");
6357                    }
6358                }
6359                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
6360                JoinKind::AsOfLeft => {
6361                    if join.use_outer_keyword {
6362                        self.write_keyword("ASOF LEFT OUTER JOIN");
6363                    } else {
6364                        self.write_keyword("ASOF LEFT JOIN");
6365                    }
6366                }
6367                JoinKind::AsOfRight => {
6368                    if join.use_outer_keyword {
6369                        self.write_keyword("ASOF RIGHT OUTER JOIN");
6370                    } else {
6371                        self.write_keyword("ASOF RIGHT JOIN");
6372                    }
6373                }
6374                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
6375                JoinKind::LeftLateral => {
6376                    if join.use_outer_keyword {
6377                        self.write_keyword("LEFT OUTER LATERAL JOIN");
6378                    } else {
6379                        self.write_keyword("LEFT LATERAL JOIN");
6380                    }
6381                }
6382                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
6383                JoinKind::Implicit => {
6384                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
6385                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
6386                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
6387                    use crate::dialects::DialectType;
6388                    let is_cj_dialect = matches!(
6389                        self.config.dialect,
6390                        Some(DialectType::BigQuery)
6391                            | Some(DialectType::Hive)
6392                            | Some(DialectType::Spark)
6393                            | Some(DialectType::Databricks)
6394                    );
6395                    let source_is_same = self.config.source_dialect.is_some()
6396                        && self.config.source_dialect == self.config.dialect;
6397                    let source_is_cj = matches!(
6398                        self.config.source_dialect,
6399                        Some(DialectType::BigQuery)
6400                            | Some(DialectType::Hive)
6401                            | Some(DialectType::Spark)
6402                            | Some(DialectType::Databricks)
6403                    );
6404                    if is_cj_dialect
6405                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
6406                    {
6407                        self.write_keyword("CROSS JOIN");
6408                    } else {
6409                        // Implicit join uses comma: FROM a, b
6410                        // We already wrote a space before the match, so replace with comma
6411                        // by removing trailing space and writing ", "
6412                        self.output.truncate(self.output.trim_end().len());
6413                        self.write(",");
6414                    }
6415                }
6416                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
6417                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
6418                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
6419                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
6420            }
6421        }
6422
6423        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
6424        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
6425            match &join.this {
6426                Expression::Tuple(t) if t.expressions.is_empty() => {}
6427                Expression::Tuple(t) => {
6428                    self.write_space();
6429                    for (i, item) in t.expressions.iter().enumerate() {
6430                        if i > 0 {
6431                            self.write(", ");
6432                        }
6433                        self.generate_expression(item)?;
6434                    }
6435                }
6436                other => {
6437                    self.write_space();
6438                    self.generate_expression(other)?;
6439                }
6440            }
6441        } else {
6442            self.write_space();
6443            self.generate_expression(&join.this)?;
6444        }
6445
6446        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
6447        if !join.deferred_condition {
6448            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
6449            if let Some(match_cond) = &join.match_condition {
6450                self.write_space();
6451                self.write_keyword("MATCH_CONDITION");
6452                self.write(" (");
6453                self.generate_expression(match_cond)?;
6454                self.write(")");
6455            }
6456
6457            if let Some(on) = &join.on {
6458                if self.config.pretty {
6459                    self.write_newline();
6460                    self.indent_level += 1;
6461                    self.write_indent();
6462                    self.write_keyword("ON");
6463                    self.write_space();
6464                    self.generate_join_on_condition(on)?;
6465                    self.indent_level -= 1;
6466                } else {
6467                    self.write_space();
6468                    self.write_keyword("ON");
6469                    self.write_space();
6470                    self.generate_expression(on)?;
6471                }
6472            }
6473
6474            if !join.using.is_empty() {
6475                if self.config.pretty {
6476                    self.write_newline();
6477                    self.indent_level += 1;
6478                    self.write_indent();
6479                    self.write_keyword("USING");
6480                    self.write(" (");
6481                    for (i, col) in join.using.iter().enumerate() {
6482                        if i > 0 {
6483                            self.write(", ");
6484                        }
6485                        self.generate_identifier(col)?;
6486                    }
6487                    self.write(")");
6488                    self.indent_level -= 1;
6489                } else {
6490                    self.write_space();
6491                    self.write_keyword("USING");
6492                    self.write(" (");
6493                    for (i, col) in join.using.iter().enumerate() {
6494                        if i > 0 {
6495                            self.write(", ");
6496                        }
6497                        self.generate_identifier(col)?;
6498                    }
6499                    self.write(")");
6500                }
6501            }
6502        }
6503
6504        // Generate PIVOT/UNPIVOT expressions that follow this join
6505        for pivot in &join.pivots {
6506            self.write_space();
6507            self.generate_expression(pivot)?;
6508        }
6509
6510        Ok(())
6511    }
6512
6513    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
6514    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
6515        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
6516        if let Some(match_cond) = &join.match_condition {
6517            self.write_space();
6518            self.write_keyword("MATCH_CONDITION");
6519            self.write(" (");
6520            self.generate_expression(match_cond)?;
6521            self.write(")");
6522        }
6523
6524        if let Some(on) = &join.on {
6525            if self.config.pretty {
6526                self.write_newline();
6527                self.indent_level += 1;
6528                self.write_indent();
6529                self.write_keyword("ON");
6530                self.write_space();
6531                // In pretty mode, split AND conditions onto separate lines
6532                self.generate_join_on_condition(on)?;
6533                self.indent_level -= 1;
6534            } else {
6535                self.write_space();
6536                self.write_keyword("ON");
6537                self.write_space();
6538                self.generate_expression(on)?;
6539            }
6540        }
6541
6542        if !join.using.is_empty() {
6543            if self.config.pretty {
6544                self.write_newline();
6545                self.indent_level += 1;
6546                self.write_indent();
6547                self.write_keyword("USING");
6548                self.write(" (");
6549                for (i, col) in join.using.iter().enumerate() {
6550                    if i > 0 {
6551                        self.write(", ");
6552                    }
6553                    self.generate_identifier(col)?;
6554                }
6555                self.write(")");
6556                self.indent_level -= 1;
6557            } else {
6558                self.write_space();
6559                self.write_keyword("USING");
6560                self.write(" (");
6561                for (i, col) in join.using.iter().enumerate() {
6562                    if i > 0 {
6563                        self.write(", ");
6564                    }
6565                    self.generate_identifier(col)?;
6566                }
6567                self.write(")");
6568            }
6569        }
6570
6571        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6572        for pivot in &join.pivots {
6573            self.write_space();
6574            self.generate_expression(pivot)?;
6575        }
6576
6577        Ok(())
6578    }
6579
6580    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6581    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6582        if let Expression::And(and_op) = expr {
6583            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6584                self.generate_expression(conditions[0])?;
6585                for condition in conditions.iter().skip(1) {
6586                    self.write_newline();
6587                    self.write_indent();
6588                    self.write_keyword("AND");
6589                    self.write_space();
6590                    self.generate_expression(condition)?;
6591                }
6592                return Ok(());
6593            }
6594        }
6595
6596        self.generate_expression(expr)
6597    }
6598
6599    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6600        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6601        self.write("(");
6602        self.generate_expression(&jt.left)?;
6603
6604        // Generate all joins
6605        for join in &jt.joins {
6606            self.generate_join(join)?;
6607        }
6608
6609        // Generate LATERAL VIEW clauses (Hive/Spark)
6610        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6611            self.generate_lateral_view(lv, lv_idx)?;
6612        }
6613
6614        self.write(")");
6615
6616        // Alias
6617        if let Some(alias) = &jt.alias {
6618            self.write_space();
6619            self.write_keyword("AS");
6620            self.write_space();
6621            self.generate_identifier(alias)?;
6622        }
6623
6624        Ok(())
6625    }
6626
6627    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6628        use crate::dialects::DialectType;
6629
6630        if self.config.pretty {
6631            self.write_newline();
6632            self.write_indent();
6633        } else {
6634            self.write_space();
6635        }
6636
6637        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6638        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6639        let use_lateral_join = matches!(
6640            self.config.dialect,
6641            Some(DialectType::PostgreSQL)
6642                | Some(DialectType::DuckDB)
6643                | Some(DialectType::Snowflake)
6644                | Some(DialectType::TSQL)
6645                | Some(DialectType::Presto)
6646                | Some(DialectType::Trino)
6647                | Some(DialectType::Athena)
6648        );
6649
6650        // Check if target dialect should use UNNEST instead of EXPLODE
6651        let use_unnest = matches!(
6652            self.config.dialect,
6653            Some(DialectType::DuckDB)
6654                | Some(DialectType::Presto)
6655                | Some(DialectType::Trino)
6656                | Some(DialectType::Athena)
6657        );
6658
6659        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6660        let (is_posexplode, is_inline, func_args) = match &lv.this {
6661            Expression::Explode(uf) => {
6662                // Expression::Explode is the dedicated EXPLODE expression type
6663                (false, false, vec![uf.this.clone()])
6664            }
6665            Expression::Unnest(uf) => {
6666                let mut args = vec![uf.this.clone()];
6667                args.extend(uf.expressions.clone());
6668                (false, false, args)
6669            }
6670            Expression::Function(func) => {
6671                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6672                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6673                {
6674                    (true, false, func.args.clone())
6675                } else if func.name.eq_ignore_ascii_case("INLINE") {
6676                    (false, true, func.args.clone())
6677                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6678                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6679                {
6680                    (false, false, func.args.clone())
6681                } else {
6682                    (false, false, vec![])
6683                }
6684            }
6685            _ => (false, false, vec![]),
6686        };
6687
6688        if use_lateral_join {
6689            // Convert to CROSS JOIN for PostgreSQL-like dialects
6690            if lv.outer {
6691                self.write_keyword("LEFT JOIN LATERAL");
6692            } else {
6693                self.write_keyword("CROSS JOIN");
6694            }
6695            self.write_space();
6696
6697            if use_unnest && !func_args.is_empty() {
6698                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6699                // For DuckDB, also convert ARRAY(y) -> [y]
6700                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6701                    // DuckDB: ARRAY(y) -> [y]
6702                    func_args
6703                        .iter()
6704                        .map(|a| {
6705                            if let Expression::Function(ref f) = a {
6706                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6707                                    return Expression::ArrayFunc(Box::new(
6708                                        crate::expressions::ArrayConstructor {
6709                                            expressions: f.args.clone(),
6710                                            bracket_notation: true,
6711                                            use_list_keyword: false,
6712                                        },
6713                                    ));
6714                                }
6715                            }
6716                            a.clone()
6717                        })
6718                        .collect::<Vec<_>>()
6719                } else if matches!(
6720                    self.config.dialect,
6721                    Some(DialectType::Presto)
6722                        | Some(DialectType::Trino)
6723                        | Some(DialectType::Athena)
6724                ) {
6725                    // Presto: ARRAY(y) -> ARRAY[y]
6726                    func_args
6727                        .iter()
6728                        .map(|a| {
6729                            if let Expression::Function(ref f) = a {
6730                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6731                                    return Expression::ArrayFunc(Box::new(
6732                                        crate::expressions::ArrayConstructor {
6733                                            expressions: f.args.clone(),
6734                                            bracket_notation: true,
6735                                            use_list_keyword: false,
6736                                        },
6737                                    ));
6738                                }
6739                            }
6740                            a.clone()
6741                        })
6742                        .collect::<Vec<_>>()
6743                } else {
6744                    func_args
6745                };
6746
6747                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6748                if is_posexplode {
6749                    self.write_keyword("LATERAL");
6750                    self.write(" (");
6751                    self.write_keyword("SELECT");
6752                    self.write_space();
6753
6754                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6755                    // column_aliases[0] is the position column, rest are data columns
6756                    let pos_alias = if !lv.column_aliases.is_empty() {
6757                        lv.column_aliases[0].clone()
6758                    } else {
6759                        Identifier::new("pos")
6760                    };
6761                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6762                        lv.column_aliases[1..].to_vec()
6763                    } else {
6764                        vec![Identifier::new("col")]
6765                    };
6766
6767                    // pos - 1 AS pos
6768                    self.generate_identifier(&pos_alias)?;
6769                    self.write(" - 1");
6770                    self.write_space();
6771                    self.write_keyword("AS");
6772                    self.write_space();
6773                    self.generate_identifier(&pos_alias)?;
6774
6775                    // , col [, key, value ...]
6776                    for data_col in &data_aliases {
6777                        self.write(", ");
6778                        self.generate_identifier(data_col)?;
6779                    }
6780
6781                    self.write_space();
6782                    self.write_keyword("FROM");
6783                    self.write_space();
6784                    self.write_keyword("UNNEST");
6785                    self.write("(");
6786                    for (i, arg) in unnest_args.iter().enumerate() {
6787                        if i > 0 {
6788                            self.write(", ");
6789                        }
6790                        self.generate_expression(arg)?;
6791                    }
6792                    self.write(")");
6793                    self.write_space();
6794                    self.write_keyword("WITH ORDINALITY");
6795                    self.write_space();
6796                    self.write_keyword("AS");
6797                    self.write_space();
6798
6799                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6800                    let table_alias_ident = lv
6801                        .table_alias
6802                        .clone()
6803                        .unwrap_or_else(|| Identifier::new("t"));
6804                    self.generate_identifier(&table_alias_ident)?;
6805                    self.write("(");
6806                    for (i, data_col) in data_aliases.iter().enumerate() {
6807                        if i > 0 {
6808                            self.write(", ");
6809                        }
6810                        self.generate_identifier(data_col)?;
6811                    }
6812                    self.write(", ");
6813                    self.generate_identifier(&pos_alias)?;
6814                    self.write("))");
6815                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6816                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6817                    self.write_keyword("LATERAL");
6818                    self.write(" (");
6819                    self.write_keyword("SELECT");
6820                    self.write_space();
6821                    self.write_keyword("UNNEST");
6822                    self.write("(");
6823                    for (i, arg) in unnest_args.iter().enumerate() {
6824                        if i > 0 {
6825                            self.write(", ");
6826                        }
6827                        self.generate_expression(arg)?;
6828                    }
6829                    self.write(", ");
6830                    self.write_keyword("max_depth");
6831                    self.write(" => 2))");
6832
6833                    // Add table and column aliases
6834                    if let Some(alias) = &lv.table_alias {
6835                        self.write_space();
6836                        self.write_keyword("AS");
6837                        self.write_space();
6838                        self.generate_identifier(alias)?;
6839                        if !lv.column_aliases.is_empty() {
6840                            self.write("(");
6841                            for (i, col) in lv.column_aliases.iter().enumerate() {
6842                                if i > 0 {
6843                                    self.write(", ");
6844                                }
6845                                self.generate_identifier(col)?;
6846                            }
6847                            self.write(")");
6848                        }
6849                    } else if !lv.column_aliases.is_empty() {
6850                        // Auto-generate alias like _u_N
6851                        self.write_space();
6852                        self.write_keyword("AS");
6853                        self.write_space();
6854                        self.write(&format!("_u_{}", lv_index));
6855                        self.write("(");
6856                        for (i, col) in lv.column_aliases.iter().enumerate() {
6857                            if i > 0 {
6858                                self.write(", ");
6859                            }
6860                            self.generate_identifier(col)?;
6861                        }
6862                        self.write(")");
6863                    }
6864                } else {
6865                    self.write_keyword("UNNEST");
6866                    self.write("(");
6867                    for (i, arg) in unnest_args.iter().enumerate() {
6868                        if i > 0 {
6869                            self.write(", ");
6870                        }
6871                        self.generate_expression(arg)?;
6872                    }
6873                    self.write(")");
6874
6875                    // Add table and column aliases for non-POSEXPLODE
6876                    if let Some(alias) = &lv.table_alias {
6877                        self.write_space();
6878                        self.write_keyword("AS");
6879                        self.write_space();
6880                        self.generate_identifier(alias)?;
6881                        if !lv.column_aliases.is_empty() {
6882                            self.write("(");
6883                            for (i, col) in lv.column_aliases.iter().enumerate() {
6884                                if i > 0 {
6885                                    self.write(", ");
6886                                }
6887                                self.generate_identifier(col)?;
6888                            }
6889                            self.write(")");
6890                        }
6891                    } else if !lv.column_aliases.is_empty() {
6892                        self.write_space();
6893                        self.write_keyword("AS");
6894                        self.write(" t(");
6895                        for (i, col) in lv.column_aliases.iter().enumerate() {
6896                            if i > 0 {
6897                                self.write(", ");
6898                            }
6899                            self.generate_identifier(col)?;
6900                        }
6901                        self.write(")");
6902                    }
6903                }
6904            } else {
6905                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6906                if !lv.outer {
6907                    self.write_keyword("LATERAL");
6908                    self.write_space();
6909                }
6910                self.generate_expression(&lv.this)?;
6911
6912                // Add table and column aliases
6913                if let Some(alias) = &lv.table_alias {
6914                    self.write_space();
6915                    self.write_keyword("AS");
6916                    self.write_space();
6917                    self.generate_identifier(alias)?;
6918                    if !lv.column_aliases.is_empty() {
6919                        self.write("(");
6920                        for (i, col) in lv.column_aliases.iter().enumerate() {
6921                            if i > 0 {
6922                                self.write(", ");
6923                            }
6924                            self.generate_identifier(col)?;
6925                        }
6926                        self.write(")");
6927                    }
6928                } else if !lv.column_aliases.is_empty() {
6929                    self.write_space();
6930                    self.write_keyword("AS");
6931                    self.write(" t(");
6932                    for (i, col) in lv.column_aliases.iter().enumerate() {
6933                        if i > 0 {
6934                            self.write(", ");
6935                        }
6936                        self.generate_identifier(col)?;
6937                    }
6938                    self.write(")");
6939                }
6940            }
6941
6942            // For LEFT JOIN LATERAL, need ON TRUE
6943            if lv.outer {
6944                self.write_space();
6945                self.write_keyword("ON TRUE");
6946            }
6947        } else {
6948            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6949            self.write_keyword("LATERAL VIEW");
6950            if lv.outer {
6951                self.write_space();
6952                self.write_keyword("OUTER");
6953            }
6954            if self.config.pretty {
6955                self.write_newline();
6956                self.write_indent();
6957            } else {
6958                self.write_space();
6959            }
6960            self.generate_expression(&lv.this)?;
6961
6962            // Table alias
6963            if let Some(alias) = &lv.table_alias {
6964                self.write_space();
6965                self.generate_identifier(alias)?;
6966            }
6967
6968            // Column aliases
6969            if !lv.column_aliases.is_empty() {
6970                self.write_space();
6971                self.write_keyword("AS");
6972                self.write_space();
6973                for (i, col) in lv.column_aliases.iter().enumerate() {
6974                    if i > 0 {
6975                        self.write(", ");
6976                    }
6977                    self.generate_identifier(col)?;
6978                }
6979            }
6980        }
6981
6982        Ok(())
6983    }
6984
6985    fn should_wrap_set_operation_modifiers(
6986        &self,
6987        order_by: &Option<OrderBy>,
6988        limit: &Option<Box<Expression>>,
6989        offset: &Option<Box<Expression>>,
6990    ) -> bool {
6991        let has_row_limit = limit.is_some() || offset.is_some();
6992        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6993            order_by
6994                .expressions
6995                .iter()
6996                .any(|ordered| ordered.nulls_first.is_some())
6997        });
6998
6999        (has_row_limit || has_emulated_null_ordering)
7000            && matches!(
7001                self.config.dialect,
7002                Some(DialectType::TSQL) | Some(DialectType::Fabric)
7003            )
7004    }
7005
7006    fn generate_tsql_wrapped_set_operation(
7007        &mut self,
7008        inner: Expression,
7009        with: Option<With>,
7010        order_by: Option<OrderBy>,
7011        limit: Option<Box<Expression>>,
7012        offset: Option<Box<Expression>>,
7013    ) -> Result<()> {
7014        let subquery = Subquery {
7015            this: inner,
7016            alias: Some(Identifier::new("_l_0".to_string())),
7017            column_aliases: Vec::new(),
7018            alias_explicit_as: true,
7019            alias_keyword: None,
7020            order_by: None,
7021            limit: None,
7022            offset: None,
7023            lateral: false,
7024            modifiers_inside: false,
7025            trailing_comments: Vec::new(),
7026            distribute_by: None,
7027            sort_by: None,
7028            cluster_by: None,
7029            inferred_type: None,
7030        };
7031
7032        let mut outer_select = Select {
7033            expressions: vec![Expression::Star(Star {
7034                table: None,
7035                except: None,
7036                replace: None,
7037                rename: None,
7038                trailing_comments: Vec::new(),
7039                span: None,
7040            })],
7041            from: Some(From {
7042                expressions: vec![Expression::Subquery(Box::new(subquery))],
7043            }),
7044            with,
7045            order_by,
7046            limit: limit.map(|limit| Limit {
7047                this: *limit,
7048                percent: false,
7049                comments: Vec::new(),
7050            }),
7051            offset: offset.map(|offset| Offset {
7052                this: *offset,
7053                rows: Some(true),
7054            }),
7055            ..Select::new()
7056        };
7057
7058        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
7059            outer_select.order_by = Some(Self::dummy_tsql_order_by());
7060        }
7061
7062        self.generate_select(&outer_select)
7063    }
7064
7065    pub(crate) fn dummy_tsql_order_by() -> OrderBy {
7066        let null_select = Expression::Select(Box::new(Select {
7067            expressions: vec![Expression::Null(Null)],
7068            ..Select::new()
7069        }));
7070
7071        OrderBy {
7072            expressions: vec![Ordered {
7073                this: Expression::Subquery(Box::new(Subquery {
7074                    this: null_select,
7075                    alias: None,
7076                    column_aliases: Vec::new(),
7077                    alias_explicit_as: false,
7078                    alias_keyword: None,
7079                    order_by: None,
7080                    limit: None,
7081                    offset: None,
7082                    lateral: false,
7083                    modifiers_inside: false,
7084                    trailing_comments: Vec::new(),
7085                    distribute_by: None,
7086                    sort_by: None,
7087                    cluster_by: None,
7088                    inferred_type: None,
7089                })),
7090                desc: false,
7091                nulls_first: None,
7092                explicit_asc: false,
7093                with_fill: None,
7094            }],
7095            siblings: false,
7096            comments: Vec::new(),
7097        }
7098    }
7099
7100    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
7101        if self.should_wrap_set_operation_modifiers(
7102            &outermost.order_by,
7103            &outermost.limit,
7104            &outermost.offset,
7105        ) {
7106            let mut inner = outermost.clone();
7107            let with = inner.with.take();
7108            let order_by = inner.order_by.take();
7109            let limit = inner.limit.take();
7110            let offset = inner.offset.take();
7111
7112            return self.generate_tsql_wrapped_set_operation(
7113                Expression::Union(Box::new(inner)),
7114                with,
7115                order_by,
7116                limit,
7117                offset,
7118            );
7119        }
7120
7121        // Collect the left-recursive chain of Union nodes iteratively.
7122        // This avoids stack overflow for deeply nested chains like
7123        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
7124        // where the parser builds: Union(Union(Union(A, B), C), D)
7125        let mut chain: Vec<&Union> = vec![outermost];
7126        let mut leftmost: &Expression = &outermost.left;
7127        while let Expression::Union(inner) = leftmost {
7128            chain.push(inner);
7129            leftmost = &inner.left;
7130        }
7131        // chain[0] = outermost, chain[last] = innermost
7132        // leftmost = innermost.left (a non-Union expression, typically Select)
7133
7134        // WITH clause (only on outermost)
7135        if let Some(with) = &outermost.with {
7136            self.generate_with(with)?;
7137            self.write_space();
7138        }
7139
7140        // Generate the base (leftmost) expression
7141        self.generate_expression(leftmost)?;
7142
7143        // Generate each union step from innermost to outermost
7144        for union in chain.iter().rev() {
7145            self.generate_union_step(union)?;
7146        }
7147        Ok(())
7148    }
7149
7150    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
7151    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
7152        if self.config.pretty {
7153            self.write_newline();
7154            self.write_indent();
7155        } else {
7156            self.write_space();
7157        }
7158
7159        // BigQuery set operation modifiers: [side] [kind] UNION
7160        if let Some(side) = &union.side {
7161            self.write_keyword(side);
7162            self.write_space();
7163        }
7164        if let Some(kind) = &union.kind {
7165            self.write_keyword(kind);
7166            self.write_space();
7167        }
7168
7169        self.write_keyword("UNION");
7170        if union.all {
7171            self.write_space();
7172            self.write_keyword("ALL");
7173        } else if union.distinct {
7174            self.write_space();
7175            self.write_keyword("DISTINCT");
7176        }
7177
7178        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7179        // DuckDB: BY NAME
7180        if union.corresponding || union.by_name {
7181            self.write_space();
7182            self.write_keyword("BY NAME");
7183        }
7184        if !union.on_columns.is_empty() {
7185            self.write_space();
7186            self.write_keyword("ON");
7187            self.write(" (");
7188            for (i, col) in union.on_columns.iter().enumerate() {
7189                if i > 0 {
7190                    self.write(", ");
7191                }
7192                self.generate_expression(col)?;
7193            }
7194            self.write(")");
7195        }
7196
7197        if self.config.pretty {
7198            self.write_newline();
7199            self.write_indent();
7200        } else {
7201            self.write_space();
7202        }
7203        self.generate_expression(&union.right)?;
7204        // ORDER BY, LIMIT, OFFSET for the set operation
7205        if let Some(order_by) = &union.order_by {
7206            if self.config.pretty {
7207                self.write_newline();
7208            } else {
7209                self.write_space();
7210            }
7211            self.write_keyword("ORDER BY");
7212            self.write_space();
7213            for (i, ordered) in order_by.expressions.iter().enumerate() {
7214                if i > 0 {
7215                    self.write(", ");
7216                }
7217                self.generate_ordered(ordered)?;
7218            }
7219        }
7220        if let Some(limit) = &union.limit {
7221            if self.config.pretty {
7222                self.write_newline();
7223            } else {
7224                self.write_space();
7225            }
7226            self.write_keyword("LIMIT");
7227            self.write_space();
7228            self.generate_expression(limit)?;
7229        }
7230        if let Some(offset) = &union.offset {
7231            if self.config.pretty {
7232                self.write_newline();
7233            } else {
7234                self.write_space();
7235            }
7236            self.write_keyword("OFFSET");
7237            self.write_space();
7238            self.generate_expression(offset)?;
7239        }
7240        // DISTRIBUTE BY (Hive/Spark)
7241        if let Some(distribute_by) = &union.distribute_by {
7242            self.write_space();
7243            self.write_keyword("DISTRIBUTE BY");
7244            self.write_space();
7245            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7246                if i > 0 {
7247                    self.write(", ");
7248                }
7249                self.generate_expression(expr)?;
7250            }
7251        }
7252        // SORT BY (Hive/Spark)
7253        if let Some(sort_by) = &union.sort_by {
7254            self.write_space();
7255            self.write_keyword("SORT BY");
7256            self.write_space();
7257            for (i, ord) in sort_by.expressions.iter().enumerate() {
7258                if i > 0 {
7259                    self.write(", ");
7260                }
7261                self.generate_ordered(ord)?;
7262            }
7263        }
7264        // CLUSTER BY (Hive/Spark)
7265        if let Some(cluster_by) = &union.cluster_by {
7266            self.write_space();
7267            self.write_keyword("CLUSTER BY");
7268            self.write_space();
7269            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7270                if i > 0 {
7271                    self.write(", ");
7272                }
7273                self.generate_ordered(ord)?;
7274            }
7275        }
7276        Ok(())
7277    }
7278
7279    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
7280        if self.should_wrap_set_operation_modifiers(
7281            &outermost.order_by,
7282            &outermost.limit,
7283            &outermost.offset,
7284        ) {
7285            let mut inner = outermost.clone();
7286            let with = inner.with.take();
7287            let order_by = inner.order_by.take();
7288            let limit = inner.limit.take();
7289            let offset = inner.offset.take();
7290
7291            return self.generate_tsql_wrapped_set_operation(
7292                Expression::Intersect(Box::new(inner)),
7293                with,
7294                order_by,
7295                limit,
7296                offset,
7297            );
7298        }
7299
7300        // Collect the left-recursive chain iteratively to avoid stack overflow
7301        let mut chain: Vec<&Intersect> = vec![outermost];
7302        let mut leftmost: &Expression = &outermost.left;
7303        while let Expression::Intersect(inner) = leftmost {
7304            chain.push(inner);
7305            leftmost = &inner.left;
7306        }
7307
7308        if let Some(with) = &outermost.with {
7309            self.generate_with(with)?;
7310            self.write_space();
7311        }
7312
7313        self.generate_expression(leftmost)?;
7314
7315        for intersect in chain.iter().rev() {
7316            self.generate_intersect_step(intersect)?;
7317        }
7318        Ok(())
7319    }
7320
7321    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
7322    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
7323        if self.config.pretty {
7324            self.write_newline();
7325            self.write_indent();
7326        } else {
7327            self.write_space();
7328        }
7329
7330        // BigQuery set operation modifiers: [side] [kind] INTERSECT
7331        if let Some(side) = &intersect.side {
7332            self.write_keyword(side);
7333            self.write_space();
7334        }
7335        if let Some(kind) = &intersect.kind {
7336            self.write_keyword(kind);
7337            self.write_space();
7338        }
7339
7340        self.write_keyword("INTERSECT");
7341        if intersect.all {
7342            if !self.config.except_intersect_support_all_clause {
7343                self.unsupported("INTERSECT ALL is not supported")?;
7344            }
7345            self.write_space();
7346            self.write_keyword("ALL");
7347        } else if intersect.distinct {
7348            self.write_space();
7349            self.write_keyword("DISTINCT");
7350        }
7351
7352        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7353        // DuckDB: BY NAME
7354        if intersect.corresponding || intersect.by_name {
7355            self.write_space();
7356            self.write_keyword("BY NAME");
7357        }
7358        if !intersect.on_columns.is_empty() {
7359            self.write_space();
7360            self.write_keyword("ON");
7361            self.write(" (");
7362            for (i, col) in intersect.on_columns.iter().enumerate() {
7363                if i > 0 {
7364                    self.write(", ");
7365                }
7366                self.generate_expression(col)?;
7367            }
7368            self.write(")");
7369        }
7370
7371        if self.config.pretty {
7372            self.write_newline();
7373            self.write_indent();
7374        } else {
7375            self.write_space();
7376        }
7377        self.generate_expression(&intersect.right)?;
7378        // ORDER BY, LIMIT, OFFSET for the set operation
7379        if let Some(order_by) = &intersect.order_by {
7380            if self.config.pretty {
7381                self.write_newline();
7382            } else {
7383                self.write_space();
7384            }
7385            self.write_keyword("ORDER BY");
7386            self.write_space();
7387            for (i, ordered) in order_by.expressions.iter().enumerate() {
7388                if i > 0 {
7389                    self.write(", ");
7390                }
7391                self.generate_ordered(ordered)?;
7392            }
7393        }
7394        if let Some(limit) = &intersect.limit {
7395            if self.config.pretty {
7396                self.write_newline();
7397            } else {
7398                self.write_space();
7399            }
7400            self.write_keyword("LIMIT");
7401            self.write_space();
7402            self.generate_expression(limit)?;
7403        }
7404        if let Some(offset) = &intersect.offset {
7405            if self.config.pretty {
7406                self.write_newline();
7407            } else {
7408                self.write_space();
7409            }
7410            self.write_keyword("OFFSET");
7411            self.write_space();
7412            self.generate_expression(offset)?;
7413        }
7414        // DISTRIBUTE BY (Hive/Spark)
7415        if let Some(distribute_by) = &intersect.distribute_by {
7416            self.write_space();
7417            self.write_keyword("DISTRIBUTE BY");
7418            self.write_space();
7419            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7420                if i > 0 {
7421                    self.write(", ");
7422                }
7423                self.generate_expression(expr)?;
7424            }
7425        }
7426        // SORT BY (Hive/Spark)
7427        if let Some(sort_by) = &intersect.sort_by {
7428            self.write_space();
7429            self.write_keyword("SORT BY");
7430            self.write_space();
7431            for (i, ord) in sort_by.expressions.iter().enumerate() {
7432                if i > 0 {
7433                    self.write(", ");
7434                }
7435                self.generate_ordered(ord)?;
7436            }
7437        }
7438        // CLUSTER BY (Hive/Spark)
7439        if let Some(cluster_by) = &intersect.cluster_by {
7440            self.write_space();
7441            self.write_keyword("CLUSTER BY");
7442            self.write_space();
7443            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7444                if i > 0 {
7445                    self.write(", ");
7446                }
7447                self.generate_ordered(ord)?;
7448            }
7449        }
7450        Ok(())
7451    }
7452
7453    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
7454        if self.should_wrap_set_operation_modifiers(
7455            &outermost.order_by,
7456            &outermost.limit,
7457            &outermost.offset,
7458        ) {
7459            let mut inner = outermost.clone();
7460            let with = inner.with.take();
7461            let order_by = inner.order_by.take();
7462            let limit = inner.limit.take();
7463            let offset = inner.offset.take();
7464
7465            return self.generate_tsql_wrapped_set_operation(
7466                Expression::Except(Box::new(inner)),
7467                with,
7468                order_by,
7469                limit,
7470                offset,
7471            );
7472        }
7473
7474        // Collect the left-recursive chain iteratively to avoid stack overflow
7475        let mut chain: Vec<&Except> = vec![outermost];
7476        let mut leftmost: &Expression = &outermost.left;
7477        while let Expression::Except(inner) = leftmost {
7478            chain.push(inner);
7479            leftmost = &inner.left;
7480        }
7481
7482        if let Some(with) = &outermost.with {
7483            self.generate_with(with)?;
7484            self.write_space();
7485        }
7486
7487        self.generate_expression(leftmost)?;
7488
7489        for except in chain.iter().rev() {
7490            self.generate_except_step(except)?;
7491        }
7492        Ok(())
7493    }
7494
7495    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
7496    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
7497        use crate::dialects::DialectType;
7498
7499        if self.config.pretty {
7500            self.write_newline();
7501            self.write_indent();
7502        } else {
7503            self.write_space();
7504        }
7505
7506        // BigQuery set operation modifiers: [side] [kind] EXCEPT
7507        if let Some(side) = &except.side {
7508            self.write_keyword(side);
7509            self.write_space();
7510        }
7511        if let Some(kind) = &except.kind {
7512            self.write_keyword(kind);
7513            self.write_space();
7514        }
7515
7516        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
7517        match self.config.dialect {
7518            Some(DialectType::Oracle) if !except.all => {
7519                self.write_keyword("MINUS");
7520            }
7521            Some(DialectType::ClickHouse) => {
7522                self.write_keyword("EXCEPT");
7523                let preserve_all = self.config.source_dialect.is_none()
7524                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
7525                if except.all && preserve_all {
7526                    self.write_space();
7527                    self.write_keyword("ALL");
7528                }
7529                if except.distinct {
7530                    self.write_space();
7531                    self.write_keyword("DISTINCT");
7532                }
7533            }
7534            Some(DialectType::BigQuery) => {
7535                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
7536                self.write_keyword("EXCEPT");
7537                if except.all {
7538                    self.write_space();
7539                    self.write_keyword("ALL");
7540                } else {
7541                    self.write_space();
7542                    self.write_keyword("DISTINCT");
7543                }
7544            }
7545            _ => {
7546                self.write_keyword("EXCEPT");
7547                if except.all {
7548                    if !self.config.except_intersect_support_all_clause {
7549                        self.unsupported("EXCEPT ALL is not supported")?;
7550                    }
7551                    self.write_space();
7552                    self.write_keyword("ALL");
7553                } else if except.distinct {
7554                    self.write_space();
7555                    self.write_keyword("DISTINCT");
7556                }
7557            }
7558        }
7559
7560        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7561        // DuckDB: BY NAME
7562        if except.corresponding || except.by_name {
7563            self.write_space();
7564            self.write_keyword("BY NAME");
7565        }
7566        if !except.on_columns.is_empty() {
7567            self.write_space();
7568            self.write_keyword("ON");
7569            self.write(" (");
7570            for (i, col) in except.on_columns.iter().enumerate() {
7571                if i > 0 {
7572                    self.write(", ");
7573                }
7574                self.generate_expression(col)?;
7575            }
7576            self.write(")");
7577        }
7578
7579        if self.config.pretty {
7580            self.write_newline();
7581            self.write_indent();
7582        } else {
7583            self.write_space();
7584        }
7585        self.generate_expression(&except.right)?;
7586        // ORDER BY, LIMIT, OFFSET for the set operation
7587        if let Some(order_by) = &except.order_by {
7588            if self.config.pretty {
7589                self.write_newline();
7590            } else {
7591                self.write_space();
7592            }
7593            self.write_keyword("ORDER BY");
7594            self.write_space();
7595            for (i, ordered) in order_by.expressions.iter().enumerate() {
7596                if i > 0 {
7597                    self.write(", ");
7598                }
7599                self.generate_ordered(ordered)?;
7600            }
7601        }
7602        if let Some(limit) = &except.limit {
7603            if self.config.pretty {
7604                self.write_newline();
7605            } else {
7606                self.write_space();
7607            }
7608            self.write_keyword("LIMIT");
7609            self.write_space();
7610            self.generate_expression(limit)?;
7611        }
7612        if let Some(offset) = &except.offset {
7613            if self.config.pretty {
7614                self.write_newline();
7615            } else {
7616                self.write_space();
7617            }
7618            self.write_keyword("OFFSET");
7619            self.write_space();
7620            self.generate_expression(offset)?;
7621        }
7622        // DISTRIBUTE BY (Hive/Spark)
7623        if let Some(distribute_by) = &except.distribute_by {
7624            self.write_space();
7625            self.write_keyword("DISTRIBUTE BY");
7626            self.write_space();
7627            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7628                if i > 0 {
7629                    self.write(", ");
7630                }
7631                self.generate_expression(expr)?;
7632            }
7633        }
7634        // SORT BY (Hive/Spark)
7635        if let Some(sort_by) = &except.sort_by {
7636            self.write_space();
7637            self.write_keyword("SORT BY");
7638            self.write_space();
7639            for (i, ord) in sort_by.expressions.iter().enumerate() {
7640                if i > 0 {
7641                    self.write(", ");
7642                }
7643                self.generate_ordered(ord)?;
7644            }
7645        }
7646        // CLUSTER BY (Hive/Spark)
7647        if let Some(cluster_by) = &except.cluster_by {
7648            self.write_space();
7649            self.write_keyword("CLUSTER BY");
7650            self.write_space();
7651            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7652                if i > 0 {
7653                    self.write(", ");
7654                }
7655                self.generate_ordered(ord)?;
7656            }
7657        }
7658        Ok(())
7659    }
7660
7661    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7662        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7663        let prepend_query_cte = if insert.with.is_none() {
7664            use crate::dialects::DialectType;
7665            let should_prepend = matches!(
7666                self.config.dialect,
7667                Some(DialectType::TSQL)
7668                    | Some(DialectType::Fabric)
7669                    | Some(DialectType::Spark)
7670                    | Some(DialectType::Databricks)
7671                    | Some(DialectType::Hive)
7672            );
7673            if should_prepend {
7674                if let Some(Expression::Select(select)) = &insert.query {
7675                    select.with.clone()
7676                } else {
7677                    None
7678                }
7679            } else {
7680                None
7681            }
7682        } else {
7683            None
7684        };
7685
7686        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7687        if let Some(with) = &insert.with {
7688            self.generate_with(with)?;
7689            self.write_space();
7690        } else if let Some(with) = &prepend_query_cte {
7691            self.generate_with(with)?;
7692            self.write_space();
7693        }
7694
7695        // Output leading comments before INSERT
7696        for comment in &insert.leading_comments {
7697            self.write_formatted_comment(comment);
7698            self.write(" ");
7699        }
7700
7701        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7702        if let Some(dir) = &insert.directory {
7703            self.write_keyword("INSERT OVERWRITE");
7704            if dir.local {
7705                self.write_space();
7706                self.write_keyword("LOCAL");
7707            }
7708            self.write_space();
7709            self.write_keyword("DIRECTORY");
7710            self.write_space();
7711            self.write("'");
7712            self.write(&dir.path);
7713            self.write("'");
7714
7715            // ROW FORMAT clause
7716            if let Some(row_format) = &dir.row_format {
7717                self.write_space();
7718                self.write_keyword("ROW FORMAT");
7719                if row_format.delimited {
7720                    self.write_space();
7721                    self.write_keyword("DELIMITED");
7722                }
7723                if let Some(val) = &row_format.fields_terminated_by {
7724                    self.write_space();
7725                    self.write_keyword("FIELDS TERMINATED BY");
7726                    self.write_space();
7727                    self.generate_string_literal(val)?;
7728                }
7729                if let Some(val) = &row_format.collection_items_terminated_by {
7730                    self.write_space();
7731                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7732                    self.write_space();
7733                    self.write("'");
7734                    self.write(val);
7735                    self.write("'");
7736                }
7737                if let Some(val) = &row_format.map_keys_terminated_by {
7738                    self.write_space();
7739                    self.write_keyword("MAP KEYS TERMINATED BY");
7740                    self.write_space();
7741                    self.write("'");
7742                    self.write(val);
7743                    self.write("'");
7744                }
7745                if let Some(val) = &row_format.lines_terminated_by {
7746                    self.write_space();
7747                    self.write_keyword("LINES TERMINATED BY");
7748                    self.write_space();
7749                    self.write("'");
7750                    self.write(val);
7751                    self.write("'");
7752                }
7753                if let Some(val) = &row_format.null_defined_as {
7754                    self.write_space();
7755                    self.write_keyword("NULL DEFINED AS");
7756                    self.write_space();
7757                    self.write("'");
7758                    self.write(val);
7759                    self.write("'");
7760                }
7761            }
7762
7763            // STORED AS clause
7764            if let Some(format) = &dir.stored_as {
7765                self.write_space();
7766                self.write_keyword("STORED AS");
7767                self.write_space();
7768                self.write_keyword(format);
7769            }
7770
7771            // Query (SELECT statement)
7772            if let Some(query) = &insert.query {
7773                self.write_space();
7774                self.generate_expression(query)?;
7775            }
7776
7777            return Ok(());
7778        }
7779
7780        if insert.is_replace {
7781            // MySQL/SQLite REPLACE INTO statement
7782            self.write_keyword("REPLACE INTO");
7783        } else if insert.overwrite {
7784            // Use dialect-specific INSERT OVERWRITE format
7785            self.write_keyword("INSERT");
7786            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7787            if let Some(ref hint) = insert.hint {
7788                self.generate_hint(hint)?;
7789            }
7790            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7791        } else if let Some(ref action) = insert.conflict_action {
7792            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7793            self.write_keyword("INSERT OR");
7794            self.write_space();
7795            self.write_keyword(action);
7796            self.write_space();
7797            self.write_keyword("INTO");
7798        } else if insert.ignore {
7799            // MySQL INSERT IGNORE syntax
7800            self.write_keyword("INSERT IGNORE INTO");
7801        } else {
7802            self.write_keyword("INSERT");
7803            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7804            if let Some(ref hint) = insert.hint {
7805                self.generate_hint(hint)?;
7806            }
7807            self.write_space();
7808            self.write_keyword("INTO");
7809        }
7810        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7811        if let Some(ref func) = insert.function_target {
7812            self.write_space();
7813            self.write_keyword("FUNCTION");
7814            self.write_space();
7815            self.generate_expression(func)?;
7816        } else {
7817            self.write_space();
7818            self.generate_table(&insert.table)?;
7819        }
7820
7821        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7822        if let Some(ref alias) = insert.alias {
7823            self.write_space();
7824            if insert.alias_explicit_as {
7825                self.write_keyword("AS");
7826                self.write_space();
7827            }
7828            self.generate_identifier(alias)?;
7829        }
7830
7831        // IF EXISTS clause (Hive)
7832        if insert.if_exists {
7833            self.write_space();
7834            self.write_keyword("IF EXISTS");
7835        }
7836
7837        // REPLACE WHERE clause (Databricks)
7838        if let Some(ref replace_where) = insert.replace_where {
7839            if self.config.pretty {
7840                self.write_newline();
7841                self.write_indent();
7842            } else {
7843                self.write_space();
7844            }
7845            self.write_keyword("REPLACE WHERE");
7846            self.write_space();
7847            self.generate_expression(replace_where)?;
7848        }
7849
7850        // Generate PARTITION clause if present
7851        if !insert.partition.is_empty() {
7852            self.write_space();
7853            self.write_keyword("PARTITION");
7854            self.write("(");
7855            for (i, (col, val)) in insert.partition.iter().enumerate() {
7856                if i > 0 {
7857                    self.write(", ");
7858                }
7859                self.generate_identifier(col)?;
7860                if let Some(v) = val {
7861                    self.write(" = ");
7862                    self.generate_expression(v)?;
7863                }
7864            }
7865            self.write(")");
7866        }
7867
7868        // ClickHouse: PARTITION BY expr
7869        if let Some(ref partition_by) = insert.partition_by {
7870            self.write_space();
7871            self.write_keyword("PARTITION BY");
7872            self.write_space();
7873            self.generate_expression(partition_by)?;
7874        }
7875
7876        // ClickHouse: SETTINGS key = val, ...
7877        if !insert.settings.is_empty() {
7878            self.write_space();
7879            self.write_keyword("SETTINGS");
7880            self.write_space();
7881            for (i, setting) in insert.settings.iter().enumerate() {
7882                if i > 0 {
7883                    self.write(", ");
7884                }
7885                self.generate_expression(setting)?;
7886            }
7887        }
7888
7889        if !insert.columns.is_empty() {
7890            if insert.alias.is_some() && insert.alias_explicit_as {
7891                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7892                self.write("(");
7893            } else {
7894                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7895                self.write(" (");
7896            }
7897            for (i, col) in insert.columns.iter().enumerate() {
7898                if i > 0 {
7899                    self.write(", ");
7900                }
7901                self.generate_identifier(col)?;
7902            }
7903            self.write(")");
7904        }
7905
7906        // OUTPUT clause (TSQL)
7907        if let Some(ref output) = insert.output {
7908            self.generate_output_clause(output)?;
7909        }
7910
7911        // BY NAME modifier (DuckDB)
7912        if insert.by_name {
7913            self.write_space();
7914            self.write_keyword("BY NAME");
7915        }
7916
7917        if insert.default_values {
7918            self.write_space();
7919            self.write_keyword("DEFAULT VALUES");
7920        } else if let Some(query) = &insert.query {
7921            if self.config.pretty {
7922                self.write_newline();
7923            } else {
7924                self.write_space();
7925            }
7926            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7927            if prepend_query_cte.is_some() {
7928                if let Expression::Select(select) = query {
7929                    let mut select_no_with = select.clone();
7930                    select_no_with.with = None;
7931                    self.generate_select(&select_no_with)?;
7932                } else {
7933                    self.generate_expression(query)?;
7934                }
7935            } else {
7936                self.generate_expression(query)?;
7937            }
7938        } else if !insert.values.is_empty() {
7939            if self.config.pretty {
7940                // Pretty printing: VALUES on new line, each tuple indented
7941                self.write_newline();
7942                self.write_keyword("VALUES");
7943                self.write_newline();
7944                self.indent_level += 1;
7945                for (i, row) in insert.values.iter().enumerate() {
7946                    if i > 0 {
7947                        self.write(",");
7948                        self.write_newline();
7949                    }
7950                    self.write_indent();
7951                    self.write("(");
7952                    for (j, val) in row.iter().enumerate() {
7953                        if j > 0 {
7954                            self.write(", ");
7955                        }
7956                        self.generate_expression(val)?;
7957                    }
7958                    self.write(")");
7959                }
7960                self.indent_level -= 1;
7961            } else {
7962                // Non-pretty: single line
7963                self.write_space();
7964                self.write_keyword("VALUES");
7965                for (i, row) in insert.values.iter().enumerate() {
7966                    if i > 0 {
7967                        self.write(",");
7968                    }
7969                    self.write(" (");
7970                    for (j, val) in row.iter().enumerate() {
7971                        if j > 0 {
7972                            self.write(", ");
7973                        }
7974                        self.generate_expression(val)?;
7975                    }
7976                    self.write(")");
7977                }
7978            }
7979        }
7980
7981        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7982        if let Some(ref source) = insert.source {
7983            self.write_space();
7984            self.write_keyword("TABLE");
7985            self.write_space();
7986            self.generate_expression(source)?;
7987        }
7988
7989        // Source alias (MySQL: VALUES (...) AS new_data)
7990        if let Some(alias) = &insert.source_alias {
7991            self.write_space();
7992            self.write_keyword("AS");
7993            self.write_space();
7994            self.generate_identifier(alias)?;
7995        }
7996
7997        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7998        if let Some(on_conflict) = &insert.on_conflict {
7999            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
8000                self.write_space();
8001                self.generate_expression(on_conflict)?;
8002            }
8003        }
8004
8005        // RETURNING clause
8006        if !insert.returning.is_empty() {
8007            self.write_space();
8008            self.write_keyword("RETURNING");
8009            self.write_space();
8010            for (i, expr) in insert.returning.iter().enumerate() {
8011                if i > 0 {
8012                    self.write(", ");
8013                }
8014                self.generate_expression(expr)?;
8015            }
8016        }
8017
8018        Ok(())
8019    }
8020
8021    fn generate_update(&mut self, update: &Update) -> Result<()> {
8022        // Output leading comments before UPDATE
8023        for comment in &update.leading_comments {
8024            self.write_formatted_comment(comment);
8025            self.write(" ");
8026        }
8027
8028        // WITH clause (CTEs)
8029        if let Some(ref with) = update.with {
8030            self.generate_with(with)?;
8031            self.write_space();
8032        }
8033
8034        self.write_keyword("UPDATE");
8035        if let Some(hint) = &update.hint {
8036            self.generate_hint(hint)?;
8037        }
8038        self.write_space();
8039        self.generate_table(&update.table)?;
8040
8041        let mysql_like_update_from = matches!(
8042            self.config.dialect,
8043            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
8044        ) && update.from_clause.is_some();
8045
8046        let mut set_pairs = update.set.clone();
8047
8048        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
8049        let mut pre_set_joins = update.table_joins.clone();
8050        if mysql_like_update_from {
8051            let target_name = update
8052                .table
8053                .alias
8054                .as_ref()
8055                .map(|a| a.name.clone())
8056                .unwrap_or_else(|| update.table.name.name.clone());
8057
8058            for (col, _) in &mut set_pairs {
8059                if !col.name.contains('.') {
8060                    col.name = format!("{}.{}", target_name, col.name);
8061                }
8062            }
8063
8064            if let Some(from_clause) = &update.from_clause {
8065                for table_expr in &from_clause.expressions {
8066                    pre_set_joins.push(crate::expressions::Join {
8067                        this: table_expr.clone(),
8068                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8069                            value: true,
8070                        })),
8071                        using: Vec::new(),
8072                        kind: crate::expressions::JoinKind::Inner,
8073                        use_inner_keyword: false,
8074                        use_outer_keyword: false,
8075                        deferred_condition: false,
8076                        join_hint: None,
8077                        match_condition: None,
8078                        pivots: Vec::new(),
8079                        comments: Vec::new(),
8080                        nesting_group: 0,
8081                        directed: false,
8082                    });
8083                }
8084            }
8085            for join in &update.from_joins {
8086                let mut join = join.clone();
8087                if join.on.is_none() && join.using.is_empty() {
8088                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8089                        value: true,
8090                    }));
8091                }
8092                pre_set_joins.push(join);
8093            }
8094        }
8095
8096        // Extra tables for multi-table UPDATE (MySQL syntax)
8097        for extra_table in &update.extra_tables {
8098            self.write(", ");
8099            self.generate_table(extra_table)?;
8100        }
8101
8102        // JOINs attached to the table list (MySQL multi-table syntax)
8103        for join in &pre_set_joins {
8104            // generate_join already adds a leading space
8105            self.generate_join(join)?;
8106        }
8107
8108        // Teradata: FROM clause comes before SET
8109        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
8110        if teradata_from_before_set && !mysql_like_update_from {
8111            if let Some(ref from_clause) = update.from_clause {
8112                self.write_space();
8113                self.write_keyword("FROM");
8114                self.write_space();
8115                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8116                    if i > 0 {
8117                        self.write(", ");
8118                    }
8119                    self.generate_expression(table_expr)?;
8120                }
8121            }
8122            for join in &update.from_joins {
8123                self.generate_join(join)?;
8124            }
8125        }
8126
8127        self.write_space();
8128        self.write_keyword("SET");
8129        self.write_space();
8130
8131        for (i, (col, val)) in set_pairs.iter().enumerate() {
8132            if i > 0 {
8133                self.write(", ");
8134            }
8135            self.generate_identifier(col)?;
8136            self.write(" = ");
8137            self.generate_expression(val)?;
8138        }
8139
8140        // OUTPUT clause (TSQL)
8141        if let Some(ref output) = update.output {
8142            self.generate_output_clause(output)?;
8143        }
8144
8145        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
8146        if !mysql_like_update_from && !teradata_from_before_set {
8147            if let Some(ref from_clause) = update.from_clause {
8148                self.write_space();
8149                self.write_keyword("FROM");
8150                self.write_space();
8151                // Generate each table in the FROM clause
8152                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8153                    if i > 0 {
8154                        self.write(", ");
8155                    }
8156                    self.generate_expression(table_expr)?;
8157                }
8158            }
8159        }
8160
8161        if !mysql_like_update_from && !teradata_from_before_set {
8162            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
8163            for join in &update.from_joins {
8164                self.generate_join(join)?;
8165            }
8166        }
8167
8168        if let Some(where_clause) = &update.where_clause {
8169            self.write_space();
8170            self.write_keyword("WHERE");
8171            self.write_space();
8172            self.generate_expression(&where_clause.this)?;
8173        }
8174
8175        // RETURNING clause
8176        if !update.returning.is_empty() {
8177            self.write_space();
8178            self.write_keyword("RETURNING");
8179            self.write_space();
8180            for (i, expr) in update.returning.iter().enumerate() {
8181                if i > 0 {
8182                    self.write(", ");
8183                }
8184                self.generate_expression(expr)?;
8185            }
8186        }
8187
8188        // ORDER BY clause (MySQL)
8189        if let Some(ref order_by) = update.order_by {
8190            self.write_space();
8191            self.generate_order_by(order_by)?;
8192        }
8193
8194        // LIMIT clause (MySQL)
8195        if let Some(ref limit) = update.limit {
8196            self.write_space();
8197            self.write_keyword("LIMIT");
8198            self.write_space();
8199            self.generate_expression(limit)?;
8200        }
8201
8202        Ok(())
8203    }
8204
8205    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
8206        // Output WITH clause if present
8207        if let Some(with) = &delete.with {
8208            self.generate_with(with)?;
8209            self.write_space();
8210        }
8211
8212        // Output leading comments before DELETE
8213        for comment in &delete.leading_comments {
8214            self.write_formatted_comment(comment);
8215            self.write(" ");
8216        }
8217
8218        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
8219        if !delete.tables.is_empty() && !delete.tables_from_using {
8220            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
8221            self.write_keyword("DELETE");
8222            if let Some(hint) = &delete.hint {
8223                self.generate_hint(hint)?;
8224            }
8225            self.write_space();
8226            for (i, tbl) in delete.tables.iter().enumerate() {
8227                if i > 0 {
8228                    self.write(", ");
8229                }
8230                self.generate_table(tbl)?;
8231            }
8232            // TSQL: OUTPUT clause between target table and FROM
8233            if let Some(ref output) = delete.output {
8234                self.generate_output_clause(output)?;
8235            }
8236            self.write_space();
8237            self.write_keyword("FROM");
8238            self.write_space();
8239            self.generate_table(&delete.table)?;
8240        } else if !delete.tables.is_empty() && delete.tables_from_using {
8241            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
8242            self.write_keyword("DELETE");
8243            if let Some(hint) = &delete.hint {
8244                self.generate_hint(hint)?;
8245            }
8246            self.write_space();
8247            self.write_keyword("FROM");
8248            self.write_space();
8249            for (i, tbl) in delete.tables.iter().enumerate() {
8250                if i > 0 {
8251                    self.write(", ");
8252                }
8253                self.generate_table(tbl)?;
8254            }
8255        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
8256            // BigQuery-style DELETE without FROM keyword
8257            self.write_keyword("DELETE");
8258            if let Some(hint) = &delete.hint {
8259                self.generate_hint(hint)?;
8260            }
8261            self.write_space();
8262            self.generate_table(&delete.table)?;
8263        } else {
8264            self.write_keyword("DELETE");
8265            if let Some(hint) = &delete.hint {
8266                self.generate_hint(hint)?;
8267            }
8268            self.write_space();
8269            self.write_keyword("FROM");
8270            self.write_space();
8271            self.generate_table(&delete.table)?;
8272        }
8273
8274        // ClickHouse: ON CLUSTER clause
8275        if let Some(ref on_cluster) = delete.on_cluster {
8276            self.write_space();
8277            self.generate_on_cluster(on_cluster)?;
8278        }
8279
8280        // FORCE INDEX hint (MySQL)
8281        if let Some(ref idx) = delete.force_index {
8282            self.write_space();
8283            self.write_keyword("FORCE INDEX");
8284            self.write(" (");
8285            self.write(idx);
8286            self.write(")");
8287        }
8288
8289        // Optional alias
8290        if let Some(ref alias) = delete.alias {
8291            self.write_space();
8292            if delete.alias_explicit_as
8293                || matches!(self.config.dialect, Some(DialectType::BigQuery))
8294            {
8295                self.write_keyword("AS");
8296                self.write_space();
8297            }
8298            self.generate_identifier(alias)?;
8299        }
8300
8301        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
8302        if !delete.tables_from_using {
8303            for join in &delete.joins {
8304                self.generate_join(join)?;
8305            }
8306        }
8307
8308        // USING clause (PostgreSQL/DuckDB/MySQL)
8309        if !delete.using.is_empty() {
8310            self.write_space();
8311            self.write_keyword("USING");
8312            for (i, table) in delete.using.iter().enumerate() {
8313                if i > 0 {
8314                    self.write(",");
8315                }
8316                self.write_space();
8317                // Check if the table has subquery hints (DuckDB USING with subquery)
8318                if !table.hints.is_empty() && table.name.is_empty() {
8319                    // Subquery in USING: (VALUES ...) AS alias(cols)
8320                    self.generate_expression(&table.hints[0])?;
8321                    if let Some(ref alias) = table.alias {
8322                        self.write_space();
8323                        if table.alias_explicit_as {
8324                            self.write_keyword("AS");
8325                            self.write_space();
8326                        }
8327                        self.generate_identifier(alias)?;
8328                        if !table.column_aliases.is_empty() {
8329                            self.write("(");
8330                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
8331                                if j > 0 {
8332                                    self.write(", ");
8333                                }
8334                                self.generate_identifier(col_alias)?;
8335                            }
8336                            self.write(")");
8337                        }
8338                    }
8339                } else {
8340                    self.generate_table(table)?;
8341                }
8342            }
8343        }
8344
8345        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
8346        if delete.tables_from_using {
8347            for join in &delete.joins {
8348                self.generate_join(join)?;
8349            }
8350        }
8351
8352        // OUTPUT clause (TSQL) - only if not already emitted in the early position
8353        let output_already_emitted =
8354            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
8355        if !output_already_emitted {
8356            if let Some(ref output) = delete.output {
8357                self.generate_output_clause(output)?;
8358            }
8359        }
8360
8361        if let Some(where_clause) = &delete.where_clause {
8362            self.write_space();
8363            self.write_keyword("WHERE");
8364            self.write_space();
8365            self.generate_expression(&where_clause.this)?;
8366        }
8367
8368        // ORDER BY clause (MySQL)
8369        if let Some(ref order_by) = delete.order_by {
8370            self.write_space();
8371            self.generate_order_by(order_by)?;
8372        }
8373
8374        // LIMIT clause (MySQL)
8375        if let Some(ref limit) = delete.limit {
8376            self.write_space();
8377            self.write_keyword("LIMIT");
8378            self.write_space();
8379            self.generate_expression(limit)?;
8380        }
8381
8382        // RETURNING clause (PostgreSQL)
8383        if !delete.returning.is_empty() {
8384            self.write_space();
8385            self.write_keyword("RETURNING");
8386            self.write_space();
8387            for (i, expr) in delete.returning.iter().enumerate() {
8388                if i > 0 {
8389                    self.write(", ");
8390                }
8391                self.generate_expression(expr)?;
8392            }
8393        }
8394
8395        Ok(())
8396    }
8397
8398    // ==================== DDL Generation ====================
8399
8400    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
8401        // Athena: Determine if this is Hive-style DDL or Trino-style DML
8402        // CREATE TABLE AS SELECT uses Trino (double quotes)
8403        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
8404        let saved_athena_hive_context = self.athena_hive_context;
8405        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
8406        if matches!(
8407            self.config.dialect,
8408            Some(crate::dialects::DialectType::Athena)
8409        ) {
8410            // Use Hive context if:
8411            // 1. It's an EXTERNAL table, OR
8412            // 2. There's no AS SELECT clause
8413            let is_external = ct
8414                .table_modifier
8415                .as_ref()
8416                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
8417                .unwrap_or(false);
8418            let has_as_select = ct.as_select.is_some();
8419            self.athena_hive_context = is_external || !has_as_select;
8420        }
8421
8422        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
8423        if matches!(
8424            self.config.dialect,
8425            Some(crate::dialects::DialectType::TSQL)
8426        ) {
8427            if let Some(ref query) = ct.as_select {
8428                // Output WITH CTE clause if present
8429                if let Some(with_cte) = &ct.with_cte {
8430                    self.generate_with(with_cte)?;
8431                    self.write_space();
8432                }
8433
8434                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
8435                self.write_keyword("SELECT");
8436                self.write(" * ");
8437                self.write_keyword("INTO");
8438                self.write_space();
8439
8440                // If temporary, prefix with # for TSQL temp table
8441                if ct.temporary {
8442                    self.write("#");
8443                }
8444                self.generate_table(&ct.name)?;
8445
8446                self.write_space();
8447                self.write_keyword("FROM");
8448                self.write(" (");
8449                // For TSQL, add aliases to select columns to preserve column names
8450                let aliased_query = Self::add_column_aliases_to_query(query.clone());
8451                self.generate_expression(&aliased_query)?;
8452                self.write(") ");
8453                self.write_keyword("AS");
8454                self.write(" temp");
8455                return Ok(());
8456            }
8457        }
8458
8459        // Output WITH CTE clause if present
8460        if let Some(with_cte) = &ct.with_cte {
8461            self.generate_with(with_cte)?;
8462            self.write_space();
8463        }
8464
8465        // Output leading comments before CREATE
8466        for comment in &ct.leading_comments {
8467            self.write_formatted_comment(comment);
8468            self.write(" ");
8469        }
8470        self.write_keyword("CREATE");
8471
8472        if ct.or_replace {
8473            self.write_space();
8474            self.write_keyword("OR REPLACE");
8475        }
8476
8477        if ct.temporary {
8478            self.write_space();
8479            // Oracle uses GLOBAL TEMPORARY TABLE syntax
8480            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
8481                self.write_keyword("GLOBAL TEMPORARY");
8482            } else {
8483                self.write_keyword("TEMPORARY");
8484            }
8485        }
8486
8487        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
8488        let is_dictionary = ct
8489            .table_modifier
8490            .as_ref()
8491            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
8492            .unwrap_or(false);
8493        if let Some(ref modifier) = ct.table_modifier {
8494            // TRANSIENT is Snowflake-specific - skip for other dialects
8495            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
8496                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
8497            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
8498            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
8499                || modifier.eq_ignore_ascii_case("SET")
8500                || modifier.eq_ignore_ascii_case("MULTISET")
8501                || modifier.to_ascii_uppercase().contains("VOLATILE")
8502                || modifier.to_ascii_uppercase().starts_with("SET ")
8503                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
8504            let skip_teradata =
8505                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
8506            if !skip_transient && !skip_teradata {
8507                self.write_space();
8508                self.write_keyword(modifier);
8509            }
8510        }
8511
8512        if !is_dictionary {
8513            self.write_space();
8514            self.write_keyword("TABLE");
8515        }
8516
8517        if ct.if_not_exists {
8518            self.write_space();
8519            self.write_keyword("IF NOT EXISTS");
8520        }
8521
8522        self.write_space();
8523        self.generate_table(&ct.name)?;
8524
8525        // ClickHouse: UUID 'xxx' clause after table name
8526        if let Some(ref uuid) = ct.uuid {
8527            self.write_space();
8528            self.write_keyword("UUID");
8529            self.write(" '");
8530            self.write(uuid);
8531            self.write("'");
8532        }
8533
8534        // ClickHouse: ON CLUSTER clause
8535        if let Some(ref on_cluster) = ct.on_cluster {
8536            self.write_space();
8537            self.generate_on_cluster(on_cluster)?;
8538        }
8539
8540        // Teradata: options after table name before column list (comma-separated)
8541        if matches!(
8542            self.config.dialect,
8543            Some(crate::dialects::DialectType::Teradata)
8544        ) && !ct.teradata_post_name_options.is_empty()
8545        {
8546            for opt in &ct.teradata_post_name_options {
8547                self.write(", ");
8548                self.write(opt);
8549            }
8550        }
8551
8552        // Snowflake: COPY GRANTS clause
8553        if ct.copy_grants {
8554            self.write_space();
8555            self.write_keyword("COPY GRANTS");
8556        }
8557
8558        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
8559        if let Some(ref using_template) = ct.using_template {
8560            self.write_space();
8561            self.write_keyword("USING TEMPLATE");
8562            self.write_space();
8563            self.generate_expression(using_template)?;
8564            return Ok(());
8565        }
8566
8567        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
8568        // When explicit columns or constraints are present, the source must be emitted
8569        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
8570        if is_clickhouse {
8571            if let Some(ref clone_source) = ct.clone_source {
8572                if ct.columns.is_empty() && ct.constraints.is_empty() {
8573                    self.write_space();
8574                    self.write_keyword("AS");
8575                    self.write_space();
8576                    self.generate_table(clone_source)?;
8577                }
8578            }
8579        }
8580
8581        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8582        if !is_clickhouse {
8583            if let Some(ref clone_source) = ct.clone_source {
8584                self.write_space();
8585                if ct.is_copy && self.config.supports_table_copy {
8586                    // BigQuery uses COPY
8587                    self.write_keyword("COPY");
8588                } else if ct.shallow_clone {
8589                    self.write_keyword("SHALLOW CLONE");
8590                } else if ct.deep_clone {
8591                    self.write_keyword("DEEP CLONE");
8592                } else {
8593                    self.write_keyword("CLONE");
8594                }
8595                self.write_space();
8596                self.generate_table(clone_source)?;
8597                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8598                if let Some(ref at_clause) = ct.clone_at_clause {
8599                    self.write_space();
8600                    self.generate_expression(at_clause)?;
8601                }
8602                return Ok(());
8603            }
8604        }
8605
8606        // Handle PARTITION OF property
8607        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8608        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8609        if let Some(ref partition_of) = ct.partition_of {
8610            self.write_space();
8611
8612            // Extract the PartitionedOfProperty parts to generate them separately
8613            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8614                // Output: PARTITION OF <table>
8615                self.write_keyword("PARTITION OF");
8616                self.write_space();
8617                self.generate_expression(&pop.this)?;
8618
8619                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8620                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8621                    self.write(" (");
8622                    let mut first = true;
8623                    for col in &ct.columns {
8624                        if !first {
8625                            self.write(", ");
8626                        }
8627                        first = false;
8628                        self.generate_column_def(col)?;
8629                    }
8630                    for constraint in &ct.constraints {
8631                        if !first {
8632                            self.write(", ");
8633                        }
8634                        first = false;
8635                        self.generate_table_constraint(constraint)?;
8636                    }
8637                    self.write(")");
8638                }
8639
8640                // Output partition bound spec: FOR VALUES ... or DEFAULT
8641                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8642                    self.write_space();
8643                    self.write_keyword("FOR VALUES");
8644                    self.write_space();
8645                    self.generate_expression(&pop.expression)?;
8646                } else {
8647                    self.write_space();
8648                    self.write_keyword("DEFAULT");
8649                }
8650            } else {
8651                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8652                self.generate_expression(partition_of)?;
8653
8654                // Output columns/constraints if present
8655                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8656                    self.write(" (");
8657                    let mut first = true;
8658                    for col in &ct.columns {
8659                        if !first {
8660                            self.write(", ");
8661                        }
8662                        first = false;
8663                        self.generate_column_def(col)?;
8664                    }
8665                    for constraint in &ct.constraints {
8666                        if !first {
8667                            self.write(", ");
8668                        }
8669                        first = false;
8670                        self.generate_table_constraint(constraint)?;
8671                    }
8672                    self.write(")");
8673                }
8674            }
8675
8676            // Output table properties (e.g., PARTITION BY RANGE(population))
8677            for prop in &ct.properties {
8678                self.write_space();
8679                self.generate_expression(prop)?;
8680            }
8681
8682            return Ok(());
8683        }
8684
8685        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8686        // This matches Python sqlglot's behavior for SQLite dialect
8687        self.sqlite_inline_pk_columns.clear();
8688        if matches!(
8689            self.config.dialect,
8690            Some(crate::dialects::DialectType::SQLite)
8691        ) {
8692            for constraint in &ct.constraints {
8693                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8694                    // Only inline if: single column, no constraint name, and column exists in table
8695                    if columns.len() == 1 && name.is_none() {
8696                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8697                        // Check if this column exists in the table
8698                        if ct
8699                            .columns
8700                            .iter()
8701                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8702                        {
8703                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8704                        }
8705                    }
8706                }
8707            }
8708        }
8709
8710        // Output columns if present (even for CTAS with columns)
8711        if !ct.columns.is_empty() {
8712            if self.config.pretty {
8713                // Pretty print: each column on new line
8714                self.write(" (");
8715                self.write_newline();
8716                self.indent_level += 1;
8717                for (i, col) in ct.columns.iter().enumerate() {
8718                    if i > 0 {
8719                        self.write(",");
8720                        self.write_newline();
8721                    }
8722                    self.write_indent();
8723                    self.generate_column_def(col)?;
8724                }
8725                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8726                for constraint in &ct.constraints {
8727                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8728                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8729                        if columns.len() == 1
8730                            && name.is_none()
8731                            && self
8732                                .sqlite_inline_pk_columns
8733                                .contains(&columns[0].name.to_ascii_lowercase())
8734                        {
8735                            continue;
8736                        }
8737                    }
8738                    self.write(",");
8739                    self.write_newline();
8740                    self.write_indent();
8741                    self.generate_table_constraint(constraint)?;
8742                }
8743                self.indent_level -= 1;
8744                self.write_newline();
8745                self.write(")");
8746            } else {
8747                self.write(" (");
8748                for (i, col) in ct.columns.iter().enumerate() {
8749                    if i > 0 {
8750                        self.write(", ");
8751                    }
8752                    self.generate_column_def(col)?;
8753                }
8754                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8755                let mut first_constraint = true;
8756                for constraint in &ct.constraints {
8757                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8758                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8759                        if columns.len() == 1
8760                            && name.is_none()
8761                            && self
8762                                .sqlite_inline_pk_columns
8763                                .contains(&columns[0].name.to_ascii_lowercase())
8764                        {
8765                            continue;
8766                        }
8767                    }
8768                    if first_constraint {
8769                        self.write(", ");
8770                        first_constraint = false;
8771                    } else {
8772                        self.write(", ");
8773                    }
8774                    self.generate_table_constraint(constraint)?;
8775                }
8776                self.write(")");
8777            }
8778        } else if !ct.constraints.is_empty() {
8779            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8780            let has_like_only = ct
8781                .constraints
8782                .iter()
8783                .all(|c| matches!(c, TableConstraint::Like { .. }));
8784            let has_tags_only = ct
8785                .constraints
8786                .iter()
8787                .all(|c| matches!(c, TableConstraint::Tags(_)));
8788            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8789            // Most dialects: CREATE TABLE A LIKE B (no parens)
8790            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8791            let is_pg_like = matches!(
8792                self.config.dialect,
8793                Some(crate::dialects::DialectType::PostgreSQL)
8794                    | Some(crate::dialects::DialectType::CockroachDB)
8795                    | Some(crate::dialects::DialectType::Materialize)
8796                    | Some(crate::dialects::DialectType::RisingWave)
8797                    | Some(crate::dialects::DialectType::Redshift)
8798                    | Some(crate::dialects::DialectType::Presto)
8799                    | Some(crate::dialects::DialectType::Trino)
8800                    | Some(crate::dialects::DialectType::Athena)
8801            );
8802            let use_parens = if has_like_only {
8803                is_pg_like
8804            } else {
8805                !has_tags_only
8806            };
8807            if self.config.pretty && use_parens {
8808                self.write(" (");
8809                self.write_newline();
8810                self.indent_level += 1;
8811                for (i, constraint) in ct.constraints.iter().enumerate() {
8812                    if i > 0 {
8813                        self.write(",");
8814                        self.write_newline();
8815                    }
8816                    self.write_indent();
8817                    self.generate_table_constraint(constraint)?;
8818                }
8819                self.indent_level -= 1;
8820                self.write_newline();
8821                self.write(")");
8822            } else {
8823                if use_parens {
8824                    self.write(" (");
8825                } else {
8826                    self.write_space();
8827                }
8828                for (i, constraint) in ct.constraints.iter().enumerate() {
8829                    if i > 0 {
8830                        self.write(", ");
8831                    }
8832                    self.generate_table_constraint(constraint)?;
8833                }
8834                if use_parens {
8835                    self.write(")");
8836                }
8837            }
8838        }
8839
8840        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8841            if let Some(ref clone_source) = ct.clone_source {
8842                self.write_space();
8843                self.write_keyword("AS");
8844                self.write_space();
8845                self.generate_table(clone_source)?;
8846            }
8847        }
8848
8849        // TSQL ON filegroup or ON filegroup (partition_column) clause
8850        if let Some(ref on_prop) = ct.on_property {
8851            self.write(" ");
8852            self.write_keyword("ON");
8853            self.write(" ");
8854            self.generate_expression(&on_prop.this)?;
8855        }
8856
8857        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8858        if !ct.with_partition_columns.is_empty() {
8859            if self.config.pretty {
8860                self.write_newline();
8861            } else {
8862                self.write_space();
8863            }
8864            self.write_keyword("WITH PARTITION COLUMNS");
8865            self.write(" (");
8866            if self.config.pretty {
8867                self.write_newline();
8868                self.indent_level += 1;
8869                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8870                    if i > 0 {
8871                        self.write(",");
8872                        self.write_newline();
8873                    }
8874                    self.write_indent();
8875                    self.generate_column_def(col)?;
8876                }
8877                self.indent_level -= 1;
8878                self.write_newline();
8879            } else {
8880                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8881                    if i > 0 {
8882                        self.write(", ");
8883                    }
8884                    self.generate_column_def(col)?;
8885                }
8886            }
8887            self.write(")");
8888        }
8889
8890        // BigQuery: WITH CONNECTION `project.region.connection`
8891        if let Some(ref conn) = ct.with_connection {
8892            if self.config.pretty {
8893                self.write_newline();
8894            } else {
8895                self.write_space();
8896            }
8897            self.write_keyword("WITH CONNECTION");
8898            self.write_space();
8899            self.generate_table(conn)?;
8900        }
8901
8902        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8903        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8904        if !is_clickhouse {
8905            for prop in &ct.properties {
8906                if let Expression::SchemaCommentProperty(_) = prop {
8907                    if self.config.pretty {
8908                        self.write_newline();
8909                    } else {
8910                        self.write_space();
8911                    }
8912                    self.generate_expression(prop)?;
8913                }
8914            }
8915        }
8916
8917        // WITH properties (output after columns if columns exist, otherwise before AS)
8918        if !ct.with_properties.is_empty() {
8919            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8920            let is_snowflake_special_table = matches!(
8921                self.config.dialect,
8922                Some(crate::dialects::DialectType::Snowflake)
8923            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8924                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8925            if is_snowflake_special_table {
8926                for (key, value) in &ct.with_properties {
8927                    self.write_space();
8928                    self.write(key);
8929                    self.write("=");
8930                    self.write(value);
8931                }
8932            } else if self.config.pretty {
8933                self.write_newline();
8934                self.write_keyword("WITH");
8935                self.write(" (");
8936                self.write_newline();
8937                self.indent_level += 1;
8938                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8939                    if i > 0 {
8940                        self.write(",");
8941                        self.write_newline();
8942                    }
8943                    self.write_indent();
8944                    self.write(key);
8945                    self.write("=");
8946                    self.write(value);
8947                }
8948                self.indent_level -= 1;
8949                self.write_newline();
8950                self.write(")");
8951            } else {
8952                self.write_space();
8953                self.write_keyword("WITH");
8954                self.write(" (");
8955                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8956                    if i > 0 {
8957                        self.write(", ");
8958                    }
8959                    self.write(key);
8960                    self.write("=");
8961                    self.write(value);
8962                }
8963                self.write(")");
8964            }
8965        }
8966
8967        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8968            if is_clickhouse && ct.as_select.is_some() {
8969                let mut pre = Vec::new();
8970                let mut post = Vec::new();
8971                for prop in &ct.properties {
8972                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8973                        post.push(prop);
8974                    } else {
8975                        pre.push(prop);
8976                    }
8977                }
8978                (pre, post)
8979            } else {
8980                (ct.properties.iter().collect(), Vec::new())
8981            };
8982
8983        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8984        for prop in pre_as_properties {
8985            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8986            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8987                continue;
8988            }
8989            if self.config.pretty {
8990                self.write_newline();
8991            } else {
8992                self.write_space();
8993            }
8994            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8995            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8996            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8997            if let Expression::Properties(props) = prop {
8998                let is_hive_dialect = matches!(
8999                    self.config.dialect,
9000                    Some(crate::dialects::DialectType::Hive)
9001                        | Some(crate::dialects::DialectType::Spark)
9002                        | Some(crate::dialects::DialectType::Databricks)
9003                        | Some(crate::dialects::DialectType::Athena)
9004                );
9005                let is_doris_starrocks = matches!(
9006                    self.config.dialect,
9007                    Some(crate::dialects::DialectType::Doris)
9008                        | Some(crate::dialects::DialectType::StarRocks)
9009                );
9010                if is_hive_dialect {
9011                    self.generate_tblproperties_clause(&props.expressions)?;
9012                } else if is_doris_starrocks {
9013                    self.generate_properties_clause(&props.expressions)?;
9014                } else {
9015                    self.generate_options_clause(&props.expressions)?;
9016                }
9017            } else {
9018                self.generate_expression(prop)?;
9019            }
9020        }
9021
9022        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
9023        for prop in &ct.post_table_properties {
9024            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
9025                self.write(" WITH(");
9026                self.generate_system_versioning_content(svp)?;
9027                self.write(")");
9028            } else if let Expression::Properties(props) = prop {
9029                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
9030                let is_doris_starrocks = matches!(
9031                    self.config.dialect,
9032                    Some(crate::dialects::DialectType::Doris)
9033                        | Some(crate::dialects::DialectType::StarRocks)
9034                );
9035                self.write_space();
9036                if is_doris_starrocks {
9037                    self.generate_properties_clause(&props.expressions)?;
9038                } else {
9039                    self.generate_options_clause(&props.expressions)?;
9040                }
9041            } else {
9042                self.write_space();
9043                self.generate_expression(prop)?;
9044            }
9045        }
9046
9047        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
9048        // Only output for StarRocks target
9049        if let Some(ref rollup) = ct.rollup {
9050            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
9051                self.write_space();
9052                self.generate_rollup_property(rollup)?;
9053            }
9054        }
9055
9056        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
9057        // Only output for MySQL-compatible dialects; strip for others during transpilation
9058        // COMMENT is also used by Hive/Spark so we selectively preserve it
9059        let is_mysql_compatible = matches!(
9060            self.config.dialect,
9061            Some(DialectType::MySQL)
9062                | Some(DialectType::TiDB)
9063                | Some(DialectType::SingleStore)
9064                | Some(DialectType::Doris)
9065                | Some(DialectType::StarRocks)
9066                | None
9067        );
9068        let is_hive_compatible = matches!(
9069            self.config.dialect,
9070            Some(DialectType::Hive)
9071                | Some(DialectType::Spark)
9072                | Some(DialectType::Databricks)
9073                | Some(DialectType::Athena)
9074        );
9075        let mysql_pretty_options =
9076            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
9077        for (key, value) in &ct.mysql_table_options {
9078            // Skip non-MySQL-specific options for non-MySQL targets
9079            let should_output = if is_mysql_compatible {
9080                true
9081            } else if is_hive_compatible && key == "COMMENT" {
9082                true // COMMENT is valid in Hive/Spark table definitions
9083            } else {
9084                false
9085            };
9086            if should_output {
9087                if mysql_pretty_options {
9088                    self.write_newline();
9089                    self.write_indent();
9090                } else {
9091                    self.write_space();
9092                }
9093                self.write_keyword(key);
9094                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
9095                if key == "COMMENT" && !self.config.schema_comment_with_eq {
9096                    self.write_space();
9097                } else {
9098                    self.write("=");
9099                }
9100                self.write(value);
9101            }
9102        }
9103
9104        for option in &ct.tidb_table_options {
9105            if self.config.pretty {
9106                self.write_newline();
9107                self.write_indent();
9108            } else {
9109                self.write_space();
9110            }
9111            self.generate_tidb_table_option(option, false)?;
9112        }
9113
9114        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
9115        if ct.temporary
9116            && matches!(
9117                self.config.dialect,
9118                Some(DialectType::Spark) | Some(DialectType::Databricks)
9119            )
9120            && ct.as_select.is_none()
9121        {
9122            self.write_space();
9123            self.write_keyword("USING PARQUET");
9124        }
9125
9126        // PostgreSQL INHERITS clause
9127        if !ct.inherits.is_empty() {
9128            self.write_space();
9129            self.write_keyword("INHERITS");
9130            self.write(" (");
9131            for (i, parent) in ct.inherits.iter().enumerate() {
9132                if i > 0 {
9133                    self.write(", ");
9134                }
9135                self.generate_table(parent)?;
9136            }
9137            self.write(")");
9138        }
9139
9140        // CREATE TABLE AS SELECT
9141        if let Some(ref query) = ct.as_select {
9142            self.write_space();
9143            self.write_keyword("AS");
9144            self.write_space();
9145            let source_is_clickhouse =
9146                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
9147            let wrap_as_select =
9148                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
9149            if wrap_as_select {
9150                self.write("(");
9151            }
9152            self.generate_expression(query)?;
9153            if wrap_as_select {
9154                self.write(")");
9155            }
9156
9157            // Teradata: WITH DATA / WITH NO DATA
9158            if let Some(with_data) = ct.with_data {
9159                self.write_space();
9160                self.write_keyword("WITH");
9161                if !with_data {
9162                    self.write_space();
9163                    self.write_keyword("NO");
9164                }
9165                self.write_space();
9166                self.write_keyword("DATA");
9167            }
9168
9169            // Teradata: AND STATISTICS / AND NO STATISTICS
9170            if let Some(with_statistics) = ct.with_statistics {
9171                self.write_space();
9172                self.write_keyword("AND");
9173                if !with_statistics {
9174                    self.write_space();
9175                    self.write_keyword("NO");
9176                }
9177                self.write_space();
9178                self.write_keyword("STATISTICS");
9179            }
9180
9181            // Teradata: Index specifications
9182            for index in &ct.teradata_indexes {
9183                self.write_space();
9184                match index.kind {
9185                    TeradataIndexKind::NoPrimary => {
9186                        self.write_keyword("NO PRIMARY INDEX");
9187                    }
9188                    TeradataIndexKind::Primary => {
9189                        self.write_keyword("PRIMARY INDEX");
9190                    }
9191                    TeradataIndexKind::PrimaryAmp => {
9192                        self.write_keyword("PRIMARY AMP INDEX");
9193                    }
9194                    TeradataIndexKind::Unique => {
9195                        self.write_keyword("UNIQUE INDEX");
9196                    }
9197                    TeradataIndexKind::UniquePrimary => {
9198                        self.write_keyword("UNIQUE PRIMARY INDEX");
9199                    }
9200                    TeradataIndexKind::Secondary => {
9201                        self.write_keyword("INDEX");
9202                    }
9203                }
9204                // Output index name if present
9205                if let Some(ref name) = index.name {
9206                    self.write_space();
9207                    self.write(name);
9208                }
9209                // Output columns if present
9210                if !index.columns.is_empty() {
9211                    self.write(" (");
9212                    for (i, col) in index.columns.iter().enumerate() {
9213                        if i > 0 {
9214                            self.write(", ");
9215                        }
9216                        self.write(col);
9217                    }
9218                    self.write(")");
9219                }
9220            }
9221
9222            // Teradata: ON COMMIT behavior for volatile tables
9223            if let Some(ref on_commit) = ct.on_commit {
9224                self.write_space();
9225                self.write_keyword("ON COMMIT");
9226                self.write_space();
9227                match on_commit {
9228                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9229                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9230                }
9231            }
9232
9233            if !post_as_properties.is_empty() {
9234                for prop in post_as_properties {
9235                    self.write_space();
9236                    self.generate_expression(prop)?;
9237                }
9238            }
9239
9240            // Restore Athena Hive context before early return
9241            self.athena_hive_context = saved_athena_hive_context;
9242            return Ok(());
9243        }
9244
9245        // ON COMMIT behavior (for non-CTAS tables)
9246        if let Some(ref on_commit) = ct.on_commit {
9247            self.write_space();
9248            self.write_keyword("ON COMMIT");
9249            self.write_space();
9250            match on_commit {
9251                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9252                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9253            }
9254        }
9255
9256        // Restore Athena Hive context
9257        self.athena_hive_context = saved_athena_hive_context;
9258
9259        Ok(())
9260    }
9261
9262    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
9263    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
9264    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
9265        // Output column name
9266        self.generate_identifier(&col.name)?;
9267        // Output data type if known
9268        if !matches!(col.data_type, DataType::Unknown) {
9269            self.write_space();
9270            self.generate_data_type(&col.data_type)?;
9271        }
9272        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
9273        for constraint in &col.constraints {
9274            if let ColumnConstraint::Path(path_expr) = constraint {
9275                self.write_space();
9276                self.write_keyword("PATH");
9277                self.write_space();
9278                self.generate_expression(path_expr)?;
9279            }
9280        }
9281        Ok(())
9282    }
9283
9284    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
9285        // Check if this is a TSQL computed column (no data type)
9286        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9287            && col
9288                .constraints
9289                .iter()
9290                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9291        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
9292        let omit_computed_type = !self.config.computed_column_with_type
9293            && col
9294                .constraints
9295                .iter()
9296                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9297
9298        // Check if this is a partition column spec (no data type, type is Unknown)
9299        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
9300        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
9301
9302        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
9303        // Also check the no_type flag for SQLite columns without types
9304        let has_no_type = col.no_type
9305            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9306                && col.constraints.is_empty());
9307
9308        self.generate_identifier(&col.name)?;
9309
9310        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
9311        let serial_expansion = if matches!(
9312            self.config.dialect,
9313            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
9314        ) {
9315            if let DataType::Custom { ref name } = col.data_type {
9316                if name.eq_ignore_ascii_case("SERIAL") {
9317                    Some("INT")
9318                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
9319                    Some("BIGINT")
9320                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
9321                    Some("SMALLINT")
9322                } else {
9323                    None
9324                }
9325            } else {
9326                None
9327            }
9328        } else {
9329            None
9330        };
9331
9332        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
9333        {
9334            self.write_space();
9335            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
9336            // since ClickHouse uses explicit Nullable() in its type system.
9337            let saved_nullable_depth = self.clickhouse_nullable_depth;
9338            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9339                self.clickhouse_nullable_depth = -1;
9340            }
9341            if let Some(int_type) = serial_expansion {
9342                // SERIAL -> INT (+ constraints added below)
9343                self.write_keyword(int_type);
9344            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9345                // For DuckDB: convert unsigned integer types to their unsigned equivalents
9346                let unsigned_type = match &col.data_type {
9347                    DataType::Int { .. } => Some("UINTEGER"),
9348                    DataType::BigInt { .. } => Some("UBIGINT"),
9349                    DataType::SmallInt { .. } => Some("USMALLINT"),
9350                    DataType::TinyInt { .. } => Some("UTINYINT"),
9351                    _ => None,
9352                };
9353                if let Some(utype) = unsigned_type {
9354                    self.write_keyword(utype);
9355                } else {
9356                    self.generate_data_type(&col.data_type)?;
9357                }
9358            } else {
9359                self.generate_data_type(&col.data_type)?;
9360            }
9361            self.clickhouse_nullable_depth = saved_nullable_depth;
9362        }
9363
9364        // MySQL type modifiers (must come right after data type)
9365        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
9366        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9367            self.write_space();
9368            self.write_keyword("UNSIGNED");
9369        }
9370        if col.zerofill {
9371            self.write_space();
9372            self.write_keyword("ZEROFILL");
9373        }
9374
9375        // Teradata column attributes (must come right after data type, in specific order)
9376        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
9377
9378        if let Some(ref charset) = col.character_set {
9379            self.write_space();
9380            self.write_keyword("CHARACTER SET");
9381            self.write_space();
9382            self.write(charset);
9383        }
9384
9385        if col.uppercase {
9386            self.write_space();
9387            self.write_keyword("UPPERCASE");
9388        }
9389
9390        if let Some(casespecific) = col.casespecific {
9391            self.write_space();
9392            if casespecific {
9393                self.write_keyword("CASESPECIFIC");
9394            } else {
9395                self.write_keyword("NOT CASESPECIFIC");
9396            }
9397        }
9398
9399        if let Some(ref format) = col.format {
9400            self.write_space();
9401            self.write_keyword("FORMAT");
9402            self.write(" '");
9403            self.write(format);
9404            self.write("'");
9405        }
9406
9407        if let Some(ref title) = col.title {
9408            self.write_space();
9409            self.write_keyword("TITLE");
9410            self.write(" '");
9411            self.write(title);
9412            self.write("'");
9413        }
9414
9415        if let Some(length) = col.inline_length {
9416            self.write_space();
9417            self.write_keyword("INLINE LENGTH");
9418            self.write(" ");
9419            self.write(&length.to_string());
9420        }
9421
9422        if let Some(ref compress) = col.compress {
9423            self.write_space();
9424            self.write_keyword("COMPRESS");
9425            if !compress.is_empty() {
9426                // Single string literal: output without parentheses (Teradata syntax)
9427                if compress.len() == 1 {
9428                    if let Expression::Literal(lit) = &compress[0] {
9429                        if let Literal::String(_) = lit.as_ref() {
9430                            self.write_space();
9431                            self.generate_expression(&compress[0])?;
9432                        }
9433                    } else {
9434                        self.write(" (");
9435                        self.generate_expression(&compress[0])?;
9436                        self.write(")");
9437                    }
9438                } else {
9439                    self.write(" (");
9440                    for (i, val) in compress.iter().enumerate() {
9441                        if i > 0 {
9442                            self.write(", ");
9443                        }
9444                        self.generate_expression(val)?;
9445                    }
9446                    self.write(")");
9447                }
9448            }
9449        }
9450
9451        // Column constraints - output in original order if constraint_order is populated
9452        // Otherwise fall back to legacy fixed order for backward compatibility
9453        if !col.constraint_order.is_empty() {
9454            // Use constraint_order for original ordering
9455            // Track indices for constraints stored in the constraints Vec
9456            let mut references_idx = 0;
9457            let mut check_idx = 0;
9458            let mut generated_idx = 0;
9459            let mut collate_idx = 0;
9460            let mut comment_idx = 0;
9461            // The preprocessing in dialects/mod.rs now handles the correct ordering of
9462            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
9463            let defer_not_null_after_identity = false;
9464            let mut pending_not_null_after_identity = false;
9465
9466            for constraint_type in &col.constraint_order {
9467                match constraint_type {
9468                    ConstraintType::PrimaryKey => {
9469                        // Materialize doesn't support PRIMARY KEY column constraints
9470                        if col.primary_key
9471                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
9472                        {
9473                            if let Some(ref cname) = col.primary_key_constraint_name {
9474                                self.write_space();
9475                                self.write_keyword("CONSTRAINT");
9476                                self.write_space();
9477                                self.write(cname);
9478                            }
9479                            self.write_space();
9480                            self.write_keyword("PRIMARY KEY");
9481                            if let Some(ref order) = col.primary_key_order {
9482                                self.write_space();
9483                                match order {
9484                                    SortOrder::Asc => self.write_keyword("ASC"),
9485                                    SortOrder::Desc => self.write_keyword("DESC"),
9486                                }
9487                            }
9488                        }
9489                    }
9490                    ConstraintType::Unique => {
9491                        if col.unique {
9492                            if let Some(ref cname) = col.unique_constraint_name {
9493                                self.write_space();
9494                                self.write_keyword("CONSTRAINT");
9495                                self.write_space();
9496                                self.write(cname);
9497                            }
9498                            self.write_space();
9499                            self.write_keyword("UNIQUE");
9500                            // PostgreSQL 15+: NULLS NOT DISTINCT
9501                            if col.unique_nulls_not_distinct {
9502                                self.write(" NULLS NOT DISTINCT");
9503                            }
9504                        }
9505                    }
9506                    ConstraintType::NotNull => {
9507                        if col.nullable == Some(false) {
9508                            if defer_not_null_after_identity {
9509                                pending_not_null_after_identity = true;
9510                                continue;
9511                            }
9512                            if let Some(ref cname) = col.not_null_constraint_name {
9513                                self.write_space();
9514                                self.write_keyword("CONSTRAINT");
9515                                self.write_space();
9516                                self.write(cname);
9517                            }
9518                            self.write_space();
9519                            self.write_keyword("NOT NULL");
9520                        }
9521                    }
9522                    ConstraintType::Null => {
9523                        if col.nullable == Some(true) {
9524                            self.write_space();
9525                            self.write_keyword("NULL");
9526                        }
9527                    }
9528                    ConstraintType::Default => {
9529                        if let Some(ref default) = col.default {
9530                            self.write_space();
9531                            self.write_keyword("DEFAULT");
9532                            self.write_space();
9533                            self.generate_expression(default)?;
9534                        }
9535                    }
9536                    ConstraintType::AutoIncrement => {
9537                        if col.auto_increment {
9538                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
9539                            if matches!(
9540                                self.config.dialect,
9541                                Some(crate::dialects::DialectType::DuckDB)
9542                            ) {
9543                                // Skip - DuckDB uses sequences or rowid instead
9544                            } else if matches!(
9545                                self.config.dialect,
9546                                Some(crate::dialects::DialectType::Materialize)
9547                            ) {
9548                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
9549                                if !matches!(col.nullable, Some(false)) {
9550                                    self.write_space();
9551                                    self.write_keyword("NOT NULL");
9552                                }
9553                            } else if matches!(
9554                                self.config.dialect,
9555                                Some(crate::dialects::DialectType::PostgreSQL)
9556                            ) {
9557                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
9558                                self.write_space();
9559                                self.generate_auto_increment_keyword(col)?;
9560                            } else if matches!(
9561                                self.config.dialect,
9562                                Some(crate::dialects::DialectType::SQLite)
9563                            ) && !col.primary_key
9564                                && self
9565                                    .sqlite_inline_pk_columns
9566                                    .contains(&col.name.name.to_ascii_lowercase())
9567                            {
9568                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
9569                                // The table-level primary key is emitted later inline.
9570                            } else {
9571                                self.write_space();
9572                                self.generate_auto_increment_keyword(col)?;
9573                                if pending_not_null_after_identity {
9574                                    self.write_space();
9575                                    self.write_keyword("NOT NULL");
9576                                    pending_not_null_after_identity = false;
9577                                }
9578                            }
9579                        } // close else for DuckDB skip
9580                    }
9581                    ConstraintType::AutoRandom => {
9582                        if let Some(ref auto_random) = col.auto_random {
9583                            self.write_space();
9584                            self.generate_tidb_auto_random(auto_random)?;
9585                        }
9586                    }
9587                    ConstraintType::References => {
9588                        // Find next References constraint
9589                        while references_idx < col.constraints.len() {
9590                            if let ColumnConstraint::References(fk_ref) =
9591                                &col.constraints[references_idx]
9592                            {
9593                                // CONSTRAINT name if present
9594                                if let Some(ref name) = fk_ref.constraint_name {
9595                                    self.write_space();
9596                                    self.write_keyword("CONSTRAINT");
9597                                    self.write_space();
9598                                    self.write(name);
9599                                }
9600                                self.write_space();
9601                                if fk_ref.has_foreign_key_keywords {
9602                                    self.write_keyword("FOREIGN KEY");
9603                                    self.write_space();
9604                                }
9605                                self.write_keyword("REFERENCES");
9606                                self.write_space();
9607                                self.generate_table(&fk_ref.table)?;
9608                                if !fk_ref.columns.is_empty() {
9609                                    self.write(" (");
9610                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9611                                        if i > 0 {
9612                                            self.write(", ");
9613                                        }
9614                                        self.generate_identifier(c)?;
9615                                    }
9616                                    self.write(")");
9617                                }
9618                                self.generate_referential_actions(fk_ref)?;
9619                                references_idx += 1;
9620                                break;
9621                            }
9622                            references_idx += 1;
9623                        }
9624                    }
9625                    ConstraintType::Check => {
9626                        // Find next Check constraint
9627                        while check_idx < col.constraints.len() {
9628                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9629                                // Output CONSTRAINT name if present (only for first CHECK)
9630                                if check_idx == 0 {
9631                                    if let Some(ref cname) = col.check_constraint_name {
9632                                        self.write_space();
9633                                        self.write_keyword("CONSTRAINT");
9634                                        self.write_space();
9635                                        self.write(cname);
9636                                    }
9637                                }
9638                                self.write_space();
9639                                self.write_keyword("CHECK");
9640                                self.write(" (");
9641                                self.generate_expression(expr)?;
9642                                self.write(")");
9643                                check_idx += 1;
9644                                break;
9645                            }
9646                            check_idx += 1;
9647                        }
9648                    }
9649                    ConstraintType::GeneratedAsIdentity => {
9650                        // Find next GeneratedAsIdentity constraint
9651                        while generated_idx < col.constraints.len() {
9652                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9653                                &col.constraints[generated_idx]
9654                            {
9655                                self.write_space();
9656                                // Redshift uses IDENTITY(start, increment) syntax
9657                                if matches!(
9658                                    self.config.dialect,
9659                                    Some(crate::dialects::DialectType::Redshift)
9660                                ) {
9661                                    self.write_keyword("IDENTITY");
9662                                    self.write("(");
9663                                    if let Some(ref start) = gen.start {
9664                                        self.generate_expression(start)?;
9665                                    } else {
9666                                        self.write("0");
9667                                    }
9668                                    self.write(", ");
9669                                    if let Some(ref incr) = gen.increment {
9670                                        self.generate_expression(incr)?;
9671                                    } else {
9672                                        self.write("1");
9673                                    }
9674                                    self.write(")");
9675                                } else {
9676                                    self.write_keyword("GENERATED");
9677                                    if gen.always {
9678                                        self.write_space();
9679                                        self.write_keyword("ALWAYS");
9680                                    } else {
9681                                        self.write_space();
9682                                        self.write_keyword("BY DEFAULT");
9683                                        if gen.on_null {
9684                                            self.write_space();
9685                                            self.write_keyword("ON NULL");
9686                                        }
9687                                    }
9688                                    self.write_space();
9689                                    self.write_keyword("AS IDENTITY");
9690
9691                                    let has_options = gen.start.is_some()
9692                                        || gen.increment.is_some()
9693                                        || gen.minvalue.is_some()
9694                                        || gen.maxvalue.is_some()
9695                                        || gen.cycle.is_some();
9696                                    if has_options {
9697                                        self.write(" (");
9698                                        let mut first = true;
9699                                        if let Some(ref start) = gen.start {
9700                                            if !first {
9701                                                self.write(" ");
9702                                            }
9703                                            first = false;
9704                                            self.write_keyword("START WITH");
9705                                            self.write_space();
9706                                            self.generate_expression(start)?;
9707                                        }
9708                                        if let Some(ref incr) = gen.increment {
9709                                            if !first {
9710                                                self.write(" ");
9711                                            }
9712                                            first = false;
9713                                            self.write_keyword("INCREMENT BY");
9714                                            self.write_space();
9715                                            self.generate_expression(incr)?;
9716                                        }
9717                                        if let Some(ref minv) = gen.minvalue {
9718                                            if !first {
9719                                                self.write(" ");
9720                                            }
9721                                            first = false;
9722                                            self.write_keyword("MINVALUE");
9723                                            self.write_space();
9724                                            self.generate_expression(minv)?;
9725                                        }
9726                                        if let Some(ref maxv) = gen.maxvalue {
9727                                            if !first {
9728                                                self.write(" ");
9729                                            }
9730                                            first = false;
9731                                            self.write_keyword("MAXVALUE");
9732                                            self.write_space();
9733                                            self.generate_expression(maxv)?;
9734                                        }
9735                                        if let Some(cycle) = gen.cycle {
9736                                            if !first {
9737                                                self.write(" ");
9738                                            }
9739                                            if cycle {
9740                                                self.write_keyword("CYCLE");
9741                                            } else {
9742                                                self.write_keyword("NO CYCLE");
9743                                            }
9744                                        }
9745                                        self.write(")");
9746                                    }
9747                                }
9748                                generated_idx += 1;
9749                                break;
9750                            }
9751                            generated_idx += 1;
9752                        }
9753                    }
9754                    ConstraintType::Collate => {
9755                        // Find next Collate constraint
9756                        while collate_idx < col.constraints.len() {
9757                            if let ColumnConstraint::Collate(collation) =
9758                                &col.constraints[collate_idx]
9759                            {
9760                                self.write_space();
9761                                self.write_keyword("COLLATE");
9762                                self.write_space();
9763                                self.generate_identifier(collation)?;
9764                                collate_idx += 1;
9765                                break;
9766                            }
9767                            collate_idx += 1;
9768                        }
9769                    }
9770                    ConstraintType::Comment => {
9771                        // Find next Comment constraint
9772                        while comment_idx < col.constraints.len() {
9773                            if let ColumnConstraint::Comment(comment) =
9774                                &col.constraints[comment_idx]
9775                            {
9776                                self.write_space();
9777                                self.write_keyword("COMMENT");
9778                                self.write_space();
9779                                self.generate_string_literal(comment)?;
9780                                comment_idx += 1;
9781                                break;
9782                            }
9783                            comment_idx += 1;
9784                        }
9785                    }
9786                    ConstraintType::Tags => {
9787                        // Find next Tags constraint (Snowflake)
9788                        for constraint in &col.constraints {
9789                            if let ColumnConstraint::Tags(tags) = constraint {
9790                                self.write_space();
9791                                self.write_keyword("TAG");
9792                                self.write(" (");
9793                                for (i, expr) in tags.expressions.iter().enumerate() {
9794                                    if i > 0 {
9795                                        self.write(", ");
9796                                    }
9797                                    self.generate_expression(expr)?;
9798                                }
9799                                self.write(")");
9800                                break;
9801                            }
9802                        }
9803                    }
9804                    ConstraintType::ComputedColumn => {
9805                        // Find next ComputedColumn constraint
9806                        for constraint in &col.constraints {
9807                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9808                                self.write_space();
9809                                self.generate_computed_column_inline(cc)?;
9810                                break;
9811                            }
9812                        }
9813                    }
9814                    ConstraintType::GeneratedAsRow => {
9815                        // Find next GeneratedAsRow constraint
9816                        for constraint in &col.constraints {
9817                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9818                                self.write_space();
9819                                self.generate_generated_as_row_inline(gar)?;
9820                                break;
9821                            }
9822                        }
9823                    }
9824                    ConstraintType::OnUpdate => {
9825                        if let Some(ref expr) = col.on_update {
9826                            self.write_space();
9827                            self.write_keyword("ON UPDATE");
9828                            self.write_space();
9829                            self.generate_expression(expr)?;
9830                        }
9831                    }
9832                    ConstraintType::Encode => {
9833                        if let Some(ref encoding) = col.encoding {
9834                            self.write_space();
9835                            self.write_keyword("ENCODE");
9836                            self.write_space();
9837                            self.write(encoding);
9838                        }
9839                    }
9840                    ConstraintType::Path => {
9841                        // Find next Path constraint
9842                        for constraint in &col.constraints {
9843                            if let ColumnConstraint::Path(path_expr) = constraint {
9844                                self.write_space();
9845                                self.write_keyword("PATH");
9846                                self.write_space();
9847                                self.generate_expression(path_expr)?;
9848                                break;
9849                            }
9850                        }
9851                    }
9852                }
9853            }
9854            if pending_not_null_after_identity {
9855                self.write_space();
9856                self.write_keyword("NOT NULL");
9857            }
9858        } else {
9859            // Legacy fixed order for backward compatibility
9860            if col.primary_key {
9861                self.write_space();
9862                self.write_keyword("PRIMARY KEY");
9863                if let Some(ref order) = col.primary_key_order {
9864                    self.write_space();
9865                    match order {
9866                        SortOrder::Asc => self.write_keyword("ASC"),
9867                        SortOrder::Desc => self.write_keyword("DESC"),
9868                    }
9869                }
9870            }
9871
9872            if col.unique {
9873                self.write_space();
9874                self.write_keyword("UNIQUE");
9875                // PostgreSQL 15+: NULLS NOT DISTINCT
9876                if col.unique_nulls_not_distinct {
9877                    self.write(" NULLS NOT DISTINCT");
9878                }
9879            }
9880
9881            match col.nullable {
9882                Some(false) => {
9883                    self.write_space();
9884                    self.write_keyword("NOT NULL");
9885                }
9886                Some(true) => {
9887                    self.write_space();
9888                    self.write_keyword("NULL");
9889                }
9890                None => {}
9891            }
9892
9893            if let Some(ref default) = col.default {
9894                self.write_space();
9895                self.write_keyword("DEFAULT");
9896                self.write_space();
9897                self.generate_expression(default)?;
9898            }
9899
9900            if col.auto_increment {
9901                self.write_space();
9902                self.generate_auto_increment_keyword(col)?;
9903            }
9904
9905            if let Some(ref auto_random) = col.auto_random {
9906                self.write_space();
9907                self.generate_tidb_auto_random(auto_random)?;
9908            }
9909
9910            // Column-level constraints from Vec
9911            for constraint in &col.constraints {
9912                match constraint {
9913                    ColumnConstraint::References(fk_ref) => {
9914                        self.write_space();
9915                        if fk_ref.has_foreign_key_keywords {
9916                            self.write_keyword("FOREIGN KEY");
9917                            self.write_space();
9918                        }
9919                        self.write_keyword("REFERENCES");
9920                        self.write_space();
9921                        self.generate_table(&fk_ref.table)?;
9922                        if !fk_ref.columns.is_empty() {
9923                            self.write(" (");
9924                            for (i, c) in fk_ref.columns.iter().enumerate() {
9925                                if i > 0 {
9926                                    self.write(", ");
9927                                }
9928                                self.generate_identifier(c)?;
9929                            }
9930                            self.write(")");
9931                        }
9932                        self.generate_referential_actions(fk_ref)?;
9933                    }
9934                    ColumnConstraint::Check(expr) => {
9935                        self.write_space();
9936                        self.write_keyword("CHECK");
9937                        self.write(" (");
9938                        self.generate_expression(expr)?;
9939                        self.write(")");
9940                    }
9941                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9942                        self.write_space();
9943                        // Redshift uses IDENTITY(start, increment) syntax
9944                        if matches!(
9945                            self.config.dialect,
9946                            Some(crate::dialects::DialectType::Redshift)
9947                        ) {
9948                            self.write_keyword("IDENTITY");
9949                            self.write("(");
9950                            if let Some(ref start) = gen.start {
9951                                self.generate_expression(start)?;
9952                            } else {
9953                                self.write("0");
9954                            }
9955                            self.write(", ");
9956                            if let Some(ref incr) = gen.increment {
9957                                self.generate_expression(incr)?;
9958                            } else {
9959                                self.write("1");
9960                            }
9961                            self.write(")");
9962                        } else {
9963                            self.write_keyword("GENERATED");
9964                            if gen.always {
9965                                self.write_space();
9966                                self.write_keyword("ALWAYS");
9967                            } else {
9968                                self.write_space();
9969                                self.write_keyword("BY DEFAULT");
9970                                if gen.on_null {
9971                                    self.write_space();
9972                                    self.write_keyword("ON NULL");
9973                                }
9974                            }
9975                            self.write_space();
9976                            self.write_keyword("AS IDENTITY");
9977
9978                            let has_options = gen.start.is_some()
9979                                || gen.increment.is_some()
9980                                || gen.minvalue.is_some()
9981                                || gen.maxvalue.is_some()
9982                                || gen.cycle.is_some();
9983                            if has_options {
9984                                self.write(" (");
9985                                let mut first = true;
9986                                if let Some(ref start) = gen.start {
9987                                    if !first {
9988                                        self.write(" ");
9989                                    }
9990                                    first = false;
9991                                    self.write_keyword("START WITH");
9992                                    self.write_space();
9993                                    self.generate_expression(start)?;
9994                                }
9995                                if let Some(ref incr) = gen.increment {
9996                                    if !first {
9997                                        self.write(" ");
9998                                    }
9999                                    first = false;
10000                                    self.write_keyword("INCREMENT BY");
10001                                    self.write_space();
10002                                    self.generate_expression(incr)?;
10003                                }
10004                                if let Some(ref minv) = gen.minvalue {
10005                                    if !first {
10006                                        self.write(" ");
10007                                    }
10008                                    first = false;
10009                                    self.write_keyword("MINVALUE");
10010                                    self.write_space();
10011                                    self.generate_expression(minv)?;
10012                                }
10013                                if let Some(ref maxv) = gen.maxvalue {
10014                                    if !first {
10015                                        self.write(" ");
10016                                    }
10017                                    first = false;
10018                                    self.write_keyword("MAXVALUE");
10019                                    self.write_space();
10020                                    self.generate_expression(maxv)?;
10021                                }
10022                                if let Some(cycle) = gen.cycle {
10023                                    if !first {
10024                                        self.write(" ");
10025                                    }
10026                                    if cycle {
10027                                        self.write_keyword("CYCLE");
10028                                    } else {
10029                                        self.write_keyword("NO CYCLE");
10030                                    }
10031                                }
10032                                self.write(")");
10033                            }
10034                        }
10035                    }
10036                    ColumnConstraint::Collate(collation) => {
10037                        self.write_space();
10038                        self.write_keyword("COLLATE");
10039                        self.write_space();
10040                        self.generate_identifier(collation)?;
10041                    }
10042                    ColumnConstraint::Comment(comment) => {
10043                        self.write_space();
10044                        self.write_keyword("COMMENT");
10045                        self.write_space();
10046                        self.generate_string_literal(comment)?;
10047                    }
10048                    ColumnConstraint::Path(path_expr) => {
10049                        self.write_space();
10050                        self.write_keyword("PATH");
10051                        self.write_space();
10052                        self.generate_expression(path_expr)?;
10053                    }
10054                    _ => {} // Other constraints handled above
10055                }
10056            }
10057
10058            // Redshift: ENCODE encoding_type (legacy path)
10059            if let Some(ref encoding) = col.encoding {
10060                self.write_space();
10061                self.write_keyword("ENCODE");
10062                self.write_space();
10063                self.write(encoding);
10064            }
10065        }
10066
10067        // ClickHouse: CODEC(...)
10068        if let Some(ref codec) = col.codec {
10069            self.write_space();
10070            self.write_keyword("CODEC");
10071            self.write("(");
10072            self.write(codec);
10073            self.write(")");
10074        }
10075
10076        if let Some(visible) = col.visible {
10077            self.write_space();
10078            if visible {
10079                self.write_keyword("VISIBLE");
10080            } else {
10081                self.write_keyword("INVISIBLE");
10082            }
10083        }
10084
10085        // ClickHouse: EPHEMERAL [expr]
10086        if let Some(ref ephemeral) = col.ephemeral {
10087            self.write_space();
10088            self.write_keyword("EPHEMERAL");
10089            if let Some(ref expr) = ephemeral {
10090                self.write_space();
10091                self.generate_expression(expr)?;
10092            }
10093        }
10094
10095        // ClickHouse: MATERIALIZED expr
10096        if let Some(ref mat_expr) = col.materialized_expr {
10097            self.write_space();
10098            self.write_keyword("MATERIALIZED");
10099            self.write_space();
10100            self.generate_expression(mat_expr)?;
10101        }
10102
10103        // ClickHouse: ALIAS expr
10104        if let Some(ref alias_expr) = col.alias_expr {
10105            self.write_space();
10106            self.write_keyword("ALIAS");
10107            self.write_space();
10108            self.generate_expression(alias_expr)?;
10109        }
10110
10111        // ClickHouse: TTL expr
10112        if let Some(ref ttl_expr) = col.ttl_expr {
10113            self.write_space();
10114            self.write_keyword("TTL");
10115            self.write_space();
10116            self.generate_expression(ttl_expr)?;
10117        }
10118
10119        // TSQL: NOT FOR REPLICATION
10120        if col.not_for_replication
10121            && matches!(
10122                self.config.dialect,
10123                Some(crate::dialects::DialectType::TSQL)
10124                    | Some(crate::dialects::DialectType::Fabric)
10125            )
10126        {
10127            self.write_space();
10128            self.write_keyword("NOT FOR REPLICATION");
10129        }
10130
10131        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
10132        if !col.options.is_empty() {
10133            self.write_space();
10134            self.generate_options_clause(&col.options)?;
10135        }
10136
10137        // SQLite: Inline PRIMARY KEY from table constraint
10138        // This comes at the end, after all existing column constraints
10139        if !col.primary_key
10140            && self
10141                .sqlite_inline_pk_columns
10142                .contains(&col.name.name.to_ascii_lowercase())
10143        {
10144            self.write_space();
10145            self.write_keyword("PRIMARY KEY");
10146            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
10147                self.write_space();
10148                self.generate_auto_increment_keyword(col)?;
10149            }
10150        }
10151
10152        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
10153        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
10154        if serial_expansion.is_some() {
10155            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
10156                self.write_space();
10157                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
10158            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
10159                self.write_space();
10160                self.write_keyword("NOT NULL");
10161            }
10162        }
10163
10164        Ok(())
10165    }
10166
10167    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
10168        match constraint {
10169            TableConstraint::PrimaryKey {
10170                name,
10171                columns,
10172                timeseries_columns,
10173                include_columns,
10174                modifiers,
10175                has_constraint_keyword,
10176            } => {
10177                if let Some(ref n) = name {
10178                    if *has_constraint_keyword {
10179                        self.write_keyword("CONSTRAINT");
10180                        self.write_space();
10181                        self.generate_identifier(n)?;
10182                        self.write_space();
10183                    }
10184                }
10185                self.write_keyword("PRIMARY KEY");
10186                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10187                if let Some(ref clustered) = modifiers.clustered {
10188                    self.write_space();
10189                    self.write_keyword(clustered);
10190                }
10191                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
10192                if let Some(ref n) = name {
10193                    if !*has_constraint_keyword {
10194                        self.write_space();
10195                        self.generate_identifier(n)?;
10196                    }
10197                }
10198                self.write(" (");
10199                for (i, col) in columns.iter().enumerate() {
10200                    if i > 0 {
10201                        self.write(", ");
10202                    }
10203                    self.generate_identifier(col)?;
10204                    if matches!(self.config.dialect, Some(DialectType::Databricks))
10205                        && timeseries_columns.iter().any(|candidate| candidate == col)
10206                    {
10207                        self.write_space();
10208                        self.write_keyword("TIMESERIES");
10209                    }
10210                }
10211                self.write(")");
10212                if !include_columns.is_empty() {
10213                    self.write_space();
10214                    self.write_keyword("INCLUDE");
10215                    self.write(" (");
10216                    for (i, col) in include_columns.iter().enumerate() {
10217                        if i > 0 {
10218                            self.write(", ");
10219                        }
10220                        self.generate_identifier(col)?;
10221                    }
10222                    self.write(")");
10223                }
10224                self.generate_constraint_modifiers(modifiers);
10225            }
10226            TableConstraint::Unique {
10227                name,
10228                columns,
10229                columns_parenthesized,
10230                modifiers,
10231                has_constraint_keyword,
10232                nulls_not_distinct,
10233            } => {
10234                if let Some(ref n) = name {
10235                    if *has_constraint_keyword {
10236                        self.write_keyword("CONSTRAINT");
10237                        self.write_space();
10238                        self.generate_identifier(n)?;
10239                        self.write_space();
10240                    }
10241                }
10242                self.write_keyword("UNIQUE");
10243                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10244                if let Some(ref clustered) = modifiers.clustered {
10245                    self.write_space();
10246                    self.write_keyword(clustered);
10247                }
10248                // PostgreSQL 15+: NULLS NOT DISTINCT
10249                if *nulls_not_distinct {
10250                    self.write(" NULLS NOT DISTINCT");
10251                }
10252                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
10253                if let Some(ref n) = name {
10254                    if !*has_constraint_keyword {
10255                        self.write_space();
10256                        self.generate_identifier(n)?;
10257                    }
10258                }
10259                if *columns_parenthesized {
10260                    self.write(" (");
10261                    for (i, col) in columns.iter().enumerate() {
10262                        if i > 0 {
10263                            self.write(", ");
10264                        }
10265                        self.generate_identifier(col)?;
10266                    }
10267                    self.write(")");
10268                } else {
10269                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
10270                    for col in columns.iter() {
10271                        self.write_space();
10272                        self.generate_identifier(col)?;
10273                    }
10274                }
10275                self.generate_constraint_modifiers(modifiers);
10276            }
10277            TableConstraint::ForeignKey {
10278                name,
10279                columns,
10280                references,
10281                on_delete,
10282                on_update,
10283                modifiers,
10284            } => {
10285                if let Some(ref n) = name {
10286                    self.write_keyword("CONSTRAINT");
10287                    self.write_space();
10288                    self.generate_identifier(n)?;
10289                    self.write_space();
10290                }
10291                self.write_keyword("FOREIGN KEY");
10292                self.write(" (");
10293                for (i, col) in columns.iter().enumerate() {
10294                    if i > 0 {
10295                        self.write(", ");
10296                    }
10297                    self.generate_identifier(col)?;
10298                }
10299                self.write(")");
10300                if let Some(ref refs) = references {
10301                    self.write(" ");
10302                    self.write_keyword("REFERENCES");
10303                    self.write_space();
10304                    self.generate_table(&refs.table)?;
10305                    if !refs.columns.is_empty() {
10306                        if self.config.pretty {
10307                            self.write(" (");
10308                            self.write_newline();
10309                            self.indent_level += 1;
10310                            for (i, col) in refs.columns.iter().enumerate() {
10311                                if i > 0 {
10312                                    self.write(",");
10313                                    self.write_newline();
10314                                }
10315                                self.write_indent();
10316                                self.generate_identifier(col)?;
10317                            }
10318                            self.indent_level -= 1;
10319                            self.write_newline();
10320                            self.write_indent();
10321                            self.write(")");
10322                        } else {
10323                            self.write(" (");
10324                            for (i, col) in refs.columns.iter().enumerate() {
10325                                if i > 0 {
10326                                    self.write(", ");
10327                                }
10328                                self.generate_identifier(col)?;
10329                            }
10330                            self.write(")");
10331                        }
10332                    }
10333                    self.generate_referential_actions(refs)?;
10334                } else {
10335                    // No REFERENCES - output ON DELETE/ON UPDATE directly
10336                    if let Some(ref action) = on_delete {
10337                        self.write_space();
10338                        self.write_keyword("ON DELETE");
10339                        self.write_space();
10340                        self.generate_referential_action(action);
10341                    }
10342                    if let Some(ref action) = on_update {
10343                        self.write_space();
10344                        self.write_keyword("ON UPDATE");
10345                        self.write_space();
10346                        self.generate_referential_action(action);
10347                    }
10348                }
10349                self.generate_constraint_modifiers(modifiers);
10350            }
10351            TableConstraint::Check {
10352                name,
10353                expression,
10354                modifiers,
10355            } => {
10356                if let Some(ref n) = name {
10357                    self.write_keyword("CONSTRAINT");
10358                    self.write_space();
10359                    self.generate_identifier(n)?;
10360                    self.write_space();
10361                }
10362                self.write_keyword("CHECK");
10363                self.write(" (");
10364                self.generate_expression(expression)?;
10365                self.write(")");
10366                self.generate_constraint_modifiers(modifiers);
10367            }
10368            TableConstraint::Assume { name, expression } => {
10369                if let Some(ref n) = name {
10370                    self.write_keyword("CONSTRAINT");
10371                    self.write_space();
10372                    self.generate_identifier(n)?;
10373                    self.write_space();
10374                }
10375                self.write_keyword("ASSUME");
10376                self.write(" (");
10377                self.generate_expression(expression)?;
10378                self.write(")");
10379            }
10380            TableConstraint::Default {
10381                name,
10382                expression,
10383                column,
10384            } => {
10385                if let Some(ref n) = name {
10386                    self.write_keyword("CONSTRAINT");
10387                    self.write_space();
10388                    self.generate_identifier(n)?;
10389                    self.write_space();
10390                }
10391                self.write_keyword("DEFAULT");
10392                self.write_space();
10393                self.generate_expression(expression)?;
10394                self.write_space();
10395                self.write_keyword("FOR");
10396                self.write_space();
10397                self.generate_identifier(column)?;
10398            }
10399            TableConstraint::Index {
10400                name,
10401                columns,
10402                key_parts,
10403                kind,
10404                modifiers,
10405                use_key_keyword,
10406                expression,
10407                index_type,
10408                granularity,
10409            } => {
10410                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
10411                if expression.is_some() {
10412                    self.write_keyword("INDEX");
10413                    if let Some(ref n) = name {
10414                        self.write_space();
10415                        self.generate_identifier(n)?;
10416                    }
10417                    if let Some(ref expr) = expression {
10418                        self.write_space();
10419                        self.generate_expression(expr)?;
10420                    }
10421                    if let Some(ref idx_type) = index_type {
10422                        self.write_space();
10423                        self.write_keyword("TYPE");
10424                        self.write_space();
10425                        self.generate_expression(idx_type)?;
10426                    }
10427                    if let Some(ref gran) = granularity {
10428                        self.write_space();
10429                        self.write_keyword("GRANULARITY");
10430                        self.write_space();
10431                        self.generate_expression(gran)?;
10432                    }
10433                } else {
10434                    // Standard INDEX syntax
10435                    // Determine the index keyword to use
10436                    // MySQL normalizes KEY to INDEX
10437                    use crate::dialects::DialectType;
10438                    let index_keyword = if *use_key_keyword
10439                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
10440                    {
10441                        "KEY"
10442                    } else {
10443                        "INDEX"
10444                    };
10445
10446                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
10447                    if let Some(ref k) = kind {
10448                        self.write_keyword(k);
10449                        // For UNIQUE, don't add INDEX/KEY keyword
10450                        if k != "UNIQUE" {
10451                            self.write_space();
10452                            self.write_keyword(index_keyword);
10453                        }
10454                    } else {
10455                        self.write_keyword(index_keyword);
10456                    }
10457
10458                    // Output USING before name if using_before_columns is true and there's no name
10459                    if modifiers.using_before_columns && name.is_none() {
10460                        if let Some(ref using) = modifiers.using {
10461                            self.write_space();
10462                            self.write_keyword("USING");
10463                            self.write_space();
10464                            self.write_keyword(using);
10465                        }
10466                    }
10467
10468                    // Output index name if present
10469                    if let Some(ref n) = name {
10470                        self.write_space();
10471                        self.generate_identifier(n)?;
10472                    }
10473
10474                    // Output USING after name but before columns if using_before_columns and there's a name
10475                    if modifiers.using_before_columns && name.is_some() {
10476                        if let Some(ref using) = modifiers.using {
10477                            self.write_space();
10478                            self.write_keyword("USING");
10479                            self.write_space();
10480                            self.write_keyword(using);
10481                        }
10482                    }
10483
10484                    // Output columns / functional key parts
10485                    self.write(" (");
10486                    if key_parts.is_empty() {
10487                        for (i, col) in columns.iter().enumerate() {
10488                            if i > 0 {
10489                                self.write(", ");
10490                            }
10491                            self.generate_identifier(col)?;
10492                        }
10493                    } else {
10494                        self.generate_index_key_parts(key_parts)?;
10495                    }
10496                    self.write(")");
10497
10498                    // Output USING after columns if not using_before_columns
10499                    if !modifiers.using_before_columns {
10500                        if let Some(ref using) = modifiers.using {
10501                            self.write_space();
10502                            self.write_keyword("USING");
10503                            self.write_space();
10504                            self.write_keyword(using);
10505                        }
10506                    }
10507
10508                    // Output other constraint modifiers (but skip USING since we already handled it)
10509                    self.generate_constraint_modifiers_without_using(modifiers);
10510                }
10511            }
10512            TableConstraint::Projection { name, expression } => {
10513                // ClickHouse: PROJECTION name (SELECT ...)
10514                self.write_keyword("PROJECTION");
10515                self.write_space();
10516                self.generate_identifier(name)?;
10517                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
10518                    if let Expression::Raw(raw) = expression {
10519                        if raw
10520                            .sql
10521                            .trim_start()
10522                            .to_ascii_uppercase()
10523                            .starts_with("INDEX ")
10524                        {
10525                            self.write_space();
10526                            self.write(raw.sql.trim());
10527                            return Ok(());
10528                        }
10529                    }
10530                }
10531                self.write(" (");
10532                self.generate_expression(expression)?;
10533                self.write(")");
10534            }
10535            TableConstraint::Like { source, options } => {
10536                self.write_keyword("LIKE");
10537                self.write_space();
10538                self.generate_table(source)?;
10539                for (action, prop) in options {
10540                    self.write_space();
10541                    match action {
10542                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
10543                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
10544                    }
10545                    self.write_space();
10546                    self.write_keyword(prop);
10547                }
10548            }
10549            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
10550                self.write_keyword("PERIOD FOR SYSTEM_TIME");
10551                self.write(" (");
10552                self.generate_identifier(start_col)?;
10553                self.write(", ");
10554                self.generate_identifier(end_col)?;
10555                self.write(")");
10556            }
10557            TableConstraint::Exclude {
10558                name,
10559                using,
10560                elements,
10561                include_columns,
10562                where_clause,
10563                with_params,
10564                using_index_tablespace,
10565                modifiers: _,
10566            } => {
10567                if let Some(ref n) = name {
10568                    self.write_keyword("CONSTRAINT");
10569                    self.write_space();
10570                    self.generate_identifier(n)?;
10571                    self.write_space();
10572                }
10573                self.write_keyword("EXCLUDE");
10574                if let Some(ref method) = using {
10575                    self.write_space();
10576                    self.write_keyword("USING");
10577                    self.write_space();
10578                    self.write(method);
10579                    self.write("(");
10580                } else {
10581                    self.write(" (");
10582                }
10583                for (i, elem) in elements.iter().enumerate() {
10584                    if i > 0 {
10585                        self.write(", ");
10586                    }
10587                    self.write(&elem.expression);
10588                    self.write_space();
10589                    self.write_keyword("WITH");
10590                    self.write_space();
10591                    self.write(&elem.operator);
10592                }
10593                self.write(")");
10594                if !include_columns.is_empty() {
10595                    self.write_space();
10596                    self.write_keyword("INCLUDE");
10597                    self.write(" (");
10598                    for (i, col) in include_columns.iter().enumerate() {
10599                        if i > 0 {
10600                            self.write(", ");
10601                        }
10602                        self.generate_identifier(col)?;
10603                    }
10604                    self.write(")");
10605                }
10606                if !with_params.is_empty() {
10607                    self.write_space();
10608                    self.write_keyword("WITH");
10609                    self.write(" (");
10610                    for (i, (key, val)) in with_params.iter().enumerate() {
10611                        if i > 0 {
10612                            self.write(", ");
10613                        }
10614                        self.write(key);
10615                        self.write("=");
10616                        self.write(val);
10617                    }
10618                    self.write(")");
10619                }
10620                if let Some(ref tablespace) = using_index_tablespace {
10621                    self.write_space();
10622                    self.write_keyword("USING INDEX TABLESPACE");
10623                    self.write_space();
10624                    self.write(tablespace);
10625                }
10626                if let Some(ref where_expr) = where_clause {
10627                    self.write_space();
10628                    self.write_keyword("WHERE");
10629                    self.write(" (");
10630                    self.generate_expression(where_expr)?;
10631                    self.write(")");
10632                }
10633            }
10634            TableConstraint::Tags(tags) => {
10635                self.write_keyword("TAG");
10636                self.write(" (");
10637                for (i, expr) in tags.expressions.iter().enumerate() {
10638                    if i > 0 {
10639                        self.write(", ");
10640                    }
10641                    self.generate_expression(expr)?;
10642                }
10643                self.write(")");
10644            }
10645            TableConstraint::InitiallyDeferred { deferred } => {
10646                self.write_keyword("INITIALLY");
10647                self.write_space();
10648                if *deferred {
10649                    self.write_keyword("DEFERRED");
10650                } else {
10651                    self.write_keyword("IMMEDIATE");
10652                }
10653            }
10654        }
10655        Ok(())
10656    }
10657
10658    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10659        // Output USING BTREE/HASH (MySQL) - comes first
10660        if let Some(using) = &modifiers.using {
10661            self.write_space();
10662            self.write_keyword("USING");
10663            self.write_space();
10664            self.write_keyword(using);
10665        }
10666        // Output ENFORCED/NOT ENFORCED
10667        if let Some(enforced) = modifiers.enforced {
10668            self.write_space();
10669            if enforced {
10670                self.write_keyword("ENFORCED");
10671            } else {
10672                self.write_keyword("NOT ENFORCED");
10673            }
10674        }
10675        // Output DEFERRABLE/NOT DEFERRABLE
10676        if let Some(deferrable) = modifiers.deferrable {
10677            self.write_space();
10678            if deferrable {
10679                self.write_keyword("DEFERRABLE");
10680            } else {
10681                self.write_keyword("NOT DEFERRABLE");
10682            }
10683        }
10684        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10685        if let Some(initially_deferred) = modifiers.initially_deferred {
10686            self.write_space();
10687            if initially_deferred {
10688                self.write_keyword("INITIALLY DEFERRED");
10689            } else {
10690                self.write_keyword("INITIALLY IMMEDIATE");
10691            }
10692        }
10693        // Output NORELY
10694        if modifiers.norely {
10695            self.write_space();
10696            self.write_keyword("NORELY");
10697        }
10698        // Output RELY
10699        if modifiers.rely {
10700            self.write_space();
10701            self.write_keyword("RELY");
10702        }
10703        // Output NOT VALID (PostgreSQL)
10704        if modifiers.not_valid {
10705            self.write_space();
10706            self.write_keyword("NOT VALID");
10707        }
10708        // Output ON CONFLICT (SQLite)
10709        if let Some(on_conflict) = &modifiers.on_conflict {
10710            self.write_space();
10711            self.write_keyword("ON CONFLICT");
10712            self.write_space();
10713            self.write_keyword(on_conflict);
10714        }
10715        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10716        if !modifiers.with_options.is_empty() {
10717            self.write_space();
10718            self.write_keyword("WITH");
10719            self.write(" (");
10720            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10721                if i > 0 {
10722                    self.write(", ");
10723                }
10724                self.write(key);
10725                self.write("=");
10726                self.write(value);
10727            }
10728            self.write(")");
10729        }
10730        // Output TSQL ON filegroup
10731        if let Some(ref fg) = modifiers.on_filegroup {
10732            self.write_space();
10733            self.write_keyword("ON");
10734            self.write_space();
10735            let _ = self.generate_identifier(fg);
10736        }
10737    }
10738
10739    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10740    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10741        // Output ENFORCED/NOT ENFORCED
10742        if let Some(enforced) = modifiers.enforced {
10743            self.write_space();
10744            if enforced {
10745                self.write_keyword("ENFORCED");
10746            } else {
10747                self.write_keyword("NOT ENFORCED");
10748            }
10749        }
10750        // Output DEFERRABLE/NOT DEFERRABLE
10751        if let Some(deferrable) = modifiers.deferrable {
10752            self.write_space();
10753            if deferrable {
10754                self.write_keyword("DEFERRABLE");
10755            } else {
10756                self.write_keyword("NOT DEFERRABLE");
10757            }
10758        }
10759        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10760        if let Some(initially_deferred) = modifiers.initially_deferred {
10761            self.write_space();
10762            if initially_deferred {
10763                self.write_keyword("INITIALLY DEFERRED");
10764            } else {
10765                self.write_keyword("INITIALLY IMMEDIATE");
10766            }
10767        }
10768        // Output NORELY
10769        if modifiers.norely {
10770            self.write_space();
10771            self.write_keyword("NORELY");
10772        }
10773        // Output RELY
10774        if modifiers.rely {
10775            self.write_space();
10776            self.write_keyword("RELY");
10777        }
10778        // Output NOT VALID (PostgreSQL)
10779        if modifiers.not_valid {
10780            self.write_space();
10781            self.write_keyword("NOT VALID");
10782        }
10783        // Output ON CONFLICT (SQLite)
10784        if let Some(on_conflict) = &modifiers.on_conflict {
10785            self.write_space();
10786            self.write_keyword("ON CONFLICT");
10787            self.write_space();
10788            self.write_keyword(on_conflict);
10789        }
10790        // Output MySQL index-specific modifiers
10791        self.generate_index_specific_modifiers(modifiers);
10792    }
10793
10794    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10795    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10796        if let Some(ref comment) = modifiers.comment {
10797            self.write_space();
10798            self.write_keyword("COMMENT");
10799            self.write(" '");
10800            self.write(comment);
10801            self.write("'");
10802        }
10803        if let Some(visible) = modifiers.visible {
10804            self.write_space();
10805            if visible {
10806                self.write_keyword("VISIBLE");
10807            } else {
10808                self.write_keyword("INVISIBLE");
10809            }
10810        }
10811        if let Some(ref attr) = modifiers.engine_attribute {
10812            self.write_space();
10813            self.write_keyword("ENGINE_ATTRIBUTE");
10814            self.write(" = '");
10815            self.write(attr);
10816            self.write("'");
10817        }
10818        if let Some(ref parser) = modifiers.with_parser {
10819            self.write_space();
10820            self.write_keyword("WITH PARSER");
10821            self.write_space();
10822            self.write(parser);
10823        }
10824    }
10825
10826    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10827        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10828        if !fk_ref.match_after_actions {
10829            if let Some(ref match_type) = fk_ref.match_type {
10830                self.write_space();
10831                self.write_keyword("MATCH");
10832                self.write_space();
10833                match match_type {
10834                    MatchType::Full => self.write_keyword("FULL"),
10835                    MatchType::Partial => self.write_keyword("PARTIAL"),
10836                    MatchType::Simple => self.write_keyword("SIMPLE"),
10837                }
10838            }
10839        }
10840
10841        // Output ON UPDATE and ON DELETE in the original order
10842        if fk_ref.on_update_first {
10843            if let Some(ref action) = fk_ref.on_update {
10844                self.write_space();
10845                self.write_keyword("ON UPDATE");
10846                self.write_space();
10847                self.generate_referential_action(action);
10848            }
10849            if let Some(ref action) = fk_ref.on_delete {
10850                self.write_space();
10851                self.write_keyword("ON DELETE");
10852                self.write_space();
10853                self.generate_referential_action(action);
10854            }
10855        } else {
10856            if let Some(ref action) = fk_ref.on_delete {
10857                self.write_space();
10858                self.write_keyword("ON DELETE");
10859                self.write_space();
10860                self.generate_referential_action(action);
10861            }
10862            if let Some(ref action) = fk_ref.on_update {
10863                self.write_space();
10864                self.write_keyword("ON UPDATE");
10865                self.write_space();
10866                self.generate_referential_action(action);
10867            }
10868        }
10869
10870        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10871        if fk_ref.match_after_actions {
10872            if let Some(ref match_type) = fk_ref.match_type {
10873                self.write_space();
10874                self.write_keyword("MATCH");
10875                self.write_space();
10876                match match_type {
10877                    MatchType::Full => self.write_keyword("FULL"),
10878                    MatchType::Partial => self.write_keyword("PARTIAL"),
10879                    MatchType::Simple => self.write_keyword("SIMPLE"),
10880                }
10881            }
10882        }
10883
10884        // DEFERRABLE / NOT DEFERRABLE
10885        if let Some(deferrable) = fk_ref.deferrable {
10886            self.write_space();
10887            if deferrable {
10888                self.write_keyword("DEFERRABLE");
10889            } else {
10890                self.write_keyword("NOT DEFERRABLE");
10891            }
10892        }
10893
10894        Ok(())
10895    }
10896
10897    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10898        match action {
10899            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10900            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10901            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10902            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10903            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10904        }
10905    }
10906
10907    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10908        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10909        if let Some(ref object_id_args) = dt.object_id_args {
10910            if matches!(
10911                self.config.dialect,
10912                Some(crate::dialects::DialectType::TSQL)
10913                    | Some(crate::dialects::DialectType::Fabric)
10914            ) {
10915                self.write_keyword("IF NOT OBJECT_ID");
10916                self.write("(");
10917                self.write(object_id_args);
10918                self.write(")");
10919                self.write_space();
10920                self.write_keyword("IS NULL BEGIN DROP TABLE");
10921                self.write_space();
10922                for (i, table) in dt.names.iter().enumerate() {
10923                    if i > 0 {
10924                        self.write(", ");
10925                    }
10926                    self.generate_table(table)?;
10927                }
10928                self.write("; ");
10929                self.write_keyword("END");
10930                return Ok(());
10931            }
10932        }
10933
10934        // Athena: DROP TABLE uses Hive engine (backticks)
10935        let saved_athena_hive_context = self.athena_hive_context;
10936        if matches!(
10937            self.config.dialect,
10938            Some(crate::dialects::DialectType::Athena)
10939        ) {
10940            self.athena_hive_context = true;
10941        }
10942
10943        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10944        for comment in &dt.leading_comments {
10945            self.write_formatted_comment(comment);
10946            self.write_space();
10947        }
10948        if dt.iceberg {
10949            self.write_keyword("DROP ICEBERG TABLE");
10950        } else {
10951            self.write_keyword("DROP TABLE");
10952        }
10953
10954        if dt.if_exists {
10955            self.write_space();
10956            self.write_keyword("IF EXISTS");
10957        }
10958
10959        self.write_space();
10960        for (i, table) in dt.names.iter().enumerate() {
10961            if i > 0 {
10962                self.write(", ");
10963            }
10964            self.generate_table(table)?;
10965        }
10966
10967        if dt.cascade_constraints {
10968            self.write_space();
10969            self.write_keyword("CASCADE CONSTRAINTS");
10970        } else if dt.cascade {
10971            self.write_space();
10972            self.write_keyword("CASCADE");
10973        }
10974
10975        if dt.restrict {
10976            self.write_space();
10977            self.write_keyword("RESTRICT");
10978        }
10979
10980        if dt.purge {
10981            self.write_space();
10982            self.write_keyword("PURGE");
10983        }
10984
10985        if dt.sync {
10986            self.write_space();
10987            self.write_keyword("SYNC");
10988        }
10989
10990        // Restore Athena Hive context
10991        self.athena_hive_context = saved_athena_hive_context;
10992
10993        Ok(())
10994    }
10995
10996    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10997        self.write_keyword("UNDROP");
10998        self.write_space();
10999        self.write_keyword(&u.kind);
11000        if u.if_exists {
11001            self.write_space();
11002            self.write_keyword("IF EXISTS");
11003        }
11004        self.write_space();
11005        self.generate_table(&u.name)?;
11006        if let Some(rename_to) = &u.rename_to {
11007            self.write_space();
11008            self.write_keyword("RENAME TO");
11009            self.write_space();
11010            self.generate_table(rename_to)?;
11011        }
11012        Ok(())
11013    }
11014
11015    fn generate_split_table(&mut self, split: &SplitTable) -> Result<()> {
11016        if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11017            return self
11018                .write_unsupported_comment("SPLIT TABLE is only supported by the TiDB dialect");
11019        }
11020
11021        match &split.partition_scope {
11022            SplitTablePartitionScope::Table => self.write_keyword("SPLIT TABLE"),
11023            SplitTablePartitionScope::AllPartitions
11024            | SplitTablePartitionScope::Partitions { .. } => {
11025                self.write_keyword("SPLIT PARTITION TABLE")
11026            }
11027        }
11028        self.write_space();
11029        self.generate_table(&split.table)?;
11030        if let SplitTablePartitionScope::Partitions { names } = &split.partition_scope {
11031            self.write_space();
11032            self.write_keyword("PARTITION");
11033            self.write(" (");
11034            for (index, name) in names.iter().enumerate() {
11035                if index > 0 {
11036                    self.write(", ");
11037                }
11038                self.generate_identifier(name)?;
11039            }
11040            self.write(")");
11041        }
11042        if let Some(index) = &split.index {
11043            self.write_space();
11044            self.write_keyword("INDEX");
11045            self.write_space();
11046            self.generate_identifier(index)?;
11047        }
11048        match &split.mode {
11049            SplitTableMode::Between {
11050                lower,
11051                upper,
11052                regions,
11053            } => {
11054                self.write_space();
11055                self.write_keyword("BETWEEN");
11056                self.write_space();
11057                self.generate_tidb_split_values(lower)?;
11058                self.write_space();
11059                self.write_keyword("AND");
11060                self.write_space();
11061                self.generate_tidb_split_values(upper)?;
11062                self.write_space();
11063                self.write_keyword("REGIONS");
11064                self.write_space();
11065                self.write(&regions.to_string());
11066            }
11067            SplitTableMode::By { points } => {
11068                self.write_space();
11069                self.write_keyword("BY");
11070                self.write_space();
11071                for (index, point) in points.iter().enumerate() {
11072                    if index > 0 {
11073                        self.write(", ");
11074                    }
11075                    self.generate_tidb_split_values(point)?;
11076                }
11077            }
11078        }
11079        Ok(())
11080    }
11081
11082    fn generate_tidb_split_values(&mut self, values: &[Expression]) -> Result<()> {
11083        self.write("(");
11084        for (index, value) in values.iter().enumerate() {
11085            if index > 0 {
11086                self.write(", ");
11087            }
11088            self.generate_expression(value)?;
11089        }
11090        self.write(")");
11091        Ok(())
11092    }
11093
11094    fn generate_flashback_table(&mut self, flashback: &FlashbackTable) -> Result<()> {
11095        if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11096            return self.write_unsupported_comment(
11097                "FLASHBACK TABLE is only supported by the TiDB dialect",
11098            );
11099        }
11100        self.write_keyword("FLASHBACK TABLE");
11101        self.write_space();
11102        self.generate_table(&flashback.table)?;
11103        if let Some(rename_to) = &flashback.rename_to {
11104            self.write_space();
11105            self.write_keyword("TO");
11106            self.write_space();
11107            self.generate_identifier(rename_to)?;
11108        }
11109        Ok(())
11110    }
11111
11112    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
11113        // Athena: ALTER TABLE uses Hive engine (backticks)
11114        let saved_athena_hive_context = self.athena_hive_context;
11115        if matches!(
11116            self.config.dialect,
11117            Some(crate::dialects::DialectType::Athena)
11118        ) {
11119            self.athena_hive_context = true;
11120        }
11121
11122        self.write_keyword("ALTER");
11123        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
11124        if let Some(ref modifier) = at.table_modifier {
11125            if !matches!(
11126                self.config.dialect,
11127                Some(crate::dialects::DialectType::DuckDB)
11128            ) {
11129                self.write_space();
11130                self.write_keyword(modifier);
11131            }
11132        }
11133        self.write(" ");
11134        self.write_keyword("TABLE");
11135        if at.if_exists {
11136            self.write_space();
11137            self.write_keyword("IF EXISTS");
11138        }
11139        self.write_space();
11140        self.generate_table(&at.name)?;
11141
11142        // ClickHouse: ON CLUSTER clause
11143        if let Some(ref on_cluster) = at.on_cluster {
11144            self.write_space();
11145            self.generate_on_cluster(on_cluster)?;
11146        }
11147
11148        // Hive: PARTITION(key=value, ...) clause
11149        if let Some(ref partition) = at.partition {
11150            self.write_space();
11151            self.write_keyword("PARTITION");
11152            self.write("(");
11153            for (i, (key, value)) in partition.iter().enumerate() {
11154                if i > 0 {
11155                    self.write(", ");
11156                }
11157                self.generate_identifier(key)?;
11158                self.write(" = ");
11159                self.generate_expression(value)?;
11160            }
11161            self.write(")");
11162        }
11163
11164        // TSQL: WITH CHECK / WITH NOCHECK modifier
11165        if let Some(ref with_check) = at.with_check {
11166            self.write_space();
11167            self.write_keyword(with_check);
11168        }
11169
11170        if self.config.pretty {
11171            // In pretty mode, format actions with newlines and indentation
11172            self.write_newline();
11173            self.indent_level += 1;
11174            for (i, action) in at.actions.iter().enumerate() {
11175                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11176                let is_continuation = i > 0
11177                    && matches!(
11178                        (&at.actions[i - 1], action),
11179                        (
11180                            AlterTableAction::AddColumn { .. },
11181                            AlterTableAction::AddColumn { .. }
11182                        ) | (
11183                            AlterTableAction::AddConstraint(_),
11184                            AlterTableAction::AddConstraint(_)
11185                        )
11186                    );
11187                if i > 0 {
11188                    self.write(",");
11189                    self.write_newline();
11190                }
11191                self.write_indent();
11192                self.generate_alter_action_with_continuation(action, is_continuation)?;
11193            }
11194            self.indent_level -= 1;
11195        } else {
11196            for (i, action) in at.actions.iter().enumerate() {
11197                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11198                let is_continuation = i > 0
11199                    && matches!(
11200                        (&at.actions[i - 1], action),
11201                        (
11202                            AlterTableAction::AddColumn { .. },
11203                            AlterTableAction::AddColumn { .. }
11204                        ) | (
11205                            AlterTableAction::AddConstraint(_),
11206                            AlterTableAction::AddConstraint(_)
11207                        )
11208                    );
11209                if i > 0 {
11210                    self.write(",");
11211                }
11212                self.write_space();
11213                self.generate_alter_action_with_continuation(action, is_continuation)?;
11214            }
11215        }
11216
11217        // MySQL ALTER TABLE trailing options
11218        if let Some(ref algorithm) = at.algorithm {
11219            self.write(", ");
11220            self.write_keyword("ALGORITHM");
11221            self.write("=");
11222            self.write_keyword(algorithm);
11223        }
11224        if let Some(ref lock) = at.lock {
11225            self.write(", ");
11226            self.write_keyword("LOCK");
11227            self.write("=");
11228            self.write_keyword(lock);
11229        }
11230
11231        // Restore Athena Hive context
11232        self.athena_hive_context = saved_athena_hive_context;
11233
11234        Ok(())
11235    }
11236
11237    fn generate_alter_action_with_continuation(
11238        &mut self,
11239        action: &AlterTableAction,
11240        is_continuation: bool,
11241    ) -> Result<()> {
11242        match action {
11243            AlterTableAction::AddColumn {
11244                column,
11245                if_not_exists,
11246                position,
11247            } => {
11248                use crate::dialects::DialectType;
11249                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
11250                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
11251                // For other dialects, repeat ADD COLUMN for each
11252                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
11253                let is_tsql_like = matches!(
11254                    self.config.dialect,
11255                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
11256                );
11257                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
11258                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
11259
11260                if is_continuation && (is_snowflake || is_tsql_like) {
11261                    // Don't write ADD keyword for continuation in Snowflake/TSQL
11262                } else if is_snowflake {
11263                    self.write_keyword("ADD");
11264                    self.write_space();
11265                } else if is_athena {
11266                    // Athena uses ADD COLUMNS (col_def) syntax
11267                    self.write_keyword("ADD COLUMNS");
11268                    self.write(" (");
11269                } else if self.config.alter_table_include_column_keyword {
11270                    self.write_keyword("ADD COLUMN");
11271                    self.write_space();
11272                } else {
11273                    // Dialects like Oracle and TSQL don't use COLUMN keyword
11274                    self.write_keyword("ADD");
11275                    self.write_space();
11276                }
11277
11278                if *if_not_exists {
11279                    self.write_keyword("IF NOT EXISTS");
11280                    self.write_space();
11281                }
11282                self.generate_column_def(column)?;
11283
11284                // Close parenthesis for Athena
11285                if is_athena {
11286                    self.write(")");
11287                }
11288
11289                // Column position (FIRST or AFTER)
11290                if let Some(pos) = position {
11291                    self.write_space();
11292                    match pos {
11293                        ColumnPosition::First => self.write_keyword("FIRST"),
11294                        ColumnPosition::After(col_name) => {
11295                            self.write_keyword("AFTER");
11296                            self.write_space();
11297                            self.generate_identifier(col_name)?;
11298                        }
11299                    }
11300                }
11301            }
11302            AlterTableAction::DropColumn {
11303                name,
11304                if_exists,
11305                cascade,
11306            } => {
11307                self.write_keyword("DROP COLUMN");
11308                if *if_exists {
11309                    self.write_space();
11310                    self.write_keyword("IF EXISTS");
11311                }
11312                self.write_space();
11313                self.generate_identifier(name)?;
11314                if *cascade {
11315                    self.write_space();
11316                    self.write_keyword("CASCADE");
11317                }
11318            }
11319            AlterTableAction::DropColumns { names } => {
11320                self.write_keyword("DROP COLUMNS");
11321                self.write(" (");
11322                for (i, name) in names.iter().enumerate() {
11323                    if i > 0 {
11324                        self.write(", ");
11325                    }
11326                    self.generate_identifier(name)?;
11327                }
11328                self.write(")");
11329            }
11330            AlterTableAction::RenameColumn {
11331                old_name,
11332                new_name,
11333                if_exists,
11334            } => {
11335                self.write_keyword("RENAME COLUMN");
11336                if *if_exists {
11337                    self.write_space();
11338                    self.write_keyword("IF EXISTS");
11339                }
11340                self.write_space();
11341                self.generate_identifier(old_name)?;
11342                self.write_space();
11343                self.write_keyword("TO");
11344                self.write_space();
11345                self.generate_identifier(new_name)?;
11346            }
11347            AlterTableAction::AlterColumn {
11348                name,
11349                action,
11350                use_modify_keyword,
11351            } => {
11352                use crate::dialects::DialectType;
11353                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
11354                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
11355                let use_modify = *use_modify_keyword
11356                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
11357                        && matches!(action, AlterColumnAction::SetDataType { .. }));
11358                if use_modify {
11359                    self.write_keyword("MODIFY COLUMN");
11360                    self.write_space();
11361                    self.generate_identifier(name)?;
11362                    // For MODIFY COLUMN, output the type directly
11363                    if let AlterColumnAction::SetDataType {
11364                        data_type,
11365                        using: _,
11366                        collate,
11367                    } = action
11368                    {
11369                        self.write_space();
11370                        self.generate_data_type(data_type)?;
11371                        // Output COLLATE clause if present
11372                        if let Some(collate_name) = collate {
11373                            self.write_space();
11374                            self.write_keyword("COLLATE");
11375                            self.write_space();
11376                            // Output as single-quoted string
11377                            self.write(&format!("'{}'", collate_name));
11378                        }
11379                    } else {
11380                        self.write_space();
11381                        self.generate_alter_column_action(action)?;
11382                    }
11383                } else if matches!(self.config.dialect, Some(DialectType::Hive))
11384                    && matches!(action, AlterColumnAction::SetDataType { .. })
11385                {
11386                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
11387                    self.write_keyword("CHANGE COLUMN");
11388                    self.write_space();
11389                    self.generate_identifier(name)?;
11390                    self.write_space();
11391                    self.generate_identifier(name)?;
11392                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
11393                        self.write_space();
11394                        self.generate_data_type(data_type)?;
11395                    }
11396                } else {
11397                    self.write_keyword("ALTER COLUMN");
11398                    self.write_space();
11399                    self.generate_identifier(name)?;
11400                    self.write_space();
11401                    self.generate_alter_column_action(action)?;
11402                }
11403            }
11404            AlterTableAction::ModifyColumn {
11405                column,
11406                if_exists,
11407                position,
11408            } => {
11409                if !matches!(
11410                    self.config.dialect,
11411                    Some(DialectType::TiDB) | Some(DialectType::MySQL)
11412                ) {
11413                    return self.write_unsupported_comment(
11414                        "MODIFY COLUMN is only supported by MySQL-compatible dialects",
11415                    );
11416                }
11417                self.write_keyword("MODIFY COLUMN");
11418                self.write_space();
11419                if *if_exists {
11420                    self.write_keyword("IF EXISTS");
11421                    self.write_space();
11422                }
11423                self.generate_column_def(column)?;
11424                if let Some(position) = position {
11425                    self.write_space();
11426                    match position {
11427                        ColumnPosition::First => self.write_keyword("FIRST"),
11428                        ColumnPosition::After(name) => {
11429                            self.write_keyword("AFTER");
11430                            self.write_space();
11431                            self.generate_identifier(name)?;
11432                        }
11433                    }
11434                }
11435            }
11436            AlterTableAction::RenameTable(new_name) => {
11437                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
11438                let mysql_like = matches!(
11439                    self.config.dialect,
11440                    Some(DialectType::MySQL)
11441                        | Some(DialectType::Doris)
11442                        | Some(DialectType::StarRocks)
11443                        | Some(DialectType::SingleStore)
11444                );
11445                if mysql_like {
11446                    self.write_keyword("RENAME");
11447                } else {
11448                    self.write_keyword("RENAME TO");
11449                }
11450                self.write_space();
11451                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
11452                let rename_table_with_db = !matches!(
11453                    self.config.dialect,
11454                    Some(DialectType::Doris)
11455                        | Some(DialectType::DuckDB)
11456                        | Some(DialectType::BigQuery)
11457                        | Some(DialectType::PostgreSQL)
11458                );
11459                if !rename_table_with_db {
11460                    let mut stripped = new_name.clone();
11461                    stripped.schema = None;
11462                    stripped.catalog = None;
11463                    self.generate_table(&stripped)?;
11464                } else {
11465                    self.generate_table(new_name)?;
11466                }
11467            }
11468            AlterTableAction::AddConstraint(constraint) => {
11469                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
11470                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
11471                if !is_continuation {
11472                    self.write_keyword("ADD");
11473                    self.write_space();
11474                }
11475                self.generate_table_constraint(constraint)?;
11476            }
11477            AlterTableAction::DropConstraint { name, if_exists } => {
11478                self.write_keyword("DROP CONSTRAINT");
11479                if *if_exists {
11480                    self.write_space();
11481                    self.write_keyword("IF EXISTS");
11482                }
11483                self.write_space();
11484                self.generate_identifier(name)?;
11485            }
11486            AlterTableAction::DropForeignKey { name } => {
11487                self.write_keyword("DROP FOREIGN KEY");
11488                self.write_space();
11489                self.generate_identifier(name)?;
11490            }
11491            AlterTableAction::DropPartition {
11492                partitions,
11493                if_exists,
11494            } => {
11495                self.write_keyword("DROP");
11496                if *if_exists {
11497                    self.write_space();
11498                    self.write_keyword("IF EXISTS");
11499                }
11500                for (i, partition) in partitions.iter().enumerate() {
11501                    if i > 0 {
11502                        self.write(",");
11503                    }
11504                    self.write_space();
11505                    self.write_keyword("PARTITION");
11506                    // Check for special ClickHouse partition formats
11507                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
11508                        // ClickHouse: PARTITION <expression>
11509                        self.write_space();
11510                        self.generate_expression(&partition[0].1)?;
11511                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
11512                        // ClickHouse: PARTITION ALL
11513                        self.write_space();
11514                        self.write_keyword("ALL");
11515                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
11516                        // ClickHouse: PARTITION ID 'string'
11517                        self.write_space();
11518                        self.write_keyword("ID");
11519                        self.write_space();
11520                        self.generate_expression(&partition[0].1)?;
11521                    } else {
11522                        // Standard SQL: PARTITION(key=value, ...)
11523                        self.write("(");
11524                        for (j, (key, value)) in partition.iter().enumerate() {
11525                            if j > 0 {
11526                                self.write(", ");
11527                            }
11528                            self.generate_identifier(key)?;
11529                            self.write(" = ");
11530                            self.generate_expression(value)?;
11531                        }
11532                        self.write(")");
11533                    }
11534                }
11535            }
11536            AlterTableAction::Delete { where_clause } => {
11537                self.write_keyword("DELETE");
11538                self.write_space();
11539                self.write_keyword("WHERE");
11540                self.write_space();
11541                self.generate_expression(where_clause)?;
11542            }
11543            AlterTableAction::SwapWith(target) => {
11544                self.write_keyword("SWAP WITH");
11545                self.write_space();
11546                self.generate_table(target)?;
11547            }
11548            AlterTableAction::SetProperty { properties } => {
11549                use crate::dialects::DialectType;
11550                self.write_keyword("SET");
11551                // Trino/Presto use SET PROPERTIES syntax with spaces around =
11552                let is_trino_presto = matches!(
11553                    self.config.dialect,
11554                    Some(DialectType::Trino) | Some(DialectType::Presto)
11555                );
11556                if is_trino_presto {
11557                    self.write_space();
11558                    self.write_keyword("PROPERTIES");
11559                }
11560                let eq = if is_trino_presto { " = " } else { "=" };
11561                for (i, (key, value)) in properties.iter().enumerate() {
11562                    if i > 0 {
11563                        self.write(",");
11564                    }
11565                    self.write_space();
11566                    // Handle quoted property names for Trino
11567                    if key.contains(' ') {
11568                        self.generate_string_literal(key)?;
11569                    } else {
11570                        self.write(key);
11571                    }
11572                    self.write(eq);
11573                    self.generate_expression(value)?;
11574                }
11575            }
11576            AlterTableAction::UnsetProperty { properties } => {
11577                self.write_keyword("UNSET");
11578                for (i, name) in properties.iter().enumerate() {
11579                    if i > 0 {
11580                        self.write(",");
11581                    }
11582                    self.write_space();
11583                    self.write(name);
11584                }
11585            }
11586            AlterTableAction::ClusterBy { expressions } => {
11587                self.write_keyword("CLUSTER BY");
11588                self.write(" (");
11589                for (i, expr) in expressions.iter().enumerate() {
11590                    if i > 0 {
11591                        self.write(", ");
11592                    }
11593                    self.generate_expression(expr)?;
11594                }
11595                self.write(")");
11596            }
11597            AlterTableAction::SetTag { expressions } => {
11598                self.write_keyword("SET TAG");
11599                for (i, (key, value)) in expressions.iter().enumerate() {
11600                    if i > 0 {
11601                        self.write(",");
11602                    }
11603                    self.write_space();
11604                    self.write(key);
11605                    self.write(" = ");
11606                    self.generate_expression(value)?;
11607                }
11608            }
11609            AlterTableAction::UnsetTag { names } => {
11610                self.write_keyword("UNSET TAG");
11611                for (i, name) in names.iter().enumerate() {
11612                    if i > 0 {
11613                        self.write(",");
11614                    }
11615                    self.write_space();
11616                    self.write(name);
11617                }
11618            }
11619            AlterTableAction::SetOptions { expressions } => {
11620                self.write_keyword("SET");
11621                self.write(" (");
11622                for (i, expr) in expressions.iter().enumerate() {
11623                    if i > 0 {
11624                        self.write(", ");
11625                    }
11626                    self.generate_expression(expr)?;
11627                }
11628                self.write(")");
11629            }
11630            AlterTableAction::AlterIndex { name, visible } => {
11631                self.write_keyword("ALTER INDEX");
11632                self.write_space();
11633                self.generate_identifier(name)?;
11634                self.write_space();
11635                if *visible {
11636                    self.write_keyword("VISIBLE");
11637                } else {
11638                    self.write_keyword("INVISIBLE");
11639                }
11640            }
11641            AlterTableAction::SetAttribute { attribute } => {
11642                self.write_keyword("SET");
11643                self.write_space();
11644                self.write_keyword(attribute);
11645            }
11646            AlterTableAction::SetStageFileFormat { options } => {
11647                self.write_keyword("SET");
11648                self.write_space();
11649                self.write_keyword("STAGE_FILE_FORMAT");
11650                self.write(" = (");
11651                if let Some(opts) = options {
11652                    self.generate_space_separated_properties(opts)?;
11653                }
11654                self.write(")");
11655            }
11656            AlterTableAction::SetStageCopyOptions { options } => {
11657                self.write_keyword("SET");
11658                self.write_space();
11659                self.write_keyword("STAGE_COPY_OPTIONS");
11660                self.write(" = (");
11661                if let Some(opts) = options {
11662                    self.generate_space_separated_properties(opts)?;
11663                }
11664                self.write(")");
11665            }
11666            AlterTableAction::AddColumns { columns, cascade } => {
11667                // Oracle uses ADD (...) without COLUMNS keyword
11668                // Hive/Spark uses ADD COLUMNS (...)
11669                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
11670                if is_oracle {
11671                    self.write_keyword("ADD");
11672                } else {
11673                    self.write_keyword("ADD COLUMNS");
11674                }
11675                self.write(" (");
11676                for (i, col) in columns.iter().enumerate() {
11677                    if i > 0 {
11678                        self.write(", ");
11679                    }
11680                    self.generate_column_def(col)?;
11681                }
11682                self.write(")");
11683                if *cascade {
11684                    self.write_space();
11685                    self.write_keyword("CASCADE");
11686                }
11687            }
11688            AlterTableAction::ChangeColumn {
11689                old_name,
11690                new_name,
11691                data_type,
11692                comment,
11693                cascade,
11694            } => {
11695                use crate::dialects::DialectType;
11696                let is_spark = matches!(
11697                    self.config.dialect,
11698                    Some(DialectType::Spark) | Some(DialectType::Databricks)
11699                );
11700                let is_rename = old_name.name != new_name.name;
11701
11702                if is_spark {
11703                    if is_rename {
11704                        // Spark: RENAME COLUMN old TO new
11705                        self.write_keyword("RENAME COLUMN");
11706                        self.write_space();
11707                        self.generate_identifier(old_name)?;
11708                        self.write_space();
11709                        self.write_keyword("TO");
11710                        self.write_space();
11711                        self.generate_identifier(new_name)?;
11712                    } else if comment.is_some() {
11713                        // Spark: ALTER COLUMN old COMMENT 'comment'
11714                        self.write_keyword("ALTER COLUMN");
11715                        self.write_space();
11716                        self.generate_identifier(old_name)?;
11717                        self.write_space();
11718                        self.write_keyword("COMMENT");
11719                        self.write_space();
11720                        self.write("'");
11721                        self.write(comment.as_ref().unwrap());
11722                        self.write("'");
11723                    } else if data_type.is_some() {
11724                        // Spark: ALTER COLUMN old TYPE data_type
11725                        self.write_keyword("ALTER COLUMN");
11726                        self.write_space();
11727                        self.generate_identifier(old_name)?;
11728                        self.write_space();
11729                        self.write_keyword("TYPE");
11730                        self.write_space();
11731                        self.generate_data_type(data_type.as_ref().unwrap())?;
11732                    } else {
11733                        // Fallback to CHANGE COLUMN
11734                        self.write_keyword("CHANGE COLUMN");
11735                        self.write_space();
11736                        self.generate_identifier(old_name)?;
11737                        self.write_space();
11738                        self.generate_identifier(new_name)?;
11739                    }
11740                } else {
11741                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
11742                    if data_type.is_some() {
11743                        self.write_keyword("CHANGE COLUMN");
11744                    } else {
11745                        self.write_keyword("CHANGE");
11746                    }
11747                    self.write_space();
11748                    self.generate_identifier(old_name)?;
11749                    self.write_space();
11750                    self.generate_identifier(new_name)?;
11751                    if let Some(ref dt) = data_type {
11752                        self.write_space();
11753                        self.generate_data_type(dt)?;
11754                    }
11755                    if let Some(ref c) = comment {
11756                        self.write_space();
11757                        self.write_keyword("COMMENT");
11758                        self.write_space();
11759                        self.write("'");
11760                        self.write(c);
11761                        self.write("'");
11762                    }
11763                    if *cascade {
11764                        self.write_space();
11765                        self.write_keyword("CASCADE");
11766                    }
11767                }
11768            }
11769            AlterTableAction::AddPartition {
11770                partition,
11771                if_not_exists,
11772                location,
11773            } => {
11774                self.write_keyword("ADD");
11775                self.write_space();
11776                if *if_not_exists {
11777                    self.write_keyword("IF NOT EXISTS");
11778                    self.write_space();
11779                }
11780                self.generate_expression(partition)?;
11781                if let Some(ref loc) = location {
11782                    self.write_space();
11783                    self.write_keyword("LOCATION");
11784                    self.write_space();
11785                    self.generate_expression(loc)?;
11786                }
11787            }
11788            AlterTableAction::AlterSortKey {
11789                this,
11790                expressions,
11791                compound,
11792            } => {
11793                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11794                self.write_keyword("ALTER");
11795                if *compound {
11796                    self.write_space();
11797                    self.write_keyword("COMPOUND");
11798                }
11799                self.write_space();
11800                self.write_keyword("SORTKEY");
11801                self.write_space();
11802                if let Some(style) = this {
11803                    self.write_keyword(style);
11804                } else if !expressions.is_empty() {
11805                    self.write("(");
11806                    for (i, expr) in expressions.iter().enumerate() {
11807                        if i > 0 {
11808                            self.write(", ");
11809                        }
11810                        self.generate_expression(expr)?;
11811                    }
11812                    self.write(")");
11813                }
11814            }
11815            AlterTableAction::AlterDistStyle { style, distkey } => {
11816                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11817                self.write_keyword("ALTER");
11818                self.write_space();
11819                self.write_keyword("DISTSTYLE");
11820                self.write_space();
11821                self.write_keyword(style);
11822                if let Some(col) = distkey {
11823                    self.write_space();
11824                    self.write_keyword("DISTKEY");
11825                    self.write_space();
11826                    self.generate_identifier(col)?;
11827                }
11828            }
11829            AlterTableAction::SetTableProperties { properties } => {
11830                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11831                self.write_keyword("SET TABLE PROPERTIES");
11832                self.write(" (");
11833                for (i, (key, value)) in properties.iter().enumerate() {
11834                    if i > 0 {
11835                        self.write(", ");
11836                    }
11837                    self.generate_expression(key)?;
11838                    self.write(" = ");
11839                    self.generate_expression(value)?;
11840                }
11841                self.write(")");
11842            }
11843            AlterTableAction::SetLocation { location } => {
11844                // Redshift: SET LOCATION 's3://bucket/folder/'
11845                self.write_keyword("SET LOCATION");
11846                self.write_space();
11847                self.write("'");
11848                self.write(location);
11849                self.write("'");
11850            }
11851            AlterTableAction::SetFileFormat { format } => {
11852                // Redshift: SET FILE FORMAT AVRO
11853                self.write_keyword("SET FILE FORMAT");
11854                self.write_space();
11855                self.write_keyword(format);
11856            }
11857            AlterTableAction::ReplacePartition { partition, source } => {
11858                // ClickHouse: REPLACE PARTITION expr FROM source
11859                self.write_keyword("REPLACE PARTITION");
11860                self.write_space();
11861                self.generate_expression(partition)?;
11862                if let Some(src) = source {
11863                    self.write_space();
11864                    self.write_keyword("FROM");
11865                    self.write_space();
11866                    self.generate_expression(src)?;
11867                }
11868            }
11869            AlterTableAction::SetTiDBTableOption { option, force } => {
11870                self.generate_tidb_table_option(option, *force)?;
11871            }
11872            AlterTableAction::RemoveTiDBTtl { executable_comment } => {
11873                if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11874                    return self.write_unsupported_comment(
11875                        "REMOVE TTL is only supported by the TiDB dialect",
11876                    );
11877                }
11878                if *executable_comment {
11879                    self.write("/*T![ttl] REMOVE TTL */");
11880                } else {
11881                    self.write_keyword("REMOVE TTL");
11882                }
11883            }
11884            AlterTableAction::Raw { sql } => {
11885                self.write(sql);
11886            }
11887        }
11888        Ok(())
11889    }
11890
11891    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11892        match action {
11893            AlterColumnAction::SetDataType {
11894                data_type,
11895                using,
11896                collate,
11897            } => {
11898                use crate::dialects::DialectType;
11899                // Dialect-specific type change syntax:
11900                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11901                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11902                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11903                let is_no_prefix = matches!(
11904                    self.config.dialect,
11905                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11906                );
11907                let is_type_only = matches!(
11908                    self.config.dialect,
11909                    Some(DialectType::Redshift)
11910                        | Some(DialectType::Spark)
11911                        | Some(DialectType::Databricks)
11912                );
11913                if is_type_only {
11914                    self.write_keyword("TYPE");
11915                    self.write_space();
11916                } else if !is_no_prefix {
11917                    self.write_keyword("SET DATA TYPE");
11918                    self.write_space();
11919                }
11920                self.generate_data_type(data_type)?;
11921                if let Some(ref collation) = collate {
11922                    self.write_space();
11923                    self.write_keyword("COLLATE");
11924                    self.write_space();
11925                    self.write(collation);
11926                }
11927                if let Some(ref using_expr) = using {
11928                    self.write_space();
11929                    self.write_keyword("USING");
11930                    self.write_space();
11931                    self.generate_expression(using_expr)?;
11932                }
11933            }
11934            AlterColumnAction::SetDefault(expr) => {
11935                self.write_keyword("SET DEFAULT");
11936                self.write_space();
11937                self.generate_expression(expr)?;
11938            }
11939            AlterColumnAction::DropDefault => {
11940                self.write_keyword("DROP DEFAULT");
11941            }
11942            AlterColumnAction::SetNotNull => {
11943                self.write_keyword("SET NOT NULL");
11944            }
11945            AlterColumnAction::DropNotNull => {
11946                self.write_keyword("DROP NOT NULL");
11947            }
11948            AlterColumnAction::Comment(comment) => {
11949                self.write_keyword("COMMENT");
11950                self.write_space();
11951                self.generate_string_literal(comment)?;
11952            }
11953            AlterColumnAction::SetVisible => {
11954                self.write_keyword("SET VISIBLE");
11955            }
11956            AlterColumnAction::SetInvisible => {
11957                self.write_keyword("SET INVISIBLE");
11958            }
11959        }
11960        Ok(())
11961    }
11962
11963    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11964        self.write_keyword("CREATE");
11965
11966        if ci.unique {
11967            self.write_space();
11968            self.write_keyword("UNIQUE");
11969        }
11970
11971        // TSQL CLUSTERED/NONCLUSTERED modifier
11972        if let Some(ref clustered) = ci.clustered {
11973            self.write_space();
11974            self.write_keyword(clustered);
11975        }
11976
11977        self.write_space();
11978        self.write_keyword("INDEX");
11979
11980        // PostgreSQL CONCURRENTLY modifier
11981        if ci.concurrently {
11982            self.write_space();
11983            self.write_keyword("CONCURRENTLY");
11984        }
11985
11986        if ci.if_not_exists {
11987            self.write_space();
11988            self.write_keyword("IF NOT EXISTS");
11989        }
11990
11991        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11992        if !ci.name.name.is_empty() {
11993            self.write_space();
11994            self.generate_identifier(&ci.name)?;
11995        }
11996        self.write_space();
11997        self.write_keyword("ON");
11998        // Hive uses ON TABLE
11999        if matches!(self.config.dialect, Some(DialectType::Hive)) {
12000            self.write_space();
12001            self.write_keyword("TABLE");
12002        }
12003        self.write_space();
12004        self.generate_table(&ci.table)?;
12005
12006        // Column list (optional for COLUMNSTORE indexes)
12007        // Standard SQL convention: ON t(a) without space before paren
12008        if !ci.columns.is_empty() || !ci.key_parts.is_empty() || ci.using.is_some() {
12009            let space_before_paren = false;
12010
12011            if let Some(ref using) = ci.using {
12012                self.write_space();
12013                self.write_keyword("USING");
12014                self.write_space();
12015                self.write(using);
12016                if space_before_paren {
12017                    self.write(" (");
12018                } else {
12019                    self.write("(");
12020                }
12021            } else {
12022                if space_before_paren {
12023                    self.write(" (");
12024                } else {
12025                    self.write("(");
12026                }
12027            }
12028            if ci.key_parts.is_empty() {
12029                for (i, col) in ci.columns.iter().enumerate() {
12030                    if i > 0 {
12031                        self.write(", ");
12032                    }
12033                    self.generate_identifier(&col.column)?;
12034                    if let Some(ref opclass) = col.opclass {
12035                        self.write_space();
12036                        self.write(opclass);
12037                    }
12038                    if col.desc {
12039                        self.write_space();
12040                        self.write_keyword("DESC");
12041                    } else if col.asc {
12042                        self.write_space();
12043                        self.write_keyword("ASC");
12044                    }
12045                    if let Some(nulls_first) = col.nulls_first {
12046                        self.write_space();
12047                        self.write_keyword("NULLS");
12048                        self.write_space();
12049                        self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
12050                    }
12051                }
12052            } else {
12053                self.generate_index_key_parts(&ci.key_parts)?;
12054            }
12055            self.write(")");
12056        }
12057
12058        // PostgreSQL INCLUDE (col1, col2) clause
12059        if !ci.include_columns.is_empty() {
12060            self.write_space();
12061            self.write_keyword("INCLUDE");
12062            self.write(" (");
12063            for (i, col) in ci.include_columns.iter().enumerate() {
12064                if i > 0 {
12065                    self.write(", ");
12066                }
12067                self.generate_identifier(col)?;
12068            }
12069            self.write(")");
12070        }
12071
12072        // TSQL: WITH (option=value, ...) clause
12073        if !ci.with_options.is_empty() {
12074            self.write_space();
12075            self.write_keyword("WITH");
12076            self.write(" (");
12077            for (i, (key, value)) in ci.with_options.iter().enumerate() {
12078                if i > 0 {
12079                    self.write(", ");
12080                }
12081                self.write(key);
12082                self.write("=");
12083                self.write(value);
12084            }
12085            self.write(")");
12086        }
12087
12088        // PostgreSQL WHERE clause for partial indexes
12089        if let Some(ref where_clause) = ci.where_clause {
12090            self.write_space();
12091            self.write_keyword("WHERE");
12092            self.write_space();
12093            self.generate_expression(where_clause)?;
12094        }
12095
12096        // TSQL: ON filegroup or partition scheme clause
12097        if let Some(ref on_fg) = ci.on_filegroup {
12098            self.write_space();
12099            self.write_keyword("ON");
12100            self.write_space();
12101            self.write(on_fg);
12102        }
12103
12104        Ok(())
12105    }
12106
12107    fn generate_index_key_parts(&mut self, key_parts: &[IndexKeyPart]) -> Result<()> {
12108        for (i, part) in key_parts.iter().enumerate() {
12109            if i > 0 {
12110                self.write(", ");
12111            }
12112            self.generate_expression(&part.expression)?;
12113            if let Some(ref prefix_length) = part.prefix_length {
12114                self.write("(");
12115                self.write(prefix_length);
12116                self.write(")");
12117            }
12118            if let Some(ref opclass) = part.opclass {
12119                self.write_space();
12120                self.write(opclass);
12121            }
12122            if part.desc {
12123                self.write_space();
12124                self.write_keyword("DESC");
12125            } else if part.asc {
12126                self.write_space();
12127                self.write_keyword("ASC");
12128            }
12129            if let Some(nulls_first) = part.nulls_first {
12130                self.write_space();
12131                self.write_keyword("NULLS");
12132                self.write_space();
12133                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
12134            }
12135        }
12136        Ok(())
12137    }
12138
12139    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
12140        self.write_keyword("DROP INDEX");
12141
12142        if di.concurrently {
12143            self.write_space();
12144            self.write_keyword("CONCURRENTLY");
12145        }
12146
12147        if di.if_exists {
12148            self.write_space();
12149            self.write_keyword("IF EXISTS");
12150        }
12151
12152        self.write_space();
12153        self.generate_table(&di.name)?;
12154
12155        if let Some(ref table) = di.table {
12156            self.write_space();
12157            self.write_keyword("ON");
12158            self.write_space();
12159            self.generate_table(table)?;
12160        }
12161
12162        Ok(())
12163    }
12164
12165    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
12166        self.write_keyword("CREATE");
12167
12168        // MySQL: ALGORITHM=...
12169        if let Some(ref algorithm) = cv.algorithm {
12170            self.write_space();
12171            self.write_keyword("ALGORITHM");
12172            self.write("=");
12173            self.write_keyword(algorithm);
12174        }
12175
12176        // MySQL: DEFINER=...
12177        if let Some(ref definer) = cv.definer {
12178            self.write_space();
12179            self.write_keyword("DEFINER");
12180            self.write("=");
12181            self.write(definer);
12182        }
12183
12184        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
12185        if cv.security_sql_style && !cv.security_after_name {
12186            if let Some(ref security) = cv.security {
12187                self.write_space();
12188                self.write_keyword("SQL SECURITY");
12189                self.write_space();
12190                match security {
12191                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12192                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12193                    FunctionSecurity::None => self.write_keyword("NONE"),
12194                }
12195            }
12196        }
12197
12198        if cv.or_alter {
12199            self.write_space();
12200            self.write_keyword("OR ALTER");
12201        } else if cv.or_replace {
12202            self.write_space();
12203            self.write_keyword("OR REPLACE");
12204        }
12205
12206        if cv.temporary {
12207            self.write_space();
12208            self.write_keyword("TEMPORARY");
12209        }
12210
12211        if cv.materialized {
12212            self.write_space();
12213            self.write_keyword("MATERIALIZED");
12214        }
12215
12216        // Snowflake: SECURE VIEW
12217        if cv.secure {
12218            self.write_space();
12219            self.write_keyword("SECURE");
12220        }
12221
12222        self.write_space();
12223        self.write_keyword("VIEW");
12224
12225        if cv.if_not_exists {
12226            self.write_space();
12227            self.write_keyword("IF NOT EXISTS");
12228        }
12229
12230        self.write_space();
12231        self.generate_table(&cv.name)?;
12232
12233        // ClickHouse: ON CLUSTER clause
12234        if let Some(ref on_cluster) = cv.on_cluster {
12235            self.write_space();
12236            self.generate_on_cluster(on_cluster)?;
12237        }
12238
12239        // ClickHouse: TO destination_table
12240        if let Some(ref to_table) = cv.to_table {
12241            self.write_space();
12242            self.write_keyword("TO");
12243            self.write_space();
12244            self.generate_table(to_table)?;
12245        }
12246
12247        // For regular VIEW: columns come before COPY GRANTS
12248        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
12249        if !cv.materialized {
12250            // Regular VIEW: columns first
12251            if let Some(ref schema) = cv.schema {
12252                self.write(" (");
12253                for (i, expr) in schema.expressions.iter().enumerate() {
12254                    if i > 0 {
12255                        self.write(", ");
12256                    }
12257                    self.generate_expression(expr)?;
12258                }
12259                self.write(")");
12260            } else if !cv.columns.is_empty() {
12261                self.write(" (");
12262                for (i, col) in cv.columns.iter().enumerate() {
12263                    if i > 0 {
12264                        self.write(", ");
12265                    }
12266                    self.generate_identifier(&col.name)?;
12267                    // BigQuery: OPTIONS (key=value, ...) on view column
12268                    if !col.options.is_empty() {
12269                        self.write_space();
12270                        self.generate_options_clause(&col.options)?;
12271                    }
12272                    if let Some(ref comment) = col.comment {
12273                        self.write_space();
12274                        self.write_keyword("COMMENT");
12275                        self.write_space();
12276                        self.generate_string_literal(comment)?;
12277                    }
12278                }
12279                self.write(")");
12280            }
12281
12282            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
12283            // Also handles SQL SECURITY after view name (security_after_name)
12284            if !cv.security_sql_style || cv.security_after_name {
12285                if let Some(ref security) = cv.security {
12286                    self.write_space();
12287                    if cv.security_sql_style {
12288                        self.write_keyword("SQL SECURITY");
12289                    } else {
12290                        self.write_keyword("SECURITY");
12291                    }
12292                    self.write_space();
12293                    match security {
12294                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12295                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12296                        FunctionSecurity::None => self.write_keyword("NONE"),
12297                    }
12298                }
12299            }
12300
12301            // Snowflake: COPY GRANTS
12302            if cv.copy_grants {
12303                self.write_space();
12304                self.write_keyword("COPY GRANTS");
12305            }
12306        } else {
12307            // MATERIALIZED VIEW: COPY GRANTS first
12308            if cv.copy_grants {
12309                self.write_space();
12310                self.write_keyword("COPY GRANTS");
12311            }
12312
12313            // Doris: If we have a schema (typed columns), generate that instead
12314            if let Some(ref schema) = cv.schema {
12315                self.write(" (");
12316                for (i, expr) in schema.expressions.iter().enumerate() {
12317                    if i > 0 {
12318                        self.write(", ");
12319                    }
12320                    self.generate_expression(expr)?;
12321                }
12322                self.write(")");
12323            } else if !cv.columns.is_empty() {
12324                // Then columns (simple column names without types)
12325                self.write(" (");
12326                for (i, col) in cv.columns.iter().enumerate() {
12327                    if i > 0 {
12328                        self.write(", ");
12329                    }
12330                    self.generate_identifier(&col.name)?;
12331                    // BigQuery: OPTIONS (key=value, ...) on view column
12332                    if !col.options.is_empty() {
12333                        self.write_space();
12334                        self.generate_options_clause(&col.options)?;
12335                    }
12336                    if let Some(ref comment) = col.comment {
12337                        self.write_space();
12338                        self.write_keyword("COMMENT");
12339                        self.write_space();
12340                        self.generate_string_literal(comment)?;
12341                    }
12342                }
12343                self.write(")");
12344            }
12345
12346            // Doris: KEY (columns) for materialized views
12347            if let Some(ref unique_key) = cv.unique_key {
12348                self.write_space();
12349                self.write_keyword("KEY");
12350                self.write(" (");
12351                for (i, expr) in unique_key.expressions.iter().enumerate() {
12352                    if i > 0 {
12353                        self.write(", ");
12354                    }
12355                    self.generate_expression(expr)?;
12356                }
12357                self.write(")");
12358            }
12359        }
12360
12361        if let Some(ref row_access_policy) = cv.row_access_policy {
12362            self.write_space();
12363            self.write_keyword("WITH");
12364            self.write_space();
12365            self.write(row_access_policy);
12366        }
12367
12368        // Snowflake: COMMENT = 'text'
12369        if let Some(ref comment) = cv.comment {
12370            self.write_space();
12371            self.write_keyword("COMMENT");
12372            self.write("=");
12373            self.generate_string_literal(comment)?;
12374        }
12375
12376        // Snowflake: TAG (name='value', ...)
12377        if !cv.tags.is_empty() {
12378            self.write_space();
12379            self.write_keyword("TAG");
12380            self.write(" (");
12381            for (i, (name, value)) in cv.tags.iter().enumerate() {
12382                if i > 0 {
12383                    self.write(", ");
12384                }
12385                self.write(name);
12386                self.write("='");
12387                self.write(value);
12388                self.write("'");
12389            }
12390            self.write(")");
12391        }
12392
12393        // BigQuery: OPTIONS (key=value, ...)
12394        if !cv.options.is_empty() {
12395            self.write_space();
12396            self.generate_options_clause(&cv.options)?;
12397        }
12398
12399        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
12400        if let Some(ref build) = cv.build {
12401            self.write_space();
12402            self.write_keyword("BUILD");
12403            self.write_space();
12404            self.write_keyword(build);
12405        }
12406
12407        // Doris: REFRESH clause for materialized views
12408        if let Some(ref refresh) = cv.refresh {
12409            self.write_space();
12410            self.generate_refresh_trigger_property(refresh)?;
12411        }
12412
12413        // Redshift: AUTO REFRESH YES|NO for materialized views
12414        if let Some(auto_refresh) = cv.auto_refresh {
12415            self.write_space();
12416            self.write_keyword("AUTO REFRESH");
12417            self.write_space();
12418            if auto_refresh {
12419                self.write_keyword("YES");
12420            } else {
12421                self.write_keyword("NO");
12422            }
12423        }
12424
12425        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
12426        for prop in &cv.table_properties {
12427            self.write_space();
12428            self.generate_expression(prop)?;
12429        }
12430
12431        // ClickHouse: POPULATE / EMPTY before AS
12432        if let Some(ref population) = cv.clickhouse_population {
12433            self.write_space();
12434            self.write_keyword(population);
12435        }
12436
12437        // Only output AS clause if there's a real query (not just NULL placeholder)
12438        if !matches!(&cv.query, Expression::Null(_)) {
12439            self.write_space();
12440            self.write_keyword("AS");
12441            self.write_space();
12442
12443            // Teradata: LOCKING clause (between AS and query)
12444            if let Some(ref mode) = cv.locking_mode {
12445                self.write_keyword("LOCKING");
12446                self.write_space();
12447                self.write_keyword(mode);
12448                if let Some(ref access) = cv.locking_access {
12449                    self.write_space();
12450                    self.write_keyword("FOR");
12451                    self.write_space();
12452                    self.write_keyword(access);
12453                }
12454                self.write_space();
12455            }
12456
12457            if cv.query_parenthesized {
12458                self.write("(");
12459            }
12460            self.generate_expression(&cv.query)?;
12461            if cv.query_parenthesized {
12462                self.write(")");
12463            }
12464        }
12465
12466        // Redshift: WITH NO SCHEMA BINDING (after query)
12467        if cv.no_schema_binding {
12468            self.write_space();
12469            self.write_keyword("WITH NO SCHEMA BINDING");
12470        }
12471
12472        Ok(())
12473    }
12474
12475    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
12476        self.write_keyword("DROP");
12477
12478        if dv.materialized {
12479            self.write_space();
12480            self.write_keyword("MATERIALIZED");
12481        }
12482
12483        self.write_space();
12484        self.write_keyword("VIEW");
12485
12486        if dv.if_exists {
12487            self.write_space();
12488            self.write_keyword("IF EXISTS");
12489        }
12490
12491        self.write_space();
12492        self.generate_table(&dv.name)?;
12493
12494        Ok(())
12495    }
12496
12497    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
12498        match tr.target {
12499            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
12500            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
12501        }
12502        if tr.if_exists {
12503            self.write_space();
12504            self.write_keyword("IF EXISTS");
12505        }
12506        self.write_space();
12507        self.generate_table(&tr.table)?;
12508
12509        // ClickHouse: ON CLUSTER clause
12510        if let Some(ref on_cluster) = tr.on_cluster {
12511            self.write_space();
12512            self.generate_on_cluster(on_cluster)?;
12513        }
12514
12515        // Check if first table has a * (multi-table with star)
12516        if !tr.extra_tables.is_empty() {
12517            // Check if the first entry matches the main table (star case)
12518            let skip_first = if let Some(first) = tr.extra_tables.first() {
12519                first.table.name == tr.table.name && first.star
12520            } else {
12521                false
12522            };
12523
12524            // PostgreSQL normalizes away the * suffix (it's the default behavior)
12525            let strip_star = matches!(
12526                self.config.dialect,
12527                Some(crate::dialects::DialectType::PostgreSQL)
12528                    | Some(crate::dialects::DialectType::Redshift)
12529            );
12530            if skip_first && !strip_star {
12531                self.write("*");
12532            }
12533
12534            // Generate additional tables
12535            for (i, entry) in tr.extra_tables.iter().enumerate() {
12536                if i == 0 && skip_first {
12537                    continue; // Already handled the star for first table
12538                }
12539                self.write(", ");
12540                self.generate_table(&entry.table)?;
12541                if entry.star && !strip_star {
12542                    self.write("*");
12543                }
12544            }
12545        }
12546
12547        // RESTART/CONTINUE IDENTITY
12548        if let Some(identity) = &tr.identity {
12549            self.write_space();
12550            match identity {
12551                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
12552                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
12553            }
12554        }
12555
12556        if tr.cascade {
12557            self.write_space();
12558            self.write_keyword("CASCADE");
12559        }
12560
12561        if tr.restrict {
12562            self.write_space();
12563            self.write_keyword("RESTRICT");
12564        }
12565
12566        // Output Hive PARTITION clause
12567        if let Some(ref partition) = tr.partition {
12568            self.write_space();
12569            self.generate_expression(partition)?;
12570        }
12571
12572        Ok(())
12573    }
12574
12575    fn generate_use(&mut self, u: &Use) -> Result<()> {
12576        // Teradata uses "DATABASE <name>" instead of "USE <name>"
12577        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
12578            self.write_keyword("DATABASE");
12579            self.write_space();
12580            self.generate_identifier(&u.this)?;
12581            return Ok(());
12582        }
12583
12584        self.write_keyword("USE");
12585
12586        if let Some(kind) = &u.kind {
12587            self.write_space();
12588            match kind {
12589                UseKind::Database => self.write_keyword("DATABASE"),
12590                UseKind::Schema => self.write_keyword("SCHEMA"),
12591                UseKind::Role => self.write_keyword("ROLE"),
12592                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
12593                UseKind::Catalog => self.write_keyword("CATALOG"),
12594                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
12595            }
12596        }
12597
12598        self.write_space();
12599        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
12600        // without quoting, since these are keywords not identifiers
12601        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
12602            self.write(&u.this.name);
12603        } else {
12604            self.generate_identifier(&u.this)?;
12605        }
12606        Ok(())
12607    }
12608
12609    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
12610        self.write_keyword("CACHE");
12611        if c.lazy {
12612            self.write_space();
12613            self.write_keyword("LAZY");
12614        }
12615        self.write_space();
12616        self.write_keyword("TABLE");
12617        self.write_space();
12618        self.generate_identifier(&c.table)?;
12619
12620        // OPTIONS clause
12621        if !c.options.is_empty() {
12622            self.write_space();
12623            self.write_keyword("OPTIONS");
12624            self.write("(");
12625            for (i, (key, value)) in c.options.iter().enumerate() {
12626                if i > 0 {
12627                    self.write(", ");
12628                }
12629                self.generate_expression(key)?;
12630                self.write(" = ");
12631                self.generate_expression(value)?;
12632            }
12633            self.write(")");
12634        }
12635
12636        // AS query
12637        if let Some(query) = &c.query {
12638            self.write_space();
12639            self.write_keyword("AS");
12640            self.write_space();
12641            self.generate_expression(query)?;
12642        }
12643
12644        Ok(())
12645    }
12646
12647    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
12648        self.write_keyword("UNCACHE TABLE");
12649        if u.if_exists {
12650            self.write_space();
12651            self.write_keyword("IF EXISTS");
12652        }
12653        self.write_space();
12654        self.generate_identifier(&u.table)?;
12655        Ok(())
12656    }
12657
12658    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
12659        self.write_keyword("LOAD DATA");
12660        if l.local {
12661            self.write_space();
12662            self.write_keyword("LOCAL");
12663        }
12664        self.write_space();
12665        self.write_keyword("INPATH");
12666        self.write_space();
12667        self.write("'");
12668        self.write(&l.inpath);
12669        self.write("'");
12670
12671        if l.overwrite {
12672            self.write_space();
12673            self.write_keyword("OVERWRITE");
12674        }
12675
12676        self.write_space();
12677        self.write_keyword("INTO TABLE");
12678        self.write_space();
12679        self.generate_expression(&l.table)?;
12680
12681        // PARTITION clause
12682        if !l.partition.is_empty() {
12683            self.write_space();
12684            self.write_keyword("PARTITION");
12685            self.write("(");
12686            for (i, (col, val)) in l.partition.iter().enumerate() {
12687                if i > 0 {
12688                    self.write(", ");
12689                }
12690                self.generate_identifier(col)?;
12691                self.write(" = ");
12692                self.generate_expression(val)?;
12693            }
12694            self.write(")");
12695        }
12696
12697        // INPUTFORMAT clause
12698        if let Some(fmt) = &l.input_format {
12699            self.write_space();
12700            self.write_keyword("INPUTFORMAT");
12701            self.write_space();
12702            self.write("'");
12703            self.write(fmt);
12704            self.write("'");
12705        }
12706
12707        // SERDE clause
12708        if let Some(serde) = &l.serde {
12709            self.write_space();
12710            self.write_keyword("SERDE");
12711            self.write_space();
12712            self.write("'");
12713            self.write(serde);
12714            self.write("'");
12715        }
12716
12717        Ok(())
12718    }
12719
12720    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
12721        self.write_keyword("PRAGMA");
12722        self.write_space();
12723
12724        // Schema prefix if present
12725        if let Some(schema) = &p.schema {
12726            self.generate_identifier(schema)?;
12727            self.write(".");
12728        }
12729
12730        // Pragma name
12731        self.generate_identifier(&p.name)?;
12732
12733        // Value assignment or function call
12734        if p.use_assignment_syntax {
12735            self.write(" = ");
12736            if let Some(value) = &p.value {
12737                self.generate_expression(value)?;
12738            } else if let Some(arg) = p.args.first() {
12739                self.generate_expression(arg)?;
12740            }
12741        } else if !p.args.is_empty() {
12742            self.write("(");
12743            for (i, arg) in p.args.iter().enumerate() {
12744                if i > 0 {
12745                    self.write(", ");
12746                }
12747                self.generate_expression(arg)?;
12748            }
12749            self.write(")");
12750        }
12751
12752        Ok(())
12753    }
12754
12755    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
12756        self.write_keyword("GRANT");
12757        self.write_space();
12758
12759        // Privileges (with optional column lists)
12760        for (i, privilege) in g.privileges.iter().enumerate() {
12761            if i > 0 {
12762                self.write(", ");
12763            }
12764            self.write_keyword(&privilege.name);
12765            // Output column list if present: SELECT(col1, col2)
12766            if !privilege.columns.is_empty() {
12767                self.write("(");
12768                for (j, col) in privilege.columns.iter().enumerate() {
12769                    if j > 0 {
12770                        self.write(", ");
12771                    }
12772                    self.write(col);
12773                }
12774                self.write(")");
12775            }
12776        }
12777
12778        self.write_space();
12779        self.write_keyword("ON");
12780        self.write_space();
12781
12782        // Object kind (TABLE, SCHEMA, etc.)
12783        if let Some(kind) = &g.kind {
12784            self.write_keyword(kind);
12785            self.write_space();
12786        }
12787
12788        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12789        {
12790            use crate::dialects::DialectType;
12791            let should_upper = matches!(
12792                self.config.dialect,
12793                Some(DialectType::PostgreSQL)
12794                    | Some(DialectType::CockroachDB)
12795                    | Some(DialectType::Materialize)
12796                    | Some(DialectType::RisingWave)
12797            ) && (g.kind.as_deref() == Some("FUNCTION")
12798                || g.kind.as_deref() == Some("PROCEDURE"));
12799            if should_upper {
12800                use crate::expressions::Identifier;
12801                let upper_id = Identifier {
12802                    name: g.securable.name.to_ascii_uppercase(),
12803                    quoted: g.securable.quoted,
12804                    ..g.securable.clone()
12805                };
12806                self.generate_identifier(&upper_id)?;
12807            } else {
12808                self.generate_identifier(&g.securable)?;
12809            }
12810        }
12811
12812        // Function parameter types (if present)
12813        if !g.function_params.is_empty() {
12814            self.write("(");
12815            for (i, param) in g.function_params.iter().enumerate() {
12816                if i > 0 {
12817                    self.write(", ");
12818                }
12819                self.write(param);
12820            }
12821            self.write(")");
12822        }
12823
12824        self.write_space();
12825        self.write_keyword("TO");
12826        self.write_space();
12827
12828        // Principals
12829        for (i, principal) in g.principals.iter().enumerate() {
12830            if i > 0 {
12831                self.write(", ");
12832            }
12833            if principal.is_role {
12834                self.write_keyword("ROLE");
12835                self.write_space();
12836            } else if principal.is_group {
12837                self.write_keyword("GROUP");
12838                self.write_space();
12839            } else if principal.is_share {
12840                self.write_keyword("SHARE");
12841                self.write_space();
12842            }
12843            self.generate_identifier(&principal.name)?;
12844        }
12845
12846        // WITH GRANT OPTION
12847        if g.grant_option {
12848            self.write_space();
12849            self.write_keyword("WITH GRANT OPTION");
12850        }
12851
12852        // TSQL: AS principal
12853        if let Some(ref principal) = g.as_principal {
12854            self.write_space();
12855            self.write_keyword("AS");
12856            self.write_space();
12857            self.generate_identifier(principal)?;
12858        }
12859
12860        Ok(())
12861    }
12862
12863    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12864        self.write_keyword("REVOKE");
12865        self.write_space();
12866
12867        // GRANT OPTION FOR
12868        if r.grant_option {
12869            self.write_keyword("GRANT OPTION FOR");
12870            self.write_space();
12871        }
12872
12873        // Privileges (with optional column lists)
12874        for (i, privilege) in r.privileges.iter().enumerate() {
12875            if i > 0 {
12876                self.write(", ");
12877            }
12878            self.write_keyword(&privilege.name);
12879            // Output column list if present: SELECT(col1, col2)
12880            if !privilege.columns.is_empty() {
12881                self.write("(");
12882                for (j, col) in privilege.columns.iter().enumerate() {
12883                    if j > 0 {
12884                        self.write(", ");
12885                    }
12886                    self.write(col);
12887                }
12888                self.write(")");
12889            }
12890        }
12891
12892        self.write_space();
12893        self.write_keyword("ON");
12894        self.write_space();
12895
12896        // Object kind
12897        if let Some(kind) = &r.kind {
12898            self.write_keyword(kind);
12899            self.write_space();
12900        }
12901
12902        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12903        {
12904            use crate::dialects::DialectType;
12905            let should_upper = matches!(
12906                self.config.dialect,
12907                Some(DialectType::PostgreSQL)
12908                    | Some(DialectType::CockroachDB)
12909                    | Some(DialectType::Materialize)
12910                    | Some(DialectType::RisingWave)
12911            ) && (r.kind.as_deref() == Some("FUNCTION")
12912                || r.kind.as_deref() == Some("PROCEDURE"));
12913            if should_upper {
12914                use crate::expressions::Identifier;
12915                let upper_id = Identifier {
12916                    name: r.securable.name.to_ascii_uppercase(),
12917                    quoted: r.securable.quoted,
12918                    ..r.securable.clone()
12919                };
12920                self.generate_identifier(&upper_id)?;
12921            } else {
12922                self.generate_identifier(&r.securable)?;
12923            }
12924        }
12925
12926        // Function parameter types (if present)
12927        if !r.function_params.is_empty() {
12928            self.write("(");
12929            for (i, param) in r.function_params.iter().enumerate() {
12930                if i > 0 {
12931                    self.write(", ");
12932                }
12933                self.write(param);
12934            }
12935            self.write(")");
12936        }
12937
12938        self.write_space();
12939        self.write_keyword("FROM");
12940        self.write_space();
12941
12942        // Principals
12943        for (i, principal) in r.principals.iter().enumerate() {
12944            if i > 0 {
12945                self.write(", ");
12946            }
12947            if principal.is_role {
12948                self.write_keyword("ROLE");
12949                self.write_space();
12950            } else if principal.is_group {
12951                self.write_keyword("GROUP");
12952                self.write_space();
12953            } else if principal.is_share {
12954                self.write_keyword("SHARE");
12955                self.write_space();
12956            }
12957            self.generate_identifier(&principal.name)?;
12958        }
12959
12960        // CASCADE or RESTRICT
12961        if r.cascade {
12962            self.write_space();
12963            self.write_keyword("CASCADE");
12964        } else if r.restrict {
12965            self.write_space();
12966            self.write_keyword("RESTRICT");
12967        }
12968
12969        Ok(())
12970    }
12971
12972    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12973        self.write_keyword("COMMENT");
12974
12975        // IF EXISTS
12976        if c.exists {
12977            self.write_space();
12978            self.write_keyword("IF EXISTS");
12979        }
12980
12981        self.write_space();
12982        self.write_keyword("ON");
12983
12984        // MATERIALIZED
12985        if c.materialized {
12986            self.write_space();
12987            self.write_keyword("MATERIALIZED");
12988        }
12989
12990        self.write_space();
12991        self.write_keyword(&c.kind);
12992        self.write_space();
12993
12994        // Object name
12995        self.generate_expression(&c.this)?;
12996
12997        self.write_space();
12998        self.write_keyword("IS");
12999        self.write_space();
13000
13001        // Comment expression
13002        self.generate_expression(&c.expression)?;
13003
13004        Ok(())
13005    }
13006
13007    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
13008        self.write_keyword("SET");
13009
13010        for (i, item) in s.items.iter().enumerate() {
13011            if i > 0 {
13012                self.write(",");
13013            }
13014            self.write_space();
13015
13016            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
13017            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
13018            if let Some(ref kind) = item.kind {
13019                // For VARIABLE kind, only output the keyword for dialects that require it
13020                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
13021                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
13022                if has_variable_kind {
13023                    if matches!(
13024                        self.config.dialect,
13025                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
13026                    ) {
13027                        self.write_keyword("VARIABLE");
13028                        self.write_space();
13029                    }
13030                } else {
13031                    self.write_keyword(kind);
13032                    self.write_space();
13033                }
13034            }
13035
13036            // Check for special SET forms by name
13037            let name_str = match &item.name {
13038                Expression::Identifier(id) => Some(id.name.as_str()),
13039                _ => None,
13040            };
13041
13042            let is_transaction = name_str == Some("TRANSACTION");
13043            let is_session_authorization = name_str == Some("SESSION AUTHORIZATION");
13044            let is_character_set = name_str == Some("CHARACTER SET");
13045            let is_names = name_str == Some("NAMES");
13046            let is_collate = name_str == Some("COLLATE");
13047            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
13048            let is_value_only =
13049                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
13050
13051            if is_session_authorization {
13052                // PostgreSQL: SET [SESSION | LOCAL] SESSION AUTHORIZATION
13053                // user_name | DEFAULT (without an equals sign).
13054                self.write_keyword("SESSION AUTHORIZATION");
13055                self.write_space();
13056                self.generate_set_value(&item.value)?;
13057            } else if is_transaction {
13058                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
13059                self.write_keyword("TRANSACTION");
13060                if let Expression::Identifier(id) = &item.value {
13061                    if !id.name.is_empty() {
13062                        self.write_space();
13063                        self.write(&id.name);
13064                    }
13065                }
13066            } else if is_character_set {
13067                // Output: SET CHARACTER SET <charset>
13068                self.write_keyword("CHARACTER SET");
13069                self.write_space();
13070                self.generate_set_value(&item.value)?;
13071            } else if is_names {
13072                // Output: SET NAMES <charset>
13073                self.write_keyword("NAMES");
13074                self.write_space();
13075                self.generate_set_value(&item.value)?;
13076            } else if is_collate {
13077                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
13078                self.write_keyword("COLLATE");
13079                self.write_space();
13080                self.generate_set_value(&item.value)?;
13081            } else if is_identity_insert {
13082                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
13083                self.write_keyword("IDENTITY_INSERT");
13084                self.write_space();
13085                self.generate_identity_insert_value(&item.value)?;
13086            } else if has_variable_kind {
13087                // Output: SET [VARIABLE] <name> = <value>
13088                // VARIABLE keyword already written above if dialect requires it
13089                if let Some(ns) = name_str {
13090                    self.write(ns);
13091                } else {
13092                    self.generate_expression(&item.name)?;
13093                }
13094                self.write(" = ");
13095                self.generate_set_value(&item.value)?;
13096            } else if is_value_only {
13097                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
13098                self.generate_expression(&item.name)?;
13099            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
13100                // SET key value without = (TSQL style)
13101                self.generate_expression(&item.name)?;
13102                self.write_space();
13103                self.generate_set_value(&item.value)?;
13104            } else {
13105                // Standard: variable = value
13106                // SET item names should not be quoted (they are config parameter names, not column refs)
13107                match &item.name {
13108                    Expression::Identifier(id) => {
13109                        self.write(&id.name);
13110                    }
13111                    _ => {
13112                        self.generate_expression(&item.name)?;
13113                    }
13114                }
13115                self.write(" = ");
13116                self.generate_set_value(&item.value)?;
13117            }
13118        }
13119
13120        Ok(())
13121    }
13122
13123    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
13124        if let Expression::Tuple(tuple) = value {
13125            if tuple.expressions.len() == 2 {
13126                self.generate_expression(&tuple.expressions[0])?;
13127                self.write_space();
13128                self.generate_set_value(&tuple.expressions[1])?;
13129                return Ok(());
13130            }
13131        }
13132
13133        self.generate_set_value(value)
13134    }
13135
13136    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
13137    /// directly to avoid reserved keyword quoting.
13138    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
13139        if let Expression::Identifier(id) = value {
13140            match id.name.as_str() {
13141                "DEFAULT" | "ON" | "OFF" => {
13142                    self.write_keyword(&id.name);
13143                    return Ok(());
13144                }
13145                _ => {}
13146            }
13147        }
13148        self.generate_expression(value)
13149    }
13150
13151    // ==================== Phase 4: Additional DDL Generation ====================
13152
13153    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
13154        self.write_keyword("ALTER");
13155        // MySQL modifiers before VIEW
13156        if let Some(ref algorithm) = av.algorithm {
13157            self.write_space();
13158            self.write_keyword("ALGORITHM");
13159            self.write(" = ");
13160            self.write_keyword(algorithm);
13161        }
13162        if let Some(ref definer) = av.definer {
13163            self.write_space();
13164            self.write_keyword("DEFINER");
13165            self.write(" = ");
13166            self.write(definer);
13167        }
13168        if let Some(ref sql_security) = av.sql_security {
13169            self.write_space();
13170            self.write_keyword("SQL SECURITY");
13171            self.write(" = ");
13172            self.write_keyword(sql_security);
13173        }
13174        self.write_space();
13175        self.write_keyword("VIEW");
13176        self.write_space();
13177        self.generate_table(&av.name)?;
13178
13179        // Hive: Column aliases with optional COMMENT
13180        if !av.columns.is_empty() {
13181            self.write(" (");
13182            for (i, col) in av.columns.iter().enumerate() {
13183                if i > 0 {
13184                    self.write(", ");
13185                }
13186                self.generate_identifier(&col.name)?;
13187                if let Some(ref comment) = col.comment {
13188                    self.write_space();
13189                    self.write_keyword("COMMENT");
13190                    self.write(" ");
13191                    self.generate_string_literal(comment)?;
13192                }
13193            }
13194            self.write(")");
13195        }
13196
13197        // TSQL: WITH option before actions
13198        if let Some(ref opt) = av.with_option {
13199            self.write_space();
13200            self.write_keyword("WITH");
13201            self.write_space();
13202            self.write_keyword(opt);
13203        }
13204
13205        for action in &av.actions {
13206            self.write_space();
13207            match action {
13208                AlterViewAction::Rename(new_name) => {
13209                    self.write_keyword("RENAME TO");
13210                    self.write_space();
13211                    self.generate_table(new_name)?;
13212                }
13213                AlterViewAction::OwnerTo(owner) => {
13214                    self.write_keyword("OWNER TO");
13215                    self.write_space();
13216                    self.generate_identifier(owner)?;
13217                }
13218                AlterViewAction::SetSchema(schema) => {
13219                    self.write_keyword("SET SCHEMA");
13220                    self.write_space();
13221                    self.generate_identifier(schema)?;
13222                }
13223                AlterViewAction::SetAuthorization(auth) => {
13224                    self.write_keyword("SET AUTHORIZATION");
13225                    self.write_space();
13226                    self.write(auth);
13227                }
13228                AlterViewAction::AlterColumn { name, action } => {
13229                    self.write_keyword("ALTER COLUMN");
13230                    self.write_space();
13231                    self.generate_identifier(name)?;
13232                    self.write_space();
13233                    self.generate_alter_column_action(action)?;
13234                }
13235                AlterViewAction::AsSelect(query) => {
13236                    self.write_keyword("AS");
13237                    self.write_space();
13238                    self.generate_expression(query)?;
13239                }
13240                AlterViewAction::SetTblproperties(props) => {
13241                    self.write_keyword("SET TBLPROPERTIES");
13242                    self.write(" (");
13243                    for (i, (key, value)) in props.iter().enumerate() {
13244                        if i > 0 {
13245                            self.write(", ");
13246                        }
13247                        self.generate_string_literal(key)?;
13248                        self.write("=");
13249                        self.generate_string_literal(value)?;
13250                    }
13251                    self.write(")");
13252                }
13253                AlterViewAction::UnsetTblproperties(keys) => {
13254                    self.write_keyword("UNSET TBLPROPERTIES");
13255                    self.write(" (");
13256                    for (i, key) in keys.iter().enumerate() {
13257                        if i > 0 {
13258                            self.write(", ");
13259                        }
13260                        self.generate_string_literal(key)?;
13261                    }
13262                    self.write(")");
13263                }
13264            }
13265        }
13266
13267        Ok(())
13268    }
13269
13270    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
13271        self.write_keyword("ALTER INDEX");
13272        self.write_space();
13273        self.generate_identifier(&ai.name)?;
13274
13275        if let Some(table) = &ai.table {
13276            self.write_space();
13277            self.write_keyword("ON");
13278            self.write_space();
13279            self.generate_table(table)?;
13280        }
13281
13282        for action in &ai.actions {
13283            self.write_space();
13284            match action {
13285                AlterIndexAction::Rename(new_name) => {
13286                    self.write_keyword("RENAME TO");
13287                    self.write_space();
13288                    self.generate_identifier(new_name)?;
13289                }
13290                AlterIndexAction::SetTablespace(tablespace) => {
13291                    self.write_keyword("SET TABLESPACE");
13292                    self.write_space();
13293                    self.generate_identifier(tablespace)?;
13294                }
13295                AlterIndexAction::Visible(visible) => {
13296                    if *visible {
13297                        self.write_keyword("VISIBLE");
13298                    } else {
13299                        self.write_keyword("INVISIBLE");
13300                    }
13301                }
13302            }
13303        }
13304
13305        Ok(())
13306    }
13307
13308    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
13309        // Output leading comments
13310        for comment in &cs.leading_comments {
13311            self.write_formatted_comment(comment);
13312            self.write_space();
13313        }
13314
13315        // Athena: CREATE SCHEMA uses Hive engine (backticks)
13316        let saved_athena_hive_context = self.athena_hive_context;
13317        if matches!(
13318            self.config.dialect,
13319            Some(crate::dialects::DialectType::Athena)
13320        ) {
13321            self.athena_hive_context = true;
13322        }
13323
13324        self.write_keyword("CREATE SCHEMA");
13325
13326        if cs.if_not_exists {
13327            self.write_space();
13328            self.write_keyword("IF NOT EXISTS");
13329        }
13330
13331        self.write_space();
13332        for (i, part) in cs.name.iter().enumerate() {
13333            if i > 0 {
13334                self.write(".");
13335            }
13336            self.generate_identifier(part)?;
13337        }
13338
13339        if let Some(ref clone_parts) = cs.clone_from {
13340            self.write_keyword(" CLONE ");
13341            for (i, part) in clone_parts.iter().enumerate() {
13342                if i > 0 {
13343                    self.write(".");
13344                }
13345                self.generate_identifier(part)?;
13346            }
13347        }
13348
13349        if let Some(ref at_clause) = cs.at_clause {
13350            self.write_space();
13351            self.generate_expression(at_clause)?;
13352        }
13353
13354        if let Some(auth) = &cs.authorization {
13355            self.write_space();
13356            self.write_keyword("AUTHORIZATION");
13357            self.write_space();
13358            self.generate_identifier(auth)?;
13359        }
13360
13361        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
13362        // Separate WITH properties from other properties
13363        let with_properties: Vec<_> = cs
13364            .properties
13365            .iter()
13366            .filter(|p| matches!(p, Expression::Property(_)))
13367            .collect();
13368        let other_properties: Vec<_> = cs
13369            .properties
13370            .iter()
13371            .filter(|p| !matches!(p, Expression::Property(_)))
13372            .collect();
13373
13374        // Generate WITH (props) if we have Property expressions
13375        if !with_properties.is_empty() {
13376            self.write_space();
13377            self.write_keyword("WITH");
13378            self.write(" (");
13379            for (i, prop) in with_properties.iter().enumerate() {
13380                if i > 0 {
13381                    self.write(", ");
13382                }
13383                self.generate_expression(prop)?;
13384            }
13385            self.write(")");
13386        }
13387
13388        // Generate other properties (like DEFAULT COLLATE)
13389        for prop in other_properties {
13390            self.write_space();
13391            self.generate_expression(prop)?;
13392        }
13393
13394        // Restore Athena Hive context
13395        self.athena_hive_context = saved_athena_hive_context;
13396
13397        Ok(())
13398    }
13399
13400    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
13401        self.write_keyword("DROP SCHEMA");
13402
13403        if ds.if_exists {
13404            self.write_space();
13405            self.write_keyword("IF EXISTS");
13406        }
13407
13408        self.write_space();
13409        self.generate_identifier(&ds.name)?;
13410
13411        if ds.cascade {
13412            self.write_space();
13413            self.write_keyword("CASCADE");
13414        }
13415
13416        Ok(())
13417    }
13418
13419    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
13420        self.write_keyword("DROP NAMESPACE");
13421
13422        if dn.if_exists {
13423            self.write_space();
13424            self.write_keyword("IF EXISTS");
13425        }
13426
13427        self.write_space();
13428        self.generate_identifier(&dn.name)?;
13429
13430        if dn.cascade {
13431            self.write_space();
13432            self.write_keyword("CASCADE");
13433        }
13434
13435        Ok(())
13436    }
13437
13438    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
13439        self.write_keyword("CREATE DATABASE");
13440
13441        if cd.if_not_exists {
13442            self.write_space();
13443            self.write_keyword("IF NOT EXISTS");
13444        }
13445
13446        self.write_space();
13447        self.generate_identifier(&cd.name)?;
13448
13449        if let Some(ref clone_src) = cd.clone_from {
13450            self.write_keyword(" CLONE ");
13451            self.generate_identifier(clone_src)?;
13452        }
13453
13454        // AT/BEFORE clause for time travel (Snowflake)
13455        if let Some(ref at_clause) = cd.at_clause {
13456            self.write_space();
13457            self.generate_expression(at_clause)?;
13458        }
13459
13460        for option in &cd.options {
13461            self.write_space();
13462            match option {
13463                DatabaseOption::CharacterSet(charset) => {
13464                    self.write_keyword("CHARACTER SET");
13465                    self.write(" = ");
13466                    self.write(&format!("'{}'", charset));
13467                }
13468                DatabaseOption::Collate(collate) => {
13469                    self.write_keyword("COLLATE");
13470                    self.write(" = ");
13471                    self.write(&format!("'{}'", collate));
13472                }
13473                DatabaseOption::Owner(owner) => {
13474                    self.write_keyword("OWNER");
13475                    self.write(" = ");
13476                    self.generate_identifier(owner)?;
13477                }
13478                DatabaseOption::Template(template) => {
13479                    self.write_keyword("TEMPLATE");
13480                    self.write(" = ");
13481                    self.generate_identifier(template)?;
13482                }
13483                DatabaseOption::Encoding(encoding) => {
13484                    self.write_keyword("ENCODING");
13485                    self.write(" = ");
13486                    self.write(&format!("'{}'", encoding));
13487                }
13488                DatabaseOption::Location(location) => {
13489                    self.write_keyword("LOCATION");
13490                    self.write(" = ");
13491                    self.write(&format!("'{}'", location));
13492                }
13493            }
13494        }
13495
13496        Ok(())
13497    }
13498
13499    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
13500        self.write_keyword("DROP DATABASE");
13501
13502        if dd.if_exists {
13503            self.write_space();
13504            self.write_keyword("IF EXISTS");
13505        }
13506
13507        self.write_space();
13508        self.generate_identifier(&dd.name)?;
13509
13510        if dd.sync {
13511            self.write_space();
13512            self.write_keyword("SYNC");
13513        }
13514
13515        Ok(())
13516    }
13517
13518    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
13519        self.write_keyword("CREATE");
13520
13521        if cf.or_alter {
13522            self.write_space();
13523            self.write_keyword("OR ALTER");
13524        } else if cf.or_replace {
13525            self.write_space();
13526            self.write_keyword("OR REPLACE");
13527        }
13528
13529        if cf.temporary {
13530            self.write_space();
13531            self.write_keyword("TEMPORARY");
13532        }
13533
13534        self.write_space();
13535        if cf.is_table_function {
13536            self.write_keyword("TABLE FUNCTION");
13537        } else {
13538            self.write_keyword("FUNCTION");
13539        }
13540
13541        if cf.if_not_exists {
13542            self.write_space();
13543            self.write_keyword("IF NOT EXISTS");
13544        }
13545
13546        self.write_space();
13547        self.generate_table(&cf.name)?;
13548        if cf.has_parens {
13549            let func_multiline = self.config.pretty
13550                && matches!(
13551                    self.config.dialect,
13552                    Some(crate::dialects::DialectType::TSQL)
13553                        | Some(crate::dialects::DialectType::Fabric)
13554                )
13555                && !cf.parameters.is_empty();
13556            if func_multiline {
13557                self.write("(\n");
13558                self.indent_level += 2;
13559                self.write_indent();
13560                self.generate_function_parameters(&cf.parameters)?;
13561                self.write("\n");
13562                self.indent_level -= 2;
13563                self.write(")");
13564            } else {
13565                self.write("(");
13566                self.generate_function_parameters(&cf.parameters)?;
13567                self.write(")");
13568            }
13569        }
13570
13571        // Output RETURNS clause (always comes first after parameters)
13572        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
13573        let use_multiline = self.config.pretty
13574            && matches!(
13575                self.config.dialect,
13576                Some(crate::dialects::DialectType::BigQuery)
13577                    | Some(crate::dialects::DialectType::TSQL)
13578                    | Some(crate::dialects::DialectType::Fabric)
13579            );
13580
13581        if cf.language_first {
13582            // LANGUAGE first, then SQL data access, then RETURNS
13583            if let Some(lang) = &cf.language {
13584                if use_multiline {
13585                    self.write_newline();
13586                } else {
13587                    self.write_space();
13588                }
13589                self.write_keyword("LANGUAGE");
13590                self.write_space();
13591                self.write(lang);
13592            }
13593
13594            // SQL data access comes after LANGUAGE in this case
13595            if let Some(sql_data) = &cf.sql_data_access {
13596                self.write_space();
13597                match sql_data {
13598                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13599                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13600                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13601                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13602                }
13603            }
13604
13605            if let Some(ref rtb) = cf.returns_table_body {
13606                if use_multiline {
13607                    self.write_newline();
13608                } else {
13609                    self.write_space();
13610                }
13611                self.write_keyword("RETURNS");
13612                self.write_space();
13613                self.write(rtb);
13614            } else if let Some(return_type) = &cf.return_type {
13615                if use_multiline {
13616                    self.write_newline();
13617                } else {
13618                    self.write_space();
13619                }
13620                self.write_keyword("RETURNS");
13621                self.write_space();
13622                self.generate_function_return_type(return_type)?;
13623            }
13624        } else {
13625            // RETURNS first (default)
13626            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
13627            let is_duckdb = matches!(
13628                self.config.dialect,
13629                Some(crate::dialects::DialectType::DuckDB)
13630            );
13631            if let Some(ref rtb) = cf.returns_table_body {
13632                if !(is_duckdb && rtb.is_empty()) {
13633                    if use_multiline {
13634                        self.write_newline();
13635                    } else {
13636                        self.write_space();
13637                    }
13638                    self.write_keyword("RETURNS");
13639                    self.write_space();
13640                    self.write(rtb);
13641                }
13642            } else if let Some(return_type) = &cf.return_type {
13643                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
13644                if !is_duckdb {
13645                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
13646                    if use_multiline {
13647                        self.write_newline();
13648                    } else {
13649                        self.write_space();
13650                    }
13651                    self.write_keyword("RETURNS");
13652                    self.write_space();
13653                    if is_table_return {
13654                        self.write_keyword("TABLE");
13655                    } else {
13656                        self.generate_function_return_type(return_type)?;
13657                    }
13658                }
13659            }
13660        }
13661
13662        // If we have property_order, use it to output properties in original order
13663        if !cf.property_order.is_empty() {
13664            // For BigQuery, OPTIONS must come before AS - reorder if needed
13665            let is_bigquery = matches!(
13666                self.config.dialect,
13667                Some(crate::dialects::DialectType::BigQuery)
13668            );
13669            let is_postgres = matches!(
13670                self.config.dialect,
13671                Some(crate::dialects::DialectType::PostgreSQL)
13672            );
13673            let property_order = if is_bigquery {
13674                // Move Options before As if both are present
13675                let mut reordered = Vec::new();
13676                let mut has_as = false;
13677                let mut has_options = false;
13678                for prop in &cf.property_order {
13679                    match prop {
13680                        FunctionPropertyKind::As => has_as = true,
13681                        FunctionPropertyKind::Options => has_options = true,
13682                        _ => {}
13683                    }
13684                }
13685                if has_as && has_options {
13686                    // Output all props except As and Options, then Options, then As
13687                    for prop in &cf.property_order {
13688                        if *prop != FunctionPropertyKind::As
13689                            && *prop != FunctionPropertyKind::Options
13690                        {
13691                            reordered.push(*prop);
13692                        }
13693                    }
13694                    reordered.push(FunctionPropertyKind::Options);
13695                    reordered.push(FunctionPropertyKind::As);
13696                    reordered
13697                } else {
13698                    cf.property_order.clone()
13699                }
13700            } else if is_postgres
13701                && cf.property_order.contains(&FunctionPropertyKind::As)
13702                && cf.property_order.contains(&FunctionPropertyKind::NullInput)
13703            {
13704                let mut reordered: Vec<_> = cf
13705                    .property_order
13706                    .iter()
13707                    .copied()
13708                    .filter(|prop| *prop != FunctionPropertyKind::As)
13709                    .collect();
13710                reordered.push(FunctionPropertyKind::As);
13711                reordered
13712            } else {
13713                cf.property_order.clone()
13714            };
13715
13716            for prop in &property_order {
13717                match prop {
13718                    FunctionPropertyKind::Set => {
13719                        self.generate_function_set_options(cf)?;
13720                    }
13721                    FunctionPropertyKind::As => {
13722                        self.generate_function_body(cf)?;
13723                    }
13724                    FunctionPropertyKind::Using => {
13725                        self.generate_function_using_resources(cf)?;
13726                    }
13727                    FunctionPropertyKind::Language => {
13728                        if !cf.language_first {
13729                            // Only output here if not already output above
13730                            if let Some(lang) = &cf.language {
13731                                // Only BigQuery uses multiline formatting
13732                                let use_multiline = self.config.pretty
13733                                    && matches!(
13734                                        self.config.dialect,
13735                                        Some(crate::dialects::DialectType::BigQuery)
13736                                    );
13737                                if use_multiline {
13738                                    self.write_newline();
13739                                } else {
13740                                    self.write_space();
13741                                }
13742                                self.write_keyword("LANGUAGE");
13743                                self.write_space();
13744                                self.write(lang);
13745                            }
13746                        }
13747                    }
13748                    FunctionPropertyKind::Determinism => {
13749                        self.generate_function_determinism(cf)?;
13750                    }
13751                    FunctionPropertyKind::NullInput => {
13752                        self.generate_function_null_input(cf)?;
13753                    }
13754                    FunctionPropertyKind::Security => {
13755                        self.generate_function_security(cf)?;
13756                    }
13757                    FunctionPropertyKind::SqlDataAccess => {
13758                        if !cf.language_first {
13759                            // Only output here if not already output above
13760                            self.generate_function_sql_data_access(cf)?;
13761                        }
13762                    }
13763                    FunctionPropertyKind::Options => {
13764                        if !cf.options.is_empty() {
13765                            self.write_space();
13766                            self.generate_options_clause(&cf.options)?;
13767                        }
13768                    }
13769                    FunctionPropertyKind::Environment => {
13770                        if !cf.environment.is_empty() {
13771                            self.write_space();
13772                            self.generate_environment_clause(&cf.environment)?;
13773                        }
13774                    }
13775                    FunctionPropertyKind::Handler => {
13776                        if let Some(ref h) = cf.handler {
13777                            self.write_space();
13778                            self.write_keyword("HANDLER");
13779                            if cf.handler_uses_eq {
13780                                self.write(" = ");
13781                            } else {
13782                                self.write_space();
13783                            }
13784                            self.write("'");
13785                            self.write(h);
13786                            self.write("'");
13787                        }
13788                    }
13789                    FunctionPropertyKind::RuntimeVersion => {
13790                        if let Some(ref runtime_version) = cf.runtime_version {
13791                            self.write_space();
13792                            self.write_keyword("RUNTIME_VERSION");
13793                            self.write("='");
13794                            self.write(runtime_version);
13795                            self.write("'");
13796                        }
13797                    }
13798                    FunctionPropertyKind::Packages => {
13799                        if let Some(ref packages) = cf.packages {
13800                            self.write_space();
13801                            self.write_keyword("PACKAGES");
13802                            self.write("=(");
13803                            for (i, package) in packages.iter().enumerate() {
13804                                if i > 0 {
13805                                    self.write(", ");
13806                                }
13807                                self.write("'");
13808                                self.write(package);
13809                                self.write("'");
13810                            }
13811                            self.write(")");
13812                        }
13813                    }
13814                    FunctionPropertyKind::ParameterStyle => {
13815                        if let Some(ref ps) = cf.parameter_style {
13816                            self.write_space();
13817                            self.write_keyword("PARAMETER STYLE");
13818                            self.write_space();
13819                            self.write_keyword(ps);
13820                        }
13821                    }
13822                }
13823            }
13824
13825            // Output OPTIONS if not tracked in property_order (legacy)
13826            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13827            {
13828                self.write_space();
13829                self.generate_options_clause(&cf.options)?;
13830            }
13831
13832            // Output ENVIRONMENT if not tracked in property_order (legacy)
13833            if !cf.environment.is_empty()
13834                && !cf
13835                    .property_order
13836                    .contains(&FunctionPropertyKind::Environment)
13837            {
13838                self.write_space();
13839                self.generate_environment_clause(&cf.environment)?;
13840            }
13841        } else {
13842            // Legacy behavior when property_order is empty
13843            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13844            if matches!(
13845                self.config.dialect,
13846                Some(crate::dialects::DialectType::BigQuery)
13847            ) {
13848                self.generate_function_determinism(cf)?;
13849            }
13850
13851            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13852            let use_multiline = self.config.pretty
13853                && matches!(
13854                    self.config.dialect,
13855                    Some(crate::dialects::DialectType::BigQuery)
13856                );
13857
13858            if !cf.language_first {
13859                if let Some(lang) = &cf.language {
13860                    if use_multiline {
13861                        self.write_newline();
13862                    } else {
13863                        self.write_space();
13864                    }
13865                    self.write_keyword("LANGUAGE");
13866                    self.write_space();
13867                    self.write(lang);
13868                }
13869
13870                // SQL data access characteristic comes after LANGUAGE
13871                self.generate_function_sql_data_access(cf)?;
13872            }
13873
13874            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13875            if !matches!(
13876                self.config.dialect,
13877                Some(crate::dialects::DialectType::BigQuery)
13878            ) {
13879                self.generate_function_determinism(cf)?;
13880            }
13881
13882            self.generate_function_null_input(cf)?;
13883            self.generate_function_security(cf)?;
13884            self.generate_function_set_options(cf)?;
13885
13886            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13887            if !cf.options.is_empty() {
13888                self.write_space();
13889                self.generate_options_clause(&cf.options)?;
13890            }
13891
13892            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13893            if !cf.environment.is_empty() {
13894                self.write_space();
13895                self.generate_environment_clause(&cf.environment)?;
13896            }
13897
13898            if let Some(ref h) = cf.handler {
13899                self.write_space();
13900                self.write_keyword("HANDLER");
13901                if cf.handler_uses_eq {
13902                    self.write(" = ");
13903                } else {
13904                    self.write_space();
13905                }
13906                self.write("'");
13907                self.write(h);
13908                self.write("'");
13909            }
13910
13911            if let Some(ref runtime_version) = cf.runtime_version {
13912                self.write_space();
13913                self.write_keyword("RUNTIME_VERSION");
13914                self.write("='");
13915                self.write(runtime_version);
13916                self.write("'");
13917            }
13918
13919            if let Some(ref packages) = cf.packages {
13920                self.write_space();
13921                self.write_keyword("PACKAGES");
13922                self.write("=(");
13923                for (i, package) in packages.iter().enumerate() {
13924                    if i > 0 {
13925                        self.write(", ");
13926                    }
13927                    self.write("'");
13928                    self.write(package);
13929                    self.write("'");
13930                }
13931                self.write(")");
13932            }
13933
13934            self.generate_function_body(cf)?;
13935            self.generate_function_using_resources(cf)?;
13936        }
13937
13938        Ok(())
13939    }
13940
13941    fn generate_function_return_type(&mut self, return_type: &DataType) -> Result<()> {
13942        if matches!(
13943            self.config.dialect,
13944            Some(crate::dialects::DialectType::PostgreSQL)
13945        ) {
13946            if let DataType::Custom { name } = return_type {
13947                if name.eq_ignore_ascii_case("integer") {
13948                    self.write_keyword("INT");
13949                    return Ok(());
13950                }
13951            }
13952        }
13953
13954        self.generate_data_type(return_type)
13955    }
13956
13957    /// Generate SET options for CREATE FUNCTION
13958    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13959        for opt in &cf.set_options {
13960            self.write_space();
13961            self.write_keyword("SET");
13962            self.write_space();
13963            self.write(&opt.name);
13964            match &opt.value {
13965                FunctionSetValue::Value { value, use_to } => {
13966                    if *use_to {
13967                        self.write(" TO ");
13968                    } else {
13969                        self.write(" = ");
13970                    }
13971                    self.write(value);
13972                }
13973                FunctionSetValue::FromCurrent => {
13974                    self.write_space();
13975                    self.write_keyword("FROM CURRENT");
13976                }
13977            }
13978        }
13979        Ok(())
13980    }
13981
13982    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13983        if cf.using_resources.is_empty() {
13984            return Ok(());
13985        }
13986
13987        self.write_space();
13988        self.write_keyword("USING");
13989        for resource in &cf.using_resources {
13990            self.write_space();
13991            self.write_keyword(&resource.kind);
13992            self.write_space();
13993            self.generate_string_literal(&resource.uri)?;
13994        }
13995        Ok(())
13996    }
13997
13998    /// Generate function body (AS clause)
13999    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
14000        if let Some(body) = &cf.body {
14001            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
14002            self.write_space();
14003            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
14004            let use_multiline = self.config.pretty
14005                && matches!(
14006                    self.config.dialect,
14007                    Some(crate::dialects::DialectType::BigQuery)
14008                );
14009            match body {
14010                FunctionBody::Block(block) => {
14011                    self.write_keyword("AS");
14012                    if matches!(
14013                        self.config.dialect,
14014                        Some(crate::dialects::DialectType::TSQL)
14015                    ) {
14016                        self.write(" BEGIN ");
14017                        self.write(block);
14018                        self.write(" END");
14019                    } else if matches!(
14020                        self.config.dialect,
14021                        Some(crate::dialects::DialectType::PostgreSQL)
14022                    ) {
14023                        self.write(" $$");
14024                        self.write(block);
14025                        self.write("$$");
14026                    } else {
14027                        // Escape content for single-quoted output
14028                        let escaped = self.escape_block_for_single_quote(block);
14029                        // In BigQuery pretty mode, body content goes on new line
14030                        if use_multiline {
14031                            self.write_newline();
14032                        } else {
14033                            self.write(" ");
14034                        }
14035                        self.write("'");
14036                        self.write(&escaped);
14037                        self.write("'");
14038                    }
14039                }
14040                FunctionBody::StringLiteral(s) => {
14041                    self.write_keyword("AS");
14042                    // In BigQuery pretty mode, body content goes on new line
14043                    if use_multiline {
14044                        self.write_newline();
14045                    } else {
14046                        self.write(" ");
14047                    }
14048                    self.generate_string_literal(s)?;
14049                }
14050                FunctionBody::Expression(expr) => {
14051                    self.write_keyword("AS");
14052                    self.write_space();
14053                    self.generate_expression(expr)?;
14054                }
14055                FunctionBody::External(name) => {
14056                    self.write_keyword("EXTERNAL NAME");
14057                    self.write(" '");
14058                    self.write(name);
14059                    self.write("'");
14060                }
14061                FunctionBody::Return(expr) => {
14062                    if matches!(
14063                        self.config.dialect,
14064                        Some(crate::dialects::DialectType::DuckDB)
14065                    ) {
14066                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
14067                        self.write_keyword("AS");
14068                        self.write_space();
14069                        // Check both returns_table_body marker and return_type = Custom "TABLE"
14070                        let is_table_return = cf.returns_table_body.is_some()
14071                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
14072                        if is_table_return {
14073                            self.write_keyword("TABLE");
14074                            self.write_space();
14075                        }
14076                        self.generate_expression(expr)?;
14077                    } else {
14078                        if self.config.create_function_return_as {
14079                            self.write_keyword("AS");
14080                            // TSQL pretty: newline between AS and RETURN
14081                            if self.config.pretty
14082                                && matches!(
14083                                    self.config.dialect,
14084                                    Some(crate::dialects::DialectType::TSQL)
14085                                        | Some(crate::dialects::DialectType::Fabric)
14086                                )
14087                            {
14088                                self.write_newline();
14089                            } else {
14090                                self.write_space();
14091                            }
14092                        }
14093                        self.write_keyword("RETURN");
14094                        self.write_space();
14095                        self.generate_expression(expr)?;
14096                    }
14097                }
14098                FunctionBody::Statements(stmts) => {
14099                    self.write_keyword("AS");
14100                    self.write(" BEGIN ");
14101                    for (i, stmt) in stmts.iter().enumerate() {
14102                        if i > 0 {
14103                            self.write(" ");
14104                        }
14105                        self.generate_expression(stmt)?;
14106                        self.write(";");
14107                    }
14108                    self.write(" END");
14109                }
14110                FunctionBody::RawBlock(text) => {
14111                    self.write_newline();
14112                    self.write(text);
14113                }
14114                FunctionBody::DollarQuoted { content, tag } => {
14115                    self.write_keyword("AS");
14116                    self.write(" ");
14117                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14118                    let supports_dollar_quoting = matches!(
14119                        self.config.dialect,
14120                        Some(crate::dialects::DialectType::PostgreSQL)
14121                            | Some(crate::dialects::DialectType::Databricks)
14122                            | Some(crate::dialects::DialectType::Redshift)
14123                            | Some(crate::dialects::DialectType::DuckDB)
14124                    );
14125                    if supports_dollar_quoting {
14126                        // Output in dollar-quoted format
14127                        self.write("$");
14128                        if let Some(t) = tag {
14129                            self.write(t);
14130                        }
14131                        self.write("$");
14132                        self.write(content);
14133                        self.write("$");
14134                        if let Some(t) = tag {
14135                            self.write(t);
14136                        }
14137                        self.write("$");
14138                    } else {
14139                        // Convert to single-quoted string for other dialects
14140                        let escaped = self.escape_block_for_single_quote(content);
14141                        self.write("'");
14142                        self.write(&escaped);
14143                        self.write("'");
14144                    }
14145                }
14146            }
14147        }
14148        Ok(())
14149    }
14150
14151    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
14152    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
14153        if let Some(det) = cf.deterministic {
14154            self.write_space();
14155            if matches!(
14156                self.config.dialect,
14157                Some(crate::dialects::DialectType::BigQuery)
14158            ) {
14159                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
14160                if det {
14161                    self.write_keyword("DETERMINISTIC");
14162                } else {
14163                    self.write_keyword("NOT DETERMINISTIC");
14164                }
14165            } else {
14166                // PostgreSQL and others use IMMUTABLE/VOLATILE
14167                if det {
14168                    self.write_keyword("IMMUTABLE");
14169                } else {
14170                    self.write_keyword("VOLATILE");
14171                }
14172            }
14173        }
14174        Ok(())
14175    }
14176
14177    /// Generate null input handling clause
14178    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
14179        if let Some(returns_null) = cf.returns_null_on_null_input {
14180            self.write_space();
14181            if returns_null {
14182                if cf.strict {
14183                    self.write_keyword("STRICT");
14184                } else {
14185                    self.write_keyword("RETURNS NULL ON NULL INPUT");
14186                }
14187            } else {
14188                self.write_keyword("CALLED ON NULL INPUT");
14189            }
14190        }
14191        Ok(())
14192    }
14193
14194    /// Generate security clause
14195    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
14196        if let Some(security) = &cf.security {
14197            self.write_space();
14198            // MySQL uses SQL SECURITY prefix
14199            if matches!(
14200                self.config.dialect,
14201                Some(crate::dialects::DialectType::MySQL)
14202            ) {
14203                self.write_keyword("SQL SECURITY");
14204            } else {
14205                self.write_keyword("SECURITY");
14206            }
14207            self.write_space();
14208            match security {
14209                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14210                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14211                FunctionSecurity::None => self.write_keyword("NONE"),
14212            }
14213        }
14214        Ok(())
14215    }
14216
14217    /// Generate SQL data access clause
14218    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
14219        if let Some(sql_data) = &cf.sql_data_access {
14220            self.write_space();
14221            match sql_data {
14222                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
14223                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
14224                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
14225                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
14226            }
14227        }
14228        Ok(())
14229    }
14230
14231    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
14232        for (i, param) in params.iter().enumerate() {
14233            if i > 0 {
14234                self.write(", ");
14235            }
14236
14237            if let Some(mode) = &param.mode {
14238                if let Some(text) = &param.mode_text {
14239                    self.write(text);
14240                } else {
14241                    match mode {
14242                        ParameterMode::In => self.write_keyword("IN"),
14243                        ParameterMode::Out => self.write_keyword("OUT"),
14244                        ParameterMode::InOut => self.write_keyword("INOUT"),
14245                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
14246                    }
14247                }
14248                self.write_space();
14249            }
14250
14251            if let Some(name) = &param.name {
14252                self.generate_identifier(name)?;
14253                // Skip space and type for empty Custom types (e.g., DuckDB macros)
14254                let skip_type =
14255                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
14256                if !skip_type {
14257                    self.write_space();
14258                    self.generate_data_type(&param.data_type)?;
14259                }
14260            } else {
14261                self.generate_data_type(&param.data_type)?;
14262            }
14263
14264            if let Some(default) = &param.default {
14265                if self.config.parameter_default_equals {
14266                    self.write(" = ");
14267                } else {
14268                    self.write(" DEFAULT ");
14269                }
14270                self.generate_expression(default)?;
14271            }
14272        }
14273
14274        Ok(())
14275    }
14276
14277    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
14278        self.write_keyword("DROP FUNCTION");
14279
14280        if df.if_exists {
14281            self.write_space();
14282            self.write_keyword("IF EXISTS");
14283        }
14284
14285        self.write_space();
14286        self.generate_table(&df.name)?;
14287
14288        if let Some(params) = &df.parameters {
14289            self.write(" (");
14290            for (i, dt) in params.iter().enumerate() {
14291                if i > 0 {
14292                    self.write(", ");
14293                }
14294                self.generate_data_type(dt)?;
14295            }
14296            self.write(")");
14297        }
14298
14299        if df.cascade {
14300            self.write_space();
14301            self.write_keyword("CASCADE");
14302        }
14303
14304        Ok(())
14305    }
14306
14307    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
14308        self.write_keyword("CREATE");
14309
14310        if cp.or_alter {
14311            self.write_space();
14312            self.write_keyword("OR ALTER");
14313        } else if cp.or_replace {
14314            self.write_space();
14315            self.write_keyword("OR REPLACE");
14316        }
14317
14318        self.write_space();
14319        if cp.use_proc_keyword {
14320            self.write_keyword("PROC");
14321        } else {
14322            self.write_keyword("PROCEDURE");
14323        }
14324
14325        if cp.if_not_exists {
14326            self.write_space();
14327            self.write_keyword("IF NOT EXISTS");
14328        }
14329
14330        self.write_space();
14331        self.generate_table(&cp.name)?;
14332        if cp.has_parens {
14333            self.write("(");
14334            self.generate_function_parameters(&cp.parameters)?;
14335            self.write(")");
14336        } else if !cp.parameters.is_empty() {
14337            // TSQL: unparenthesized parameters
14338            self.write_space();
14339            self.generate_function_parameters(&cp.parameters)?;
14340        }
14341
14342        // RETURNS clause (Snowflake)
14343        if let Some(return_type) = &cp.return_type {
14344            self.write_space();
14345            self.write_keyword("RETURNS");
14346            self.write_space();
14347            self.generate_data_type(return_type)?;
14348        }
14349
14350        // EXECUTE AS clause (Snowflake)
14351        if let Some(execute_as) = &cp.execute_as {
14352            self.write_space();
14353            self.write_keyword("EXECUTE AS");
14354            self.write_space();
14355            self.write_keyword(execute_as);
14356        }
14357
14358        if let Some(lang) = &cp.language {
14359            self.write_space();
14360            self.write_keyword("LANGUAGE");
14361            self.write_space();
14362            self.write(lang);
14363        }
14364
14365        if let Some(security) = &cp.security {
14366            self.write_space();
14367            self.write_keyword("SECURITY");
14368            self.write_space();
14369            match security {
14370                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14371                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14372                FunctionSecurity::None => self.write_keyword("NONE"),
14373            }
14374        }
14375
14376        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
14377        if !cp.with_options.is_empty() {
14378            self.write_space();
14379            self.write_keyword("WITH");
14380            self.write_space();
14381            for (i, opt) in cp.with_options.iter().enumerate() {
14382                if i > 0 {
14383                    self.write(", ");
14384                }
14385                self.write(opt);
14386            }
14387        }
14388
14389        if let Some(body) = &cp.body {
14390            self.write_space();
14391            match body {
14392                FunctionBody::Block(block) => {
14393                    self.write_keyword("AS");
14394                    if matches!(
14395                        self.config.dialect,
14396                        Some(crate::dialects::DialectType::TSQL)
14397                    ) {
14398                        self.write(" BEGIN ");
14399                        self.write(block);
14400                        self.write(" END");
14401                    } else if matches!(
14402                        self.config.dialect,
14403                        Some(crate::dialects::DialectType::PostgreSQL)
14404                    ) {
14405                        self.write(" $$");
14406                        self.write(block);
14407                        self.write("$$");
14408                    } else {
14409                        // Escape content for single-quoted output
14410                        let escaped = self.escape_block_for_single_quote(block);
14411                        self.write(" '");
14412                        self.write(&escaped);
14413                        self.write("'");
14414                    }
14415                }
14416                FunctionBody::StringLiteral(s) => {
14417                    self.write_keyword("AS");
14418                    self.write_space();
14419                    self.generate_string_literal(s)?;
14420                }
14421                FunctionBody::Expression(expr) => {
14422                    self.write_keyword("AS");
14423                    self.write_space();
14424                    self.generate_expression(expr)?;
14425                }
14426                FunctionBody::External(name) => {
14427                    self.write_keyword("EXTERNAL NAME");
14428                    self.write(" '");
14429                    self.write(name);
14430                    self.write("'");
14431                }
14432                FunctionBody::Return(expr) => {
14433                    self.write_keyword("RETURN");
14434                    self.write_space();
14435                    self.generate_expression(expr)?;
14436                }
14437                FunctionBody::Statements(stmts) => {
14438                    self.write_keyword("AS");
14439                    self.write(" BEGIN ");
14440                    for (i, stmt) in stmts.iter().enumerate() {
14441                        if i > 0 {
14442                            self.write(" ");
14443                        }
14444                        self.generate_expression(stmt)?;
14445                        self.write(";");
14446                    }
14447                    self.write(" END");
14448                }
14449                FunctionBody::RawBlock(text) => {
14450                    self.write_newline();
14451                    self.write(text);
14452                }
14453                FunctionBody::DollarQuoted { content, tag } => {
14454                    self.write_keyword("AS");
14455                    self.write(" ");
14456                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14457                    let supports_dollar_quoting = matches!(
14458                        self.config.dialect,
14459                        Some(crate::dialects::DialectType::PostgreSQL)
14460                            | Some(crate::dialects::DialectType::Databricks)
14461                            | Some(crate::dialects::DialectType::Redshift)
14462                            | Some(crate::dialects::DialectType::DuckDB)
14463                    );
14464                    if supports_dollar_quoting {
14465                        // Output in dollar-quoted format
14466                        self.write("$");
14467                        if let Some(t) = tag {
14468                            self.write(t);
14469                        }
14470                        self.write("$");
14471                        self.write(content);
14472                        self.write("$");
14473                        if let Some(t) = tag {
14474                            self.write(t);
14475                        }
14476                        self.write("$");
14477                    } else {
14478                        // Convert to single-quoted string for other dialects
14479                        let escaped = self.escape_block_for_single_quote(content);
14480                        self.write("'");
14481                        self.write(&escaped);
14482                        self.write("'");
14483                    }
14484                }
14485            }
14486        }
14487
14488        Ok(())
14489    }
14490
14491    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
14492        self.write_keyword("DROP PROCEDURE");
14493
14494        if dp.if_exists {
14495            self.write_space();
14496            self.write_keyword("IF EXISTS");
14497        }
14498
14499        self.write_space();
14500        self.generate_table(&dp.name)?;
14501
14502        if let Some(params) = &dp.parameters {
14503            self.write(" (");
14504            for (i, dt) in params.iter().enumerate() {
14505                if i > 0 {
14506                    self.write(", ");
14507                }
14508                self.generate_data_type(dt)?;
14509            }
14510            self.write(")");
14511        }
14512
14513        if dp.cascade {
14514            self.write_space();
14515            self.write_keyword("CASCADE");
14516        }
14517
14518        Ok(())
14519    }
14520
14521    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
14522        self.write_keyword("CREATE");
14523
14524        if cs.or_replace {
14525            self.write_space();
14526            self.write_keyword("OR REPLACE");
14527        }
14528
14529        if cs.temporary {
14530            self.write_space();
14531            self.write_keyword("TEMPORARY");
14532        }
14533
14534        self.write_space();
14535        self.write_keyword("SEQUENCE");
14536
14537        if cs.if_not_exists {
14538            self.write_space();
14539            self.write_keyword("IF NOT EXISTS");
14540        }
14541
14542        self.write_space();
14543        self.generate_table(&cs.name)?;
14544
14545        // Output AS <type> if present
14546        if let Some(as_type) = &cs.as_type {
14547            self.write_space();
14548            self.write_keyword("AS");
14549            self.write_space();
14550            self.generate_data_type(as_type)?;
14551        }
14552
14553        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
14554        if let Some(comment) = &cs.comment {
14555            self.write_space();
14556            self.write_keyword("COMMENT");
14557            self.write("=");
14558            self.generate_string_literal(comment)?;
14559        }
14560
14561        // If property_order is available, use it to preserve original order
14562        if !cs.property_order.is_empty() {
14563            for prop in &cs.property_order {
14564                match prop {
14565                    SeqPropKind::Start => {
14566                        if let Some(start) = cs.start {
14567                            self.write_space();
14568                            self.write_keyword("START WITH");
14569                            self.write(&format!(" {}", start));
14570                        }
14571                    }
14572                    SeqPropKind::Increment => {
14573                        if let Some(inc) = cs.increment {
14574                            self.write_space();
14575                            self.write_keyword("INCREMENT BY");
14576                            self.write(&format!(" {}", inc));
14577                        }
14578                    }
14579                    SeqPropKind::Minvalue => {
14580                        if let Some(min) = &cs.minvalue {
14581                            self.write_space();
14582                            match min {
14583                                SequenceBound::Value(v) => {
14584                                    self.write_keyword("MINVALUE");
14585                                    self.write(&format!(" {}", v));
14586                                }
14587                                SequenceBound::None => {
14588                                    self.write_keyword("NO MINVALUE");
14589                                }
14590                            }
14591                        }
14592                    }
14593                    SeqPropKind::Maxvalue => {
14594                        if let Some(max) = &cs.maxvalue {
14595                            self.write_space();
14596                            match max {
14597                                SequenceBound::Value(v) => {
14598                                    self.write_keyword("MAXVALUE");
14599                                    self.write(&format!(" {}", v));
14600                                }
14601                                SequenceBound::None => {
14602                                    self.write_keyword("NO MAXVALUE");
14603                                }
14604                            }
14605                        }
14606                    }
14607                    SeqPropKind::Cache => {
14608                        if let Some(cache) = cs.cache {
14609                            self.write_space();
14610                            self.write_keyword("CACHE");
14611                            self.write(&format!(" {}", cache));
14612                        }
14613                    }
14614                    SeqPropKind::NoCache => {
14615                        self.write_space();
14616                        self.write_keyword("NO CACHE");
14617                    }
14618                    SeqPropKind::NoCacheWord => {
14619                        self.write_space();
14620                        self.write_keyword("NOCACHE");
14621                    }
14622                    SeqPropKind::Cycle => {
14623                        self.write_space();
14624                        self.write_keyword("CYCLE");
14625                    }
14626                    SeqPropKind::NoCycle => {
14627                        self.write_space();
14628                        self.write_keyword("NO CYCLE");
14629                    }
14630                    SeqPropKind::NoCycleWord => {
14631                        self.write_space();
14632                        self.write_keyword("NOCYCLE");
14633                    }
14634                    SeqPropKind::OwnedBy => {
14635                        // Skip OWNED BY NONE (it's a no-op)
14636                        if !cs.owned_by_none {
14637                            if let Some(owned) = &cs.owned_by {
14638                                self.write_space();
14639                                self.write_keyword("OWNED BY");
14640                                self.write_space();
14641                                self.generate_table(owned)?;
14642                            }
14643                        }
14644                    }
14645                    SeqPropKind::Order => {
14646                        self.write_space();
14647                        self.write_keyword("ORDER");
14648                    }
14649                    SeqPropKind::NoOrder => {
14650                        self.write_space();
14651                        self.write_keyword("NOORDER");
14652                    }
14653                    SeqPropKind::Comment => {
14654                        // COMMENT is output above, before property_order iteration
14655                    }
14656                    SeqPropKind::Sharing => {
14657                        if let Some(val) = &cs.sharing {
14658                            self.write_space();
14659                            self.write(&format!("SHARING={}", val));
14660                        }
14661                    }
14662                    SeqPropKind::Keep => {
14663                        self.write_space();
14664                        self.write_keyword("KEEP");
14665                    }
14666                    SeqPropKind::NoKeep => {
14667                        self.write_space();
14668                        self.write_keyword("NOKEEP");
14669                    }
14670                    SeqPropKind::Scale => {
14671                        self.write_space();
14672                        self.write_keyword("SCALE");
14673                        if let Some(modifier) = &cs.scale_modifier {
14674                            if !modifier.is_empty() {
14675                                self.write_space();
14676                                self.write_keyword(modifier);
14677                            }
14678                        }
14679                    }
14680                    SeqPropKind::NoScale => {
14681                        self.write_space();
14682                        self.write_keyword("NOSCALE");
14683                    }
14684                    SeqPropKind::Shard => {
14685                        self.write_space();
14686                        self.write_keyword("SHARD");
14687                        if let Some(modifier) = &cs.shard_modifier {
14688                            if !modifier.is_empty() {
14689                                self.write_space();
14690                                self.write_keyword(modifier);
14691                            }
14692                        }
14693                    }
14694                    SeqPropKind::NoShard => {
14695                        self.write_space();
14696                        self.write_keyword("NOSHARD");
14697                    }
14698                    SeqPropKind::Session => {
14699                        self.write_space();
14700                        self.write_keyword("SESSION");
14701                    }
14702                    SeqPropKind::Global => {
14703                        self.write_space();
14704                        self.write_keyword("GLOBAL");
14705                    }
14706                    SeqPropKind::NoMinvalueWord => {
14707                        self.write_space();
14708                        self.write_keyword("NOMINVALUE");
14709                    }
14710                    SeqPropKind::NoMaxvalueWord => {
14711                        self.write_space();
14712                        self.write_keyword("NOMAXVALUE");
14713                    }
14714                }
14715            }
14716        } else {
14717            // Fallback: default order for backwards compatibility
14718            if let Some(inc) = cs.increment {
14719                self.write_space();
14720                self.write_keyword("INCREMENT BY");
14721                self.write(&format!(" {}", inc));
14722            }
14723
14724            if let Some(min) = &cs.minvalue {
14725                self.write_space();
14726                match min {
14727                    SequenceBound::Value(v) => {
14728                        self.write_keyword("MINVALUE");
14729                        self.write(&format!(" {}", v));
14730                    }
14731                    SequenceBound::None => {
14732                        self.write_keyword("NO MINVALUE");
14733                    }
14734                }
14735            }
14736
14737            if let Some(max) = &cs.maxvalue {
14738                self.write_space();
14739                match max {
14740                    SequenceBound::Value(v) => {
14741                        self.write_keyword("MAXVALUE");
14742                        self.write(&format!(" {}", v));
14743                    }
14744                    SequenceBound::None => {
14745                        self.write_keyword("NO MAXVALUE");
14746                    }
14747                }
14748            }
14749
14750            if let Some(start) = cs.start {
14751                self.write_space();
14752                self.write_keyword("START WITH");
14753                self.write(&format!(" {}", start));
14754            }
14755
14756            if let Some(cache) = cs.cache {
14757                self.write_space();
14758                self.write_keyword("CACHE");
14759                self.write(&format!(" {}", cache));
14760            }
14761
14762            if cs.cycle {
14763                self.write_space();
14764                self.write_keyword("CYCLE");
14765            }
14766
14767            if let Some(owned) = &cs.owned_by {
14768                self.write_space();
14769                self.write_keyword("OWNED BY");
14770                self.write_space();
14771                self.generate_table(owned)?;
14772            }
14773        }
14774
14775        Ok(())
14776    }
14777
14778    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
14779        self.write_keyword("DROP SEQUENCE");
14780
14781        if ds.if_exists {
14782            self.write_space();
14783            self.write_keyword("IF EXISTS");
14784        }
14785
14786        self.write_space();
14787        self.generate_table(&ds.name)?;
14788
14789        if ds.cascade {
14790            self.write_space();
14791            self.write_keyword("CASCADE");
14792        }
14793
14794        Ok(())
14795    }
14796
14797    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
14798        self.write_keyword("ALTER SEQUENCE");
14799
14800        if als.if_exists {
14801            self.write_space();
14802            self.write_keyword("IF EXISTS");
14803        }
14804
14805        self.write_space();
14806        self.generate_table(&als.name)?;
14807
14808        if let Some(inc) = als.increment {
14809            self.write_space();
14810            self.write_keyword("INCREMENT BY");
14811            self.write(&format!(" {}", inc));
14812        }
14813
14814        if let Some(min) = &als.minvalue {
14815            self.write_space();
14816            match min {
14817                SequenceBound::Value(v) => {
14818                    self.write_keyword("MINVALUE");
14819                    self.write(&format!(" {}", v));
14820                }
14821                SequenceBound::None => {
14822                    self.write_keyword("NO MINVALUE");
14823                }
14824            }
14825        }
14826
14827        if let Some(max) = &als.maxvalue {
14828            self.write_space();
14829            match max {
14830                SequenceBound::Value(v) => {
14831                    self.write_keyword("MAXVALUE");
14832                    self.write(&format!(" {}", v));
14833                }
14834                SequenceBound::None => {
14835                    self.write_keyword("NO MAXVALUE");
14836                }
14837            }
14838        }
14839
14840        if let Some(start) = als.start {
14841            self.write_space();
14842            self.write_keyword("START WITH");
14843            self.write(&format!(" {}", start));
14844        }
14845
14846        if let Some(restart) = &als.restart {
14847            self.write_space();
14848            self.write_keyword("RESTART");
14849            if let Some(val) = restart {
14850                self.write_keyword(" WITH");
14851                self.write(&format!(" {}", val));
14852            }
14853        }
14854
14855        if let Some(cache) = als.cache {
14856            self.write_space();
14857            self.write_keyword("CACHE");
14858            self.write(&format!(" {}", cache));
14859        }
14860
14861        if let Some(cycle) = als.cycle {
14862            self.write_space();
14863            if cycle {
14864                self.write_keyword("CYCLE");
14865            } else {
14866                self.write_keyword("NO CYCLE");
14867            }
14868        }
14869
14870        if let Some(owned) = &als.owned_by {
14871            self.write_space();
14872            self.write_keyword("OWNED BY");
14873            self.write_space();
14874            if let Some(table) = owned {
14875                self.generate_table(table)?;
14876            } else {
14877                self.write_keyword("NONE");
14878            }
14879        }
14880
14881        Ok(())
14882    }
14883
14884    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14885        self.write_keyword("CREATE");
14886
14887        if ct.or_alter {
14888            self.write_space();
14889            self.write_keyword("OR ALTER");
14890        } else if ct.or_replace {
14891            self.write_space();
14892            self.write_keyword("OR REPLACE");
14893        }
14894
14895        if ct.constraint {
14896            self.write_space();
14897            self.write_keyword("CONSTRAINT");
14898        }
14899
14900        self.write_space();
14901        self.write_keyword("TRIGGER");
14902        self.write_space();
14903        self.generate_identifier(&ct.name)?;
14904
14905        self.write_space();
14906        match ct.timing {
14907            TriggerTiming::Before => self.write_keyword("BEFORE"),
14908            TriggerTiming::After => self.write_keyword("AFTER"),
14909            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14910        }
14911
14912        // Events
14913        for (i, event) in ct.events.iter().enumerate() {
14914            if i > 0 {
14915                self.write_keyword(" OR");
14916            }
14917            self.write_space();
14918            match event {
14919                TriggerEvent::Insert => self.write_keyword("INSERT"),
14920                TriggerEvent::Update(cols) => {
14921                    self.write_keyword("UPDATE");
14922                    if let Some(cols) = cols {
14923                        self.write_space();
14924                        self.write_keyword("OF");
14925                        for (j, col) in cols.iter().enumerate() {
14926                            if j > 0 {
14927                                self.write(",");
14928                            }
14929                            self.write_space();
14930                            self.generate_identifier(col)?;
14931                        }
14932                    }
14933                }
14934                TriggerEvent::Delete => self.write_keyword("DELETE"),
14935                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14936            }
14937        }
14938
14939        self.write_space();
14940        self.write_keyword("ON");
14941        self.write_space();
14942        self.generate_table(&ct.table)?;
14943
14944        // Referencing clause
14945        if let Some(ref_clause) = &ct.referencing {
14946            self.write_space();
14947            self.write_keyword("REFERENCING");
14948            if let Some(old_table) = &ref_clause.old_table {
14949                self.write_space();
14950                self.write_keyword("OLD TABLE AS");
14951                self.write_space();
14952                self.generate_identifier(old_table)?;
14953            }
14954            if let Some(new_table) = &ref_clause.new_table {
14955                self.write_space();
14956                self.write_keyword("NEW TABLE AS");
14957                self.write_space();
14958                self.generate_identifier(new_table)?;
14959            }
14960            if let Some(old_row) = &ref_clause.old_row {
14961                self.write_space();
14962                self.write_keyword("OLD ROW AS");
14963                self.write_space();
14964                self.generate_identifier(old_row)?;
14965            }
14966            if let Some(new_row) = &ref_clause.new_row {
14967                self.write_space();
14968                self.write_keyword("NEW ROW AS");
14969                self.write_space();
14970                self.generate_identifier(new_row)?;
14971            }
14972        }
14973
14974        // Deferrable options for constraint triggers (must come before FOR EACH)
14975        if let Some(deferrable) = ct.deferrable {
14976            self.write_space();
14977            if deferrable {
14978                self.write_keyword("DEFERRABLE");
14979            } else {
14980                self.write_keyword("NOT DEFERRABLE");
14981            }
14982        }
14983
14984        if let Some(initially) = ct.initially_deferred {
14985            self.write_space();
14986            self.write_keyword("INITIALLY");
14987            self.write_space();
14988            if initially {
14989                self.write_keyword("DEFERRED");
14990            } else {
14991                self.write_keyword("IMMEDIATE");
14992            }
14993        }
14994
14995        if let Some(for_each) = ct.for_each {
14996            self.write_space();
14997            self.write_keyword("FOR EACH");
14998            self.write_space();
14999            match for_each {
15000                TriggerForEach::Row => self.write_keyword("ROW"),
15001                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
15002            }
15003        }
15004
15005        // When clause
15006        if let Some(when) = &ct.when {
15007            self.write_space();
15008            self.write_keyword("WHEN");
15009            if ct.when_paren {
15010                self.write(" (");
15011                self.generate_expression(when)?;
15012                self.write(")");
15013            } else {
15014                self.write_space();
15015                self.generate_expression(when)?;
15016            }
15017        }
15018
15019        // Body
15020        self.write_space();
15021        match &ct.body {
15022            TriggerBody::Execute { function, args } => {
15023                self.write_keyword("EXECUTE FUNCTION");
15024                self.write_space();
15025                self.generate_table(function)?;
15026                self.write("(");
15027                for (i, arg) in args.iter().enumerate() {
15028                    if i > 0 {
15029                        self.write(", ");
15030                    }
15031                    self.generate_expression(arg)?;
15032                }
15033                self.write(")");
15034            }
15035            TriggerBody::Block(block) => {
15036                self.write_keyword("BEGIN");
15037                self.write_space();
15038                self.write(block);
15039                self.write_space();
15040                self.write_keyword("END");
15041            }
15042        }
15043
15044        Ok(())
15045    }
15046
15047    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
15048        self.write_keyword("DROP TRIGGER");
15049
15050        if dt.if_exists {
15051            self.write_space();
15052            self.write_keyword("IF EXISTS");
15053        }
15054
15055        self.write_space();
15056        self.generate_identifier(&dt.name)?;
15057
15058        if let Some(table) = &dt.table {
15059            self.write_space();
15060            self.write_keyword("ON");
15061            self.write_space();
15062            self.generate_table(table)?;
15063        }
15064
15065        if dt.cascade {
15066            self.write_space();
15067            self.write_keyword("CASCADE");
15068        }
15069
15070        Ok(())
15071    }
15072
15073    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
15074        self.write_keyword("CREATE TYPE");
15075
15076        if ct.if_not_exists {
15077            self.write_space();
15078            self.write_keyword("IF NOT EXISTS");
15079        }
15080
15081        self.write_space();
15082        self.generate_table(&ct.name)?;
15083
15084        if let TypeDefinition::Base {
15085            input,
15086            output,
15087            internallength,
15088        } = &ct.definition
15089        {
15090            if input.is_empty() && output.is_empty() && internallength.is_none() {
15091                return Ok(());
15092            }
15093        }
15094
15095        self.write_space();
15096        self.write_keyword("AS");
15097        self.write_space();
15098
15099        match &ct.definition {
15100            TypeDefinition::Enum(values) => {
15101                self.write_keyword("ENUM");
15102                self.write(" (");
15103                for (i, val) in values.iter().enumerate() {
15104                    if i > 0 {
15105                        self.write(", ");
15106                    }
15107                    self.write(&format!("'{}'", val));
15108                }
15109                self.write(")");
15110            }
15111            TypeDefinition::Composite(attrs) => {
15112                self.write("(");
15113                for (i, attr) in attrs.iter().enumerate() {
15114                    if i > 0 {
15115                        self.write(", ");
15116                    }
15117                    self.generate_identifier(&attr.name)?;
15118                    self.write_space();
15119                    self.generate_data_type(&attr.data_type)?;
15120                    if let Some(collate) = &attr.collate {
15121                        self.write_space();
15122                        self.write_keyword("COLLATE");
15123                        self.write_space();
15124                        self.generate_identifier(collate)?;
15125                    }
15126                }
15127                self.write(")");
15128            }
15129            TypeDefinition::Range {
15130                subtype,
15131                subtype_diff,
15132                canonical,
15133            } => {
15134                self.write_keyword("RANGE");
15135                self.write(" (");
15136                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15137                    self.write("subtype");
15138                } else {
15139                    self.write_keyword("SUBTYPE");
15140                }
15141                self.write(" = ");
15142                self.generate_data_type(subtype)?;
15143                if let Some(diff) = subtype_diff {
15144                    self.write(", ");
15145                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15146                        self.write("subtype_diff");
15147                    } else {
15148                        self.write_keyword("SUBTYPE_DIFF");
15149                    }
15150                    self.write(" = ");
15151                    self.write(diff);
15152                }
15153                if let Some(canon) = canonical {
15154                    self.write(", ");
15155                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15156                        self.write("canonical");
15157                    } else {
15158                        self.write_keyword("CANONICAL");
15159                    }
15160                    self.write(" = ");
15161                    self.write(canon);
15162                }
15163                self.write(")");
15164            }
15165            TypeDefinition::Base {
15166                input,
15167                output,
15168                internallength,
15169            } => {
15170                self.write("(");
15171                self.write_keyword("INPUT");
15172                self.write(" = ");
15173                self.write(input);
15174                self.write(", ");
15175                self.write_keyword("OUTPUT");
15176                self.write(" = ");
15177                self.write(output);
15178                if let Some(len) = internallength {
15179                    self.write(", ");
15180                    self.write_keyword("INTERNALLENGTH");
15181                    self.write(" = ");
15182                    self.write(&len.to_string());
15183                }
15184                self.write(")");
15185            }
15186            TypeDefinition::Domain {
15187                base_type,
15188                default,
15189                constraints,
15190            } => {
15191                self.generate_data_type(base_type)?;
15192                if let Some(def) = default {
15193                    self.write_space();
15194                    self.write_keyword("DEFAULT");
15195                    self.write_space();
15196                    self.generate_expression(def)?;
15197                }
15198                for constr in constraints {
15199                    self.write_space();
15200                    if let Some(name) = &constr.name {
15201                        self.write_keyword("CONSTRAINT");
15202                        self.write_space();
15203                        self.generate_identifier(name)?;
15204                        self.write_space();
15205                    }
15206                    self.write_keyword("CHECK");
15207                    self.write(" (");
15208                    self.generate_expression(&constr.check)?;
15209                    self.write(")");
15210                }
15211            }
15212        }
15213
15214        Ok(())
15215    }
15216
15217    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
15218        self.write_keyword("CREATE");
15219        if task.or_replace {
15220            self.write_space();
15221            self.write_keyword("OR REPLACE");
15222        }
15223        self.write_space();
15224        self.write_keyword("TASK");
15225        if task.if_not_exists {
15226            self.write_space();
15227            self.write_keyword("IF NOT EXISTS");
15228        }
15229        self.write_space();
15230        self.write(&task.name);
15231        if !task.properties.is_empty() {
15232            // Properties already include leading whitespace from tokens_to_sql
15233            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
15234                self.write_space();
15235            }
15236            self.write(&task.properties);
15237        }
15238        self.write_space();
15239        self.write_keyword("AS");
15240        self.write_space();
15241        self.generate_expression(&task.body)?;
15242        Ok(())
15243    }
15244
15245    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
15246        self.write_keyword("BEGIN TRY");
15247        self.generate_tsql_block_statements(&try_catch.try_body)?;
15248        self.write_keyword("END TRY");
15249
15250        if let Some(catch_body) = &try_catch.catch_body {
15251            if self.config.pretty {
15252                self.write_newline();
15253                self.write_indent();
15254            } else {
15255                self.write_space();
15256            }
15257            self.write_keyword("BEGIN CATCH");
15258            self.generate_tsql_block_statements(catch_body)?;
15259            self.write_keyword("END CATCH");
15260        }
15261
15262        Ok(())
15263    }
15264
15265    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
15266        if statements.is_empty() {
15267            self.write_space();
15268            return Ok(());
15269        }
15270
15271        if self.config.pretty {
15272            self.indent_level += 1;
15273            for stmt in statements {
15274                self.write_newline();
15275                self.write_indent();
15276                self.generate_expression(stmt)?;
15277                self.write(";");
15278            }
15279            self.indent_level -= 1;
15280            self.write_newline();
15281            self.write_indent();
15282        } else {
15283            self.write_space();
15284            for (i, stmt) in statements.iter().enumerate() {
15285                if i > 0 {
15286                    self.write_space();
15287                }
15288                self.generate_expression(stmt)?;
15289                self.write(";");
15290            }
15291            self.write_space();
15292        }
15293
15294        Ok(())
15295    }
15296
15297    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
15298        self.write_keyword("DROP TYPE");
15299
15300        if dt.if_exists {
15301            self.write_space();
15302            self.write_keyword("IF EXISTS");
15303        }
15304
15305        self.write_space();
15306        self.generate_table(&dt.name)?;
15307
15308        if dt.cascade {
15309            self.write_space();
15310            self.write_keyword("CASCADE");
15311        }
15312
15313        Ok(())
15314    }
15315
15316    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
15317        // Athena: DESCRIBE uses Hive engine (backticks)
15318        let saved_athena_hive_context = self.athena_hive_context;
15319        if matches!(
15320            self.config.dialect,
15321            Some(crate::dialects::DialectType::Athena)
15322        ) {
15323            self.athena_hive_context = true;
15324        }
15325
15326        // Output leading comments before DESCRIBE
15327        for comment in &d.leading_comments {
15328            self.write_formatted_comment(comment);
15329            self.write(" ");
15330        }
15331
15332        self.write_keyword("DESCRIBE");
15333
15334        if d.extended {
15335            self.write_space();
15336            self.write_keyword("EXTENDED");
15337        } else if d.formatted {
15338            self.write_space();
15339            self.write_keyword("FORMATTED");
15340        }
15341
15342        // Output style like ANALYZE, HISTORY
15343        if let Some(ref style) = d.style {
15344            self.write_space();
15345            self.write_keyword(style);
15346        }
15347
15348        // Handle object kind (TABLE, VIEW) based on dialect
15349        let should_output_kind = match self.config.dialect {
15350            // Spark doesn't use TABLE/VIEW after DESCRIBE
15351            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
15352                false
15353            }
15354            // Snowflake always includes TABLE
15355            Some(DialectType::Snowflake) => true,
15356            _ => d.kind.is_some(),
15357        };
15358        if should_output_kind {
15359            if let Some(ref kind) = d.kind {
15360                self.write_space();
15361                self.write_keyword(kind);
15362            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15363                self.write_space();
15364                self.write_keyword("TABLE");
15365            }
15366        }
15367
15368        self.write_space();
15369        self.generate_expression(&d.target)?;
15370
15371        // Output parenthesized parameter types for PROCEDURE/FUNCTION
15372        if !d.params.is_empty() {
15373            self.write("(");
15374            for (i, param) in d.params.iter().enumerate() {
15375                if i > 0 {
15376                    self.write(", ");
15377                }
15378                self.write(param);
15379            }
15380            self.write(")");
15381        }
15382
15383        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
15384        if let Some(ref partition) = d.partition {
15385            self.write_space();
15386            self.generate_expression(partition)?;
15387        }
15388
15389        // Databricks: AS JSON
15390        if d.as_json {
15391            self.write_space();
15392            self.write_keyword("AS JSON");
15393        }
15394
15395        // Output properties like type=stage
15396        for (name, value) in &d.properties {
15397            self.write_space();
15398            self.write(name);
15399            self.write("=");
15400            self.write(value);
15401        }
15402
15403        // Restore Athena Hive context
15404        self.athena_hive_context = saved_athena_hive_context;
15405
15406        Ok(())
15407    }
15408
15409    /// Generate SHOW statement (Snowflake, MySQL, etc.)
15410    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
15411    fn generate_show(&mut self, s: &Show) -> Result<()> {
15412        self.write_keyword("SHOW");
15413        self.write_space();
15414
15415        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
15416        // where TERSE is syntactically valid but has no effect on output
15417        let show_terse = s.terse
15418            && !matches!(
15419                s.this.as_str(),
15420                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
15421            );
15422        if show_terse {
15423            self.write_keyword("TERSE");
15424            self.write_space();
15425        }
15426
15427        // Object type (USERS, TABLES, DATABASES, etc.)
15428        self.write_keyword(&s.this);
15429
15430        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
15431        if let Some(ref target_expr) = s.target {
15432            self.write_space();
15433            self.generate_expression(target_expr)?;
15434        }
15435
15436        // HISTORY keyword
15437        if s.history {
15438            self.write_space();
15439            self.write_keyword("HISTORY");
15440        }
15441
15442        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
15443        if let Some(ref for_target) = s.for_target {
15444            self.write_space();
15445            self.write_keyword("FOR");
15446            self.write_space();
15447            self.generate_expression(for_target)?;
15448        }
15449
15450        // Determine ordering based on dialect:
15451        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
15452        // - MySQL: IN, FROM, LIKE (when FROM is present)
15453        use crate::dialects::DialectType;
15454        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
15455        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
15456        let mysql_tables_scope_as_from = is_mysql
15457            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
15458            && s.scope_kind.as_deref() == Some("SCHEMA")
15459            && s.scope.is_some()
15460            && s.from.is_none();
15461
15462        if !is_snowflake && s.from.is_some() {
15463            // MySQL ordering: IN, FROM, LIKE
15464
15465            // IN scope_kind [scope]
15466            if let Some(ref scope_kind) = s.scope_kind {
15467                self.write_space();
15468                self.write_keyword("IN");
15469                self.write_space();
15470                self.write_keyword(scope_kind);
15471                if let Some(ref scope) = s.scope {
15472                    self.write_space();
15473                    self.generate_expression(scope)?;
15474                }
15475            } else if let Some(ref scope) = s.scope {
15476                self.write_space();
15477                self.write_keyword("IN");
15478                self.write_space();
15479                self.generate_expression(scope)?;
15480            }
15481
15482            // FROM clause
15483            if let Some(ref from) = s.from {
15484                self.write_space();
15485                self.write_keyword("FROM");
15486                self.write_space();
15487                self.generate_expression(from)?;
15488            }
15489
15490            // Second FROM clause (db name)
15491            if let Some(ref db) = s.db {
15492                self.write_space();
15493                self.write_keyword("FROM");
15494                self.write_space();
15495                self.generate_expression(db)?;
15496            }
15497
15498            // LIKE pattern
15499            if let Some(ref like) = s.like {
15500                self.write_space();
15501                self.write_keyword("LIKE");
15502                self.write_space();
15503                self.generate_expression(like)?;
15504            }
15505        } else {
15506            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
15507
15508            // LIKE pattern
15509            if let Some(ref like) = s.like {
15510                self.write_space();
15511                self.write_keyword("LIKE");
15512                self.write_space();
15513                self.generate_expression(like)?;
15514            }
15515
15516            // IN scope_kind [scope]
15517            if mysql_tables_scope_as_from {
15518                self.write_space();
15519                self.write_keyword("FROM");
15520                self.write_space();
15521                self.generate_expression(s.scope.as_ref().unwrap())?;
15522            } else if let Some(ref scope_kind) = s.scope_kind {
15523                self.write_space();
15524                self.write_keyword("IN");
15525                self.write_space();
15526                self.write_keyword(scope_kind);
15527                if let Some(ref scope) = s.scope {
15528                    self.write_space();
15529                    self.generate_expression(scope)?;
15530                }
15531            } else if let Some(ref scope) = s.scope {
15532                self.write_space();
15533                self.write_keyword("IN");
15534                self.write_space();
15535                self.generate_expression(scope)?;
15536            }
15537        }
15538
15539        // STARTS WITH pattern
15540        if let Some(ref starts_with) = s.starts_with {
15541            self.write_space();
15542            self.write_keyword("STARTS WITH");
15543            self.write_space();
15544            self.generate_expression(starts_with)?;
15545        }
15546
15547        // LIMIT clause
15548        if let Some(ref limit) = s.limit {
15549            self.write_space();
15550            self.generate_limit(limit)?;
15551        }
15552
15553        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
15554        if is_snowflake {
15555            if let Some(ref from) = s.from {
15556                self.write_space();
15557                self.write_keyword("FROM");
15558                self.write_space();
15559                self.generate_expression(from)?;
15560            }
15561        }
15562
15563        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
15564        if let Some(ref where_clause) = s.where_clause {
15565            self.write_space();
15566            self.write_keyword("WHERE");
15567            self.write_space();
15568            self.generate_expression(where_clause)?;
15569        }
15570
15571        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
15572        if let Some(is_mutex) = s.mutex {
15573            self.write_space();
15574            if is_mutex {
15575                self.write_keyword("MUTEX");
15576            } else {
15577                self.write_keyword("STATUS");
15578            }
15579        }
15580
15581        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
15582        if !s.privileges.is_empty() {
15583            self.write_space();
15584            self.write_keyword("WITH PRIVILEGES");
15585            self.write_space();
15586            for (i, priv_name) in s.privileges.iter().enumerate() {
15587                if i > 0 {
15588                    self.write(", ");
15589                }
15590                self.write_keyword(priv_name);
15591            }
15592        }
15593
15594        Ok(())
15595    }
15596
15597    // ==================== End DDL Generation ====================
15598
15599    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
15600        use crate::dialects::DialectType;
15601        match lit {
15602            Literal::String(s) => {
15603                self.generate_string_literal(s)?;
15604            }
15605            Literal::Number(n) => {
15606                if matches!(self.config.dialect, Some(DialectType::MySQL))
15607                    && n.len() > 2
15608                    && (n.starts_with("0x") || n.starts_with("0X"))
15609                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
15610                {
15611                    return self.generate_identifier(&Identifier {
15612                        name: n.clone(),
15613                        quoted: true,
15614                        trailing_comments: Vec::new(),
15615                        span: None,
15616                    });
15617                }
15618                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
15619                // for dialects that don't support them (MySQL interprets as identifier).
15620                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
15621                let n = if n.contains('_')
15622                    && !matches!(
15623                        self.config.dialect,
15624                        Some(DialectType::ClickHouse)
15625                            | Some(DialectType::DuckDB)
15626                            | Some(DialectType::PostgreSQL)
15627                            | Some(DialectType::Hive)
15628                            | Some(DialectType::Spark)
15629                            | Some(DialectType::Databricks)
15630                    ) {
15631                    std::borrow::Cow::Owned(n.replace('_', ""))
15632                } else {
15633                    std::borrow::Cow::Borrowed(n.as_str())
15634                };
15635                // Normalize numbers starting with decimal point to have leading zero
15636                // e.g., .25 -> 0.25 (matches sqlglot behavior)
15637                if n.starts_with('.') {
15638                    self.write("0");
15639                    self.write(&n);
15640                } else if n.starts_with("-.") {
15641                    // Handle negative numbers like -.25 -> -0.25
15642                    self.write("-0");
15643                    self.write(&n[1..]);
15644                } else {
15645                    self.write(&n);
15646                }
15647            }
15648            Literal::HexString(h) => {
15649                // Most dialects use lowercase x'...' for hex literals.
15650                match self.config.dialect {
15651                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15652                        self.write("0x");
15653                        self.write(h);
15654                        return Ok(());
15655                    }
15656                    Some(DialectType::Spark)
15657                    | Some(DialectType::Databricks)
15658                    | Some(DialectType::Teradata) => self.write("X'"),
15659                    _ => self.write("x'"),
15660                }
15661                self.write(h);
15662                self.write("'");
15663            }
15664            Literal::HexNumber(h) => {
15665                // Hex number (0xA) - integer in hex notation (from BigQuery)
15666                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
15667                // For other dialects, convert to decimal integer
15668                match self.config.dialect {
15669                    Some(DialectType::BigQuery)
15670                    | Some(DialectType::ClickHouse)
15671                    | Some(DialectType::TSQL)
15672                    | Some(DialectType::Fabric) => {
15673                        self.write("0x");
15674                        self.write(h);
15675                    }
15676                    _ => {
15677                        // Convert hex to decimal
15678                        if let Ok(val) = u64::from_str_radix(h, 16) {
15679                            self.write(&val.to_string());
15680                        } else {
15681                            // Fallback: keep as 0x notation
15682                            self.write("0x");
15683                            self.write(h);
15684                        }
15685                    }
15686                }
15687            }
15688            Literal::BitString(b) => {
15689                // Bit string B'0101...'
15690                self.write("B'");
15691                self.write(b);
15692                self.write("'");
15693            }
15694            Literal::ByteString(b) => {
15695                // Byte string b'...' (BigQuery style)
15696                self.write("b'");
15697                // Escape special characters for output
15698                self.write_escaped_byte_string(b);
15699                self.write("'");
15700            }
15701            Literal::NationalString(s) => {
15702                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
15703                // Other dialects strip the N prefix and output as regular string
15704                let keep_n_prefix = matches!(
15705                    self.config.dialect,
15706                    Some(DialectType::TSQL)
15707                        | Some(DialectType::Oracle)
15708                        | Some(DialectType::MySQL)
15709                        | None
15710                );
15711                if keep_n_prefix {
15712                    self.write("N'");
15713                } else {
15714                    self.write("'");
15715                }
15716                self.write(s);
15717                self.write("'");
15718            }
15719            Literal::Date(d) => {
15720                self.generate_date_literal(d)?;
15721            }
15722            Literal::Time(t) => {
15723                self.generate_time_literal(t)?;
15724            }
15725            Literal::Timestamp(ts) => {
15726                self.generate_timestamp_literal(ts)?;
15727            }
15728            Literal::Datetime(dt) => {
15729                self.generate_datetime_literal(dt)?;
15730            }
15731            Literal::TripleQuotedString(s, _quote_char) => {
15732                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
15733                if matches!(
15734                    self.config.dialect,
15735                    Some(crate::dialects::DialectType::BigQuery)
15736                        | Some(crate::dialects::DialectType::DuckDB)
15737                        | Some(crate::dialects::DialectType::Snowflake)
15738                        | Some(crate::dialects::DialectType::Spark)
15739                        | Some(crate::dialects::DialectType::Hive)
15740                        | Some(crate::dialects::DialectType::Presto)
15741                        | Some(crate::dialects::DialectType::Trino)
15742                        | Some(crate::dialects::DialectType::PostgreSQL)
15743                        | Some(crate::dialects::DialectType::MySQL)
15744                        | Some(crate::dialects::DialectType::Redshift)
15745                        | Some(crate::dialects::DialectType::TSQL)
15746                        | Some(crate::dialects::DialectType::Oracle)
15747                        | Some(crate::dialects::DialectType::ClickHouse)
15748                        | Some(crate::dialects::DialectType::Databricks)
15749                        | Some(crate::dialects::DialectType::SQLite)
15750                ) {
15751                    self.generate_string_literal(s)?;
15752                } else {
15753                    // Preserve triple-quoted string syntax for generic/unknown dialects
15754                    let quotes = format!("{0}{0}{0}", _quote_char);
15755                    self.write(&quotes);
15756                    self.write(s);
15757                    self.write(&quotes);
15758                }
15759            }
15760            Literal::EscapeString(s) => {
15761                // PostgreSQL escape string: e'...' or E'...'
15762                // Token text format is "e:content" or "E:content"
15763                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
15764                use crate::dialects::DialectType;
15765                let content = if let Some(c) = s.strip_prefix("e:") {
15766                    c
15767                } else if let Some(c) = s.strip_prefix("E:") {
15768                    c
15769                } else {
15770                    s.as_str()
15771                };
15772
15773                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
15774                if matches!(
15775                    self.config.dialect,
15776                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
15777                ) {
15778                    self.write("'");
15779                    self.write(&content.replace('\'', "''"));
15780                    self.write("'");
15781                } else {
15782                    // Some dialects use lowercase e' prefix
15783                    let prefix = if matches!(
15784                        self.config.dialect,
15785                        Some(DialectType::SingleStore)
15786                            | Some(DialectType::DuckDB)
15787                            | Some(DialectType::PostgreSQL)
15788                            | Some(DialectType::CockroachDB)
15789                            | Some(DialectType::Materialize)
15790                            | Some(DialectType::RisingWave)
15791                    ) {
15792                        "e'"
15793                    } else {
15794                        "E'"
15795                    };
15796
15797                    // Normalize \' to '' for output
15798                    let normalized = content.replace("\\'", "''");
15799                    self.write(prefix);
15800                    self.write(&normalized);
15801                    self.write("'");
15802                }
15803            }
15804            Literal::DollarString(s) => {
15805                // Convert dollar-quoted strings to single-quoted strings
15806                // (like Python sqlglot's rawstring_sql)
15807                use crate::dialects::DialectType;
15808                // Extract content from tag\x00content format
15809                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
15810                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
15811                let escape_backslash = matches!(
15812                    self.config.dialect,
15813                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
15814                );
15815                // Step 2: Determine quote escaping style
15816                // Snowflake: ' -> \' (backslash escape)
15817                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
15818                let use_backslash_quote =
15819                    matches!(self.config.dialect, Some(DialectType::Snowflake));
15820
15821                let mut escaped = String::with_capacity(content.len() + 4);
15822                for ch in content.chars() {
15823                    if escape_backslash && ch == '\\' {
15824                        // Escape backslash first (before quote escaping)
15825                        escaped.push('\\');
15826                        escaped.push('\\');
15827                    } else if ch == '\'' {
15828                        if use_backslash_quote {
15829                            escaped.push('\\');
15830                            escaped.push('\'');
15831                        } else {
15832                            escaped.push('\'');
15833                            escaped.push('\'');
15834                        }
15835                    } else {
15836                        escaped.push(ch);
15837                    }
15838                }
15839                self.write("'");
15840                self.write(&escaped);
15841                self.write("'");
15842            }
15843            Literal::RawString(s) => {
15844                // Raw strings (r"..." or r'...') contain literal backslashes.
15845                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15846                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15847                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15848                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15849                use crate::dialects::DialectType;
15850
15851                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15852                let escape_backslash = matches!(
15853                    self.config.dialect,
15854                    Some(DialectType::BigQuery)
15855                        | Some(DialectType::MySQL)
15856                        | Some(DialectType::SingleStore)
15857                        | Some(DialectType::TiDB)
15858                        | Some(DialectType::Hive)
15859                        | Some(DialectType::Spark)
15860                        | Some(DialectType::Databricks)
15861                        | Some(DialectType::Drill)
15862                        | Some(DialectType::Snowflake)
15863                        | Some(DialectType::Redshift)
15864                        | Some(DialectType::ClickHouse)
15865                );
15866
15867                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15868                // These escape quotes as \' instead of ''
15869                let backslash_escapes_quote = matches!(
15870                    self.config.dialect,
15871                    Some(DialectType::BigQuery)
15872                        | Some(DialectType::Hive)
15873                        | Some(DialectType::Spark)
15874                        | Some(DialectType::Databricks)
15875                        | Some(DialectType::Drill)
15876                        | Some(DialectType::Snowflake)
15877                        | Some(DialectType::Redshift)
15878                );
15879
15880                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15881                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15882                let supports_escape_sequences = escape_backslash;
15883
15884                let mut escaped = String::with_capacity(s.len() + 4);
15885                for ch in s.chars() {
15886                    if escape_backslash && ch == '\\' {
15887                        // Double the backslash for the target dialect
15888                        escaped.push('\\');
15889                        escaped.push('\\');
15890                    } else if ch == '\'' {
15891                        if backslash_escapes_quote {
15892                            // Use backslash to escape the quote: \'
15893                            escaped.push('\\');
15894                            escaped.push('\'');
15895                        } else {
15896                            // Use SQL standard quote doubling: ''
15897                            escaped.push('\'');
15898                            escaped.push('\'');
15899                        }
15900                    } else if supports_escape_sequences {
15901                        // Apply ESCAPED_SEQUENCES mapping for special chars
15902                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15903                        match ch {
15904                            '\n' => {
15905                                escaped.push('\\');
15906                                escaped.push('n');
15907                            }
15908                            '\r' => {
15909                                escaped.push('\\');
15910                                escaped.push('r');
15911                            }
15912                            '\t' => {
15913                                escaped.push('\\');
15914                                escaped.push('t');
15915                            }
15916                            '\x07' => {
15917                                escaped.push('\\');
15918                                escaped.push('a');
15919                            }
15920                            '\x08' => {
15921                                escaped.push('\\');
15922                                escaped.push('b');
15923                            }
15924                            '\x0C' => {
15925                                escaped.push('\\');
15926                                escaped.push('f');
15927                            }
15928                            '\x0B' => {
15929                                escaped.push('\\');
15930                                escaped.push('v');
15931                            }
15932                            _ => escaped.push(ch),
15933                        }
15934                    } else {
15935                        escaped.push(ch);
15936                    }
15937                }
15938                self.write("'");
15939                self.write(&escaped);
15940                self.write("'");
15941            }
15942        }
15943        Ok(())
15944    }
15945
15946    /// Generate a DATE literal with dialect-specific formatting
15947    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15948        use crate::dialects::DialectType;
15949
15950        match self.config.dialect {
15951            // SQL Server / Fabric use CONVERT or CAST
15952            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15953                self.write("CAST('");
15954                self.write(d);
15955                self.write("' AS DATE)");
15956            }
15957            // BigQuery uses CAST syntax for type literals
15958            // DATE 'value' -> CAST('value' AS DATE)
15959            Some(DialectType::BigQuery) => {
15960                self.write("CAST('");
15961                self.write(d);
15962                self.write("' AS DATE)");
15963            }
15964            // Exasol uses CAST syntax for DATE literals
15965            // DATE 'value' -> CAST('value' AS DATE)
15966            Some(DialectType::Exasol) => {
15967                self.write("CAST('");
15968                self.write(d);
15969                self.write("' AS DATE)");
15970            }
15971            // Snowflake uses CAST syntax for DATE literals
15972            // DATE 'value' -> CAST('value' AS DATE)
15973            Some(DialectType::Snowflake) => {
15974                self.write("CAST('");
15975                self.write(d);
15976                self.write("' AS DATE)");
15977            }
15978            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15979            Some(DialectType::PostgreSQL)
15980            | Some(DialectType::MySQL)
15981            | Some(DialectType::SingleStore)
15982            | Some(DialectType::TiDB)
15983            | Some(DialectType::Redshift) => {
15984                self.write("CAST('");
15985                self.write(d);
15986                self.write("' AS DATE)");
15987            }
15988            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15989            Some(DialectType::DuckDB)
15990            | Some(DialectType::Presto)
15991            | Some(DialectType::Trino)
15992            | Some(DialectType::Athena)
15993            | Some(DialectType::Spark)
15994            | Some(DialectType::Databricks)
15995            | Some(DialectType::Hive) => {
15996                self.write("CAST('");
15997                self.write(d);
15998                self.write("' AS DATE)");
15999            }
16000            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
16001            Some(DialectType::Oracle) => {
16002                self.write("TO_DATE('");
16003                self.write(d);
16004                self.write("', 'YYYY-MM-DD')");
16005            }
16006            // Standard SQL: DATE '...'
16007            _ => {
16008                self.write_keyword("DATE");
16009                self.write(" '");
16010                self.write(d);
16011                self.write("'");
16012            }
16013        }
16014        Ok(())
16015    }
16016
16017    /// Generate a TIME literal with dialect-specific formatting
16018    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
16019        use crate::dialects::DialectType;
16020
16021        match self.config.dialect {
16022            // SQL Server uses CONVERT or CAST
16023            Some(DialectType::TSQL) => {
16024                self.write("CAST('");
16025                self.write(t);
16026                self.write("' AS TIME)");
16027            }
16028            Some(DialectType::Fabric) => {
16029                self.write("CAST('");
16030                self.write(t);
16031                self.write("' AS TIME(6))");
16032            }
16033            // Standard SQL: TIME '...'
16034            _ => {
16035                self.write_keyword("TIME");
16036                self.write(" '");
16037                self.write(t);
16038                self.write("'");
16039            }
16040        }
16041        Ok(())
16042    }
16043
16044    /// Generate a date expression for Dremio, converting DATE literals to CAST
16045    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
16046        use crate::expressions::Literal;
16047
16048        match expr {
16049            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
16050                let Literal::Date(d) = lit.as_ref() else {
16051                    unreachable!()
16052                };
16053                // DATE 'value' -> CAST('value' AS DATE)
16054                self.write("CAST('");
16055                self.write(d);
16056                self.write("' AS DATE)");
16057            }
16058            _ => {
16059                // For all other expressions, generate normally
16060                self.generate_expression(expr)?;
16061            }
16062        }
16063        Ok(())
16064    }
16065
16066    /// Generate a TIMESTAMP literal with dialect-specific formatting
16067    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
16068        use crate::dialects::DialectType;
16069
16070        match self.config.dialect {
16071            // SQL Server uses CONVERT or CAST
16072            Some(DialectType::TSQL) => {
16073                self.write("CAST('");
16074                self.write(ts);
16075                self.write("' AS DATETIME2)");
16076            }
16077            // BigQuery uses CAST syntax for type literals
16078            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16079            Some(DialectType::BigQuery) => {
16080                self.write("CAST('");
16081                self.write(ts);
16082                self.write("' AS TIMESTAMP)");
16083            }
16084            // Snowflake uses CAST syntax for TIMESTAMP literals
16085            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16086            Some(DialectType::Snowflake) => {
16087                self.write("CAST('");
16088                self.write(ts);
16089                self.write("' AS TIMESTAMP)");
16090            }
16091            // Dremio uses CAST syntax for TIMESTAMP literals
16092            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16093            Some(DialectType::Dremio) => {
16094                self.write("CAST('");
16095                self.write(ts);
16096                self.write("' AS TIMESTAMP)");
16097            }
16098            // Exasol uses CAST syntax for TIMESTAMP literals
16099            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16100            Some(DialectType::Exasol) => {
16101                self.write("CAST('");
16102                self.write(ts);
16103                self.write("' AS TIMESTAMP)");
16104            }
16105            // Oracle prefers TO_TIMESTAMP function call
16106            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
16107            Some(DialectType::Oracle) => {
16108                self.write("TO_TIMESTAMP('");
16109                self.write(ts);
16110                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
16111            }
16112            // Presto/Trino: always use CAST for TIMESTAMP literals
16113            Some(DialectType::Presto) | Some(DialectType::Trino) => {
16114                if Self::timestamp_has_timezone(ts) {
16115                    self.write("CAST('");
16116                    self.write(ts);
16117                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
16118                } else {
16119                    self.write("CAST('");
16120                    self.write(ts);
16121                    self.write("' AS TIMESTAMP)");
16122                }
16123            }
16124            // ClickHouse: CAST('...' AS Nullable(DateTime))
16125            Some(DialectType::ClickHouse) => {
16126                self.write("CAST('");
16127                self.write(ts);
16128                self.write("' AS Nullable(DateTime))");
16129            }
16130            // Spark: CAST('...' AS TIMESTAMP)
16131            Some(DialectType::Spark) => {
16132                self.write("CAST('");
16133                self.write(ts);
16134                self.write("' AS TIMESTAMP)");
16135            }
16136            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
16137            // but TIMESTAMP '...' for special values like 'epoch'
16138            Some(DialectType::Redshift) => {
16139                if ts == "epoch" {
16140                    self.write_keyword("TIMESTAMP");
16141                    self.write(" '");
16142                    self.write(ts);
16143                    self.write("'");
16144                } else {
16145                    self.write("CAST('");
16146                    self.write(ts);
16147                    self.write("' AS TIMESTAMP)");
16148                }
16149            }
16150            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
16151            Some(DialectType::PostgreSQL)
16152            | Some(DialectType::Hive)
16153            | Some(DialectType::SQLite)
16154            | Some(DialectType::DuckDB)
16155            | Some(DialectType::Athena)
16156            | Some(DialectType::Drill)
16157            | Some(DialectType::Teradata) => {
16158                self.write("CAST('");
16159                self.write(ts);
16160                self.write("' AS TIMESTAMP)");
16161            }
16162            // MySQL/StarRocks: CAST('...' AS DATETIME)
16163            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
16164                self.write("CAST('");
16165                self.write(ts);
16166                self.write("' AS DATETIME)");
16167            }
16168            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
16169            Some(DialectType::Databricks) => {
16170                self.write("CAST('");
16171                self.write(ts);
16172                self.write("' AS TIMESTAMP_NTZ)");
16173            }
16174            // Standard SQL: TIMESTAMP '...'
16175            _ => {
16176                self.write_keyword("TIMESTAMP");
16177                self.write(" '");
16178                self.write(ts);
16179                self.write("'");
16180            }
16181        }
16182        Ok(())
16183    }
16184
16185    /// Check if a timestamp string contains a timezone identifier
16186    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
16187    fn timestamp_has_timezone(ts: &str) -> bool {
16188        // Check for common IANA timezone patterns: Continent/City format
16189        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
16190        // Also handles: UTC, GMT, Etc/GMT+0, etc.
16191        let ts_lower = ts.to_ascii_lowercase();
16192
16193        // Check for Continent/City pattern (most common)
16194        let continent_prefixes = [
16195            "africa/",
16196            "america/",
16197            "antarctica/",
16198            "arctic/",
16199            "asia/",
16200            "atlantic/",
16201            "australia/",
16202            "europe/",
16203            "indian/",
16204            "pacific/",
16205            "etc/",
16206            "brazil/",
16207            "canada/",
16208            "chile/",
16209            "mexico/",
16210            "us/",
16211        ];
16212
16213        for prefix in &continent_prefixes {
16214            if ts_lower.contains(prefix) {
16215                return true;
16216            }
16217        }
16218
16219        // Check for standalone timezone abbreviations at the end
16220        // These typically appear after the time portion
16221        let tz_abbrevs = [
16222            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
16223            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
16224            " sgt", " aest", " aedt", " acst", " acdt", " awst",
16225        ];
16226
16227        for abbrev in &tz_abbrevs {
16228            if ts_lower.ends_with(abbrev) {
16229                return true;
16230            }
16231        }
16232
16233        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
16234        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
16235        // Look for pattern: space followed by + or - and digits (optionally with :)
16236        let trimmed = ts.trim();
16237        if let Some(last_space) = trimmed.rfind(' ') {
16238            let suffix = &trimmed[last_space + 1..];
16239            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
16240                // Check if rest is numeric (possibly with : for hh:mm format)
16241                let rest = &suffix[1..];
16242                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
16243                    return true;
16244                }
16245            }
16246        }
16247
16248        false
16249    }
16250
16251    /// Generate a DATETIME literal with dialect-specific formatting
16252    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
16253        use crate::dialects::DialectType;
16254
16255        match self.config.dialect {
16256            // BigQuery uses CAST syntax for type literals
16257            // DATETIME 'value' -> CAST('value' AS DATETIME)
16258            Some(DialectType::BigQuery) => {
16259                self.write("CAST('");
16260                self.write(dt);
16261                self.write("' AS DATETIME)");
16262            }
16263            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
16264            Some(DialectType::DuckDB) => {
16265                self.write("CAST('");
16266                self.write(dt);
16267                self.write("' AS TIMESTAMP)");
16268            }
16269            // DATETIME is primarily a BigQuery type
16270            // Output as DATETIME '...' for dialects that support it
16271            _ => {
16272                self.write_keyword("DATETIME");
16273                self.write(" '");
16274                self.write(dt);
16275                self.write("'");
16276            }
16277        }
16278        Ok(())
16279    }
16280
16281    /// Generate a string literal with dialect-specific escaping
16282    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
16283        use crate::dialects::DialectType;
16284
16285        match self.config.dialect {
16286            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
16287            // and backslash escaping for special characters like newlines
16288            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
16289            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
16290                // Hive/Spark use backslash escaping for quotes (\') and special chars
16291                self.write("'");
16292                for c in s.chars() {
16293                    match c {
16294                        '\'' => self.write("\\'"),
16295                        '\\' => self.write("\\\\"),
16296                        '\n' => self.write("\\n"),
16297                        '\r' => self.write("\\r"),
16298                        '\t' => self.write("\\t"),
16299                        '\0' => self.write("\\0"),
16300                        _ => self.output.push(c),
16301                    }
16302                }
16303                self.write("'");
16304            }
16305            Some(DialectType::Drill) => {
16306                // Drill uses SQL-standard quote doubling ('') for quotes,
16307                // but backslash escaping for special characters
16308                self.write("'");
16309                for c in s.chars() {
16310                    match c {
16311                        '\'' => self.write("''"),
16312                        '\\' => self.write("\\\\"),
16313                        '\n' => self.write("\\n"),
16314                        '\r' => self.write("\\r"),
16315                        '\t' => self.write("\\t"),
16316                        '\0' => self.write("\\0"),
16317                        _ => self.output.push(c),
16318                    }
16319                }
16320                self.write("'");
16321            }
16322            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
16323                self.write("'");
16324                for c in s.chars() {
16325                    match c {
16326                        // MySQL uses SQL standard quote doubling
16327                        '\'' => self.write("''"),
16328                        '\\' => self.write("\\\\"),
16329                        '\n' => self.write("\\n"),
16330                        '\r' => self.write("\\r"),
16331                        '\t' => self.write("\\t"),
16332                        // sqlglot writes a literal NUL for this case
16333                        '\0' => self.output.push('\0'),
16334                        _ => self.output.push(c),
16335                    }
16336                }
16337                self.write("'");
16338            }
16339            // BigQuery: Uses backslash escaping
16340            Some(DialectType::BigQuery) => {
16341                self.write("'");
16342                for c in s.chars() {
16343                    match c {
16344                        '\'' => self.write("\\'"),
16345                        '\\' => self.write("\\\\"),
16346                        '\n' => self.write("\\n"),
16347                        '\r' => self.write("\\r"),
16348                        '\t' => self.write("\\t"),
16349                        '\0' => self.write("\\0"),
16350                        '\x07' => self.write("\\a"),
16351                        '\x08' => self.write("\\b"),
16352                        '\x0C' => self.write("\\f"),
16353                        '\x0B' => self.write("\\v"),
16354                        _ => self.output.push(c),
16355                    }
16356                }
16357                self.write("'");
16358            }
16359            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
16360            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
16361            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
16362            Some(DialectType::Athena) => {
16363                if self.athena_hive_context {
16364                    // Hive-style: backslash escaping
16365                    self.write("'");
16366                    for c in s.chars() {
16367                        match c {
16368                            '\'' => self.write("\\'"),
16369                            '\\' => self.write("\\\\"),
16370                            '\n' => self.write("\\n"),
16371                            '\r' => self.write("\\r"),
16372                            '\t' => self.write("\\t"),
16373                            '\0' => self.write("\\0"),
16374                            _ => self.output.push(c),
16375                        }
16376                    }
16377                    self.write("'");
16378                } else {
16379                    // Trino-style: SQL-standard escaping, preserve backslashes
16380                    self.write("'");
16381                    for c in s.chars() {
16382                        match c {
16383                            '\'' => self.write("''"),
16384                            // Preserve backslashes literally (no re-escaping)
16385                            _ => self.output.push(c),
16386                        }
16387                    }
16388                    self.write("'");
16389                }
16390            }
16391            // Snowflake uses backslash escaping (STRING_ESCAPES = ["\\", "'"]).
16392            Some(DialectType::Snowflake) => {
16393                self.write("'");
16394                for c in s.chars() {
16395                    match c {
16396                        '\'' => self.write("\\'"),
16397                        '\\' => self.write("\\\\"),
16398                        '\0' => self.write("\\x00"),
16399                        '\x08' => self.write("\\b"),
16400                        '\x0C' => self.write("\\f"),
16401                        '\n' => self.write("\\n"),
16402                        '\r' => self.write("\\r"),
16403                        '\t' => self.write("\\t"),
16404                        _ => self.output.push(c),
16405                    }
16406                }
16407                self.write("'");
16408            }
16409            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
16410            Some(DialectType::PostgreSQL) => {
16411                self.write("'");
16412                for c in s.chars() {
16413                    match c {
16414                        '\'' => self.write("''"),
16415                        _ => self.output.push(c),
16416                    }
16417                }
16418                self.write("'");
16419            }
16420            // Redshift: Uses backslash escaping for single quotes
16421            Some(DialectType::Redshift) => {
16422                self.write("'");
16423                for c in s.chars() {
16424                    match c {
16425                        '\'' => self.write("\\'"),
16426                        _ => self.output.push(c),
16427                    }
16428                }
16429                self.write("'");
16430            }
16431            // Oracle: Uses standard double single-quote escaping
16432            Some(DialectType::Oracle) => {
16433                self.write("'");
16434                for ch in s.chars() {
16435                    if ch == '\'' {
16436                        self.output.push_str("''");
16437                    } else {
16438                        self.output.push(ch);
16439                    }
16440                }
16441                self.write("'");
16442            }
16443            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
16444            // backslash escaping for backslashes and special characters
16445            Some(DialectType::ClickHouse) => {
16446                self.write("'");
16447                for c in s.chars() {
16448                    match c {
16449                        '\'' => self.write("''"),
16450                        '\\' => self.write("\\\\"),
16451                        '\n' => self.write("\\n"),
16452                        '\r' => self.write("\\r"),
16453                        '\t' => self.write("\\t"),
16454                        '\0' => self.write("\\0"),
16455                        '\x07' => self.write("\\a"),
16456                        '\x08' => self.write("\\b"),
16457                        '\x0C' => self.write("\\f"),
16458                        '\x0B' => self.write("\\v"),
16459                        // Non-printable characters: emit as \xNN hex escapes
16460                        c if c.is_control() || (c as u32) < 0x20 => {
16461                            let byte = c as u32;
16462                            if byte < 256 {
16463                                self.write(&format!("\\x{:02X}", byte));
16464                            } else {
16465                                self.output.push(c);
16466                            }
16467                        }
16468                        _ => self.output.push(c),
16469                    }
16470                }
16471                self.write("'");
16472            }
16473            // Default: SQL standard double single quotes (works for most dialects)
16474            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
16475            _ => {
16476                self.write("'");
16477                for ch in s.chars() {
16478                    if ch == '\'' {
16479                        self.output.push_str("''");
16480                    } else {
16481                        self.output.push(ch);
16482                    }
16483                }
16484                self.write("'");
16485            }
16486        }
16487        Ok(())
16488    }
16489
16490    /// Write a byte string with proper escaping for BigQuery-style byte literals
16491    /// Escapes characters as \xNN hex escapes where needed
16492    fn write_escaped_byte_string(&mut self, s: &str) {
16493        for c in s.chars() {
16494            match c {
16495                // Escape single quotes
16496                '\'' => self.write("\\'"),
16497                // Escape backslashes
16498                '\\' => self.write("\\\\"),
16499                // Keep all printable characters (including non-ASCII) as-is
16500                _ if !c.is_control() => self.output.push(c),
16501                // Escape control characters as hex
16502                _ => {
16503                    let byte = c as u32;
16504                    if byte < 256 {
16505                        self.write(&format!("\\x{:02x}", byte));
16506                    } else {
16507                        // For unicode characters, write each UTF-8 byte
16508                        for b in c.to_string().as_bytes() {
16509                            self.write(&format!("\\x{:02x}", b));
16510                        }
16511                    }
16512                }
16513            }
16514        }
16515    }
16516
16517    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
16518        use crate::dialects::DialectType;
16519
16520        // Different dialects have different boolean literal formats
16521        match self.config.dialect {
16522            // SQL Server typically uses 1/0 for boolean literals in many contexts
16523            // However, TRUE/FALSE also works in modern versions
16524            Some(DialectType::TSQL) => {
16525                self.write(if b.value { "1" } else { "0" });
16526            }
16527            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
16528            Some(DialectType::Oracle) => {
16529                self.write(if b.value { "1" } else { "0" });
16530            }
16531            // MySQL accepts TRUE/FALSE as aliases for 1/0
16532            Some(DialectType::MySQL) => {
16533                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16534            }
16535            // Most other dialects support TRUE/FALSE
16536            _ => {
16537                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16538            }
16539        }
16540        Ok(())
16541    }
16542
16543    /// Generate an identifier that's used as an alias name
16544    /// This quotes reserved keywords in addition to already-quoted identifiers
16545    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
16546        let name = &id.name;
16547        let quote_style = &self.config.identifier_quote_style;
16548
16549        // For aliases, quote if:
16550        // 1. The identifier was explicitly quoted in the source
16551        // 2. The identifier is a reserved keyword for the current dialect
16552        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
16553
16554        // Normalize identifier if configured
16555        let output_name = if self.config.normalize_identifiers && !id.quoted {
16556            name.to_ascii_lowercase()
16557        } else {
16558            name.to_string()
16559        };
16560
16561        if needs_quoting {
16562            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
16563                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16564                && quote_style.start == '"'
16565                && output_name.contains('"')
16566            {
16567                &IdentifierQuoteStyle::BACKTICK
16568            } else {
16569                quote_style
16570            };
16571            // Escape any quote characters within the identifier
16572            let escaped_name = if quote_style.start == quote_style.end {
16573                output_name.replace(
16574                    quote_style.end,
16575                    &format!("{}{}", quote_style.end, quote_style.end),
16576                )
16577            } else {
16578                output_name.replace(
16579                    quote_style.end,
16580                    &format!("{}{}", quote_style.end, quote_style.end),
16581                )
16582            };
16583            self.write(&format!(
16584                "{}{}{}",
16585                quote_style.start, escaped_name, quote_style.end
16586            ));
16587        } else {
16588            self.write(&output_name);
16589        }
16590
16591        // Output trailing comments
16592        for comment in &id.trailing_comments {
16593            self.write(" ");
16594            self.write_formatted_comment(comment);
16595        }
16596        Ok(())
16597    }
16598
16599    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
16600        use crate::dialects::DialectType;
16601
16602        let name = &id.name;
16603
16604        // For Athena, use backticks in Hive context, double quotes in Trino context
16605        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
16606            && self.athena_hive_context
16607        {
16608            &IdentifierQuoteStyle::BACKTICK
16609        } else {
16610            &self.config.identifier_quote_style
16611        };
16612
16613        // Quote if:
16614        // 1. The identifier was explicitly quoted in the source
16615        // 2. The identifier is a reserved keyword for the current dialect
16616        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
16617        // This matches Python sqlglot's identifier_sql behavior
16618        // Also quote identifiers starting with digits if the target dialect doesn't support them
16619        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
16620        let needs_digit_quoting = starts_with_digit
16621            && !self.config.identifiers_can_start_with_digit
16622            && self.config.dialect.is_some();
16623        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
16624            && name.len() > 2
16625            && (name.starts_with("0x") || name.starts_with("0X"))
16626            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
16627        let clickhouse_unsafe_identifier =
16628            matches!(self.config.dialect, Some(DialectType::ClickHouse))
16629                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16630                && !name.starts_with('{')
16631                && !name.contains('(')
16632                && !name.contains(')')
16633                && name != "?"
16634                && name
16635                    .chars()
16636                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
16637        let needs_quoting = id.quoted
16638            || self.is_reserved_keyword(name)
16639            || self.config.always_quote_identifiers
16640            || needs_digit_quoting
16641            || mysql_invalid_hex_identifier
16642            || clickhouse_unsafe_identifier;
16643
16644        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
16645        // When quoted, we need to output `name`(16) not `name(16)`
16646        let (base_name, suffix) = if needs_quoting {
16647            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
16648            if let Some(paren_pos) = name.find('(') {
16649                let base = &name[..paren_pos];
16650                let rest = &name[paren_pos..];
16651                // Verify it looks like (digits) or (digits) ASC/DESC
16652                if rest.starts_with('(')
16653                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
16654                {
16655                    // Check if content between parens is all digits
16656                    let close_paren = rest.find(')').unwrap_or(rest.len());
16657                    let inside = &rest[1..close_paren];
16658                    if inside.chars().all(|c| c.is_ascii_digit()) {
16659                        (base.to_string(), rest.to_string())
16660                    } else {
16661                        (name.to_string(), String::new())
16662                    }
16663                } else {
16664                    (name.to_string(), String::new())
16665                }
16666            } else if name.ends_with(" ASC") {
16667                let base = &name[..name.len() - 4];
16668                (base.to_string(), " ASC".to_string())
16669            } else if name.ends_with(" DESC") {
16670                let base = &name[..name.len() - 5];
16671                (base.to_string(), " DESC".to_string())
16672            } else {
16673                (name.to_string(), String::new())
16674            }
16675        } else {
16676            (name.to_string(), String::new())
16677        };
16678
16679        // Normalize identifier if configured, with special handling for Exasol
16680        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
16681        // should be uppercased when not already quoted (to match Python sqlglot behavior)
16682        let output_name = if self.config.normalize_identifiers && !id.quoted {
16683            base_name.to_ascii_lowercase()
16684        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
16685            && !id.quoted
16686            && self.is_reserved_keyword(name)
16687        {
16688            // Exasol: uppercase reserved keywords when quoting them
16689            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
16690            base_name.to_ascii_uppercase()
16691        } else {
16692            base_name
16693        };
16694
16695        if needs_quoting {
16696            // Escape any quote characters within the identifier
16697            let escaped_name = if quote_style.start == quote_style.end {
16698                // Same start/end char (e.g., " or `) - double the quote char
16699                output_name.replace(
16700                    quote_style.end,
16701                    &format!("{}{}", quote_style.end, quote_style.end),
16702                )
16703            } else {
16704                // Different start/end (e.g., [ and ]) - escape only the end char
16705                output_name.replace(
16706                    quote_style.end,
16707                    &format!("{}{}", quote_style.end, quote_style.end),
16708                )
16709            };
16710            self.write(&format!(
16711                "{}{}{}{}",
16712                quote_style.start, escaped_name, quote_style.end, suffix
16713            ));
16714        } else {
16715            self.write(&output_name);
16716        }
16717
16718        // Output trailing comments
16719        for comment in &id.trailing_comments {
16720            self.write(" ");
16721            self.write_formatted_comment(comment);
16722        }
16723        Ok(())
16724    }
16725
16726    fn generate_column(&mut self, col: &Column) -> Result<()> {
16727        use crate::dialects::DialectType;
16728
16729        if let Some(table) = &col.table {
16730            // Exasol special case: LOCAL as column table prefix should NOT be quoted
16731            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
16732            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
16733            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
16734                && !table.quoted
16735                && table.name.eq_ignore_ascii_case("LOCAL");
16736
16737            if is_exasol_local_prefix {
16738                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
16739                self.write("LOCAL");
16740            } else {
16741                self.generate_identifier(table)?;
16742            }
16743            self.write(".");
16744        }
16745        let postgres_qualified_keyword = col.table.is_some()
16746            && matches!(self.config.dialect, Some(DialectType::PostgreSQL))
16747            && !col.name.quoted
16748            && matches!(
16749                col.name.name.to_ascii_uppercase().as_str(),
16750                "NULL" | "TRUE" | "FALSE"
16751            );
16752        if postgres_qualified_keyword {
16753            self.write(&col.name.name);
16754        } else {
16755            self.generate_identifier(&col.name)?;
16756        }
16757        // Oracle-style join marker (+)
16758        // Only output if dialect supports it (Oracle, Exasol)
16759        if col.join_mark && self.config.supports_column_join_marks {
16760            self.write(" (+)");
16761        }
16762        // Output trailing comments
16763        for comment in &col.trailing_comments {
16764            self.write_space();
16765            self.write_formatted_comment(comment);
16766        }
16767        Ok(())
16768    }
16769
16770    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
16771        self.write_keyword("PREPARE");
16772        self.write_space();
16773        self.generate_identifier(&prepare.name)?;
16774
16775        if !prepare.parameter_types.is_empty() {
16776            self.write(" (");
16777            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
16778                if i > 0 {
16779                    self.write(", ");
16780                }
16781                self.generate_data_type(data_type)?;
16782            }
16783            self.write(")");
16784        }
16785
16786        self.write_space();
16787        self.write_keyword("AS");
16788        self.write_space();
16789        self.generate_expression(&prepare.statement)
16790    }
16791
16792    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
16793    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
16794    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
16795        use crate::dialects::DialectType;
16796        use crate::expressions::PseudocolumnType;
16797
16798        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
16799        if pc.kind == PseudocolumnType::Sysdate
16800            && !matches!(
16801                self.config.dialect,
16802                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
16803            )
16804        {
16805            self.write_keyword("CURRENT_TIMESTAMP");
16806            // Add () for dialects that expect it
16807            if matches!(
16808                self.config.dialect,
16809                Some(DialectType::MySQL)
16810                    | Some(DialectType::ClickHouse)
16811                    | Some(DialectType::Spark)
16812                    | Some(DialectType::Databricks)
16813                    | Some(DialectType::Hive)
16814            ) {
16815                self.write("()");
16816            }
16817        } else {
16818            self.write(pc.kind.as_str());
16819        }
16820        Ok(())
16821    }
16822
16823    /// Generate CONNECT BY clause (Oracle hierarchical queries)
16824    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
16825        use crate::dialects::DialectType;
16826
16827        // Generate native CONNECT BY for Oracle and Snowflake
16828        // For other dialects, add a comment noting manual conversion needed
16829        let supports_connect_by = matches!(
16830            self.config.dialect,
16831            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
16832        );
16833
16834        if !supports_connect_by && self.config.dialect.is_some() {
16835            // Add comment for unsupported dialects
16836            if self.config.pretty {
16837                self.write_newline();
16838            } else {
16839                self.write_space();
16840            }
16841            self.write_unsupported_comment(
16842                "CONNECT BY requires manual conversion to recursive CTE",
16843            )?;
16844        }
16845
16846        // Generate START WITH if present (before CONNECT BY)
16847        if let Some(start) = &connect.start {
16848            if self.config.pretty {
16849                self.write_newline();
16850            } else {
16851                self.write_space();
16852            }
16853            self.write_keyword("START WITH");
16854            self.write_space();
16855            self.generate_expression(start)?;
16856        }
16857
16858        // Generate CONNECT BY
16859        if self.config.pretty {
16860            self.write_newline();
16861        } else {
16862            self.write_space();
16863        }
16864        self.write_keyword("CONNECT BY");
16865        if connect.nocycle {
16866            self.write_space();
16867            self.write_keyword("NOCYCLE");
16868        }
16869        self.write_space();
16870        self.generate_expression(&connect.connect)?;
16871
16872        Ok(())
16873    }
16874
16875    /// Generate Connect expression (for Expression::Connect variant)
16876    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16877        self.generate_connect(connect)
16878    }
16879
16880    /// Generate PRIOR expression
16881    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16882        self.write_keyword("PRIOR");
16883        self.write_space();
16884        self.generate_expression(&prior.this)?;
16885        Ok(())
16886    }
16887
16888    /// Generate CONNECT_BY_ROOT function
16889    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16890    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16891        self.write_keyword("CONNECT_BY_ROOT");
16892        self.write_space();
16893        self.generate_expression(&cbr.this)?;
16894        Ok(())
16895    }
16896
16897    /// Generate MATCH_RECOGNIZE clause
16898    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16899        use crate::dialects::DialectType;
16900
16901        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16902        let supports_match_recognize = matches!(
16903            self.config.dialect,
16904            Some(DialectType::Oracle)
16905                | Some(DialectType::Snowflake)
16906                | Some(DialectType::Presto)
16907                | Some(DialectType::Trino)
16908        );
16909
16910        // Generate the source table first
16911        if let Some(source) = &mr.this {
16912            self.generate_expression(source)?;
16913        }
16914
16915        if !supports_match_recognize {
16916            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16917            return Ok(());
16918        }
16919
16920        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16921        if self.config.pretty {
16922            self.write_newline();
16923        } else {
16924            self.write_space();
16925        }
16926
16927        self.write_keyword("MATCH_RECOGNIZE");
16928        self.write(" (");
16929
16930        if self.config.pretty {
16931            self.indent_level += 1;
16932        }
16933
16934        let mut needs_separator = false;
16935
16936        // PARTITION BY
16937        if let Some(partition_by) = &mr.partition_by {
16938            if !partition_by.is_empty() {
16939                if self.config.pretty {
16940                    self.write_newline();
16941                    self.write_indent();
16942                }
16943                self.write_keyword("PARTITION BY");
16944                self.write_space();
16945                for (i, expr) in partition_by.iter().enumerate() {
16946                    if i > 0 {
16947                        self.write(", ");
16948                    }
16949                    self.generate_expression(expr)?;
16950                }
16951                needs_separator = true;
16952            }
16953        }
16954
16955        // ORDER BY
16956        if let Some(order_by) = &mr.order_by {
16957            if !order_by.is_empty() {
16958                if needs_separator {
16959                    if self.config.pretty {
16960                        self.write_newline();
16961                        self.write_indent();
16962                    } else {
16963                        self.write_space();
16964                    }
16965                } else if self.config.pretty {
16966                    self.write_newline();
16967                    self.write_indent();
16968                }
16969                self.write_keyword("ORDER BY");
16970                // In pretty mode, put each ORDER BY column on a new indented line
16971                if self.config.pretty {
16972                    self.indent_level += 1;
16973                    for (i, ordered) in order_by.iter().enumerate() {
16974                        if i > 0 {
16975                            self.write(",");
16976                        }
16977                        self.write_newline();
16978                        self.write_indent();
16979                        self.generate_ordered(ordered)?;
16980                    }
16981                    self.indent_level -= 1;
16982                } else {
16983                    self.write_space();
16984                    for (i, ordered) in order_by.iter().enumerate() {
16985                        if i > 0 {
16986                            self.write(", ");
16987                        }
16988                        self.generate_ordered(ordered)?;
16989                    }
16990                }
16991                needs_separator = true;
16992            }
16993        }
16994
16995        // MEASURES
16996        if let Some(measures) = &mr.measures {
16997            if !measures.is_empty() {
16998                if needs_separator {
16999                    if self.config.pretty {
17000                        self.write_newline();
17001                        self.write_indent();
17002                    } else {
17003                        self.write_space();
17004                    }
17005                } else if self.config.pretty {
17006                    self.write_newline();
17007                    self.write_indent();
17008                }
17009                self.write_keyword("MEASURES");
17010                // In pretty mode, put each MEASURE on a new indented line
17011                if self.config.pretty {
17012                    self.indent_level += 1;
17013                    for (i, measure) in measures.iter().enumerate() {
17014                        if i > 0 {
17015                            self.write(",");
17016                        }
17017                        self.write_newline();
17018                        self.write_indent();
17019                        // Handle RUNNING/FINAL prefix
17020                        if let Some(semantics) = &measure.window_frame {
17021                            match semantics {
17022                                MatchRecognizeSemantics::Running => {
17023                                    self.write_keyword("RUNNING");
17024                                    self.write_space();
17025                                }
17026                                MatchRecognizeSemantics::Final => {
17027                                    self.write_keyword("FINAL");
17028                                    self.write_space();
17029                                }
17030                            }
17031                        }
17032                        self.generate_expression(&measure.this)?;
17033                    }
17034                    self.indent_level -= 1;
17035                } else {
17036                    self.write_space();
17037                    for (i, measure) in measures.iter().enumerate() {
17038                        if i > 0 {
17039                            self.write(", ");
17040                        }
17041                        // Handle RUNNING/FINAL prefix
17042                        if let Some(semantics) = &measure.window_frame {
17043                            match semantics {
17044                                MatchRecognizeSemantics::Running => {
17045                                    self.write_keyword("RUNNING");
17046                                    self.write_space();
17047                                }
17048                                MatchRecognizeSemantics::Final => {
17049                                    self.write_keyword("FINAL");
17050                                    self.write_space();
17051                                }
17052                            }
17053                        }
17054                        self.generate_expression(&measure.this)?;
17055                    }
17056                }
17057                needs_separator = true;
17058            }
17059        }
17060
17061        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
17062        if let Some(rows) = &mr.rows {
17063            if needs_separator {
17064                if self.config.pretty {
17065                    self.write_newline();
17066                    self.write_indent();
17067                } else {
17068                    self.write_space();
17069                }
17070            } else if self.config.pretty {
17071                self.write_newline();
17072                self.write_indent();
17073            }
17074            match rows {
17075                MatchRecognizeRows::OneRowPerMatch => {
17076                    self.write_keyword("ONE ROW PER MATCH");
17077                }
17078                MatchRecognizeRows::AllRowsPerMatch => {
17079                    self.write_keyword("ALL ROWS PER MATCH");
17080                }
17081                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
17082                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
17083                }
17084                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
17085                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
17086                }
17087                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
17088                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
17089                }
17090            }
17091            needs_separator = true;
17092        }
17093
17094        // AFTER MATCH SKIP
17095        if let Some(after) = &mr.after {
17096            if needs_separator {
17097                if self.config.pretty {
17098                    self.write_newline();
17099                    self.write_indent();
17100                } else {
17101                    self.write_space();
17102                }
17103            } else if self.config.pretty {
17104                self.write_newline();
17105                self.write_indent();
17106            }
17107            match after {
17108                MatchRecognizeAfter::PastLastRow => {
17109                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
17110                }
17111                MatchRecognizeAfter::ToNextRow => {
17112                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
17113                }
17114                MatchRecognizeAfter::ToFirst(ident) => {
17115                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
17116                    self.write_space();
17117                    self.generate_identifier(ident)?;
17118                }
17119                MatchRecognizeAfter::ToLast(ident) => {
17120                    self.write_keyword("AFTER MATCH SKIP TO LAST");
17121                    self.write_space();
17122                    self.generate_identifier(ident)?;
17123                }
17124            }
17125            needs_separator = true;
17126        }
17127
17128        // PATTERN
17129        if let Some(pattern) = &mr.pattern {
17130            if needs_separator {
17131                if self.config.pretty {
17132                    self.write_newline();
17133                    self.write_indent();
17134                } else {
17135                    self.write_space();
17136                }
17137            } else if self.config.pretty {
17138                self.write_newline();
17139                self.write_indent();
17140            }
17141            self.write_keyword("PATTERN");
17142            self.write_space();
17143            self.write("(");
17144            self.write(pattern);
17145            self.write(")");
17146            needs_separator = true;
17147        }
17148
17149        // DEFINE
17150        if let Some(define) = &mr.define {
17151            if !define.is_empty() {
17152                if needs_separator {
17153                    if self.config.pretty {
17154                        self.write_newline();
17155                        self.write_indent();
17156                    } else {
17157                        self.write_space();
17158                    }
17159                } else if self.config.pretty {
17160                    self.write_newline();
17161                    self.write_indent();
17162                }
17163                self.write_keyword("DEFINE");
17164                // In pretty mode, put each DEFINE on a new indented line
17165                if self.config.pretty {
17166                    self.indent_level += 1;
17167                    for (i, (name, expr)) in define.iter().enumerate() {
17168                        if i > 0 {
17169                            self.write(",");
17170                        }
17171                        self.write_newline();
17172                        self.write_indent();
17173                        self.generate_identifier(name)?;
17174                        self.write(" AS ");
17175                        self.generate_expression(expr)?;
17176                    }
17177                    self.indent_level -= 1;
17178                } else {
17179                    self.write_space();
17180                    for (i, (name, expr)) in define.iter().enumerate() {
17181                        if i > 0 {
17182                            self.write(", ");
17183                        }
17184                        self.generate_identifier(name)?;
17185                        self.write(" AS ");
17186                        self.generate_expression(expr)?;
17187                    }
17188                }
17189            }
17190        }
17191
17192        if self.config.pretty {
17193            self.indent_level -= 1;
17194            self.write_newline();
17195        }
17196        self.write(")");
17197
17198        // Alias - only include AS if it was explicitly present in the input
17199        if let Some(alias) = &mr.alias {
17200            self.write(" ");
17201            if mr.alias_explicit_as {
17202                self.write_keyword("AS");
17203                self.write(" ");
17204            }
17205            self.generate_identifier(alias)?;
17206        }
17207
17208        Ok(())
17209    }
17210
17211    /// Generate a query hint /*+ ... */
17212    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
17213        use crate::dialects::DialectType;
17214
17215        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
17216        let supports_hints = matches!(
17217            self.config.dialect,
17218            None |  // No dialect = preserve everything
17219            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
17220            Some(DialectType::Spark) | Some(DialectType::Hive) |
17221            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
17222        );
17223
17224        if !supports_hints || hint.expressions.is_empty() {
17225            return Ok(());
17226        }
17227
17228        // First, expand raw hint text into individual hint strings
17229        // This handles the case where the parser stored multiple hints as a single raw string
17230        let mut hint_strings: Vec<String> = Vec::new();
17231        for expr in &hint.expressions {
17232            match expr {
17233                HintExpression::Raw(text) => {
17234                    // Parse raw hint text into individual hint function calls
17235                    let parsed = self.parse_raw_hint_text(text);
17236                    hint_strings.extend(parsed);
17237                }
17238                _ => {
17239                    hint_strings.push(self.hint_expression_to_string(expr)?);
17240                }
17241            }
17242        }
17243
17244        // In pretty mode with multiple hints, always use multiline format
17245        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
17246        // always joins with newlines in pretty mode
17247        let use_multiline = self.config.pretty && hint_strings.len() > 1;
17248
17249        if use_multiline {
17250            // Pretty print with each hint on its own line
17251            self.write(" /*+ ");
17252            for (i, hint_str) in hint_strings.iter().enumerate() {
17253                if i > 0 {
17254                    self.write_newline();
17255                    self.write("  "); // 2-space indent within hint block
17256                }
17257                self.write(hint_str);
17258            }
17259            self.write(" */");
17260        } else {
17261            // Single line format
17262            self.write(" /*+ ");
17263            let sep = match self.config.dialect {
17264                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
17265                _ => " ",
17266            };
17267            for (i, hint_str) in hint_strings.iter().enumerate() {
17268                if i > 0 {
17269                    self.write(sep);
17270                }
17271                self.write(hint_str);
17272            }
17273            self.write(" */");
17274        }
17275
17276        Ok(())
17277    }
17278
17279    /// Parse raw hint text into individual hint function calls
17280    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
17281    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
17282    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
17283        let mut results = Vec::new();
17284        let mut chars = text.chars().peekable();
17285        let mut current = String::new();
17286        let mut paren_depth = 0;
17287        let mut has_unparseable_content = false;
17288        let mut position_after_last_function = 0;
17289        let mut char_position = 0;
17290
17291        while let Some(c) = chars.next() {
17292            char_position += c.len_utf8();
17293            match c {
17294                '(' => {
17295                    paren_depth += 1;
17296                    current.push(c);
17297                }
17298                ')' => {
17299                    paren_depth -= 1;
17300                    current.push(c);
17301                    // When we close the outer parenthesis, we've completed a hint function
17302                    if paren_depth == 0 {
17303                        let trimmed = current.trim().to_string();
17304                        if !trimmed.is_empty() {
17305                            // Format this hint for pretty printing if needed
17306                            let formatted = self.format_hint_function(&trimmed);
17307                            results.push(formatted);
17308                        }
17309                        current.clear();
17310                        position_after_last_function = char_position;
17311                    }
17312                }
17313                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
17314                    // Space/comma/whitespace outside parentheses - skip
17315                }
17316                _ if paren_depth == 0 => {
17317                    // Character outside parentheses - accumulate for potential hint name
17318                    current.push(c);
17319                }
17320                _ => {
17321                    current.push(c);
17322                }
17323            }
17324        }
17325
17326        // Check if there's remaining text after the last function call
17327        let remaining_text = text[position_after_last_function..].trim();
17328        if !remaining_text.is_empty() {
17329            // Check if it looks like valid hint function names
17330            // Valid hint identifiers typically are uppercase alphanumeric with underscores
17331            // If we see multiple words without parens, it's likely unparseable
17332            let words: Vec<&str> = remaining_text.split_whitespace().collect();
17333            let looks_like_hint_functions = words.iter().all(|word| {
17334                // A valid hint name followed by opening paren, or a standalone uppercase identifier
17335                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
17336            });
17337
17338            if !looks_like_hint_functions && words.len() > 1 {
17339                has_unparseable_content = true;
17340            }
17341        }
17342
17343        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
17344        if has_unparseable_content {
17345            return vec![text.trim().to_string()];
17346        }
17347
17348        // If we couldn't parse anything, return the original text as a single hint
17349        if results.is_empty() {
17350            results.push(text.trim().to_string());
17351        }
17352
17353        results
17354    }
17355
17356    /// Format a hint function for pretty printing
17357    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
17358    fn format_hint_function(&self, hint: &str) -> String {
17359        if !self.config.pretty {
17360            return hint.to_string();
17361        }
17362
17363        // Try to parse NAME(args) pattern
17364        if let Some(paren_pos) = hint.find('(') {
17365            if hint.ends_with(')') {
17366                let name = &hint[..paren_pos];
17367                let args_str = &hint[paren_pos + 1..hint.len() - 1];
17368
17369                // Parse arguments (space-separated for Oracle hints)
17370                let args: Vec<&str> = args_str.split_whitespace().collect();
17371
17372                // Calculate total width of arguments
17373                let total_args_width: usize =
17374                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
17375
17376                // If too wide, format on multiple lines
17377                if total_args_width > self.config.max_text_width && !args.is_empty() {
17378                    let mut result = format!("{}(\n", name);
17379                    for arg in &args {
17380                        result.push_str("    "); // 4-space indent for args
17381                        result.push_str(arg);
17382                        result.push('\n');
17383                    }
17384                    result.push_str("  )"); // 2-space indent for closing paren
17385                    return result;
17386                }
17387            }
17388        }
17389
17390        hint.to_string()
17391    }
17392
17393    /// Convert a hint expression to a string, handling multiline formatting for long arguments
17394    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
17395        match expr {
17396            HintExpression::Function { name, args } => {
17397                // Generate each argument to a string
17398                let arg_strings: Vec<String> = args
17399                    .iter()
17400                    .map(|arg| {
17401                        let mut gen = Generator::with_arc_config(self.config.clone());
17402                        gen.generate_expression(arg)?;
17403                        Ok(gen.output)
17404                    })
17405                    .collect::<Result<Vec<_>>>()?;
17406
17407                // Oracle hints use space-separated arguments, not comma-separated
17408                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
17409                    + arg_strings.len().saturating_sub(1); // spaces between args
17410
17411                // Check if function args need multiline formatting
17412                // Use too_wide check for argument formatting
17413                let args_multiline =
17414                    self.config.pretty && total_args_width > self.config.max_text_width;
17415
17416                if args_multiline && !arg_strings.is_empty() {
17417                    // Multiline format for long argument lists
17418                    let mut result = format!("{}(\n", name);
17419                    for arg_str in &arg_strings {
17420                        result.push_str("    "); // 4-space indent for args
17421                        result.push_str(arg_str);
17422                        result.push('\n');
17423                    }
17424                    result.push_str("  )"); // 2-space indent for closing paren
17425                    Ok(result)
17426                } else {
17427                    // Single line format with space-separated args (Oracle style)
17428                    let args_str = arg_strings.join(" ");
17429                    Ok(format!("{}({})", name, args_str))
17430                }
17431            }
17432            HintExpression::Identifier(name) => Ok(name.clone()),
17433            HintExpression::Raw(text) => {
17434                // For pretty printing, try to format the raw text
17435                if self.config.pretty {
17436                    Ok(self.format_hint_function(text))
17437                } else {
17438                    Ok(text.clone())
17439                }
17440            }
17441        }
17442    }
17443
17444    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
17445        // PostgreSQL ONLY modifier: prevents scanning child tables
17446        if table.only {
17447            self.write_keyword("ONLY");
17448            self.write_space();
17449        }
17450
17451        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
17452        if let Some(ref identifier_func) = table.identifier_func {
17453            self.generate_expression(identifier_func)?;
17454            // If table name parts are present, emit .catalog.schema.name after the function
17455            if !table.name.name.is_empty() {
17456                if let Some(catalog) = &table.catalog {
17457                    self.write(".");
17458                    self.generate_identifier(catalog)?;
17459                }
17460                if let Some(schema) = &table.schema {
17461                    self.write(".");
17462                    self.generate_identifier(schema)?;
17463                }
17464                self.write(".");
17465                self.generate_identifier(&table.name)?;
17466            }
17467        } else {
17468            if let Some(catalog) = &table.catalog {
17469                self.generate_identifier(catalog)?;
17470                self.write(".");
17471            }
17472            if let Some(schema) = &table.schema {
17473                self.generate_identifier(schema)?;
17474                self.write(".");
17475            }
17476            self.generate_identifier(&table.name)?;
17477        }
17478
17479        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
17480        if let Some(changes) = &table.changes {
17481            self.write(" ");
17482            self.generate_changes(changes)?;
17483        }
17484
17485        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
17486        if !table.partitions.is_empty() {
17487            self.write_space();
17488            self.write_keyword("PARTITION");
17489            self.write("(");
17490            for (i, partition) in table.partitions.iter().enumerate() {
17491                if i > 0 {
17492                    self.write(", ");
17493                }
17494                self.generate_identifier(partition)?;
17495            }
17496            self.write(")");
17497        }
17498
17499        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
17500        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
17501        let historical_data_post_alias = matches!(self.config.dialect, Some(DialectType::DuckDB));
17502        if table.changes.is_none() && !historical_data_post_alias {
17503            if let Some(when) = &table.when {
17504                self.write_space();
17505                self.generate_historical_data(when)?;
17506            }
17507        }
17508
17509        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
17510        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
17511        if !system_time_post_alias {
17512            if let Some(ref system_time) = table.system_time {
17513                self.write_space();
17514                self.write(system_time);
17515            }
17516        }
17517
17518        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
17519        if let Some(ref version) = table.version {
17520            self.write_space();
17521            self.generate_version(version)?;
17522        }
17523
17524        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
17525        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
17526        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
17527        let alias_post_tablesample = self.config.alias_post_tablesample;
17528
17529        if alias_post_tablesample {
17530            // TABLESAMPLE before alias (Oracle, Hive, Spark)
17531            self.generate_table_sample_clause(table)?;
17532        }
17533
17534        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
17535        // For SQLite, INDEXED BY hints come after the alias, so skip here
17536        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
17537            && table.hints.iter().any(|h| {
17538                if let Expression::Identifier(id) = h {
17539                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
17540                } else {
17541                    false
17542                }
17543            });
17544        if !table.hints.is_empty() && !is_sqlite_hint {
17545            for hint in &table.hints {
17546                self.write_space();
17547                self.generate_expression(hint)?;
17548            }
17549        }
17550
17551        if let Some(alias) = &table.alias {
17552            self.write_space();
17553            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
17554            // Generic mode and most dialects always use AS for table aliases
17555            let always_use_as = self.config.dialect.is_none()
17556                || matches!(
17557                    self.config.dialect,
17558                    Some(DialectType::Generic)
17559                        | Some(DialectType::PostgreSQL)
17560                        | Some(DialectType::Redshift)
17561                        | Some(DialectType::Snowflake)
17562                        | Some(DialectType::BigQuery)
17563                        | Some(DialectType::DuckDB)
17564                        | Some(DialectType::Presto)
17565                        | Some(DialectType::Trino)
17566                        | Some(DialectType::TSQL)
17567                        | Some(DialectType::Fabric)
17568                        | Some(DialectType::MySQL)
17569                        | Some(DialectType::Spark)
17570                        | Some(DialectType::Hive)
17571                        | Some(DialectType::SQLite)
17572                        | Some(DialectType::Drill)
17573                );
17574            let is_stage_ref = table.name.name.starts_with('@');
17575            // Oracle never uses AS for table aliases
17576            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17577            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
17578                self.write_keyword("AS");
17579                self.write_space();
17580            }
17581            self.generate_identifier(alias)?;
17582
17583            // Output column aliases if present: AS t(c1, c2)
17584            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
17585            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
17586                self.write("(");
17587                for (i, col_alias) in table.column_aliases.iter().enumerate() {
17588                    if i > 0 {
17589                        self.write(", ");
17590                    }
17591                    self.generate_identifier(col_alias)?;
17592                }
17593                self.write(")");
17594            }
17595        }
17596
17597        // BigQuery: FOR SYSTEM_TIME AS OF after alias
17598        if system_time_post_alias {
17599            if let Some(ref system_time) = table.system_time {
17600                self.write_space();
17601                self.write(system_time);
17602            }
17603        }
17604
17605        if historical_data_post_alias && table.changes.is_none() {
17606            if let Some(when) = &table.when {
17607                self.write_space();
17608                self.generate_historical_data(when)?;
17609            }
17610        }
17611
17612        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
17613        if !alias_post_tablesample {
17614            self.generate_table_sample_clause(table)?;
17615        }
17616
17617        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
17618        if is_sqlite_hint {
17619            for hint in &table.hints {
17620                self.write_space();
17621                self.generate_expression(hint)?;
17622            }
17623        }
17624
17625        // ClickHouse FINAL modifier
17626        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17627            self.write_space();
17628            self.write_keyword("FINAL");
17629        }
17630
17631        // Output trailing comments
17632        for comment in &table.trailing_comments {
17633            self.write_space();
17634            self.write_formatted_comment(comment);
17635        }
17636        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
17637        // output here - they are output by the FROM/PIVOT generator after the full expression
17638
17639        Ok(())
17640    }
17641
17642    /// Helper to output TABLESAMPLE clause for a table reference
17643    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
17644        if let Some(ref ts) = table.table_sample {
17645            self.write_space();
17646            if ts.is_using_sample {
17647                self.write_keyword("USING SAMPLE");
17648            } else {
17649                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
17650                self.write_keyword(self.config.tablesample_keywords);
17651            }
17652            self.generate_sample_body(ts)?;
17653            // Seed for table-level sample - use dialect's configured keyword
17654            if let Some(ref seed) = ts.seed {
17655                self.write_space();
17656                self.write_keyword(self.config.tablesample_seed_keyword);
17657                self.write(" (");
17658                self.generate_expression(seed)?;
17659                self.write(")");
17660            }
17661        }
17662        Ok(())
17663    }
17664
17665    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
17666        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
17667        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
17668
17669        if sr.quoted {
17670            self.write("'");
17671        }
17672
17673        self.write(&sr.name);
17674        if let Some(path) = &sr.path {
17675            self.write(path);
17676        }
17677
17678        if sr.quoted {
17679            self.write("'");
17680        }
17681
17682        // Output FILE_FORMAT and PATTERN if present
17683        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
17684        if has_options {
17685            self.write(" (");
17686            let mut first = true;
17687
17688            if let Some(file_format) = &sr.file_format {
17689                if !first {
17690                    self.write(", ");
17691                }
17692                self.write_keyword("FILE_FORMAT");
17693                self.write(" => ");
17694                self.generate_expression(file_format)?;
17695                first = false;
17696            }
17697
17698            if let Some(pattern) = &sr.pattern {
17699                if !first {
17700                    self.write(", ");
17701                }
17702                self.write_keyword("PATTERN");
17703                self.write(" => '");
17704                self.write(pattern);
17705                self.write("'");
17706            }
17707
17708            self.write(")");
17709        }
17710        Ok(())
17711    }
17712
17713    fn generate_star(&mut self, star: &Star) -> Result<()> {
17714        use crate::dialects::DialectType;
17715
17716        if let Some(table) = &star.table {
17717            self.generate_identifier(table)?;
17718            self.write(".");
17719        }
17720        self.write("*");
17721
17722        // Generate EXCLUDE/EXCEPT clause based on dialect
17723        if let Some(except) = &star.except {
17724            if !except.is_empty() {
17725                self.write_space();
17726                // Use dialect-appropriate keyword
17727                match self.config.dialect {
17728                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
17729                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
17730                        self.write_keyword("EXCLUDE")
17731                    }
17732                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
17733                }
17734                self.write(" (");
17735                for (i, col) in except.iter().enumerate() {
17736                    if i > 0 {
17737                        self.write(", ");
17738                    }
17739                    self.generate_identifier(col)?;
17740                }
17741                self.write(")");
17742            }
17743        }
17744
17745        // Generate REPLACE clause
17746        if let Some(replace) = &star.replace {
17747            if !replace.is_empty() {
17748                self.write_space();
17749                self.write_keyword("REPLACE");
17750                self.write(" (");
17751                for (i, alias) in replace.iter().enumerate() {
17752                    if i > 0 {
17753                        self.write(", ");
17754                    }
17755                    self.generate_expression(&alias.this)?;
17756                    self.write_space();
17757                    self.write_keyword("AS");
17758                    self.write_space();
17759                    self.generate_identifier(&alias.alias)?;
17760                }
17761                self.write(")");
17762            }
17763        }
17764
17765        // Generate RENAME clause (Snowflake specific)
17766        if let Some(rename) = &star.rename {
17767            if !rename.is_empty() {
17768                self.write_space();
17769                self.write_keyword("RENAME");
17770                self.write(" (");
17771                for (i, (old_name, new_name)) in rename.iter().enumerate() {
17772                    if i > 0 {
17773                        self.write(", ");
17774                    }
17775                    self.generate_identifier(old_name)?;
17776                    self.write_space();
17777                    self.write_keyword("AS");
17778                    self.write_space();
17779                    self.generate_identifier(new_name)?;
17780                }
17781                self.write(")");
17782            }
17783        }
17784
17785        // Output trailing comments
17786        for comment in &star.trailing_comments {
17787            self.write_space();
17788            self.write_formatted_comment(comment);
17789        }
17790
17791        Ok(())
17792    }
17793
17794    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
17795    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
17796        self.write("{");
17797        match expr {
17798            Expression::Star(star) => {
17799                // Generate the star (table.* or just * with optional EXCLUDE)
17800                self.generate_star(star)?;
17801            }
17802            Expression::ILike(ilike) => {
17803                // {* ILIKE 'pattern'} syntax
17804                self.generate_expression(&ilike.left)?;
17805                self.write_space();
17806                self.write_keyword("ILIKE");
17807                self.write_space();
17808                self.generate_expression(&ilike.right)?;
17809            }
17810            _ => {
17811                self.generate_expression(expr)?;
17812            }
17813        }
17814        self.write("}");
17815        Ok(())
17816    }
17817
17818    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
17819        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
17820        // to avoid duplication (comments are captured as both Column.trailing_comments
17821        // and Alias.pre_alias_comments during parsing)
17822        match &alias.this {
17823            Expression::Column(col) => {
17824                // Generate column without trailing comments - they're in pre_alias_comments
17825                if let Some(table) = &col.table {
17826                    self.generate_identifier(table)?;
17827                    self.write(".");
17828                }
17829                self.generate_identifier(&col.name)?;
17830            }
17831            _ => {
17832                self.generate_expression(&alias.this)?;
17833            }
17834        }
17835
17836        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
17837        // moves pre-alias comments to after the alias. When there are also trailing_comments,
17838        // keep pre-alias comments in their original position (between expression and AS).
17839        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
17840            for comment in &alias.pre_alias_comments {
17841                self.write_space();
17842                self.write_formatted_comment(comment);
17843            }
17844        }
17845
17846        use crate::dialects::DialectType;
17847
17848        // Determine if we should skip AS keyword for table-valued function aliases
17849        // Oracle and some other dialects don't use AS for table aliases
17850        // Note: We specifically use TableFromRows here, NOT Function, because Function
17851        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
17852        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
17853        let is_table_source = matches!(
17854            &alias.this,
17855            Expression::JSONTable(_)
17856                | Expression::XMLTable(_)
17857                | Expression::TableFromRows(_)
17858                | Expression::Unnest(_)
17859                | Expression::MatchRecognize(_)
17860                | Expression::Select(_)
17861                | Expression::Subquery(_)
17862                | Expression::Paren(_)
17863                | Expression::JoinedTable(_)
17864        );
17865        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17866        let skip_as = is_table_source && dialect_skips_table_alias_as;
17867
17868        self.write_space();
17869        if !skip_as {
17870            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17871                if let Some(ref alias_keyword) = alias.alias_keyword {
17872                    self.write(alias_keyword);
17873                } else {
17874                    self.write_keyword("AS");
17875                }
17876            } else {
17877                self.write_keyword("AS");
17878            }
17879            self.write_space();
17880        }
17881
17882        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17883        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17884
17885        // Check if we have column aliases only (no table alias name)
17886        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17887            // Generate AS (col1, col2, ...)
17888            self.write("(");
17889            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17890                if i > 0 {
17891                    self.write(", ");
17892                }
17893                self.generate_alias_identifier(col_alias)?;
17894            }
17895            self.write(")");
17896        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17897            // Generate AS alias(col1, col2, ...)
17898            self.generate_alias_identifier(&alias.alias)?;
17899            self.write("(");
17900            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17901                if i > 0 {
17902                    self.write(", ");
17903                }
17904                self.generate_alias_identifier(col_alias)?;
17905            }
17906            self.write(")");
17907        } else {
17908            // Simple alias (or BigQuery without column aliases)
17909            self.generate_alias_identifier(&alias.alias)?;
17910        }
17911
17912        // Output trailing comments (comments after the alias)
17913        for comment in &alias.trailing_comments {
17914            self.write_space();
17915            self.write_formatted_comment(comment);
17916        }
17917
17918        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17919        // moves pre-alias comments to after the alias. When there are trailing_comments,
17920        // the pre-alias comments were already lost (consumed as column trailing comments
17921        // that were then used as pre_alias_comments). We always emit them after alias.
17922        if alias.trailing_comments.is_empty() {
17923            for comment in &alias.pre_alias_comments {
17924                self.write_space();
17925                self.write_formatted_comment(comment);
17926            }
17927        }
17928
17929        Ok(())
17930    }
17931
17932    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17933        use crate::dialects::DialectType;
17934
17935        // SingleStore uses :> syntax
17936        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17937            self.generate_expression(&cast.this)?;
17938            self.write(" :> ");
17939            self.generate_data_type(&cast.to)?;
17940            return Ok(());
17941        }
17942
17943        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17944        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17945            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17946                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17947            if is_unknown_type {
17948                if let Some(format) = &cast.format {
17949                    self.write_keyword("CAST");
17950                    self.write("(");
17951                    self.generate_expression(&cast.this)?;
17952                    self.write_space();
17953                    self.write_keyword("AS");
17954                    self.write_space();
17955                    self.write_keyword("FORMAT");
17956                    self.write_space();
17957                    self.generate_expression(format)?;
17958                    self.write(")");
17959                    return Ok(());
17960                }
17961            }
17962        }
17963
17964        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17965        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17966        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17967            if let Some(format) = &cast.format {
17968                // Check if target type is DATE or TIMESTAMP
17969                let is_date = matches!(
17970                    cast.to,
17971                    DataType::Date
17972                        | DataType::Oracle {
17973                            oracle_type: OracleDataType::Date
17974                        }
17975                );
17976                let is_timestamp = matches!(
17977                    cast.to,
17978                    DataType::Timestamp { .. }
17979                        | DataType::Oracle {
17980                            oracle_type: OracleDataType::Timestamp { .. }
17981                        }
17982                );
17983
17984                if is_date || is_timestamp {
17985                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17986                    self.write_keyword(func_name);
17987                    self.write("(");
17988                    self.generate_expression(&cast.this)?;
17989                    self.write(", ");
17990
17991                    // Normalize format string for Oracle (HH -> HH12)
17992                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17993                    if let Expression::Literal(lit) = format.as_ref() {
17994                        if let Literal::String(fmt_str) = lit.as_ref() {
17995                            let normalized = self.normalize_oracle_format(fmt_str);
17996                            self.write("'");
17997                            self.write(&normalized);
17998                            self.write("'");
17999                        }
18000                    } else {
18001                        self.generate_expression(format)?;
18002                    }
18003
18004                    self.write(")");
18005                    return Ok(());
18006                }
18007            }
18008        }
18009
18010        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
18011        // This preserves sqlglot's typed inline array literal output.
18012        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18013            if let Expression::Array(arr) = &cast.this {
18014                self.generate_data_type(&cast.to)?;
18015                // Output just the bracket content [values] without the ARRAY prefix
18016                self.write("[");
18017                for (i, expr) in arr.expressions.iter().enumerate() {
18018                    if i > 0 {
18019                        self.write(", ");
18020                    }
18021                    self.generate_expression(expr)?;
18022                }
18023                self.write("]");
18024                return Ok(());
18025            }
18026            if matches!(&cast.this, Expression::ArrayFunc(_)) {
18027                self.generate_data_type(&cast.to)?;
18028                self.generate_expression(&cast.this)?;
18029                return Ok(());
18030            }
18031        }
18032
18033        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
18034        // convert the inner Struct to ROW(values...) format
18035        if matches!(
18036            self.config.dialect,
18037            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
18038        ) {
18039            if let Expression::Struct(ref s) = cast.this {
18040                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
18041                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
18042                    self.write_keyword("CAST");
18043                    self.write("(");
18044                    self.generate_struct_as_row(s)?;
18045                    self.write_space();
18046                    self.write_keyword("AS");
18047                    self.write_space();
18048                    self.generate_data_type(&cast.to)?;
18049                    self.write(")");
18050                    return Ok(());
18051                }
18052            }
18053        }
18054
18055        // Determine if we should use :: syntax based on dialect
18056        // PostgreSQL prefers :: for identity, most others prefer CAST()
18057        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
18058
18059        if use_double_colon {
18060            // PostgreSQL :: syntax: expr::type
18061            self.generate_expression(&cast.this)?;
18062            self.write("::");
18063            self.generate_data_type(&cast.to)?;
18064        } else {
18065            // Standard CAST() syntax
18066            self.write_keyword("CAST");
18067            self.write("(");
18068            self.generate_expression(&cast.this)?;
18069            self.write_space();
18070            self.write_keyword("AS");
18071            self.write_space();
18072            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
18073            // This matches Python sqlglot's CAST_MAPPING behavior
18074            if matches!(
18075                self.config.dialect,
18076                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
18077            ) {
18078                match &cast.to {
18079                    DataType::Custom { ref name } => {
18080                        if name.eq_ignore_ascii_case("LONGTEXT")
18081                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
18082                            || name.eq_ignore_ascii_case("TINYTEXT")
18083                            || name.eq_ignore_ascii_case("LONGBLOB")
18084                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
18085                            || name.eq_ignore_ascii_case("TINYBLOB")
18086                        {
18087                            self.write_keyword("CHAR");
18088                        } else {
18089                            self.generate_data_type(&cast.to)?;
18090                        }
18091                    }
18092                    DataType::VarChar { length, .. } => {
18093                        // MySQL CAST: VARCHAR -> CHAR
18094                        self.write_keyword("CHAR");
18095                        if let Some(n) = length {
18096                            self.write(&format!("({})", n));
18097                        }
18098                    }
18099                    DataType::Text => {
18100                        // MySQL CAST: TEXT -> CHAR
18101                        self.write_keyword("CHAR");
18102                    }
18103                    DataType::Timestamp {
18104                        precision,
18105                        timezone: false,
18106                    } => {
18107                        // MySQL CAST: TIMESTAMP -> DATETIME
18108                        self.write_keyword("DATETIME");
18109                        if let Some(p) = precision {
18110                            self.write(&format!("({})", p));
18111                        }
18112                    }
18113                    _ => {
18114                        self.generate_data_type(&cast.to)?;
18115                    }
18116                }
18117            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
18118                // Snowflake CAST: STRING -> VARCHAR
18119                match &cast.to {
18120                    DataType::String { length } => {
18121                        self.write_keyword("VARCHAR");
18122                        if let Some(n) = length {
18123                            self.write(&format!("({})", n));
18124                        }
18125                    }
18126                    _ => {
18127                        self.generate_data_type(&cast.to)?;
18128                    }
18129                }
18130            } else if matches!(self.config.dialect, Some(DialectType::Oracle)) {
18131                // Oracle canonicalizes binary floating-point CAST targets even though
18132                // column definitions retain the BINARY_FLOAT/BINARY_DOUBLE spellings.
18133                match &cast.to {
18134                    DataType::Oracle {
18135                        oracle_type: OracleDataType::BinaryFloat,
18136                    } => self.write_keyword("FLOAT"),
18137                    DataType::Oracle {
18138                        oracle_type: OracleDataType::BinaryDouble,
18139                    } => self.write_keyword("DOUBLE PRECISION"),
18140                    _ => self.generate_data_type(&cast.to)?,
18141                }
18142            } else {
18143                self.generate_data_type(&cast.to)?;
18144            }
18145
18146            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
18147            if let Some(default) = &cast.default {
18148                self.write_space();
18149                self.write_keyword("DEFAULT");
18150                self.write_space();
18151                self.generate_expression(default)?;
18152                self.write_space();
18153                self.write_keyword("ON");
18154                self.write_space();
18155                self.write_keyword("CONVERSION");
18156                self.write_space();
18157                self.write_keyword("ERROR");
18158            }
18159
18160            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
18161            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
18162            if let Some(format) = &cast.format {
18163                // Check if Oracle dialect - use comma syntax
18164                if matches!(
18165                    self.config.dialect,
18166                    Some(crate::dialects::DialectType::Oracle)
18167                ) {
18168                    self.write(", ");
18169                } else {
18170                    self.write_space();
18171                    self.write_keyword("FORMAT");
18172                    self.write_space();
18173                }
18174                self.generate_expression(format)?;
18175            }
18176
18177            self.write(")");
18178            // Output trailing comments
18179            for comment in &cast.trailing_comments {
18180                self.write_space();
18181                self.write_formatted_comment(comment);
18182            }
18183        }
18184        Ok(())
18185    }
18186
18187    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
18188    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
18189    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
18190        self.write_keyword("ROW");
18191        self.write("(");
18192        for (i, (_, expr)) in s.fields.iter().enumerate() {
18193            if i > 0 {
18194                self.write(", ");
18195            }
18196            // Recursively convert inner Struct to ROW format
18197            if let Expression::Struct(ref inner_s) = expr {
18198                self.generate_struct_as_row(inner_s)?;
18199            } else {
18200                self.generate_expression(expr)?;
18201            }
18202        }
18203        self.write(")");
18204        Ok(())
18205    }
18206
18207    /// Normalize Oracle date/time format strings
18208    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
18209    fn normalize_oracle_format(&self, format: &str) -> String {
18210        // Replace standalone HH with HH12 (but not HH12 or HH24)
18211        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
18212        let mut result = String::new();
18213        let chars: Vec<char> = format.chars().collect();
18214        let mut i = 0;
18215
18216        while i < chars.len() {
18217            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
18218                // Check what follows HH
18219                if i + 2 < chars.len() {
18220                    let next = chars[i + 2];
18221                    if next == '1' || next == '2' {
18222                        // This is HH12 or HH24, keep as is
18223                        result.push('H');
18224                        result.push('H');
18225                        i += 2;
18226                        continue;
18227                    }
18228                }
18229                // Standalone HH -> HH12
18230                result.push_str("HH12");
18231                i += 2;
18232            } else {
18233                result.push(chars[i]);
18234                i += 1;
18235            }
18236        }
18237
18238        result
18239    }
18240
18241    /// Check if the current dialect prefers :: cast syntax
18242    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
18243    fn dialect_prefers_double_colon(&self) -> bool {
18244        matches!(self.config.dialect, Some(DialectType::ClickHouse))
18245    }
18246
18247    /// Generate MOD function - uses % operator for dialects that prefer or require it.
18248    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18249        use crate::dialects::DialectType;
18250
18251        // Several dialects prefer or require x % y instead of MOD(x, y).
18252        let use_percent_operator = matches!(
18253            self.config.dialect,
18254            Some(DialectType::Snowflake)
18255                | Some(DialectType::MySQL)
18256                | Some(DialectType::Presto)
18257                | Some(DialectType::Trino)
18258                | Some(DialectType::PostgreSQL)
18259                | Some(DialectType::DuckDB)
18260                | Some(DialectType::Hive)
18261                | Some(DialectType::Spark)
18262                | Some(DialectType::Databricks)
18263                | Some(DialectType::Athena)
18264                | Some(DialectType::TSQL)
18265                | Some(DialectType::Fabric)
18266        );
18267
18268        if use_percent_operator {
18269            // MOD(a, b) treats both arguments as grouped expressions. When
18270            // lowering to an infix operator, keep binary arguments grouped.
18271            let needs_paren = |e: &Expression| {
18272                matches!(
18273                    e,
18274                    Expression::Add(_)
18275                        | Expression::Sub(_)
18276                        | Expression::Mul(_)
18277                        | Expression::Div(_)
18278                        | Expression::Mod(_)
18279                        | Expression::ModFunc(_)
18280                )
18281            };
18282            if needs_paren(&f.this) {
18283                self.write("(");
18284                self.generate_expression(&f.this)?;
18285                self.write(")");
18286            } else {
18287                self.generate_expression(&f.this)?;
18288            }
18289            self.write(" % ");
18290            if needs_paren(&f.expression) {
18291                self.write("(");
18292                self.generate_expression(&f.expression)?;
18293                self.write(")");
18294            } else {
18295                self.generate_expression(&f.expression)?;
18296            }
18297            Ok(())
18298        } else {
18299            self.generate_binary_func("MOD", &f.this, &f.expression)
18300        }
18301    }
18302
18303    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
18304    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18305        use crate::dialects::DialectType;
18306
18307        // Snowflake normalizes IFNULL to COALESCE
18308        let func_name = match self.config.dialect {
18309            Some(DialectType::Snowflake) => "COALESCE",
18310            _ => "IFNULL",
18311        };
18312
18313        self.generate_binary_func(func_name, &f.this, &f.expression)
18314    }
18315
18316    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
18317    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18318        // Use original function name if preserved (for identity tests)
18319        if let Some(ref original_name) = f.original_name {
18320            return self.generate_binary_func(original_name, &f.this, &f.expression);
18321        }
18322
18323        // Otherwise, use dialect-specific function names
18324        use crate::dialects::DialectType;
18325        let func_name = match self.config.dialect {
18326            Some(DialectType::Snowflake)
18327            | Some(DialectType::ClickHouse)
18328            | Some(DialectType::PostgreSQL)
18329            | Some(DialectType::Presto)
18330            | Some(DialectType::Trino)
18331            | Some(DialectType::Athena)
18332            | Some(DialectType::DuckDB)
18333            | Some(DialectType::BigQuery)
18334            | Some(DialectType::Spark)
18335            | Some(DialectType::Databricks)
18336            | Some(DialectType::Hive) => "COALESCE",
18337            Some(DialectType::MySQL)
18338            | Some(DialectType::Doris)
18339            | Some(DialectType::StarRocks)
18340            | Some(DialectType::SingleStore)
18341            | Some(DialectType::TiDB) => "IFNULL",
18342            _ => "NVL",
18343        };
18344
18345        self.generate_binary_func(func_name, &f.this, &f.expression)
18346    }
18347
18348    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
18349    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
18350        use crate::dialects::DialectType;
18351
18352        // Snowflake normalizes STDDEV_SAMP to STDDEV
18353        let func_name = match self.config.dialect {
18354            Some(DialectType::Snowflake) => "STDDEV",
18355            _ => "STDDEV_SAMP",
18356        };
18357
18358        self.generate_agg_func(func_name, f)
18359    }
18360
18361    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
18362        self.generate_expression(&coll.this)?;
18363        self.write_space();
18364        self.write_keyword("COLLATE");
18365        self.write_space();
18366        if coll.quoted {
18367            // Single-quoted string: COLLATE 'de_DE'
18368            self.write("'");
18369            self.write(&coll.collation);
18370            self.write("'");
18371        } else if coll.double_quoted {
18372            // Double-quoted identifier: COLLATE "de_DE"
18373            self.write("\"");
18374            self.write(&coll.collation);
18375            self.write("\"");
18376        } else {
18377            // Unquoted identifier: COLLATE de_DE
18378            self.write(&coll.collation);
18379        }
18380        Ok(())
18381    }
18382
18383    fn generate_case(&mut self, case: &Case) -> Result<()> {
18384        // In pretty mode, decide whether to expand based on total text width
18385        let multiline_case = if self.config.pretty {
18386            // Build the flat representation to check width
18387            let mut statements: Vec<String> = Vec::new();
18388            let operand_str = if let Some(operand) = &case.operand {
18389                let s = self.generate_to_string(operand)?;
18390                statements.push(format!("CASE {}", s));
18391                s
18392            } else {
18393                statements.push("CASE".to_string());
18394                String::new()
18395            };
18396            let _ = operand_str;
18397            for (condition, result) in &case.whens {
18398                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
18399                statements.push(format!("THEN {}", self.generate_to_string(result)?));
18400            }
18401            if let Some(else_) = &case.else_ {
18402                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
18403            }
18404            statements.push("END".to_string());
18405            self.too_wide(&statements)
18406        } else {
18407            false
18408        };
18409
18410        self.write_keyword("CASE");
18411        if let Some(operand) = &case.operand {
18412            self.write_space();
18413            self.generate_expression(operand)?;
18414        }
18415        if multiline_case {
18416            self.indent_level += 1;
18417        }
18418        for (condition, result) in &case.whens {
18419            if multiline_case {
18420                self.write_newline();
18421                self.write_indent();
18422            } else {
18423                self.write_space();
18424            }
18425            self.write_keyword("WHEN");
18426            self.write_space();
18427            self.generate_expression(condition)?;
18428            if multiline_case {
18429                self.write_newline();
18430                self.write_indent();
18431            } else {
18432                self.write_space();
18433            }
18434            self.write_keyword("THEN");
18435            self.write_space();
18436            self.generate_expression(result)?;
18437        }
18438        if let Some(else_) = &case.else_ {
18439            if multiline_case {
18440                self.write_newline();
18441                self.write_indent();
18442            } else {
18443                self.write_space();
18444            }
18445            self.write_keyword("ELSE");
18446            self.write_space();
18447            self.generate_expression(else_)?;
18448        }
18449        if multiline_case {
18450            self.indent_level -= 1;
18451            self.write_newline();
18452            self.write_indent();
18453        } else {
18454            self.write_space();
18455        }
18456        self.write_keyword("END");
18457        // Emit any comments that were attached to the CASE keyword
18458        for comment in &case.comments {
18459            self.write(" ");
18460            self.write_formatted_comment(comment);
18461        }
18462        Ok(())
18463    }
18464
18465    fn generate_function(&mut self, func: &Function) -> Result<()> {
18466        // Normalize function name based on dialect settings
18467        let normalized_name = if func.name.eq_ignore_ascii_case("GROUPING")
18468            && func.args.len() > 1
18469            && matches!(
18470                self.config.dialect,
18471                Some(DialectType::TSQL | DialectType::Fabric)
18472            ) {
18473            Cow::Borrowed("GROUPING_ID")
18474        } else if matches!(self.config.dialect, Some(DialectType::Snowflake))
18475            && func.name.to_ascii_uppercase().starts_with("IDENTIFIER(")
18476        {
18477            Cow::Borrowed(func.name.as_str())
18478        } else {
18479            self.normalize_func_name(&func.name)
18480        };
18481
18482        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
18483        if matches!(self.config.dialect, Some(DialectType::DuckDB))
18484            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
18485        {
18486            self.write("LIST_FILTER(");
18487            self.write("[");
18488            for (i, arg) in func.args.iter().enumerate() {
18489                if i > 0 {
18490                    self.write(", ");
18491                }
18492                self.generate_expression(arg)?;
18493            }
18494            self.write("], _u -> NOT _u IS NULL)");
18495            return Ok(());
18496        }
18497
18498        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
18499        // rather than bracket-array syntax.
18500        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18501            && func.name.eq_ignore_ascii_case("TO_VARIANT")
18502            && func.args.len() == 1
18503        {
18504            let array_expressions = match &func.args[0] {
18505                Expression::ArrayFunc(arr) => Some(&arr.expressions),
18506                Expression::Array(arr) => Some(&arr.expressions),
18507                _ => None,
18508            };
18509            if let Some(expressions) = array_expressions {
18510                self.write_keyword("TO_VARIANT");
18511                self.write("(");
18512                self.write_keyword("ARRAY_CONSTRUCT");
18513                self.write("(");
18514                for (i, arg) in expressions.iter().enumerate() {
18515                    if i > 0 {
18516                        self.write(", ");
18517                    }
18518                    self.generate_expression(arg)?;
18519                }
18520                self.write(")");
18521                self.write(")");
18522                return Ok(());
18523            }
18524        }
18525
18526        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
18527        if func.name.eq_ignore_ascii_case("STRUCT")
18528            && !matches!(
18529                self.config.dialect,
18530                Some(DialectType::BigQuery)
18531                    | Some(DialectType::Spark)
18532                    | Some(DialectType::Databricks)
18533                    | Some(DialectType::Hive)
18534                    | None
18535            )
18536        {
18537            return self.generate_struct_function_cross_dialect(func);
18538        }
18539
18540        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
18541        // This is an internal marker function for ::? JSON path syntax
18542        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
18543            self.generate_expression(&func.args[0])?;
18544            self.write("::?");
18545            // Extract the key from the string literal
18546            if let Expression::Literal(lit) = &func.args[1] {
18547                if let crate::expressions::Literal::String(key) = lit.as_ref() {
18548                    self.write(key);
18549                }
18550            } else {
18551                self.generate_expression(&func.args[1])?;
18552            }
18553            return Ok(());
18554        }
18555
18556        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
18557        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
18558            self.generate_expression(&func.args[0])?;
18559            self.write(" # ");
18560            self.generate_expression(&func.args[1])?;
18561            return Ok(());
18562        }
18563
18564        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
18565        if matches!(
18566            self.config.dialect,
18567            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
18568        ) && func.name.eq_ignore_ascii_case("TRY")
18569            && func.args.len() == 1
18570        {
18571            self.generate_expression(&func.args[0])?;
18572            return Ok(());
18573        }
18574
18575        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
18576        if self.config.dialect == Some(DialectType::ClickHouse)
18577            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
18578            && func.args.len() == 1
18579        {
18580            self.write("dateTrunc('DAY', ");
18581            self.generate_expression(&func.args[0])?;
18582            self.write(")");
18583            return Ok(());
18584        }
18585
18586        // ClickHouse uses dateTrunc casing.
18587        if self.config.dialect == Some(DialectType::ClickHouse)
18588            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18589            && func.args.len() == 2
18590        {
18591            self.write("dateTrunc(");
18592            self.generate_expression(&func.args[0])?;
18593            self.write(", ");
18594            self.generate_expression(&func.args[1])?;
18595            self.write(")");
18596            return Ok(());
18597        }
18598
18599        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
18600        if matches!(
18601            self.config.dialect,
18602            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
18603        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
18604        {
18605            self.write_keyword("SUBSTR");
18606            self.write("(");
18607            for (i, arg) in func.args.iter().enumerate() {
18608                if i > 0 {
18609                    self.write(", ");
18610                }
18611                self.generate_expression(arg)?;
18612            }
18613            self.write(")");
18614            return Ok(());
18615        }
18616
18617        if self.config.dialect == Some(DialectType::Snowflake)
18618            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
18619            && func.args.len() == 1
18620        {
18621            self.write_keyword("ARRAY_DISTINCT");
18622            self.write("(");
18623            self.write_keyword("ARRAY_COMPACT");
18624            self.write("(");
18625            self.generate_expression(&func.args[0])?;
18626            self.write("))");
18627            return Ok(());
18628        }
18629
18630        if self.config.dialect == Some(DialectType::Snowflake)
18631            && func.name.eq_ignore_ascii_case("LIST")
18632            && func.args.len() == 1
18633            && !matches!(func.args.first(), Some(Expression::Select(_)))
18634        {
18635            self.write_keyword("ARRAY_AGG");
18636            self.write("(");
18637            self.generate_expression(&func.args[0])?;
18638            self.write(")");
18639            return Ok(());
18640        }
18641
18642        // Redshift: CONCAT(a, b, ...) -> a || b || ...
18643        if self.config.dialect == Some(DialectType::Redshift)
18644            && func.name.eq_ignore_ascii_case("CONCAT")
18645            && func.args.len() >= 2
18646        {
18647            for (i, arg) in func.args.iter().enumerate() {
18648                if i > 0 {
18649                    self.write(" || ");
18650                }
18651                self.generate_expression(arg)?;
18652            }
18653            return Ok(());
18654        }
18655
18656        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
18657        if self.config.dialect == Some(DialectType::Redshift)
18658            && func.name.eq_ignore_ascii_case("CONCAT_WS")
18659            && func.args.len() >= 2
18660        {
18661            let sep = &func.args[0];
18662            for (i, arg) in func.args.iter().skip(1).enumerate() {
18663                if i > 0 {
18664                    self.write(" || ");
18665                    self.generate_expression(sep)?;
18666                    self.write(" || ");
18667                }
18668                self.generate_expression(arg)?;
18669            }
18670            return Ok(());
18671        }
18672
18673        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
18674        // Unit should be unquoted uppercase identifier
18675        if self.config.dialect == Some(DialectType::Redshift)
18676            && (func.name.eq_ignore_ascii_case("DATEDIFF")
18677                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
18678            && func.args.len() == 3
18679        {
18680            self.write_keyword("DATEDIFF");
18681            self.write("(");
18682            // First arg is unit - normalize to unquoted uppercase
18683            self.write_redshift_date_part(&func.args[0]);
18684            self.write(", ");
18685            self.generate_expression(&func.args[1])?;
18686            self.write(", ");
18687            self.generate_expression(&func.args[2])?;
18688            self.write(")");
18689            return Ok(());
18690        }
18691
18692        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
18693        // Unit should be unquoted uppercase identifier
18694        if self.config.dialect == Some(DialectType::Redshift)
18695            && (func.name.eq_ignore_ascii_case("DATEADD")
18696                || func.name.eq_ignore_ascii_case("DATE_ADD"))
18697            && func.args.len() == 3
18698        {
18699            self.write_keyword("DATEADD");
18700            self.write("(");
18701            // First arg is unit - normalize to unquoted uppercase
18702            self.write_redshift_date_part(&func.args[0]);
18703            self.write(", ");
18704            self.generate_expression(&func.args[1])?;
18705            self.write(", ");
18706            self.generate_expression(&func.args[2])?;
18707            self.write(")");
18708            return Ok(());
18709        }
18710
18711        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
18712        if func.name.eq_ignore_ascii_case("UUID_STRING")
18713            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
18714        {
18715            if matches!(
18716                self.config.dialect,
18717                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
18718            ) {
18719                self.write_keyword("CAST");
18720                self.write("(");
18721                self.write_keyword("UUID");
18722                self.write("() ");
18723                self.write_keyword("AS");
18724                self.write(" ");
18725                self.write_keyword("STRING");
18726                self.write(")");
18727                return Ok(());
18728            }
18729
18730            if matches!(
18731                self.config.dialect,
18732                Some(DialectType::Presto | DialectType::Trino)
18733            ) {
18734                self.write_keyword("CAST");
18735                self.write("(");
18736                self.write_keyword("UUID");
18737                self.write("() ");
18738                self.write_keyword("AS");
18739                self.write(" ");
18740                self.write_keyword("VARCHAR");
18741                self.write(")");
18742                return Ok(());
18743            }
18744
18745            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
18746                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(");
18747                self.generate_expression(&func.args[0])?;
18748                self.write(", '-', '')) || ENCODE(");
18749                self.generate_expression(&func.args[1])?;
18750                self.write(")), 1, 32) AS h))");
18751                return Ok(());
18752            }
18753
18754            let func_name = match self.config.dialect {
18755                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
18756                Some(DialectType::BigQuery) => "GENERATE_UUID",
18757                _ => "UUID",
18758            };
18759            self.write_keyword(func_name);
18760            self.write("()");
18761            return Ok(());
18762        }
18763
18764        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
18765        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
18766        // Positional args are mapped to named parameters.
18767        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18768            && func.name.eq_ignore_ascii_case("GENERATOR")
18769        {
18770            let has_positional_args =
18771                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
18772            if has_positional_args {
18773                let param_names = ["ROWCOUNT", "TIMELIMIT"];
18774                self.write_keyword("GENERATOR");
18775                self.write("(");
18776                for (i, arg) in func.args.iter().enumerate() {
18777                    if i > 0 {
18778                        self.write(", ");
18779                    }
18780                    if i < param_names.len() {
18781                        self.write_keyword(param_names[i]);
18782                        self.write(" => ");
18783                        self.generate_expression(arg)?;
18784                    } else {
18785                        self.generate_expression(arg)?;
18786                    }
18787                }
18788                self.write(")");
18789                return Ok(());
18790            }
18791        }
18792
18793        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
18794        // Unit should be quoted uppercase string
18795        if self.config.dialect == Some(DialectType::Redshift)
18796            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18797            && func.args.len() == 2
18798        {
18799            self.write_keyword("DATE_TRUNC");
18800            self.write("(");
18801            // First arg is unit - normalize to quoted uppercase
18802            self.write_redshift_date_part_quoted(&func.args[0]);
18803            self.write(", ");
18804            self.generate_expression(&func.args[1])?;
18805            self.write(")");
18806            return Ok(());
18807        }
18808
18809        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
18810        if matches!(
18811            self.config.dialect,
18812            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18813        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18814            || func.name.eq_ignore_ascii_case("DATEPART"))
18815            && func.args.len() == 2
18816        {
18817            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18818                let date_part = self.classify_tsql_date_part_name(&part);
18819                self.generate_tsql_date_part(date_part, &func.args[1])?;
18820            } else {
18821                self.write_keyword("DATEPART");
18822                self.write("(");
18823                self.generate_expression(&func.args[0])?;
18824                self.write(", ");
18825                self.generate_expression(&func.args[1])?;
18826                self.write(")");
18827            }
18828            return Ok(());
18829        }
18830
18831        // TSQL/Fabric: DATETRUNC(datepart, value) requires an unquoted datepart keyword.
18832        if matches!(
18833            self.config.dialect,
18834            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18835        ) && (func.name.eq_ignore_ascii_case("DATETRUNC")
18836            || func.name.eq_ignore_ascii_case("DATE_TRUNC"))
18837            && func.args.len() == 2
18838        {
18839            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18840                let date_part = self.classify_tsql_date_trunc_name(&part);
18841                self.generate_tsql_date_trunc(date_part, &func.args[1])?;
18842            } else {
18843                self.write_keyword("DATETRUNC");
18844                self.write("(");
18845                self.generate_expression(&func.args[0])?;
18846                self.write(", ");
18847                self.generate_expression(&func.args[1])?;
18848                self.write(")");
18849            }
18850            return Ok(());
18851        }
18852
18853        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
18854        if matches!(
18855            self.config.dialect,
18856            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
18857        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18858            || func.name.eq_ignore_ascii_case("DATEPART"))
18859            && func.args.len() == 2
18860        {
18861            self.write_keyword("EXTRACT");
18862            self.write("(");
18863            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
18864            match &func.args[0] {
18865                Expression::Literal(lit)
18866                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18867                {
18868                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
18869                        unreachable!()
18870                    };
18871                    self.write(&s.to_ascii_lowercase());
18872                }
18873                _ => self.generate_expression(&func.args[0])?,
18874            }
18875            self.write_space();
18876            self.write_keyword("FROM");
18877            self.write_space();
18878            self.generate_expression(&func.args[1])?;
18879            self.write(")");
18880            return Ok(());
18881        }
18882
18883        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
18884        if self.config.dialect == Some(DialectType::PostgreSQL)
18885            && matches!(
18886                func.name.to_ascii_uppercase().as_str(),
18887                "DATE_ADD" | "DATE_SUB"
18888            )
18889            && func.args.len() == 2
18890            && matches!(func.args[1], Expression::Interval(_))
18891        {
18892            self.generate_expression(&func.args[0])?;
18893            self.write_space();
18894            if func.name.eq_ignore_ascii_case("DATE_SUB") {
18895                self.write("-");
18896            } else {
18897                self.write("+");
18898            }
18899            self.write_space();
18900            self.generate_expression(&func.args[1])?;
18901            return Ok(());
18902        }
18903
18904        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
18905        // Also DATE literals in Dremio should be CAST(...AS DATE)
18906        if self.config.dialect == Some(DialectType::Dremio)
18907            && (func.name.eq_ignore_ascii_case("DATE_PART")
18908                || func.name.eq_ignore_ascii_case("DATEPART"))
18909            && func.args.len() == 2
18910        {
18911            self.write_keyword("EXTRACT");
18912            self.write("(");
18913            self.generate_expression(&func.args[0])?;
18914            self.write_space();
18915            self.write_keyword("FROM");
18916            self.write_space();
18917            // For Dremio, DATE literals should become CAST('value' AS DATE)
18918            self.generate_dremio_date_expression(&func.args[1])?;
18919            self.write(")");
18920            return Ok(());
18921        }
18922
18923        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18924        if self.config.dialect == Some(DialectType::Dremio)
18925            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18926            && func.args.is_empty()
18927        {
18928            self.write_keyword("CURRENT_DATE_UTC");
18929            return Ok(());
18930        }
18931
18932        // Dremio: DATETYPE(year, month, day) transformation
18933        // - If all args are integer literals: DATE('YYYY-MM-DD')
18934        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18935        if self.config.dialect == Some(DialectType::Dremio)
18936            && func.name.eq_ignore_ascii_case("DATETYPE")
18937            && func.args.len() == 3
18938        {
18939            // Helper function to extract integer from number literal
18940            fn get_int_literal(expr: &Expression) -> Option<i64> {
18941                if let Expression::Literal(lit) = expr {
18942                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18943                        s.parse::<i64>().ok()
18944                    } else {
18945                        None
18946                    }
18947                } else {
18948                    None
18949                }
18950            }
18951
18952            // Check if all arguments are integer literals
18953            if let (Some(year), Some(month), Some(day)) = (
18954                get_int_literal(&func.args[0]),
18955                get_int_literal(&func.args[1]),
18956                get_int_literal(&func.args[2]),
18957            ) {
18958                // All are integer literals: DATE('YYYY-MM-DD')
18959                self.write_keyword("DATE");
18960                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18961                return Ok(());
18962            }
18963
18964            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18965            self.write_keyword("CAST");
18966            self.write("(");
18967            self.write_keyword("CONCAT");
18968            self.write("(");
18969            self.generate_expression(&func.args[0])?;
18970            self.write(", '-', ");
18971            self.generate_expression(&func.args[1])?;
18972            self.write(", '-', ");
18973            self.generate_expression(&func.args[2])?;
18974            self.write(")");
18975            self.write_space();
18976            self.write_keyword("AS");
18977            self.write_space();
18978            self.write_keyword("DATE");
18979            self.write(")");
18980            return Ok(());
18981        }
18982
18983        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18984        // when it's not an integer literal
18985        let is_presto_like = matches!(
18986            self.config.dialect,
18987            Some(DialectType::Presto) | Some(DialectType::Trino)
18988        );
18989        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18990            self.write_keyword("DATE_ADD");
18991            self.write("(");
18992            // First arg: unit (pass through as-is, e.g., 'DAY')
18993            self.generate_expression(&func.args[0])?;
18994            self.write(", ");
18995            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18996            let interval = &func.args[1];
18997            let needs_cast = !self.returns_integer_type(interval);
18998            if needs_cast {
18999                self.write_keyword("CAST");
19000                self.write("(");
19001            }
19002            self.generate_expression(interval)?;
19003            if needs_cast {
19004                self.write_space();
19005                self.write_keyword("AS");
19006                self.write_space();
19007                self.write_keyword("BIGINT");
19008                self.write(")");
19009            }
19010            self.write(", ");
19011            // Third arg: date
19012            self.generate_expression(&func.args[2])?;
19013            self.write(")");
19014            return Ok(());
19015        }
19016
19017        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
19018        let use_brackets = func.use_bracket_syntax;
19019
19020        // Special case: functions WITH ORDINALITY need special output order
19021        // Input: FUNC(args) WITH ORDINALITY
19022        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
19023        // Output must be: FUNC(args) WITH ORDINALITY
19024        let has_ordinality = func.name.len() >= 16
19025            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
19026        let output_name = if has_ordinality {
19027            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
19028            self.normalize_func_name(base_name)
19029        } else {
19030            normalized_name.clone()
19031        };
19032
19033        // For qualified names (schema.function or object.method), preserve original case
19034        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
19035        let quote_source_clickhouse_function =
19036            matches!(self.config.dialect, Some(DialectType::ClickHouse))
19037                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
19038                && func.quoted;
19039
19040        if quote_source_clickhouse_function {
19041            self.generate_identifier(&Identifier {
19042                name: func.name.clone(),
19043                quoted: true,
19044                trailing_comments: Vec::new(),
19045                span: None,
19046            })?;
19047        } else if func.name.contains('.') && !has_ordinality {
19048            // Don't normalize qualified functions - preserve original case
19049            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
19050            if func.quoted {
19051                self.write("`");
19052                self.write(&func.name);
19053                self.write("`");
19054            } else {
19055                self.write(&func.name);
19056            }
19057        } else {
19058            self.write(&output_name);
19059        }
19060
19061        // If no_parens is true and there are no args, output just the function name
19062        // Unless the target dialect requires parens for this function
19063        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
19064            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
19065                || func.name.eq_ignore_ascii_case("SESSION_USER")
19066                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
19067            {
19068                matches!(
19069                    self.config.dialect,
19070                    Some(DialectType::Snowflake)
19071                        | Some(DialectType::Spark)
19072                        | Some(DialectType::Databricks)
19073                        | Some(DialectType::Hive)
19074                )
19075            } else {
19076                false
19077            };
19078            !needs_parens
19079        };
19080        if force_parens {
19081            // Output trailing comments
19082            for comment in &func.trailing_comments {
19083                self.write_space();
19084                self.write_formatted_comment(comment);
19085            }
19086            return Ok(());
19087        }
19088
19089        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
19090        if func.name.eq_ignore_ascii_case("CUBE")
19091            || func.name.eq_ignore_ascii_case("ROLLUP")
19092            || func.name.eq_ignore_ascii_case("GROUPING SETS")
19093        {
19094            self.write(" (");
19095        } else if use_brackets {
19096            self.write("[");
19097        } else {
19098            self.write("(");
19099        }
19100        if func.distinct {
19101            self.write_keyword("DISTINCT");
19102            self.write_space();
19103        }
19104
19105        // Check if arguments should be split onto multiple lines (pretty + too wide)
19106        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
19107            && (func.name.eq_ignore_ascii_case("TABLE")
19108                || func.name.eq_ignore_ascii_case("FLATTEN"));
19109        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
19110        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
19111            || func.name.eq_ignore_ascii_case("CUBE")
19112            || func.name.eq_ignore_ascii_case("ROLLUP");
19113        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
19114            if is_grouping_func {
19115                true
19116            } else {
19117                // Pre-render arguments to check total width
19118                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
19119                for arg in &func.args {
19120                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
19121                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
19122                    temp_gen.generate_expression(arg)?;
19123                    expr_strings.push(temp_gen.output);
19124                }
19125                self.too_wide(&expr_strings)
19126            }
19127        } else {
19128            false
19129        };
19130
19131        if should_split {
19132            // Split onto multiple lines
19133            self.write_newline();
19134            self.indent_level += 1;
19135            for (i, arg) in func.args.iter().enumerate() {
19136                self.write_indent();
19137                self.generate_expression(arg)?;
19138                if i + 1 < func.args.len() {
19139                    self.write(",");
19140                }
19141                self.write_newline();
19142            }
19143            self.indent_level -= 1;
19144            self.write_indent();
19145        } else {
19146            // All on one line
19147            for (i, arg) in func.args.iter().enumerate() {
19148                if i > 0 {
19149                    self.write(", ");
19150                }
19151                self.generate_expression(arg)?;
19152            }
19153        }
19154
19155        if use_brackets {
19156            self.write("]");
19157        } else {
19158            self.write(")");
19159        }
19160        // Append WITH ORDINALITY after closing paren for table-valued functions
19161        if has_ordinality {
19162            self.write_space();
19163            self.write_keyword("WITH ORDINALITY");
19164        }
19165        // Output trailing comments
19166        for comment in &func.trailing_comments {
19167            self.write_space();
19168            self.write_formatted_comment(comment);
19169        }
19170        Ok(())
19171    }
19172
19173    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
19174        self.generate_expression(&fe.this)?;
19175        self.write_keyword(" EMITS ");
19176        self.generate_expression(&fe.emits)?;
19177        Ok(())
19178    }
19179
19180    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
19181        // Normalize function name based on dialect settings
19182        let mut normalized_name = self.normalize_func_name(&func.name);
19183
19184        // Dialect-specific name mappings for aggregate functions
19185        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
19186            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
19187            match self.config.dialect {
19188                Some(DialectType::ClickHouse) => {
19189                    normalized_name = if is_max {
19190                        Cow::Borrowed("argMax")
19191                    } else {
19192                        Cow::Borrowed("argMin")
19193                    };
19194                }
19195                Some(DialectType::DuckDB) => {
19196                    normalized_name = if is_max {
19197                        Cow::Borrowed("ARG_MAX")
19198                    } else {
19199                        Cow::Borrowed("ARG_MIN")
19200                    };
19201                }
19202                _ => {}
19203            }
19204        }
19205        self.write(normalized_name.as_ref());
19206        self.write("(");
19207        if func.distinct {
19208            self.write_keyword("DISTINCT");
19209            self.write_space();
19210        }
19211
19212        // Check if we need to transform multi-arg COUNT DISTINCT
19213        // When dialect doesn't support multi_arg_distinct, transform:
19214        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19215        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
19216        let needs_multi_arg_transform =
19217            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
19218
19219        if needs_multi_arg_transform {
19220            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19221            self.write_keyword("CASE");
19222            for arg in &func.args {
19223                self.write_space();
19224                self.write_keyword("WHEN");
19225                self.write_space();
19226                self.generate_expression(arg)?;
19227                self.write_space();
19228                self.write_keyword("IS NULL THEN NULL");
19229            }
19230            self.write_space();
19231            self.write_keyword("ELSE");
19232            self.write(" (");
19233            for (i, arg) in func.args.iter().enumerate() {
19234                if i > 0 {
19235                    self.write(", ");
19236                }
19237                self.generate_expression(arg)?;
19238            }
19239            self.write(")");
19240            self.write_space();
19241            self.write_keyword("END");
19242        } else {
19243            for (i, arg) in func.args.iter().enumerate() {
19244                if i > 0 {
19245                    self.write(", ");
19246                }
19247                self.generate_expression(arg)?;
19248            }
19249        }
19250
19251        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
19252        let clickhouse_ignore_nulls_outside =
19253            matches!(self.config.dialect, Some(DialectType::ClickHouse));
19254        if self.config.ignore_nulls_in_func
19255            && !matches!(
19256                self.config.dialect,
19257                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
19258            )
19259        {
19260            if let Some(ignore) = func.ignore_nulls {
19261                self.write_space();
19262                if ignore {
19263                    self.write_keyword("IGNORE NULLS");
19264                } else {
19265                    self.write_keyword("RESPECT NULLS");
19266                }
19267            }
19268        }
19269
19270        // ORDER BY inside aggregate
19271        if !func.order_by.is_empty() {
19272            self.write_space();
19273            self.write_keyword("ORDER BY");
19274            self.write_space();
19275            for (i, ord) in func.order_by.iter().enumerate() {
19276                if i > 0 {
19277                    self.write(", ");
19278                }
19279                self.generate_ordered(ord)?;
19280            }
19281        }
19282
19283        // LIMIT inside aggregate
19284        if let Some(limit) = &func.limit {
19285            self.write_space();
19286            self.write_keyword("LIMIT");
19287            self.write_space();
19288            // Check if this is a Tuple representing LIMIT offset, count
19289            if let Expression::Tuple(t) = limit.as_ref() {
19290                if t.expressions.len() == 2 {
19291                    self.generate_expression(&t.expressions[0])?;
19292                    self.write(", ");
19293                    self.generate_expression(&t.expressions[1])?;
19294                } else {
19295                    self.generate_expression(limit)?;
19296                }
19297            } else {
19298                self.generate_expression(limit)?;
19299            }
19300        }
19301
19302        self.write(")");
19303
19304        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
19305        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
19306            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19307        {
19308            if let Some(ignore) = func.ignore_nulls {
19309                self.write_space();
19310                if ignore {
19311                    self.write_keyword("IGNORE NULLS");
19312                } else {
19313                    self.write_keyword("RESPECT NULLS");
19314                }
19315            }
19316        }
19317
19318        if let Some(filter) = &func.filter {
19319            self.write_space();
19320            self.write_keyword("FILTER");
19321            self.write("(");
19322            self.write_keyword("WHERE");
19323            self.write_space();
19324            self.generate_expression(filter)?;
19325            self.write(")");
19326        }
19327
19328        Ok(())
19329    }
19330
19331    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
19332        self.generate_expression(&wf.this)?;
19333
19334        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
19335        if let Some(keep) = &wf.keep {
19336            self.write_space();
19337            self.write_keyword("KEEP");
19338            self.write(" (");
19339            self.write_keyword("DENSE_RANK");
19340            self.write_space();
19341            if keep.first {
19342                self.write_keyword("FIRST");
19343            } else {
19344                self.write_keyword("LAST");
19345            }
19346            self.write_space();
19347            self.write_keyword("ORDER BY");
19348            self.write_space();
19349            for (i, ord) in keep.order_by.iter().enumerate() {
19350                if i > 0 {
19351                    self.write(", ");
19352                }
19353                self.generate_ordered(ord)?;
19354            }
19355            self.write(")");
19356        }
19357
19358        // Check if there's any OVER clause content
19359        let has_over = !wf.over.partition_by.is_empty()
19360            || !wf.over.order_by.is_empty()
19361            || wf.over.frame.is_some()
19362            || wf.over.window_name.is_some();
19363
19364        // Only output OVER if there's actual window specification (not just KEEP alone)
19365        if has_over {
19366            self.write_space();
19367            self.write_keyword("OVER");
19368
19369            // Check if this is just a bare named window reference (no parens needed)
19370            let has_specs = !wf.over.partition_by.is_empty()
19371                || !wf.over.order_by.is_empty()
19372                || wf.over.frame.is_some();
19373
19374            if wf.over.window_name.is_some() && !has_specs {
19375                // OVER window_name (without parentheses)
19376                self.write_space();
19377                self.write(&wf.over.window_name.as_ref().unwrap().name);
19378            } else {
19379                // OVER (...) or OVER (window_name ...)
19380                self.write(" (");
19381                self.generate_over(&wf.over)?;
19382                self.write(")");
19383            }
19384        } else if wf.keep.is_none() {
19385            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
19386            self.write_space();
19387            self.write_keyword("OVER");
19388            self.write(" ()");
19389        }
19390
19391        Ok(())
19392    }
19393
19394    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
19395    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
19396        self.generate_expression(&wg.this)?;
19397        self.write_space();
19398        self.write_keyword("WITHIN GROUP");
19399        self.write(" (");
19400        self.write_keyword("ORDER BY");
19401        self.write_space();
19402        for (i, ord) in wg.order_by.iter().enumerate() {
19403            if i > 0 {
19404                self.write(", ");
19405            }
19406            self.generate_ordered(ord)?;
19407        }
19408        self.write(")");
19409        Ok(())
19410    }
19411
19412    /// Generate the contents of an OVER clause (without parentheses)
19413    fn generate_over(&mut self, over: &Over) -> Result<()> {
19414        let mut has_content = false;
19415
19416        // Named window reference
19417        if let Some(name) = &over.window_name {
19418            self.write(&name.name);
19419            has_content = true;
19420        }
19421
19422        // PARTITION BY
19423        if !over.partition_by.is_empty() {
19424            if has_content {
19425                self.write_space();
19426            }
19427            self.write_keyword("PARTITION BY");
19428            self.write_space();
19429            for (i, expr) in over.partition_by.iter().enumerate() {
19430                if i > 0 {
19431                    self.write(", ");
19432                }
19433                self.generate_expression(expr)?;
19434            }
19435            has_content = true;
19436        }
19437
19438        // ORDER BY
19439        if !over.order_by.is_empty() {
19440            if has_content {
19441                self.write_space();
19442            }
19443            self.write_keyword("ORDER BY");
19444            self.write_space();
19445            for (i, ordered) in over.order_by.iter().enumerate() {
19446                if i > 0 {
19447                    self.write(", ");
19448                }
19449                self.generate_ordered(ordered)?;
19450            }
19451            has_content = true;
19452        }
19453
19454        // Window frame
19455        if let Some(frame) = &over.frame {
19456            if has_content {
19457                self.write_space();
19458            }
19459            self.generate_window_frame(frame)?;
19460        }
19461
19462        Ok(())
19463    }
19464
19465    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
19466        // Exasol uses lowercase for frame kind (rows/range/groups)
19467        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19468
19469        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
19470        if !lowercase_frame {
19471            if let Some(kind_text) = &frame.kind_text {
19472                self.write(kind_text);
19473            } else {
19474                match frame.kind {
19475                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
19476                    WindowFrameKind::Range => self.write_keyword("RANGE"),
19477                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
19478                }
19479            }
19480        } else {
19481            match frame.kind {
19482                WindowFrameKind::Rows => self.write("rows"),
19483                WindowFrameKind::Range => self.write("range"),
19484                WindowFrameKind::Groups => self.write("groups"),
19485            }
19486        }
19487
19488        // Use BETWEEN format only when there's an explicit end bound,
19489        // or when normalize_window_frame_between is enabled and the start is a directional bound
19490        self.write_space();
19491        let should_normalize = self.config.normalize_window_frame_between
19492            && frame.end.is_none()
19493            && matches!(
19494                frame.start,
19495                WindowFrameBound::Preceding(_)
19496                    | WindowFrameBound::Following(_)
19497                    | WindowFrameBound::UnboundedPreceding
19498                    | WindowFrameBound::UnboundedFollowing
19499            );
19500
19501        if let Some(end) = &frame.end {
19502            // BETWEEN format: RANGE BETWEEN start AND end
19503            self.write_keyword("BETWEEN");
19504            self.write_space();
19505            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19506            self.write_space();
19507            self.write_keyword("AND");
19508            self.write_space();
19509            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
19510        } else if should_normalize {
19511            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
19512            self.write_keyword("BETWEEN");
19513            self.write_space();
19514            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19515            self.write_space();
19516            self.write_keyword("AND");
19517            self.write_space();
19518            self.write_keyword("CURRENT ROW");
19519        } else {
19520            // Single bound format: RANGE CURRENT ROW
19521            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19522        }
19523
19524        // EXCLUDE clause
19525        if let Some(exclude) = &frame.exclude {
19526            self.write_space();
19527            self.write_keyword("EXCLUDE");
19528            self.write_space();
19529            match exclude {
19530                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
19531                WindowFrameExclude::Group => self.write_keyword("GROUP"),
19532                WindowFrameExclude::Ties => self.write_keyword("TIES"),
19533                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
19534            }
19535        }
19536
19537        Ok(())
19538    }
19539
19540    fn generate_window_frame_bound(
19541        &mut self,
19542        bound: &WindowFrameBound,
19543        side_text: Option<&str>,
19544    ) -> Result<()> {
19545        // Exasol uses lowercase for preceding/following
19546        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19547
19548        match bound {
19549            WindowFrameBound::CurrentRow => {
19550                self.write_keyword("CURRENT ROW");
19551            }
19552            WindowFrameBound::UnboundedPreceding => {
19553                self.write_keyword("UNBOUNDED");
19554                self.write_space();
19555                if lowercase_frame {
19556                    self.write("preceding");
19557                } else if let Some(text) = side_text {
19558                    self.write(text);
19559                } else {
19560                    self.write_keyword("PRECEDING");
19561                }
19562            }
19563            WindowFrameBound::UnboundedFollowing => {
19564                self.write_keyword("UNBOUNDED");
19565                self.write_space();
19566                if lowercase_frame {
19567                    self.write("following");
19568                } else if let Some(text) = side_text {
19569                    self.write(text);
19570                } else {
19571                    self.write_keyword("FOLLOWING");
19572                }
19573            }
19574            WindowFrameBound::Preceding(expr) => {
19575                self.generate_expression(expr)?;
19576                self.write_space();
19577                if lowercase_frame {
19578                    self.write("preceding");
19579                } else if let Some(text) = side_text {
19580                    self.write(text);
19581                } else {
19582                    self.write_keyword("PRECEDING");
19583                }
19584            }
19585            WindowFrameBound::Following(expr) => {
19586                self.generate_expression(expr)?;
19587                self.write_space();
19588                if lowercase_frame {
19589                    self.write("following");
19590                } else if let Some(text) = side_text {
19591                    self.write(text);
19592                } else {
19593                    self.write_keyword("FOLLOWING");
19594                }
19595            }
19596            WindowFrameBound::BarePreceding => {
19597                if lowercase_frame {
19598                    self.write("preceding");
19599                } else if let Some(text) = side_text {
19600                    self.write(text);
19601                } else {
19602                    self.write_keyword("PRECEDING");
19603                }
19604            }
19605            WindowFrameBound::BareFollowing => {
19606                if lowercase_frame {
19607                    self.write("following");
19608                } else if let Some(text) = side_text {
19609                    self.write(text);
19610                } else {
19611                    self.write_keyword("FOLLOWING");
19612                }
19613            }
19614            WindowFrameBound::Value(expr) => {
19615                // Bare numeric bound without PRECEDING/FOLLOWING
19616                self.generate_expression(expr)?;
19617            }
19618        }
19619        Ok(())
19620    }
19621
19622    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
19623        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
19624        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
19625        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
19626            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
19627            && !matches!(&interval.this, Some(Expression::Literal(_)));
19628
19629        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
19630        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
19631        if self.config.single_string_interval {
19632            if let (
19633                Some(Expression::Literal(lit)),
19634                Some(IntervalUnitSpec::Simple {
19635                    ref unit,
19636                    ref use_plural,
19637                }),
19638            ) = (&interval.this, &interval.unit)
19639            {
19640                if let Literal::String(ref val) = lit.as_ref() {
19641                    self.write_keyword("INTERVAL");
19642                    self.write_space();
19643                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19644                    let unit_str = self.interval_unit_str(unit, effective_plural);
19645                    self.write("'");
19646                    self.write(val);
19647                    self.write(" ");
19648                    self.write(&unit_str);
19649                    self.write("'");
19650                    return Ok(());
19651                }
19652            }
19653        }
19654
19655        if !skip_interval_keyword {
19656            self.write_keyword("INTERVAL");
19657        }
19658
19659        // Generate value if present
19660        if let Some(ref value) = interval.this {
19661            if !skip_interval_keyword {
19662                self.write_space();
19663            }
19664            // If the value is a complex expression (not a literal/column/function call)
19665            // and there's a unit, wrap it in parentheses
19666            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
19667            let needs_parens = interval.unit.is_some()
19668                && matches!(
19669                    value,
19670                    Expression::Add(_)
19671                        | Expression::Sub(_)
19672                        | Expression::Mul(_)
19673                        | Expression::Div(_)
19674                        | Expression::Mod(_)
19675                        | Expression::BitwiseAnd(_)
19676                        | Expression::BitwiseOr(_)
19677                        | Expression::BitwiseXor(_)
19678                );
19679            if needs_parens {
19680                self.write("(");
19681            }
19682            self.generate_expression(value)?;
19683            if needs_parens {
19684                self.write(")");
19685            }
19686        }
19687
19688        // Generate unit if present
19689        if let Some(ref unit_spec) = interval.unit {
19690            self.write_space();
19691            self.write_interval_unit_spec(unit_spec)?;
19692        }
19693
19694        Ok(())
19695    }
19696
19697    /// Return the string representation of an interval unit
19698    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
19699        match (unit, use_plural) {
19700            (IntervalUnit::Year, false) => "YEAR",
19701            (IntervalUnit::Year, true) => "YEARS",
19702            (IntervalUnit::Quarter, false) => "QUARTER",
19703            (IntervalUnit::Quarter, true) => "QUARTERS",
19704            (IntervalUnit::Month, false) => "MONTH",
19705            (IntervalUnit::Month, true) => "MONTHS",
19706            (IntervalUnit::Week, false) => "WEEK",
19707            (IntervalUnit::Week, true) => "WEEKS",
19708            (IntervalUnit::Day, false) => "DAY",
19709            (IntervalUnit::Day, true) => "DAYS",
19710            (IntervalUnit::Hour, false) => "HOUR",
19711            (IntervalUnit::Hour, true) => "HOURS",
19712            (IntervalUnit::Minute, false) => "MINUTE",
19713            (IntervalUnit::Minute, true) => "MINUTES",
19714            (IntervalUnit::Second, false) => "SECOND",
19715            (IntervalUnit::Second, true) => "SECONDS",
19716            (IntervalUnit::Millisecond, false) => "MILLISECOND",
19717            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
19718            (IntervalUnit::Microsecond, false) => "MICROSECOND",
19719            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
19720            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
19721            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
19722        }
19723    }
19724
19725    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
19726        match unit_spec {
19727            IntervalUnitSpec::Simple { unit, use_plural } => {
19728                // If dialect doesn't allow plural forms, force singular
19729                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19730                self.write_simple_interval_unit(unit, effective_plural);
19731            }
19732            IntervalUnitSpec::Span(span) => {
19733                self.write_simple_interval_unit(&span.this, false);
19734                self.write_space();
19735                self.write_keyword("TO");
19736                self.write_space();
19737                self.write_simple_interval_unit(&span.expression, false);
19738            }
19739            IntervalUnitSpec::ExprSpan(span) => {
19740                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
19741                self.generate_expression(&span.this)?;
19742                self.write_space();
19743                self.write_keyword("TO");
19744                self.write_space();
19745                self.generate_expression(&span.expression)?;
19746            }
19747            IntervalUnitSpec::Expr(expr) => {
19748                self.generate_expression(expr)?;
19749            }
19750        }
19751        Ok(())
19752    }
19753
19754    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
19755        // Output interval unit, respecting plural preference
19756        match (unit, use_plural) {
19757            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
19758            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
19759            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
19760            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
19761            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
19762            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
19763            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
19764            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
19765            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
19766            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
19767            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
19768            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
19769            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
19770            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
19771            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
19772            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
19773            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
19774            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
19775            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
19776            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
19777            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
19778            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
19779        }
19780    }
19781
19782    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
19783    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
19784    fn write_redshift_date_part(&mut self, expr: &Expression) {
19785        let part_str = self.extract_date_part_string(expr);
19786        if let Some(part) = part_str {
19787            let normalized = self.normalize_date_part(&part);
19788            self.write_keyword(&normalized);
19789        } else {
19790            // If we can't extract a date part string, fall back to generating the expression
19791            let _ = self.generate_expression(expr);
19792        }
19793    }
19794
19795    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
19796    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
19797    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
19798        let part_str = self.extract_date_part_string(expr);
19799        if let Some(part) = part_str {
19800            let normalized = self.normalize_date_part(&part);
19801            self.write("'");
19802            self.write(&normalized);
19803            self.write("'");
19804        } else {
19805            // If we can't extract a date part string, fall back to generating the expression
19806            let _ = self.generate_expression(expr);
19807        }
19808    }
19809
19810    /// Extract date part string from expression (handles string literals and identifiers)
19811    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
19812        match expr {
19813            Expression::Literal(lit)
19814                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
19815            {
19816                let crate::expressions::Literal::String(s) = lit.as_ref() else {
19817                    unreachable!()
19818                };
19819                Some(s.clone())
19820            }
19821            Expression::Identifier(id) => Some(id.name.clone()),
19822            Expression::Var(v) => Some(v.this.clone()),
19823            Expression::Column(col) if col.table.is_none() => {
19824                // Simple column reference without table prefix, treat as identifier
19825                Some(col.name.name.clone())
19826            }
19827            Expression::Cast(cast)
19828                if cast.format.is_none()
19829                    && cast.default.is_none()
19830                    && Self::is_string_data_type(&cast.to)
19831                    && matches!(
19832                        &cast.this,
19833                        Expression::Literal(lit)
19834                            if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
19835                    ) =>
19836            {
19837                self.extract_date_part_string(&cast.this)
19838            }
19839            _ => None,
19840        }
19841    }
19842
19843    fn classify_tsql_datetime_field(&self, field: &DateTimeField) -> TsqlDatePart {
19844        match field {
19845            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19846            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19847            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19848            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19849            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19850            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19851            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19852            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19853            DateTimeField::DayOfWeek => TsqlDatePart::Native("WEEKDAY".to_string()),
19854            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19855            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19856            DateTimeField::WeekWithModifier(modifier) => {
19857                TsqlDatePart::Unsupported(format!("WEEK({modifier})"))
19858            }
19859            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19860            DateTimeField::Epoch => TsqlDatePart::Epoch,
19861            DateTimeField::TimezoneMinute => TsqlDatePart::Native("TZOFFSET".to_string()),
19862            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19863            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19864            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19865            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19866            DateTimeField::Custom(name) => self.classify_tsql_date_part_name(name),
19867        }
19868    }
19869
19870    fn classify_tsql_date_part_name(&self, part: &str) -> TsqlDatePart {
19871        let trimmed = part.trim();
19872        let upper = trimmed.to_ascii_uppercase();
19873        match upper.as_str() {
19874            "YEAR" => TsqlDatePart::Native(trimmed.to_string()),
19875            "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19876                TsqlDatePart::Native("YEAR".to_string())
19877            }
19878            "QUARTER" => TsqlDatePart::Native(trimmed.to_string()),
19879            "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => TsqlDatePart::Native("QUARTER".to_string()),
19880            "MONTH" => TsqlDatePart::Native(trimmed.to_string()),
19881            "MM" | "M" | "MON" | "MONS" | "MONTHS" => TsqlDatePart::Native("MONTH".to_string()),
19882            "DAYOFYEAR" => TsqlDatePart::Native(trimmed.to_string()),
19883            "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19884            "DAY" => TsqlDatePart::Native(trimmed.to_string()),
19885            "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19886            "WEEK" | "W" | "WK" | "WW" | "WEEKOFYEAR" | "WOY" | "WY" | "WEEKS" => {
19887                if upper == "WEEK" {
19888                    TsqlDatePart::Native(trimmed.to_string())
19889                } else {
19890                    TsqlDatePart::Native("WEEK".to_string())
19891                }
19892            }
19893            "WEEKDAY" => TsqlDatePart::Native(trimmed.to_string()),
19894            "DAYOFWEEK" | "DOW" | "DW" => TsqlDatePart::Native("WEEKDAY".to_string()),
19895            "ISODOW" | "ISO_DOW" | "DOW_ISO" | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY"
19896            | "DAYOFWEEKISO" | "DAYOFWEEK_ISO" | "WEEKDAY_ISO" => TsqlDatePart::IsoDayOfWeek,
19897            "HOUR" => TsqlDatePart::Native(trimmed.to_string()),
19898            "H" | "HH" | "HR" | "HOURS" | "HRS" => TsqlDatePart::Native("HOUR".to_string()),
19899            "MINUTE" => TsqlDatePart::Native(trimmed.to_string()),
19900            "MI" | "MIN" | "MINUTES" | "MINS" | "N" => TsqlDatePart::Native("MINUTE".to_string()),
19901            "SECOND" => TsqlDatePart::Native(trimmed.to_string()),
19902            "S" | "SEC" | "SECONDS" | "SECS" | "SS" => TsqlDatePart::Native("SECOND".to_string()),
19903            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19904            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19905                if upper == "MILLISECOND" {
19906                    TsqlDatePart::Native(trimmed.to_string())
19907                } else {
19908                    TsqlDatePart::Native("MILLISECOND".to_string())
19909                }
19910            }
19911            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19912            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19913                if upper == "MICROSECOND" {
19914                    TsqlDatePart::Native(trimmed.to_string())
19915                } else {
19916                    TsqlDatePart::Native("MICROSECOND".to_string())
19917                }
19918            }
19919            "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS" | "NANOSECS" => {
19920                if upper == "NANOSECOND" {
19921                    TsqlDatePart::Native(trimmed.to_string())
19922                } else {
19923                    TsqlDatePart::Native("NANOSECOND".to_string())
19924                }
19925            }
19926            "TZOFFSET" => TsqlDatePart::Native(trimmed.to_string()),
19927            "TZ" | "TZM" | "TIMEZONE_MINUTE" => TsqlDatePart::Native("TZOFFSET".to_string()),
19928            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19929            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => {
19930                if upper == "ISO_WEEK" {
19931                    TsqlDatePart::Native(trimmed.to_string())
19932                } else {
19933                    TsqlDatePart::Native("ISO_WEEK".to_string())
19934                }
19935            }
19936            "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" => TsqlDatePart::Epoch,
19937            "DECADE" | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19938            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "TIMEZONE" | "TIMEZONE_HOUR" | "TZH"
19939            | "DATE" | "TIME" => TsqlDatePart::Unsupported(upper),
19940            _ => TsqlDatePart::Unsupported(upper),
19941        }
19942    }
19943
19944    fn classify_tsql_date_trunc_field(&self, field: &DateTimeField) -> TsqlDatePart {
19945        match field {
19946            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19947            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19948            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19949            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19950            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19951            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19952            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19953            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19954            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19955            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19956            DateTimeField::WeekWithModifier(modifier) => {
19957                match modifier.to_ascii_uppercase().as_str() {
19958                    "MONDAY" | "ISO" | "ISO_WEEK" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19959                    "SUNDAY" => TsqlDatePart::Native("WEEK".to_string()),
19960                    _ => TsqlDatePart::Unsupported(format!("WEEK({modifier})")),
19961                }
19962            }
19963            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19964            DateTimeField::DayOfWeek => TsqlDatePart::Unsupported("WEEKDAY".to_string()),
19965            DateTimeField::Epoch => TsqlDatePart::Unsupported("EPOCH".to_string()),
19966            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19967            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19968            DateTimeField::TimezoneMinute => {
19969                TsqlDatePart::Unsupported("TIMEZONE_MINUTE".to_string())
19970            }
19971            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19972            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19973            DateTimeField::Custom(name) => self.classify_tsql_date_trunc_name(name),
19974        }
19975    }
19976
19977    fn classify_tsql_date_trunc_name(&self, part: &str) -> TsqlDatePart {
19978        let upper = part.trim().to_ascii_uppercase();
19979        match upper.as_str() {
19980            "YEAR" | "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19981                TsqlDatePart::Native("YEAR".to_string())
19982            }
19983            "QUARTER" | "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => {
19984                TsqlDatePart::Native("QUARTER".to_string())
19985            }
19986            "MONTH" | "MM" | "M" | "MON" | "MONS" | "MONTHS" => {
19987                TsqlDatePart::Native("MONTH".to_string())
19988            }
19989            "DAYOFYEAR" | "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19990            "DAY" | "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19991            "WEEK" | "W" | "WK" | "WW" | "WEEKS" | "WEEKOFYEAR" | "WOY" | "WY" => {
19992                TsqlDatePart::Native("WEEK".to_string())
19993            }
19994            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19995            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19996            "HOUR" | "H" | "HH" | "HR" | "HOURS" | "HRS" => {
19997                TsqlDatePart::Native("HOUR".to_string())
19998            }
19999            "MINUTE" | "MI" | "MIN" | "MINUTES" | "MINS" | "N" => {
20000                TsqlDatePart::Native("MINUTE".to_string())
20001            }
20002            "SECOND" | "S" | "SEC" | "SECONDS" | "SECS" | "SS" => {
20003                TsqlDatePart::Native("SECOND".to_string())
20004            }
20005            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
20006            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
20007                TsqlDatePart::Native("MILLISECOND".to_string())
20008            }
20009            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
20010            | "USECONDS" | "MICROSECONDS" | "MCS" => {
20011                TsqlDatePart::Native("MICROSECOND".to_string())
20012            }
20013            "WEEKDAY" | "DAYOFWEEK" | "DOW" | "DW" | "ISODOW" | "ISO_DOW" | "DOW_ISO"
20014            | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY" | "DAYOFWEEKISO" | "DAYOFWEEK_ISO"
20015            | "WEEKDAY_ISO" | "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS"
20016            | "NANOSECS" | "TZOFFSET" | "TZ" | "TZM" | "TIMEZONE" | "TIMEZONE_HOUR"
20017            | "TIMEZONE_MINUTE" | "TZH" | "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" | "DECADE"
20018            | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
20019            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "DATE" | "TIME" => {
20020                TsqlDatePart::Unsupported(upper)
20021            }
20022            _ => TsqlDatePart::Unsupported(upper),
20023        }
20024    }
20025
20026    fn generate_tsql_date_part(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
20027        match part {
20028            TsqlDatePart::Native(name) => self.generate_tsql_native_datepart(&name, expr),
20029            TsqlDatePart::Epoch => self.generate_tsql_epoch_seconds(expr),
20030            TsqlDatePart::IsoDayOfWeek => self.generate_tsql_iso_day_of_week(expr),
20031            TsqlDatePart::Unsupported(name) => {
20032                self.unsupported(format!("DATEPART {name} is not supported by T-SQL/Fabric"))?;
20033                self.write_keyword("DATEPART");
20034                self.write("(");
20035                self.write_keyword(&name);
20036                self.write(", ");
20037                self.generate_expression(expr)?;
20038                self.write(")");
20039                Ok(())
20040            }
20041        }
20042    }
20043
20044    fn generate_tsql_native_datepart(&mut self, name: &str, expr: &Expression) -> Result<()> {
20045        self.write_keyword("DATEPART");
20046        self.write("(");
20047        self.write_keyword(name);
20048        self.write(", ");
20049        self.generate_expression(expr)?;
20050        self.write(")");
20051        Ok(())
20052    }
20053
20054    fn generate_tsql_epoch_seconds(&mut self, expr: &Expression) -> Result<()> {
20055        self.write_keyword("DATEDIFF");
20056        self.write("(SECOND, ");
20057        self.write_keyword("CAST");
20058        self.write("('1970-01-01' AS ");
20059        self.write_keyword("DATETIME2");
20060        self.write("), ");
20061        self.generate_expression(expr)?;
20062        self.write(")");
20063        Ok(())
20064    }
20065
20066    fn generate_tsql_iso_day_of_week(&mut self, expr: &Expression) -> Result<()> {
20067        self.write("(((DATEPART(WEEKDAY, ");
20068        self.generate_expression(expr)?;
20069        self.write(") + @@DATEFIRST - 2) % 7) + 1)");
20070        Ok(())
20071    }
20072
20073    fn generate_tsql_date_trunc(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
20074        match part {
20075            TsqlDatePart::Native(name) => {
20076                self.write_keyword("DATETRUNC");
20077                self.write("(");
20078                self.write_keyword(&name);
20079                self.write(", ");
20080                self.generate_expression(expr)?;
20081                self.write(")");
20082                Ok(())
20083            }
20084            TsqlDatePart::Epoch => {
20085                self.generate_tsql_unsupported_date_trunc("EPOCH".to_string(), expr)
20086            }
20087            TsqlDatePart::IsoDayOfWeek => {
20088                self.generate_tsql_unsupported_date_trunc("ISODOW".to_string(), expr)
20089            }
20090            TsqlDatePart::Unsupported(name) => {
20091                self.generate_tsql_unsupported_date_trunc(name, expr)
20092            }
20093        }
20094    }
20095
20096    fn generate_tsql_unsupported_date_trunc(
20097        &mut self,
20098        name: String,
20099        expr: &Expression,
20100    ) -> Result<()> {
20101        self.unsupported(format!("DATETRUNC {name} is not supported by T-SQL/Fabric"))?;
20102        self.write_keyword("DATETRUNC");
20103        self.write("(");
20104        self.write_keyword(&name);
20105        self.write(", ");
20106        self.generate_expression(expr)?;
20107        self.write(")");
20108        Ok(())
20109    }
20110
20111    /// Normalize date part to uppercase singular form
20112    /// days -> DAY, months -> MONTH, etc.
20113    fn normalize_date_part(&self, part: &str) -> String {
20114        let mut buf = [0u8; 64];
20115        let lower: &str = if part.len() <= 64 {
20116            for (i, b) in part.bytes().enumerate() {
20117                buf[i] = b.to_ascii_lowercase();
20118            }
20119            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
20120        } else {
20121            return part.to_ascii_uppercase();
20122        };
20123        match lower {
20124            "day" | "days" | "d" => "DAY".to_string(),
20125            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
20126            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
20127            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
20128            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
20129            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
20130            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
20131            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
20132            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
20133            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
20134            _ => part.to_ascii_uppercase(),
20135        }
20136    }
20137
20138    fn write_datetime_field(&mut self, field: &DateTimeField) {
20139        match field {
20140            DateTimeField::Year => self.write_keyword("YEAR"),
20141            DateTimeField::Month => self.write_keyword("MONTH"),
20142            DateTimeField::Day => self.write_keyword("DAY"),
20143            DateTimeField::Hour => self.write_keyword("HOUR"),
20144            DateTimeField::Minute => self.write_keyword("MINUTE"),
20145            DateTimeField::Second => self.write_keyword("SECOND"),
20146            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
20147            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
20148            DateTimeField::DayOfWeek => {
20149                let name = match self.config.dialect {
20150                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
20151                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
20152                    _ => "DOW",
20153                };
20154                self.write_keyword(name);
20155            }
20156            DateTimeField::DayOfYear => {
20157                let name = match self.config.dialect {
20158                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
20159                    _ => "DOY",
20160                };
20161                self.write_keyword(name);
20162            }
20163            DateTimeField::Week => self.write_keyword("WEEK"),
20164            DateTimeField::WeekWithModifier(modifier) => {
20165                self.write_keyword("WEEK");
20166                self.write("(");
20167                self.write(modifier);
20168                self.write(")");
20169            }
20170            DateTimeField::Quarter => self.write_keyword("QUARTER"),
20171            DateTimeField::Epoch => self.write_keyword("EPOCH"),
20172            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
20173            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
20174            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
20175            DateTimeField::Date => self.write_keyword("DATE"),
20176            DateTimeField::Time => self.write_keyword("TIME"),
20177            DateTimeField::Custom(name) => self.write(name),
20178        }
20179    }
20180
20181    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
20182    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
20183        match field {
20184            DateTimeField::Year => self.write("year"),
20185            DateTimeField::Month => self.write("month"),
20186            DateTimeField::Day => self.write("day"),
20187            DateTimeField::Hour => self.write("hour"),
20188            DateTimeField::Minute => self.write("minute"),
20189            DateTimeField::Second => self.write("second"),
20190            DateTimeField::Millisecond => self.write("millisecond"),
20191            DateTimeField::Microsecond => self.write("microsecond"),
20192            DateTimeField::DayOfWeek => self.write("dow"),
20193            DateTimeField::DayOfYear => self.write("doy"),
20194            DateTimeField::Week => self.write("week"),
20195            DateTimeField::WeekWithModifier(modifier) => {
20196                self.write("week(");
20197                self.write(modifier);
20198                self.write(")");
20199            }
20200            DateTimeField::Quarter => self.write("quarter"),
20201            DateTimeField::Epoch => self.write("epoch"),
20202            DateTimeField::Timezone => self.write("timezone"),
20203            DateTimeField::TimezoneHour => self.write("timezone_hour"),
20204            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
20205            DateTimeField::Date => self.write("date"),
20206            DateTimeField::Time => self.write("time"),
20207            DateTimeField::Custom(name) => self.write(name),
20208        }
20209    }
20210
20211    // Helper function generators
20212
20213    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
20214        self.write_keyword(name);
20215        self.write("(");
20216        self.generate_expression(arg)?;
20217        self.write(")");
20218        Ok(())
20219    }
20220
20221    /// Generate a unary function, using the original name if available for round-trip preservation
20222    fn generate_unary_func(
20223        &mut self,
20224        default_name: &str,
20225        f: &crate::expressions::UnaryFunc,
20226    ) -> Result<()> {
20227        let name = f.original_name.as_deref().unwrap_or(default_name);
20228        self.write_keyword(name);
20229        self.write("(");
20230        self.generate_expression(&f.this)?;
20231        self.write(")");
20232        Ok(())
20233    }
20234
20235    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
20236    fn generate_sqrt_cbrt(
20237        &mut self,
20238        f: &crate::expressions::UnaryFunc,
20239        func_name: &str,
20240        _op: &str,
20241    ) -> Result<()> {
20242        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
20243        // Always use function syntax for consistency
20244        self.write_keyword(func_name);
20245        self.write("(");
20246        self.generate_expression(&f.this)?;
20247        self.write(")");
20248        Ok(())
20249    }
20250
20251    fn generate_binary_func(
20252        &mut self,
20253        name: &str,
20254        arg1: &Expression,
20255        arg2: &Expression,
20256    ) -> Result<()> {
20257        self.write_keyword(name);
20258        self.write("(");
20259        self.generate_expression(arg1)?;
20260        self.write(", ");
20261        self.generate_expression(arg2)?;
20262        self.write(")");
20263        Ok(())
20264    }
20265
20266    /// Generate CHAR/CHR function with optional USING charset
20267    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
20268    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
20269    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
20270        // Use stored name if available, otherwise default to CHAR
20271        let func_name = f.name.as_deref().unwrap_or("CHAR");
20272        self.write_keyword(func_name);
20273        self.write("(");
20274        for (i, arg) in f.args.iter().enumerate() {
20275            if i > 0 {
20276                self.write(", ");
20277            }
20278            self.generate_expression(arg)?;
20279        }
20280        if let Some(ref charset) = f.charset {
20281            self.write(" ");
20282            self.write_keyword("USING");
20283            self.write(" ");
20284            self.write(charset);
20285        }
20286        self.write(")");
20287        Ok(())
20288    }
20289
20290    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
20291        use crate::dialects::DialectType;
20292
20293        match self.config.dialect {
20294            Some(DialectType::Teradata) => {
20295                // Teradata uses ** operator for exponentiation
20296                self.generate_expression(&f.this)?;
20297                self.write(" ** ");
20298                self.generate_expression(&f.expression)?;
20299                Ok(())
20300            }
20301            _ => {
20302                // Other dialects use POWER function
20303                self.generate_binary_func("POWER", &f.this, &f.expression)
20304            }
20305        }
20306    }
20307
20308    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
20309        self.write_func_name(name);
20310        self.write("(");
20311        for (i, arg) in args.iter().enumerate() {
20312            if i > 0 {
20313                self.write(", ");
20314            }
20315            self.generate_expression(arg)?;
20316        }
20317        self.write(")");
20318        Ok(())
20319    }
20320
20321    // String function generators
20322
20323    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
20324        self.write_keyword("CONCAT_WS");
20325        self.write("(");
20326        self.generate_expression(&f.separator)?;
20327        for expr in &f.expressions {
20328            self.write(", ");
20329            self.generate_expression(expr)?;
20330        }
20331        self.write(")");
20332        Ok(())
20333    }
20334
20335    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20336        if let Expression::Concat(op) = expr {
20337            Self::collect_concat_operands(&op.left, out);
20338            Self::collect_concat_operands(&op.right, out);
20339        } else {
20340            out.push(expr);
20341        }
20342    }
20343
20344    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
20345        let mut operands = Vec::new();
20346        Self::collect_concat_operands(&op.left, &mut operands);
20347        Self::collect_concat_operands(&op.right, &mut operands);
20348
20349        self.write_keyword("CONCAT");
20350        self.write("(");
20351        for (i, operand) in operands.iter().enumerate() {
20352            if i > 0 {
20353                self.write(", ");
20354            }
20355            self.generate_expression(operand)?;
20356        }
20357        self.write(")");
20358        Ok(())
20359    }
20360
20361    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20362        if let Expression::DPipe(dpipe) = expr {
20363            Self::collect_dpipe_operands(&dpipe.this, out);
20364            Self::collect_dpipe_operands(&dpipe.expression, out);
20365        } else {
20366            out.push(expr);
20367        }
20368    }
20369
20370    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
20371        let mut operands = Vec::new();
20372        Self::collect_dpipe_operands(&e.this, &mut operands);
20373        Self::collect_dpipe_operands(&e.expression, &mut operands);
20374
20375        self.write_keyword("CONCAT");
20376        self.write("(");
20377        for (i, operand) in operands.iter().enumerate() {
20378            if i > 0 {
20379                self.write(", ");
20380            }
20381            self.generate_expression(operand)?;
20382        }
20383        self.write(")");
20384        Ok(())
20385    }
20386
20387    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
20388        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
20389        let use_substr = matches!(
20390            self.config.dialect,
20391            Some(
20392                DialectType::Oracle
20393                    | DialectType::Presto
20394                    | DialectType::Trino
20395                    | DialectType::Athena
20396            )
20397        );
20398        if use_substr {
20399            self.write_keyword("SUBSTR");
20400        } else {
20401            self.write_keyword("SUBSTRING");
20402        }
20403        self.write("(");
20404        self.generate_expression(&f.this)?;
20405        // PostgreSQL always uses FROM/FOR syntax
20406        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
20407        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
20408        let use_comma_syntax = matches!(
20409            self.config.dialect,
20410            Some(DialectType::Spark)
20411                | Some(DialectType::Hive)
20412                | Some(DialectType::Databricks)
20413                | Some(DialectType::TSQL)
20414                | Some(DialectType::Fabric)
20415        );
20416        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
20417            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
20418            self.write_space();
20419            self.write_keyword("FROM");
20420            self.write_space();
20421            self.generate_expression(&f.start)?;
20422            if let Some(length) = &f.length {
20423                self.write_space();
20424                self.write_keyword("FOR");
20425                self.write_space();
20426                self.generate_expression(length)?;
20427            }
20428        } else {
20429            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
20430            self.write(", ");
20431            self.generate_expression(&f.start)?;
20432            if let Some(length) = &f.length {
20433                self.write(", ");
20434                self.generate_expression(length)?;
20435            }
20436        }
20437        self.write(")");
20438        Ok(())
20439    }
20440
20441    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
20442        self.write_keyword("OVERLAY");
20443        self.write("(");
20444        self.generate_expression(&f.this)?;
20445        self.write_space();
20446        self.write_keyword("PLACING");
20447        self.write_space();
20448        self.generate_expression(&f.replacement)?;
20449        self.write_space();
20450        self.write_keyword("FROM");
20451        self.write_space();
20452        self.generate_expression(&f.from)?;
20453        if let Some(length) = &f.length {
20454            self.write_space();
20455            self.write_keyword("FOR");
20456            self.write_space();
20457            self.generate_expression(length)?;
20458        }
20459        self.write(")");
20460        Ok(())
20461    }
20462
20463    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
20464        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
20465        // when no characters are specified (PostgreSQL style)
20466        if f.position_explicit && f.characters.is_none() {
20467            match f.position {
20468                TrimPosition::Leading => {
20469                    self.write_keyword("LTRIM");
20470                    self.write("(");
20471                    self.generate_expression(&f.this)?;
20472                    self.write(")");
20473                    return Ok(());
20474                }
20475                TrimPosition::Trailing => {
20476                    self.write_keyword("RTRIM");
20477                    self.write("(");
20478                    self.generate_expression(&f.this)?;
20479                    self.write(")");
20480                    return Ok(());
20481                }
20482                TrimPosition::Both => {
20483                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
20484                    // Fall through to standard TRIM handling
20485                }
20486            }
20487        }
20488
20489        self.write_keyword("TRIM");
20490        self.write("(");
20491        // When BOTH is specified without trim characters, simplify to just TRIM(str)
20492        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
20493        let force_standard = f.characters.is_some()
20494            && !f.sql_standard_syntax
20495            && matches!(
20496                self.config.dialect,
20497                Some(DialectType::Hive)
20498                    | Some(DialectType::Spark)
20499                    | Some(DialectType::Databricks)
20500                    | Some(DialectType::ClickHouse)
20501            );
20502        let use_standard = (f.sql_standard_syntax || force_standard)
20503            && !(f.position_explicit
20504                && f.characters.is_none()
20505                && matches!(f.position, TrimPosition::Both));
20506        if use_standard {
20507            // SQL standard syntax: TRIM(BOTH chars FROM str)
20508            // Only output position if it was explicitly specified
20509            if f.position_explicit {
20510                match f.position {
20511                    TrimPosition::Both => self.write_keyword("BOTH"),
20512                    TrimPosition::Leading => self.write_keyword("LEADING"),
20513                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
20514                }
20515                self.write_space();
20516            }
20517            if let Some(chars) = &f.characters {
20518                self.generate_expression(chars)?;
20519                self.write_space();
20520            }
20521            self.write_keyword("FROM");
20522            self.write_space();
20523            self.generate_expression(&f.this)?;
20524        } else {
20525            // Simple function syntax: TRIM(str) or TRIM(str, chars)
20526            self.generate_expression(&f.this)?;
20527            if let Some(chars) = &f.characters {
20528                self.write(", ");
20529                self.generate_expression(chars)?;
20530            }
20531        }
20532        self.write(")");
20533        Ok(())
20534    }
20535
20536    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
20537        self.write_keyword("REPLACE");
20538        self.write("(");
20539        self.generate_expression(&f.this)?;
20540        self.write(", ");
20541        self.generate_expression(&f.old)?;
20542        self.write(", ");
20543        self.generate_expression(&f.new)?;
20544        self.write(")");
20545        Ok(())
20546    }
20547
20548    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
20549        self.write_keyword(name);
20550        self.write("(");
20551        self.generate_expression(&f.this)?;
20552        self.write(", ");
20553        self.generate_expression(&f.length)?;
20554        self.write(")");
20555        Ok(())
20556    }
20557
20558    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
20559        self.write_keyword("REPEAT");
20560        self.write("(");
20561        self.generate_expression(&f.this)?;
20562        self.write(", ");
20563        self.generate_expression(&f.times)?;
20564        self.write(")");
20565        Ok(())
20566    }
20567
20568    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
20569        self.write_keyword(name);
20570        self.write("(");
20571        self.generate_expression(&f.this)?;
20572        self.write(", ");
20573        self.generate_expression(&f.length)?;
20574        if let Some(fill) = &f.fill {
20575            self.write(", ");
20576            self.generate_expression(fill)?;
20577        }
20578        self.write(")");
20579        Ok(())
20580    }
20581
20582    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
20583        self.write_keyword("SPLIT");
20584        self.write("(");
20585        self.generate_expression(&f.this)?;
20586        self.write(", ");
20587        self.generate_expression(&f.delimiter)?;
20588        self.write(")");
20589        Ok(())
20590    }
20591
20592    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
20593        use crate::dialects::DialectType;
20594        // PostgreSQL uses ~ operator for regex matching
20595        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
20596            self.generate_expression(&f.this)?;
20597            self.write(" ~ ");
20598            self.generate_expression(&f.pattern)?;
20599        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && f.flags.is_none()
20600        {
20601            // ClickHouse has no REGEXP_LIKE; the regex-match operator is match(haystack, pattern).
20602            self.write("match(");
20603            self.generate_expression(&f.this)?;
20604            self.write(", ");
20605            self.generate_expression(&f.pattern)?;
20606            self.write(")");
20607        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
20608            // Exasol uses REGEXP_LIKE as infix binary operator
20609            self.generate_expression(&f.this)?;
20610            self.write_keyword(" REGEXP_LIKE ");
20611            self.generate_expression(&f.pattern)?;
20612        } else if matches!(
20613            self.config.dialect,
20614            Some(DialectType::SingleStore)
20615                | Some(DialectType::Spark)
20616                | Some(DialectType::Hive)
20617                | Some(DialectType::Databricks)
20618        ) && f.flags.is_none()
20619        {
20620            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
20621            self.generate_expression(&f.this)?;
20622            self.write_keyword(" RLIKE ");
20623            self.generate_expression(&f.pattern)?;
20624        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
20625            // StarRocks uses REGEXP function syntax
20626            self.write_keyword("REGEXP");
20627            self.write("(");
20628            self.generate_expression(&f.this)?;
20629            self.write(", ");
20630            self.generate_expression(&f.pattern)?;
20631            if let Some(flags) = &f.flags {
20632                self.write(", ");
20633                self.generate_expression(flags)?;
20634            }
20635            self.write(")");
20636        } else {
20637            self.write_keyword("REGEXP_LIKE");
20638            self.write("(");
20639            self.generate_expression(&f.this)?;
20640            self.write(", ");
20641            self.generate_expression(&f.pattern)?;
20642            if let Some(flags) = &f.flags {
20643                self.write(", ");
20644                self.generate_expression(flags)?;
20645            }
20646            self.write(")");
20647        }
20648        Ok(())
20649    }
20650
20651    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
20652        self.write_keyword("REGEXP_REPLACE");
20653        self.write("(");
20654        self.generate_expression(&f.this)?;
20655        self.write(", ");
20656        self.generate_expression(&f.pattern)?;
20657        self.write(", ");
20658        self.generate_expression(&f.replacement)?;
20659        if let Some(flags) = &f.flags {
20660            self.write(", ");
20661            self.generate_expression(flags)?;
20662        }
20663        self.write(")");
20664        Ok(())
20665    }
20666
20667    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
20668        self.write_keyword("REGEXP_EXTRACT");
20669        self.write("(");
20670        self.generate_expression(&f.this)?;
20671        self.write(", ");
20672        self.generate_expression(&f.pattern)?;
20673        if let Some(group) = &f.group {
20674            self.write(", ");
20675            self.generate_expression(group)?;
20676        }
20677        self.write(")");
20678        Ok(())
20679    }
20680
20681    // Math function generators
20682
20683    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
20684        self.write_keyword("ROUND");
20685        self.write("(");
20686        self.generate_expression(&f.this)?;
20687        if let Some(decimals) = &f.decimals {
20688            self.write(", ");
20689            self.generate_expression(decimals)?;
20690        }
20691        self.write(")");
20692        Ok(())
20693    }
20694
20695    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
20696        self.write_keyword("FLOOR");
20697        self.write("(");
20698        self.generate_expression(&f.this)?;
20699        // Handle Druid-style FLOOR(time TO unit) syntax
20700        if let Some(to) = &f.to {
20701            self.write(" ");
20702            self.write_keyword("TO");
20703            self.write(" ");
20704            self.generate_expression(to)?;
20705        } else if let Some(scale) = &f.scale {
20706            self.write(", ");
20707            self.generate_expression(scale)?;
20708        }
20709        self.write(")");
20710        Ok(())
20711    }
20712
20713    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
20714        self.write_keyword("CEIL");
20715        self.write("(");
20716        self.generate_expression(&f.this)?;
20717        // Handle Druid-style CEIL(time TO unit) syntax
20718        if let Some(to) = &f.to {
20719            self.write(" ");
20720            self.write_keyword("TO");
20721            self.write(" ");
20722            self.generate_expression(to)?;
20723        } else if let Some(decimals) = &f.decimals {
20724            self.write(", ");
20725            self.generate_expression(decimals)?;
20726        }
20727        self.write(")");
20728        Ok(())
20729    }
20730
20731    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
20732        use crate::expressions::Literal;
20733
20734        if let Some(base) = &f.base {
20735            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
20736            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
20737            if self.is_log_base_none() {
20738                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
20739                {
20740                    self.write_func_name("LOG2");
20741                    self.write("(");
20742                    self.generate_expression(&f.this)?;
20743                    self.write(")");
20744                    return Ok(());
20745                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
20746                {
20747                    self.write_func_name("LOG10");
20748                    self.write("(");
20749                    self.generate_expression(&f.this)?;
20750                    self.write(")");
20751                    return Ok(());
20752                }
20753                // Other bases: fall through to LOG(base, value) — best effort
20754            }
20755
20756            self.write_func_name("LOG");
20757            self.write("(");
20758            if self.is_log_value_first() {
20759                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
20760                self.generate_expression(&f.this)?;
20761                self.write(", ");
20762                self.generate_expression(base)?;
20763            } else {
20764                // Default (PostgreSQL, etc.): LOG(base, value)
20765                self.generate_expression(base)?;
20766                self.write(", ");
20767                self.generate_expression(&f.this)?;
20768            }
20769            self.write(")");
20770        } else {
20771            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
20772            self.write_func_name("LOG");
20773            self.write("(");
20774            self.generate_expression(&f.this)?;
20775            self.write(")");
20776        }
20777        Ok(())
20778    }
20779
20780    /// Whether the target dialect uses LOG(value, base) order (value first).
20781    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
20782    fn is_log_value_first(&self) -> bool {
20783        use crate::dialects::DialectType;
20784        matches!(
20785            self.config.dialect,
20786            Some(DialectType::BigQuery)
20787                | Some(DialectType::TSQL)
20788                | Some(DialectType::Tableau)
20789                | Some(DialectType::Fabric)
20790        )
20791    }
20792
20793    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
20794    /// Presto, Trino, ClickHouse, Athena.
20795    fn is_log_base_none(&self) -> bool {
20796        use crate::dialects::DialectType;
20797        matches!(
20798            self.config.dialect,
20799            Some(DialectType::Presto)
20800                | Some(DialectType::Trino)
20801                | Some(DialectType::ClickHouse)
20802                | Some(DialectType::Athena)
20803        )
20804    }
20805
20806    // Date/time function generators
20807
20808    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
20809        self.write_keyword("CURRENT_TIME");
20810        if let Some(precision) = f.precision {
20811            self.write(&format!("({})", precision));
20812        } else if matches!(
20813            self.config.dialect,
20814            Some(crate::dialects::DialectType::MySQL)
20815                | Some(crate::dialects::DialectType::SingleStore)
20816                | Some(crate::dialects::DialectType::TiDB)
20817        ) {
20818            self.write("()");
20819        }
20820        Ok(())
20821    }
20822
20823    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
20824        use crate::dialects::DialectType;
20825
20826        // Oracle/Redshift SYSDATE handling
20827        if f.sysdate {
20828            match self.config.dialect {
20829                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
20830                    self.write_keyword("SYSDATE");
20831                    return Ok(());
20832                }
20833                Some(DialectType::Snowflake) => {
20834                    // Snowflake uses SYSDATE() function
20835                    self.write_keyword("SYSDATE");
20836                    self.write("()");
20837                    return Ok(());
20838                }
20839                _ => {
20840                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
20841                }
20842            }
20843        }
20844
20845        self.write_keyword("CURRENT_TIMESTAMP");
20846        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
20847        if let Some(precision) = f.precision {
20848            self.write(&format!("({})", precision));
20849        } else if matches!(
20850            self.config.dialect,
20851            Some(crate::dialects::DialectType::MySQL)
20852                | Some(crate::dialects::DialectType::SingleStore)
20853                | Some(crate::dialects::DialectType::TiDB)
20854                | Some(crate::dialects::DialectType::Spark)
20855                | Some(crate::dialects::DialectType::Hive)
20856                | Some(crate::dialects::DialectType::Databricks)
20857                | Some(crate::dialects::DialectType::ClickHouse)
20858                | Some(crate::dialects::DialectType::BigQuery)
20859                | Some(crate::dialects::DialectType::Snowflake)
20860                | Some(crate::dialects::DialectType::Exasol)
20861        ) {
20862            self.write("()");
20863        }
20864        Ok(())
20865    }
20866
20867    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
20868        if matches!(&f.zone, Expression::Identifier(identifier) if identifier.name.eq_ignore_ascii_case("LOCAL"))
20869        {
20870            self.generate_expression(&f.this)?;
20871            self.write_space();
20872            self.write_keyword("AT LOCAL");
20873            return Ok(());
20874        }
20875
20876        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
20877        if self.config.dialect == Some(DialectType::Exasol) {
20878            self.write_keyword("CONVERT_TZ");
20879            self.write("(");
20880            self.generate_expression(&f.this)?;
20881            self.write(", 'UTC', ");
20882            self.generate_expression(&f.zone)?;
20883            self.write(")");
20884            return Ok(());
20885        }
20886
20887        self.generate_expression(&f.this)?;
20888        self.write_space();
20889        self.write_keyword("AT TIME ZONE");
20890        self.write_space();
20891        self.generate_expression(&f.zone)?;
20892        Ok(())
20893    }
20894
20895    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
20896        use crate::dialects::DialectType;
20897
20898        // Presto/Trino use DATE_ADD('unit', interval, date) format
20899        // with the interval cast to BIGINT when needed
20900        let is_presto_like = matches!(
20901            self.config.dialect,
20902            Some(DialectType::Presto) | Some(DialectType::Trino)
20903        );
20904
20905        if is_presto_like {
20906            self.write_keyword(name);
20907            self.write("(");
20908            // Unit as string literal
20909            self.write("'");
20910            self.write_simple_interval_unit(&f.unit, false);
20911            self.write("'");
20912            self.write(", ");
20913            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
20914            let needs_cast = !self.returns_integer_type(&f.interval);
20915            if needs_cast {
20916                self.write_keyword("CAST");
20917                self.write("(");
20918            }
20919            self.generate_expression(&f.interval)?;
20920            if needs_cast {
20921                self.write_space();
20922                self.write_keyword("AS");
20923                self.write_space();
20924                self.write_keyword("BIGINT");
20925                self.write(")");
20926            }
20927            self.write(", ");
20928            self.generate_expression(&f.this)?;
20929            self.write(")");
20930        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20931            self.generate_expression(&f.this)?;
20932            self.write_space();
20933            if name.eq_ignore_ascii_case("DATE_SUB") {
20934                self.write("-");
20935            } else {
20936                self.write("+");
20937            }
20938            self.write_space();
20939            self.write_keyword("INTERVAL");
20940            self.write_space();
20941            self.write("'");
20942            let mut interval_gen = Generator::with_arc_config(self.config.clone());
20943            let interval_sql = interval_gen.generate(&f.interval)?;
20944            self.write(&interval_sql);
20945            self.write(" ");
20946            self.write_simple_interval_unit(&f.unit, false);
20947            self.write("'");
20948        } else {
20949            self.write_keyword(name);
20950            self.write("(");
20951            self.generate_expression(&f.this)?;
20952            self.write(", ");
20953            self.write_keyword("INTERVAL");
20954            self.write_space();
20955            self.generate_expression(&f.interval)?;
20956            self.write_space();
20957            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
20958            self.write(")");
20959        }
20960        Ok(())
20961    }
20962
20963    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
20964    /// This is a heuristic to avoid full type inference
20965    fn returns_integer_type(&self, expr: &Expression) -> bool {
20966        use crate::expressions::{DataType, Literal};
20967        match expr {
20968            // Integer literals (no decimal point)
20969            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
20970                let Literal::Number(n) = lit.as_ref() else {
20971                    unreachable!()
20972                };
20973                !n.contains('.')
20974            }
20975
20976            // FLOOR(x) returns integer if x is integer
20977            Expression::Floor(f) => self.returns_integer_type(&f.this),
20978
20979            // ROUND(x) returns integer if x is integer
20980            Expression::Round(f) => {
20981                // Only if no decimals arg or it's returning an integer
20982                f.decimals.is_none() && self.returns_integer_type(&f.this)
20983            }
20984
20985            // SIGN returns integer if input is integer
20986            Expression::Sign(f) => self.returns_integer_type(&f.this),
20987
20988            // ABS returns the same type as input
20989            Expression::Abs(f) => self.returns_integer_type(&f.this),
20990
20991            // Arithmetic operations on integers return integers
20992            Expression::Mul(op) => {
20993                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20994            }
20995            Expression::Add(op) => {
20996                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20997            }
20998            Expression::Sub(op) => {
20999                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
21000            }
21001            Expression::Mod(op) => self.returns_integer_type(&op.left),
21002
21003            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
21004            Expression::Cast(c) => matches!(
21005                &c.to,
21006                DataType::BigInt { .. }
21007                    | DataType::Int { .. }
21008                    | DataType::SmallInt { .. }
21009                    | DataType::TinyInt { .. }
21010            ),
21011
21012            // Negation: -x returns integer if x is integer
21013            Expression::Neg(op) => self.returns_integer_type(&op.this),
21014
21015            // Parenthesized expression
21016            Expression::Paren(p) => self.returns_integer_type(&p.this),
21017
21018            // Column references and most expressions are assumed to need casting
21019            // since we don't have full type information
21020            _ => false,
21021        }
21022    }
21023
21024    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
21025        self.write_keyword("DATEDIFF");
21026        self.write("(");
21027        if let Some(unit) = &f.unit {
21028            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
21029            self.write(", ");
21030        }
21031        if self.config.dialect == Some(DialectType::Snowflake) {
21032            self.generate_expression(&f.expression)?;
21033            self.write(", ");
21034            self.generate_expression(&f.this)?;
21035        } else {
21036            self.generate_expression(&f.this)?;
21037            self.write(", ");
21038            self.generate_expression(&f.expression)?;
21039        }
21040        self.write(")");
21041        Ok(())
21042    }
21043
21044    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
21045        if matches!(
21046            self.config.dialect,
21047            Some(DialectType::TSQL) | Some(DialectType::Fabric)
21048        ) {
21049            let date_part = self.classify_tsql_date_trunc_field(&f.unit);
21050            self.generate_tsql_date_trunc(date_part, &f.this)?;
21051            return Ok(());
21052        }
21053        if self.config.dialect == Some(DialectType::ClickHouse) {
21054            self.write("dateTrunc");
21055        } else {
21056            self.write_keyword("DATE_TRUNC");
21057        }
21058        self.write("('");
21059        self.write_datetime_field(&f.unit);
21060        self.write("', ");
21061        self.generate_expression(&f.this)?;
21062        self.write(")");
21063        Ok(())
21064    }
21065
21066    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
21067        use crate::dialects::DialectType;
21068        use crate::expressions::DateTimeField;
21069
21070        self.write_keyword("LAST_DAY");
21071        self.write("(");
21072        self.generate_expression(&f.this)?;
21073        if let Some(unit) = &f.unit {
21074            self.write(", ");
21075            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
21076            // WEEK(SUNDAY) -> WEEK
21077            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21078                if let DateTimeField::WeekWithModifier(_) = unit {
21079                    self.write_keyword("WEEK");
21080                } else {
21081                    self.write_datetime_field(unit);
21082                }
21083            } else {
21084                self.write_datetime_field(unit);
21085            }
21086        }
21087        self.write(")");
21088        Ok(())
21089    }
21090
21091    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
21092        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
21093        if matches!(
21094            self.config.dialect,
21095            Some(DialectType::TSQL) | Some(DialectType::Fabric)
21096        ) {
21097            let date_part = self.classify_tsql_datetime_field(&f.field);
21098            self.generate_tsql_date_part(date_part, &f.this)?;
21099            return Ok(());
21100        }
21101        self.write_keyword("EXTRACT");
21102        self.write("(");
21103        // Hive/Spark use lowercase datetime fields in EXTRACT
21104        if matches!(
21105            self.config.dialect,
21106            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
21107        ) {
21108            self.write_datetime_field_lower(&f.field);
21109        } else {
21110            self.write_datetime_field(&f.field);
21111        }
21112        self.write_space();
21113        self.write_keyword("FROM");
21114        self.write_space();
21115        self.generate_expression(&f.this)?;
21116        self.write(")");
21117        Ok(())
21118    }
21119
21120    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
21121        self.write_keyword("TO_DATE");
21122        self.write("(");
21123        self.generate_expression(&f.this)?;
21124        if let Some(format) = &f.format {
21125            self.write(", ");
21126            self.generate_expression(format)?;
21127        }
21128        self.write(")");
21129        Ok(())
21130    }
21131
21132    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
21133        self.write_keyword("TO_TIMESTAMP");
21134        self.write("(");
21135        self.generate_expression(&f.this)?;
21136        if let Some(format) = &f.format {
21137            self.write(", ");
21138            self.generate_expression(format)?;
21139        }
21140        self.write(")");
21141        Ok(())
21142    }
21143
21144    // Control flow function generators
21145
21146    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
21147        use crate::dialects::DialectType;
21148
21149        // Generic mode: normalize IF to CASE WHEN
21150        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
21151            self.write_keyword("CASE WHEN");
21152            self.write_space();
21153            self.generate_expression(&f.condition)?;
21154            self.write_space();
21155            self.write_keyword("THEN");
21156            self.write_space();
21157            self.generate_expression(&f.true_value)?;
21158            if let Some(false_val) = &f.false_value {
21159                self.write_space();
21160                self.write_keyword("ELSE");
21161                self.write_space();
21162                self.generate_expression(false_val)?;
21163            }
21164            self.write_space();
21165            self.write_keyword("END");
21166            return Ok(());
21167        }
21168
21169        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
21170        if self.config.dialect == Some(DialectType::Exasol) {
21171            self.write_keyword("IF");
21172            self.write_space();
21173            self.generate_expression(&f.condition)?;
21174            self.write_space();
21175            self.write_keyword("THEN");
21176            self.write_space();
21177            self.generate_expression(&f.true_value)?;
21178            if let Some(false_val) = &f.false_value {
21179                self.write_space();
21180                self.write_keyword("ELSE");
21181                self.write_space();
21182                self.generate_expression(false_val)?;
21183            }
21184            self.write_space();
21185            self.write_keyword("ENDIF");
21186            return Ok(());
21187        }
21188
21189        // Choose function name based on target dialect
21190        let func_name = match self.config.dialect {
21191            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
21192            Some(DialectType::Snowflake) => "IFF",
21193            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
21194            Some(DialectType::Drill) => "`IF`",
21195            _ => "IF",
21196        };
21197        self.write(func_name);
21198        self.write("(");
21199        self.generate_expression(&f.condition)?;
21200        self.write(", ");
21201        self.generate_expression(&f.true_value)?;
21202        if let Some(false_val) = &f.false_value {
21203            self.write(", ");
21204            self.generate_expression(false_val)?;
21205        }
21206        self.write(")");
21207        Ok(())
21208    }
21209
21210    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
21211        self.write_keyword("NVL2");
21212        self.write("(");
21213        self.generate_expression(&f.this)?;
21214        self.write(", ");
21215        self.generate_expression(&f.true_value)?;
21216        self.write(", ");
21217        self.generate_expression(&f.false_value)?;
21218        self.write(")");
21219        Ok(())
21220    }
21221
21222    // Typed aggregate function generators
21223
21224    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
21225        // Use normalize_functions for COUNT to respect ClickHouse case preservation
21226        let count_name = match self.config.normalize_functions {
21227            NormalizeFunctions::Upper => "COUNT".to_string(),
21228            NormalizeFunctions::Lower => "count".to_string(),
21229            NormalizeFunctions::None => f
21230                .original_name
21231                .clone()
21232                .unwrap_or_else(|| "COUNT".to_string()),
21233        };
21234        self.write(&count_name);
21235        self.write("(");
21236        if f.distinct {
21237            self.write_keyword("DISTINCT");
21238            self.write_space();
21239        }
21240        if f.star {
21241            self.write("*");
21242        } else if let Some(ref expr) = f.this {
21243            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
21244            if let Expression::Tuple(tuple) = expr {
21245                // Check if we need to transform multi-arg COUNT DISTINCT
21246                // When dialect doesn't support multi_arg_distinct, transform:
21247                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
21248                let needs_transform =
21249                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
21250
21251                if needs_transform {
21252                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
21253                    self.write_keyword("CASE");
21254                    for e in &tuple.expressions {
21255                        self.write_space();
21256                        self.write_keyword("WHEN");
21257                        self.write_space();
21258                        self.generate_expression(e)?;
21259                        self.write_space();
21260                        self.write_keyword("IS NULL THEN NULL");
21261                    }
21262                    self.write_space();
21263                    self.write_keyword("ELSE");
21264                    self.write(" (");
21265                    for (i, e) in tuple.expressions.iter().enumerate() {
21266                        if i > 0 {
21267                            self.write(", ");
21268                        }
21269                        self.generate_expression(e)?;
21270                    }
21271                    self.write(")");
21272                    self.write_space();
21273                    self.write_keyword("END");
21274                } else {
21275                    for (i, e) in tuple.expressions.iter().enumerate() {
21276                        if i > 0 {
21277                            self.write(", ");
21278                        }
21279                        self.generate_expression(e)?;
21280                    }
21281                }
21282            } else {
21283                self.generate_expression(expr)?;
21284            }
21285        }
21286        let clickhouse_ignore_nulls_outside =
21287            matches!(self.config.dialect, Some(DialectType::ClickHouse));
21288        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
21289            self.write_space();
21290            if ignore {
21291                self.write_keyword("IGNORE NULLS");
21292            } else {
21293                self.write_keyword("RESPECT NULLS");
21294            }
21295        }
21296        self.write(")");
21297        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
21298            self.write_space();
21299            if ignore {
21300                self.write_keyword("IGNORE NULLS");
21301            } else {
21302                self.write_keyword("RESPECT NULLS");
21303            }
21304        }
21305        if let Some(ref filter) = f.filter {
21306            self.write_space();
21307            self.write_keyword("FILTER");
21308            self.write("(");
21309            self.write_keyword("WHERE");
21310            self.write_space();
21311            self.generate_expression(filter)?;
21312            self.write(")");
21313        }
21314        Ok(())
21315    }
21316
21317    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21318        // Apply function name normalization based on config
21319        let func_name: Cow<'_, str> = match self.config.normalize_functions {
21320            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21321            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21322            NormalizeFunctions::None => {
21323                // Use the original function name from parsing if available,
21324                // otherwise fall back to lowercase of the hardcoded constant
21325                if let Some(ref original) = f.name {
21326                    Cow::Owned(original.clone())
21327                } else {
21328                    Cow::Owned(name.to_ascii_lowercase())
21329                }
21330            }
21331        };
21332        self.write(func_name.as_ref());
21333        self.write("(");
21334        if f.distinct {
21335            self.write_keyword("DISTINCT");
21336            self.write_space();
21337        }
21338        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
21339        // Other aggregates may legitimately receive NULL as an explicit argument.
21340        let is_zero_arg_mode =
21341            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
21342        if !is_zero_arg_mode {
21343            self.generate_expression(&f.this)?;
21344        }
21345        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
21346        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21347        if self.config.ignore_nulls_in_func
21348            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21349        {
21350            match f.ignore_nulls {
21351                Some(true) => {
21352                    self.write_space();
21353                    self.write_keyword("IGNORE NULLS");
21354                }
21355                Some(false) => {
21356                    self.write_space();
21357                    self.write_keyword("RESPECT NULLS");
21358                }
21359                None => {}
21360            }
21361        }
21362        // Generate HAVING MAX/MIN if present (BigQuery syntax)
21363        // e.g., ANY_VALUE(fruit HAVING MAX sold)
21364        if let Some((ref expr, is_max)) = f.having_max {
21365            self.write_space();
21366            self.write_keyword("HAVING");
21367            self.write_space();
21368            if is_max {
21369                self.write_keyword("MAX");
21370            } else {
21371                self.write_keyword("MIN");
21372            }
21373            self.write_space();
21374            self.generate_expression(expr)?;
21375        }
21376        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
21377        if !f.order_by.is_empty() {
21378            self.write_space();
21379            self.write_keyword("ORDER BY");
21380            self.write_space();
21381            for (i, ord) in f.order_by.iter().enumerate() {
21382                if i > 0 {
21383                    self.write(", ");
21384                }
21385                self.generate_ordered(ord)?;
21386            }
21387        }
21388        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
21389        if let Some(ref limit) = f.limit {
21390            self.write_space();
21391            self.write_keyword("LIMIT");
21392            self.write_space();
21393            // Check if this is a Tuple representing LIMIT offset, count
21394            if let Expression::Tuple(t) = limit.as_ref() {
21395                if t.expressions.len() == 2 {
21396                    self.generate_expression(&t.expressions[0])?;
21397                    self.write(", ");
21398                    self.generate_expression(&t.expressions[1])?;
21399                } else {
21400                    self.generate_expression(limit)?;
21401                }
21402            } else {
21403                self.generate_expression(limit)?;
21404            }
21405        }
21406        self.write(")");
21407        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
21408        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21409        if !self.config.ignore_nulls_in_func
21410            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21411        {
21412            match f.ignore_nulls {
21413                Some(true) => {
21414                    self.write_space();
21415                    self.write_keyword("IGNORE NULLS");
21416                }
21417                Some(false) => {
21418                    self.write_space();
21419                    self.write_keyword("RESPECT NULLS");
21420                }
21421                None => {}
21422            }
21423        }
21424        if let Some(ref filter) = f.filter {
21425            self.write_space();
21426            self.write_keyword("FILTER");
21427            self.write("(");
21428            self.write_keyword("WHERE");
21429            self.write_space();
21430            self.generate_expression(filter)?;
21431            self.write(")");
21432        }
21433        Ok(())
21434    }
21435
21436    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
21437    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
21438    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21439        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
21440        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21441            // Create a modified copy without ignore_nulls, add TRUE as part of the output
21442            let func_name: Cow<'_, str> = match self.config.normalize_functions {
21443                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21444                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21445                NormalizeFunctions::None => {
21446                    if let Some(ref original) = f.name {
21447                        Cow::Owned(original.clone())
21448                    } else {
21449                        Cow::Owned(name.to_ascii_lowercase())
21450                    }
21451                }
21452            };
21453            self.write(func_name.as_ref());
21454            self.write("(");
21455            if f.distinct {
21456                self.write_keyword("DISTINCT");
21457                self.write_space();
21458            }
21459            if !matches!(f.this, Expression::Null(_)) {
21460                self.generate_expression(&f.this)?;
21461            }
21462            self.write(", ");
21463            self.write_keyword("TRUE");
21464            self.write(")");
21465            return Ok(());
21466        }
21467        self.generate_agg_func(name, f)
21468    }
21469
21470    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
21471        self.write_keyword("GROUP_CONCAT");
21472        self.write("(");
21473        if f.distinct {
21474            self.write_keyword("DISTINCT");
21475            self.write_space();
21476        }
21477        self.generate_expression(&f.this)?;
21478        if let Some(ref order_by) = f.order_by {
21479            self.write_space();
21480            self.write_keyword("ORDER BY");
21481            self.write_space();
21482            for (i, ord) in order_by.iter().enumerate() {
21483                if i > 0 {
21484                    self.write(", ");
21485                }
21486                self.generate_ordered(ord)?;
21487            }
21488        }
21489        if let Some(ref sep) = f.separator {
21490            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
21491            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
21492            if matches!(
21493                self.config.dialect,
21494                Some(crate::dialects::DialectType::SQLite)
21495            ) {
21496                self.write(", ");
21497                self.generate_expression(sep)?;
21498            } else {
21499                self.write_space();
21500                self.write_keyword("SEPARATOR");
21501                self.write_space();
21502                self.generate_expression(sep)?;
21503            }
21504        }
21505        if let Some(ref limit) = f.limit {
21506            self.write_space();
21507            self.write_keyword("LIMIT");
21508            self.write_space();
21509            self.generate_expression(limit)?;
21510        }
21511        self.write(")");
21512        if let Some(ref filter) = f.filter {
21513            self.write_space();
21514            self.write_keyword("FILTER");
21515            self.write("(");
21516            self.write_keyword("WHERE");
21517            self.write_space();
21518            self.generate_expression(filter)?;
21519            self.write(")");
21520        }
21521        Ok(())
21522    }
21523
21524    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
21525        let uses_within_group_order = matches!(
21526            self.config.dialect,
21527            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21528        );
21529        self.write_keyword("STRING_AGG");
21530        self.write("(");
21531        if f.distinct {
21532            self.write_keyword("DISTINCT");
21533            self.write_space();
21534        }
21535        self.generate_expression(&f.this)?;
21536        if let Some(ref separator) = f.separator {
21537            self.write(", ");
21538            self.generate_string_agg_separator(separator)?;
21539        }
21540        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
21541        if !uses_within_group_order {
21542            if let Some(ref order_by) = f.order_by {
21543                self.write_space();
21544                self.write_keyword("ORDER BY");
21545                self.write_space();
21546                for (i, ord) in order_by.iter().enumerate() {
21547                    if i > 0 {
21548                        self.write(", ");
21549                    }
21550                    self.generate_ordered(ord)?;
21551                }
21552            }
21553        }
21554        if let Some(ref limit) = f.limit {
21555            self.write_space();
21556            self.write_keyword("LIMIT");
21557            self.write_space();
21558            self.generate_expression(limit)?;
21559        }
21560        self.write(")");
21561        if uses_within_group_order {
21562            if let Some(ref order_by) = f.order_by {
21563                self.write_space();
21564                self.write_keyword("WITHIN GROUP");
21565                self.write(" (");
21566                self.write_keyword("ORDER BY");
21567                self.write_space();
21568                for (i, ord) in order_by.iter().enumerate() {
21569                    if i > 0 {
21570                        self.write(", ");
21571                    }
21572                    self.generate_ordered(ord)?;
21573                }
21574                self.write(")");
21575            }
21576        }
21577        if let Some(ref filter) = f.filter {
21578            self.write_space();
21579            self.write_keyword("FILTER");
21580            self.write("(");
21581            self.write_keyword("WHERE");
21582            self.write_space();
21583            self.generate_expression(filter)?;
21584            self.write(")");
21585        }
21586        Ok(())
21587    }
21588
21589    fn generate_string_agg_separator(&mut self, separator: &Expression) -> Result<()> {
21590        if matches!(
21591            self.config.dialect,
21592            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21593        ) {
21594            if let Some(inner) = Self::tsql_string_agg_literal_separator_cast_inner(separator) {
21595                return self.generate_expression(inner);
21596            }
21597        }
21598
21599        self.generate_expression(separator)
21600    }
21601
21602    fn tsql_string_agg_literal_separator_cast_inner(separator: &Expression) -> Option<&Expression> {
21603        let Expression::Cast(cast) = separator else {
21604            return None;
21605        };
21606
21607        if cast.format.is_some() || cast.default.is_some() {
21608            return None;
21609        }
21610
21611        if !Self::is_string_data_type(&cast.to) {
21612            return None;
21613        }
21614
21615        match &cast.this {
21616            Expression::Literal(literal) if literal.is_string() => Some(&cast.this),
21617            _ => None,
21618        }
21619    }
21620
21621    fn is_string_data_type(data_type: &DataType) -> bool {
21622        matches!(
21623            data_type,
21624            DataType::Char { .. }
21625                | DataType::VarChar { .. }
21626                | DataType::String { .. }
21627                | DataType::Text
21628                | DataType::TextWithLength { .. }
21629        ) || matches!(
21630            data_type,
21631            DataType::Custom { name, .. }
21632                if name.eq_ignore_ascii_case("VARCHAR")
21633                    || name.eq_ignore_ascii_case("NVARCHAR")
21634                    || name.eq_ignore_ascii_case("VARCHAR(MAX)")
21635                    || name.eq_ignore_ascii_case("NVARCHAR(MAX)")
21636        )
21637    }
21638
21639    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
21640        use crate::dialects::DialectType;
21641        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
21642        self.write_keyword("LISTAGG");
21643        self.write("(");
21644        if f.distinct {
21645            self.write_keyword("DISTINCT");
21646            self.write_space();
21647        }
21648        self.generate_expression(&f.this)?;
21649        if let Some(ref sep) = f.separator {
21650            self.write(", ");
21651            self.generate_expression(sep)?;
21652        } else if matches!(
21653            self.config.dialect,
21654            Some(DialectType::Trino) | Some(DialectType::Presto)
21655        ) {
21656            // Trino/Presto require explicit separator; default to ','
21657            self.write(", ','");
21658        }
21659        if let Some(ref overflow) = f.on_overflow {
21660            self.write_space();
21661            self.write_keyword("ON OVERFLOW");
21662            self.write_space();
21663            match overflow {
21664                ListAggOverflow::Error => self.write_keyword("ERROR"),
21665                ListAggOverflow::Truncate { filler, with_count } => {
21666                    self.write_keyword("TRUNCATE");
21667                    if let Some(ref fill) = filler {
21668                        self.write_space();
21669                        self.generate_expression(fill)?;
21670                    }
21671                    if *with_count {
21672                        self.write_space();
21673                        self.write_keyword("WITH COUNT");
21674                    } else {
21675                        self.write_space();
21676                        self.write_keyword("WITHOUT COUNT");
21677                    }
21678                }
21679            }
21680        }
21681        if order_inside_args {
21682            if let Some(ref order_by) = f.order_by {
21683                self.write_space();
21684                self.write_keyword("ORDER BY");
21685                self.write_space();
21686                for (i, ord) in order_by.iter().enumerate() {
21687                    if i > 0 {
21688                        self.write(", ");
21689                    }
21690                    self.generate_ordered(ord)?;
21691                }
21692            }
21693        }
21694        self.write(")");
21695        if !order_inside_args {
21696            if let Some(ref order_by) = f.order_by {
21697                self.write_space();
21698                self.write_keyword("WITHIN GROUP");
21699                self.write(" (");
21700                self.write_keyword("ORDER BY");
21701                self.write_space();
21702                for (i, ord) in order_by.iter().enumerate() {
21703                    if i > 0 {
21704                        self.write(", ");
21705                    }
21706                    self.generate_ordered(ord)?;
21707                }
21708                self.write(")");
21709            }
21710        }
21711        if let Some(ref filter) = f.filter {
21712            self.write_space();
21713            self.write_keyword("FILTER");
21714            self.write("(");
21715            self.write_keyword("WHERE");
21716            self.write_space();
21717            self.generate_expression(filter)?;
21718            self.write(")");
21719        }
21720        Ok(())
21721    }
21722
21723    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
21724        self.write_keyword("SUM_IF");
21725        self.write("(");
21726        self.generate_expression(&f.this)?;
21727        self.write(", ");
21728        self.generate_expression(&f.condition)?;
21729        self.write(")");
21730        if let Some(ref filter) = f.filter {
21731            self.write_space();
21732            self.write_keyword("FILTER");
21733            self.write("(");
21734            self.write_keyword("WHERE");
21735            self.write_space();
21736            self.generate_expression(filter)?;
21737            self.write(")");
21738        }
21739        Ok(())
21740    }
21741
21742    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
21743        self.write_keyword("APPROX_PERCENTILE");
21744        self.write("(");
21745        self.generate_expression(&f.this)?;
21746        self.write(", ");
21747        self.generate_expression(&f.percentile)?;
21748        if let Some(ref acc) = f.accuracy {
21749            self.write(", ");
21750            self.generate_expression(acc)?;
21751        }
21752        self.write(")");
21753        if let Some(ref filter) = f.filter {
21754            self.write_space();
21755            self.write_keyword("FILTER");
21756            self.write("(");
21757            self.write_keyword("WHERE");
21758            self.write_space();
21759            self.generate_expression(filter)?;
21760            self.write(")");
21761        }
21762        Ok(())
21763    }
21764
21765    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
21766        self.write_keyword(name);
21767        self.write("(");
21768        self.generate_expression(&f.percentile)?;
21769        self.write(")");
21770        if let Some(ref order_by) = f.order_by {
21771            self.write_space();
21772            self.write_keyword("WITHIN GROUP");
21773            self.write(" (");
21774            self.write_keyword("ORDER BY");
21775            self.write_space();
21776            self.generate_expression(&f.this)?;
21777            for ord in order_by.iter() {
21778                if ord.desc {
21779                    self.write_space();
21780                    self.write_keyword("DESC");
21781                }
21782            }
21783            self.write(")");
21784        }
21785        if let Some(ref filter) = f.filter {
21786            self.write_space();
21787            self.write_keyword("FILTER");
21788            self.write("(");
21789            self.write_keyword("WHERE");
21790            self.write_space();
21791            self.generate_expression(filter)?;
21792            self.write(")");
21793        }
21794        Ok(())
21795    }
21796
21797    // Window function generators
21798
21799    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
21800        self.write_keyword("NTILE");
21801        self.write("(");
21802        if let Some(num_buckets) = &f.num_buckets {
21803            self.generate_expression(num_buckets)?;
21804        }
21805        if let Some(order_by) = &f.order_by {
21806            self.write_keyword(" ORDER BY ");
21807            for (i, ob) in order_by.iter().enumerate() {
21808                if i > 0 {
21809                    self.write(", ");
21810                }
21811                self.generate_ordered(ob)?;
21812            }
21813        }
21814        self.write(")");
21815        Ok(())
21816    }
21817
21818    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
21819        self.write_keyword(name);
21820        self.write("(");
21821        self.generate_expression(&f.this)?;
21822        if let Some(ref offset) = f.offset {
21823            self.write(", ");
21824            self.generate_expression(offset)?;
21825            if let Some(ref default) = f.default {
21826                self.write(", ");
21827                self.generate_expression(default)?;
21828            }
21829        }
21830        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
21831        if self.config.ignore_nulls_in_func {
21832            match f.ignore_nulls {
21833                Some(true) => {
21834                    self.write_space();
21835                    self.write_keyword("IGNORE NULLS");
21836                }
21837                Some(false) => {
21838                    self.write_space();
21839                    self.write_keyword("RESPECT NULLS");
21840                }
21841                None => {}
21842            }
21843        }
21844        self.write(")");
21845        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21846        if !self.config.ignore_nulls_in_func {
21847            match f.ignore_nulls {
21848                Some(true) => {
21849                    self.write_space();
21850                    self.write_keyword("IGNORE NULLS");
21851                }
21852                Some(false) => {
21853                    self.write_space();
21854                    self.write_keyword("RESPECT NULLS");
21855                }
21856                None => {}
21857            }
21858        }
21859        Ok(())
21860    }
21861
21862    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
21863        self.write_keyword(name);
21864        self.write("(");
21865        self.generate_expression(&f.this)?;
21866        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
21867        if !f.order_by.is_empty() {
21868            self.write_space();
21869            self.write_keyword("ORDER BY");
21870            self.write_space();
21871            for (i, ordered) in f.order_by.iter().enumerate() {
21872                if i > 0 {
21873                    self.write(", ");
21874                }
21875                self.generate_ordered(ordered)?;
21876            }
21877        }
21878        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21879        if self.config.ignore_nulls_in_func {
21880            match f.ignore_nulls {
21881                Some(true) => {
21882                    self.write_space();
21883                    self.write_keyword("IGNORE NULLS");
21884                }
21885                Some(false) => {
21886                    self.write_space();
21887                    self.write_keyword("RESPECT NULLS");
21888                }
21889                None => {}
21890            }
21891        }
21892        self.write(")");
21893        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21894        if !self.config.ignore_nulls_in_func {
21895            match f.ignore_nulls {
21896                Some(true) => {
21897                    self.write_space();
21898                    self.write_keyword("IGNORE NULLS");
21899                }
21900                Some(false) => {
21901                    self.write_space();
21902                    self.write_keyword("RESPECT NULLS");
21903                }
21904                None => {}
21905            }
21906        }
21907        Ok(())
21908    }
21909
21910    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
21911    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
21912    fn generate_value_func_with_ignore_nulls_bool(
21913        &mut self,
21914        name: &str,
21915        f: &ValueFunc,
21916    ) -> Result<()> {
21917        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21918            self.write_keyword(name);
21919            self.write("(");
21920            self.generate_expression(&f.this)?;
21921            self.write(", ");
21922            self.write_keyword("TRUE");
21923            self.write(")");
21924            return Ok(());
21925        }
21926        self.generate_value_func(name, f)
21927    }
21928
21929    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
21930        self.write_keyword("NTH_VALUE");
21931        self.write("(");
21932        self.generate_expression(&f.this)?;
21933        self.write(", ");
21934        self.generate_expression(&f.offset)?;
21935        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21936        if self.config.ignore_nulls_in_func {
21937            match f.ignore_nulls {
21938                Some(true) => {
21939                    self.write_space();
21940                    self.write_keyword("IGNORE NULLS");
21941                }
21942                Some(false) => {
21943                    self.write_space();
21944                    self.write_keyword("RESPECT NULLS");
21945                }
21946                None => {}
21947            }
21948        }
21949        self.write(")");
21950        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
21951        if matches!(
21952            self.config.dialect,
21953            Some(crate::dialects::DialectType::Snowflake)
21954        ) {
21955            match f.from_first {
21956                Some(true) => {
21957                    self.write_space();
21958                    self.write_keyword("FROM FIRST");
21959                }
21960                Some(false) => {
21961                    self.write_space();
21962                    self.write_keyword("FROM LAST");
21963                }
21964                None => {}
21965            }
21966        }
21967        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21968        if !self.config.ignore_nulls_in_func {
21969            match f.ignore_nulls {
21970                Some(true) => {
21971                    self.write_space();
21972                    self.write_keyword("IGNORE NULLS");
21973                }
21974                Some(false) => {
21975                    self.write_space();
21976                    self.write_keyword("RESPECT NULLS");
21977                }
21978                None => {}
21979            }
21980        }
21981        Ok(())
21982    }
21983
21984    // Additional string function generators
21985
21986    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
21987        // Standard syntax: POSITION(substr IN str)
21988        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
21989        if matches!(
21990            self.config.dialect,
21991            Some(crate::dialects::DialectType::ClickHouse)
21992        ) {
21993            self.write_keyword("POSITION");
21994            self.write("(");
21995            self.generate_expression(&f.string)?;
21996            self.write(", ");
21997            self.generate_expression(&f.substring)?;
21998            if let Some(ref start) = f.start {
21999                self.write(", ");
22000                self.generate_expression(start)?;
22001            }
22002            self.write(")");
22003            return Ok(());
22004        }
22005
22006        self.write_keyword("POSITION");
22007        self.write("(");
22008        self.generate_expression(&f.substring)?;
22009        self.write_space();
22010        self.write_keyword("IN");
22011        self.write_space();
22012        self.generate_expression(&f.string)?;
22013        if let Some(ref start) = f.start {
22014            self.write(", ");
22015            self.generate_expression(start)?;
22016        }
22017        self.write(")");
22018        Ok(())
22019    }
22020
22021    // Additional math function generators
22022
22023    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
22024        // Teradata RANDOM(lower, upper)
22025        if f.lower.is_some() || f.upper.is_some() {
22026            self.write_keyword("RANDOM");
22027            self.write("(");
22028            if let Some(ref lower) = f.lower {
22029                self.generate_expression(lower)?;
22030            }
22031            if let Some(ref upper) = f.upper {
22032                self.write(", ");
22033                self.generate_expression(upper)?;
22034            }
22035            self.write(")");
22036            return Ok(());
22037        }
22038        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
22039        let func_name = match self.config.dialect {
22040            Some(crate::dialects::DialectType::Snowflake)
22041            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
22042            _ => "RAND",
22043        };
22044        self.write_keyword(func_name);
22045        self.write("(");
22046        // DuckDB doesn't support seeded RANDOM, so skip the seed
22047        if !matches!(
22048            self.config.dialect,
22049            Some(crate::dialects::DialectType::DuckDB)
22050        ) {
22051            if let Some(ref seed) = f.seed {
22052                self.generate_expression(seed)?;
22053            }
22054        }
22055        self.write(")");
22056        Ok(())
22057    }
22058
22059    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
22060        self.write_keyword("TRUNCATE");
22061        self.write("(");
22062        self.generate_expression(&f.this)?;
22063        if let Some(ref decimals) = f.decimals {
22064            self.write(", ");
22065            self.generate_expression(decimals)?;
22066        }
22067        self.write(")");
22068        Ok(())
22069    }
22070
22071    // Control flow generators
22072
22073    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
22074        self.write_keyword("DECODE");
22075        self.write("(");
22076        self.generate_expression(&f.this)?;
22077        for (search, result) in &f.search_results {
22078            self.write(", ");
22079            self.generate_expression(search)?;
22080            self.write(", ");
22081            self.generate_expression(result)?;
22082        }
22083        if let Some(ref default) = f.default {
22084            self.write(", ");
22085            self.generate_expression(default)?;
22086        }
22087        self.write(")");
22088        Ok(())
22089    }
22090
22091    // Date/time function generators
22092
22093    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
22094        self.write_keyword(name);
22095        self.write("(");
22096        self.generate_expression(&f.this)?;
22097        self.write(", ");
22098        self.generate_expression(&f.format)?;
22099        self.write(")");
22100        Ok(())
22101    }
22102
22103    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
22104        self.write_keyword("FROM_UNIXTIME");
22105        self.write("(");
22106        self.generate_expression(&f.this)?;
22107        if let Some(ref format) = f.format {
22108            self.write(", ");
22109            self.generate_expression(format)?;
22110        }
22111        self.write(")");
22112        Ok(())
22113    }
22114
22115    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
22116        self.write_keyword("UNIX_TIMESTAMP");
22117        self.write("(");
22118        if let Some(ref expr) = f.this {
22119            self.generate_expression(expr)?;
22120            if let Some(ref format) = f.format {
22121                self.write(", ");
22122                self.generate_expression(format)?;
22123            }
22124        } else if matches!(
22125            self.config.dialect,
22126            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
22127        ) {
22128            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
22129            self.write_keyword("CURRENT_TIMESTAMP");
22130            self.write("()");
22131        }
22132        self.write(")");
22133        Ok(())
22134    }
22135
22136    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
22137        self.write_keyword("MAKE_DATE");
22138        self.write("(");
22139        self.generate_expression(&f.year)?;
22140        self.write(", ");
22141        self.generate_expression(&f.month)?;
22142        self.write(", ");
22143        self.generate_expression(&f.day)?;
22144        self.write(")");
22145        Ok(())
22146    }
22147
22148    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
22149        self.write_keyword("MAKE_TIMESTAMP");
22150        self.write("(");
22151        self.generate_expression(&f.year)?;
22152        self.write(", ");
22153        self.generate_expression(&f.month)?;
22154        self.write(", ");
22155        self.generate_expression(&f.day)?;
22156        self.write(", ");
22157        self.generate_expression(&f.hour)?;
22158        self.write(", ");
22159        self.generate_expression(&f.minute)?;
22160        self.write(", ");
22161        self.generate_expression(&f.second)?;
22162        if let Some(ref tz) = f.timezone {
22163            self.write(", ");
22164            self.generate_expression(tz)?;
22165        }
22166        self.write(")");
22167        Ok(())
22168    }
22169
22170    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
22171    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
22172        match expr {
22173            Expression::Struct(s) => {
22174                if s.fields.iter().all(|(name, _)| name.is_some()) {
22175                    Some(
22176                        s.fields
22177                            .iter()
22178                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
22179                            .collect(),
22180                    )
22181                } else {
22182                    None
22183                }
22184            }
22185            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22186                // Check if all args are Alias (named fields)
22187                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
22188                    Some(
22189                        f.args
22190                            .iter()
22191                            .filter_map(|a| {
22192                                if let Expression::Alias(alias) = a {
22193                                    Some(alias.alias.name.clone())
22194                                } else {
22195                                    None
22196                                }
22197                            })
22198                            .collect(),
22199                    )
22200                } else {
22201                    None
22202                }
22203            }
22204            _ => None,
22205        }
22206    }
22207
22208    /// Check if a struct expression has any unnamed fields
22209    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
22210        match expr {
22211            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
22212            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22213                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
22214            }
22215            _ => false,
22216        }
22217    }
22218
22219    /// Get the field count of a struct expression
22220    fn struct_field_count(expr: &Expression) -> usize {
22221        match expr {
22222            Expression::Struct(s) => s.fields.len(),
22223            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
22224            _ => 0,
22225        }
22226    }
22227
22228    /// Apply field names to an unnamed struct expression, producing a new expression with names
22229    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
22230        match expr {
22231            Expression::Struct(s) => {
22232                let mut new_fields = Vec::with_capacity(s.fields.len());
22233                for (i, (name, value)) in s.fields.iter().enumerate() {
22234                    if name.is_none() && i < field_names.len() {
22235                        new_fields.push((Some(field_names[i].clone()), value.clone()));
22236                    } else {
22237                        new_fields.push((name.clone(), value.clone()));
22238                    }
22239                }
22240                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
22241            }
22242            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22243                let mut new_args = Vec::with_capacity(f.args.len());
22244                for (i, arg) in f.args.iter().enumerate() {
22245                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
22246                        // Wrap the value in an Alias with the inherited name
22247                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
22248                            this: arg.clone(),
22249                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
22250                            column_aliases: Vec::new(),
22251                            alias_explicit_as: false,
22252                            alias_keyword: None,
22253                            pre_alias_comments: Vec::new(),
22254                            trailing_comments: Vec::new(),
22255                            inferred_type: None,
22256                        })));
22257                    } else {
22258                        new_args.push(arg.clone());
22259                    }
22260                }
22261                Expression::Function(Box::new(crate::expressions::Function {
22262                    name: f.name.clone(),
22263                    args: new_args,
22264                    distinct: f.distinct,
22265                    trailing_comments: f.trailing_comments.clone(),
22266                    use_bracket_syntax: f.use_bracket_syntax,
22267                    no_parens: f.no_parens,
22268                    quoted: f.quoted,
22269                    span: None,
22270                    inferred_type: None,
22271                }))
22272            }
22273            _ => expr.clone(),
22274        }
22275    }
22276
22277    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
22278    /// This implements BigQuery's implicit field name inheritance for struct arrays.
22279    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
22280    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
22281        let first = match expressions.first() {
22282            Some(e) => e,
22283            None => return expressions.to_vec(),
22284        };
22285
22286        let field_names = match Self::extract_struct_field_names(first) {
22287            Some(names) if !names.is_empty() => names,
22288            _ => return expressions.to_vec(),
22289        };
22290
22291        let mut result = Vec::with_capacity(expressions.len());
22292        for (idx, expr) in expressions.iter().enumerate() {
22293            if idx == 0 {
22294                result.push(expr.clone());
22295                continue;
22296            }
22297            // Check if this is a struct with unnamed fields that needs name propagation
22298            if Self::struct_field_count(expr) == field_names.len()
22299                && Self::struct_has_unnamed_fields(expr)
22300            {
22301                result.push(Self::apply_struct_field_names(expr, &field_names));
22302            } else {
22303                result.push(expr.clone());
22304            }
22305        }
22306        result
22307    }
22308
22309    // Array function generators
22310
22311    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
22312        // Apply struct name inheritance for target dialects that need it
22313        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
22314        let needs_inheritance = matches!(
22315            self.config.dialect,
22316            Some(DialectType::DuckDB)
22317                | Some(DialectType::Spark)
22318                | Some(DialectType::Databricks)
22319                | Some(DialectType::Hive)
22320                | Some(DialectType::Snowflake)
22321                | Some(DialectType::Presto)
22322                | Some(DialectType::Trino)
22323        );
22324        let propagated: Vec<Expression>;
22325        let expressions = if needs_inheritance && f.expressions.len() > 1 {
22326            propagated = Self::inherit_struct_field_names(&f.expressions);
22327            &propagated
22328        } else {
22329            &f.expressions
22330        };
22331
22332        // Check if elements should be split onto multiple lines (pretty + too wide)
22333        let should_split = if self.config.pretty && !expressions.is_empty() {
22334            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
22335            for expr in expressions {
22336                let mut temp_gen = Generator::with_arc_config(self.config.clone());
22337                Arc::make_mut(&mut temp_gen.config).pretty = false;
22338                temp_gen.generate_expression(expr)?;
22339                expr_strings.push(temp_gen.output);
22340            }
22341            self.too_wide(&expr_strings)
22342        } else {
22343            false
22344        };
22345
22346        if f.bracket_notation {
22347            // For Spark/Databricks, use ARRAY(...) with parens
22348            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
22349            // For others (DuckDB, Snowflake), use bare [...]
22350            let (open, close) = match self.config.dialect {
22351                None
22352                | Some(DialectType::Generic)
22353                | Some(DialectType::Spark)
22354                | Some(DialectType::Databricks)
22355                | Some(DialectType::Hive) => {
22356                    self.write_keyword("ARRAY");
22357                    ("(", ")")
22358                }
22359                Some(DialectType::Presto)
22360                | Some(DialectType::Trino)
22361                | Some(DialectType::PostgreSQL)
22362                | Some(DialectType::Redshift)
22363                | Some(DialectType::Materialize)
22364                | Some(DialectType::RisingWave)
22365                | Some(DialectType::CockroachDB) => {
22366                    self.write_keyword("ARRAY");
22367                    ("[", "]")
22368                }
22369                _ => ("[", "]"),
22370            };
22371            self.write(open);
22372            if should_split {
22373                self.write_newline();
22374                self.indent_level += 1;
22375                for (i, expr) in expressions.iter().enumerate() {
22376                    self.write_indent();
22377                    self.generate_expression(expr)?;
22378                    if i + 1 < expressions.len() {
22379                        self.write(",");
22380                    }
22381                    self.write_newline();
22382                }
22383                self.indent_level -= 1;
22384                self.write_indent();
22385            } else {
22386                for (i, expr) in expressions.iter().enumerate() {
22387                    if i > 0 {
22388                        self.write(", ");
22389                    }
22390                    self.generate_expression(expr)?;
22391                }
22392            }
22393            self.write(close);
22394        } else {
22395            // Use LIST keyword if that was the original syntax (DuckDB)
22396            if f.use_list_keyword {
22397                self.write_keyword("LIST");
22398            } else {
22399                self.write_keyword("ARRAY");
22400            }
22401            // For Spark/Hive, always use ARRAY(...) with parens
22402            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
22403            let has_subquery = expressions
22404                .iter()
22405                .any(|e| matches!(e, Expression::Select(_)));
22406            let (open, close) = if matches!(
22407                self.config.dialect,
22408                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
22409            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
22410                && has_subquery)
22411            {
22412                ("(", ")")
22413            } else {
22414                ("[", "]")
22415            };
22416            self.write(open);
22417            if should_split {
22418                self.write_newline();
22419                self.indent_level += 1;
22420                for (i, expr) in expressions.iter().enumerate() {
22421                    self.write_indent();
22422                    self.generate_expression(expr)?;
22423                    if i + 1 < expressions.len() {
22424                        self.write(",");
22425                    }
22426                    self.write_newline();
22427                }
22428                self.indent_level -= 1;
22429                self.write_indent();
22430            } else {
22431                for (i, expr) in expressions.iter().enumerate() {
22432                    if i > 0 {
22433                        self.write(", ");
22434                    }
22435                    self.generate_expression(expr)?;
22436                }
22437            }
22438            self.write(close);
22439        }
22440        Ok(())
22441    }
22442
22443    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
22444        self.write_keyword("ARRAY_SORT");
22445        self.write("(");
22446        self.generate_expression(&f.this)?;
22447        if let Some(ref comp) = f.comparator {
22448            self.write(", ");
22449            self.generate_expression(comp)?;
22450        }
22451        self.write(")");
22452        Ok(())
22453    }
22454
22455    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
22456        self.write_keyword(name);
22457        self.write("(");
22458        self.generate_expression(&f.this)?;
22459        self.write(", ");
22460        self.generate_expression(&f.separator)?;
22461        if let Some(ref null_rep) = f.null_replacement {
22462            self.write(", ");
22463            self.generate_expression(null_rep)?;
22464        }
22465        self.write(")");
22466        Ok(())
22467    }
22468
22469    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
22470        self.write_keyword("UNNEST");
22471        self.write("(");
22472        self.generate_expression(&f.this)?;
22473        for extra in &f.expressions {
22474            self.write(", ");
22475            self.generate_expression(extra)?;
22476        }
22477        self.write(")");
22478        if f.with_ordinality {
22479            self.write_space();
22480            if self.config.unnest_with_ordinality {
22481                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
22482                self.write_keyword("WITH ORDINALITY");
22483            } else if f.offset_alias.is_some() {
22484                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
22485                // Alias (if any) comes BEFORE WITH OFFSET
22486                if let Some(ref alias) = f.alias {
22487                    self.write_keyword("AS");
22488                    self.write_space();
22489                    self.generate_identifier(alias)?;
22490                    self.write_space();
22491                }
22492                self.write_keyword("WITH OFFSET");
22493                if let Some(ref offset_alias) = f.offset_alias {
22494                    self.write_space();
22495                    self.write_keyword("AS");
22496                    self.write_space();
22497                    self.generate_identifier(offset_alias)?;
22498                }
22499            } else {
22500                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
22501                self.write_keyword("WITH OFFSET");
22502                if f.alias.is_none() {
22503                    self.write(" AS offset");
22504                }
22505            }
22506        }
22507        if let Some(ref alias) = f.alias {
22508            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
22509            let should_add_alias = if !f.with_ordinality {
22510                true
22511            } else if self.config.unnest_with_ordinality {
22512                // Presto/Trino: alias comes after WITH ORDINALITY
22513                true
22514            } else if f.offset_alias.is_some() {
22515                // BigQuery expansion: alias already handled above
22516                false
22517            } else {
22518                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
22519                true
22520            };
22521            if should_add_alias {
22522                self.write_space();
22523                self.write_keyword("AS");
22524                self.write_space();
22525                self.generate_identifier(alias)?;
22526            }
22527        }
22528        Ok(())
22529    }
22530
22531    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
22532        self.write_keyword("FILTER");
22533        self.write("(");
22534        self.generate_expression(&f.this)?;
22535        self.write(", ");
22536        self.generate_expression(&f.filter)?;
22537        self.write(")");
22538        Ok(())
22539    }
22540
22541    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
22542        self.write_keyword("TRANSFORM");
22543        self.write("(");
22544        self.generate_expression(&f.this)?;
22545        self.write(", ");
22546        self.generate_expression(&f.transform)?;
22547        self.write(")");
22548        Ok(())
22549    }
22550
22551    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
22552        self.write_keyword(name);
22553        self.write("(");
22554        self.generate_expression(&f.start)?;
22555        self.write(", ");
22556        self.generate_expression(&f.stop)?;
22557        if let Some(ref step) = f.step {
22558            self.write(", ");
22559            self.generate_expression(step)?;
22560        }
22561        self.write(")");
22562        Ok(())
22563    }
22564
22565    // Struct function generators
22566
22567    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
22568        self.write_keyword("STRUCT");
22569        self.write("(");
22570        for (i, (name, expr)) in f.fields.iter().enumerate() {
22571            if i > 0 {
22572                self.write(", ");
22573            }
22574            if let Some(ref id) = name {
22575                self.generate_identifier(id)?;
22576                self.write(" ");
22577                self.write_keyword("AS");
22578                self.write(" ");
22579            }
22580            self.generate_expression(expr)?;
22581        }
22582        self.write(")");
22583        Ok(())
22584    }
22585
22586    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
22587    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
22588        // Extract named/unnamed fields from function args
22589        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
22590        let mut names: Vec<Option<String>> = Vec::new();
22591        let mut values: Vec<&Expression> = Vec::new();
22592        let mut all_named = true;
22593
22594        for arg in &func.args {
22595            match arg {
22596                Expression::Alias(a) => {
22597                    names.push(Some(a.alias.name.clone()));
22598                    values.push(&a.this);
22599                }
22600                _ => {
22601                    names.push(None);
22602                    values.push(arg);
22603                    all_named = false;
22604                }
22605            }
22606        }
22607
22608        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22609            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
22610            self.write("{");
22611            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22612                if i > 0 {
22613                    self.write(", ");
22614                }
22615                if let Some(n) = name {
22616                    self.write("'");
22617                    self.write(n);
22618                    self.write("'");
22619                } else {
22620                    self.write("'_");
22621                    self.write(&i.to_string());
22622                    self.write("'");
22623                }
22624                self.write(": ");
22625                self.generate_expression(value)?;
22626            }
22627            self.write("}");
22628            return Ok(());
22629        }
22630
22631        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22632            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
22633            self.write_keyword("OBJECT_CONSTRUCT");
22634            self.write("(");
22635            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22636                if i > 0 {
22637                    self.write(", ");
22638                }
22639                if let Some(n) = name {
22640                    self.write("'");
22641                    self.write(n);
22642                    self.write("'");
22643                } else {
22644                    self.write("'_");
22645                    self.write(&i.to_string());
22646                    self.write("'");
22647                }
22648                self.write(", ");
22649                self.generate_expression(value)?;
22650            }
22651            self.write(")");
22652            return Ok(());
22653        }
22654
22655        if matches!(
22656            self.config.dialect,
22657            Some(DialectType::Presto) | Some(DialectType::Trino)
22658        ) {
22659            if all_named && !names.is_empty() {
22660                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
22661                // Need to infer types from values
22662                self.write_keyword("CAST");
22663                self.write("(");
22664                self.write_keyword("ROW");
22665                self.write("(");
22666                for (i, value) in values.iter().enumerate() {
22667                    if i > 0 {
22668                        self.write(", ");
22669                    }
22670                    self.generate_expression(value)?;
22671                }
22672                self.write(")");
22673                self.write(" ");
22674                self.write_keyword("AS");
22675                self.write(" ");
22676                self.write_keyword("ROW");
22677                self.write("(");
22678                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22679                    if i > 0 {
22680                        self.write(", ");
22681                    }
22682                    if let Some(n) = name {
22683                        self.write(n);
22684                    }
22685                    self.write(" ");
22686                    let type_str = Self::infer_sql_type_for_presto(value);
22687                    self.write_keyword(&type_str);
22688                }
22689                self.write(")");
22690                self.write(")");
22691            } else {
22692                // Unnamed: ROW(values...)
22693                self.write_keyword("ROW");
22694                self.write("(");
22695                for (i, value) in values.iter().enumerate() {
22696                    if i > 0 {
22697                        self.write(", ");
22698                    }
22699                    self.generate_expression(value)?;
22700                }
22701                self.write(")");
22702            }
22703            return Ok(());
22704        }
22705
22706        // Default: ROW(values...) for other dialects
22707        self.write_keyword("ROW");
22708        self.write("(");
22709        for (i, value) in values.iter().enumerate() {
22710            if i > 0 {
22711                self.write(", ");
22712            }
22713            self.generate_expression(value)?;
22714        }
22715        self.write(")");
22716        Ok(())
22717    }
22718
22719    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
22720    fn infer_sql_type_for_presto(expr: &Expression) -> String {
22721        match expr {
22722            Expression::Literal(lit)
22723                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
22724            {
22725                "VARCHAR".to_string()
22726            }
22727            Expression::Literal(lit)
22728                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
22729            {
22730                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
22731                    unreachable!()
22732                };
22733                if n.contains('.') {
22734                    "DOUBLE".to_string()
22735                } else {
22736                    "INTEGER".to_string()
22737                }
22738            }
22739            Expression::Boolean(_) => "BOOLEAN".to_string(),
22740            Expression::Literal(lit)
22741                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
22742            {
22743                "DATE".to_string()
22744            }
22745            Expression::Literal(lit)
22746                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
22747            {
22748                "TIMESTAMP".to_string()
22749            }
22750            Expression::Literal(lit)
22751                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
22752            {
22753                "TIMESTAMP".to_string()
22754            }
22755            Expression::Array(_) | Expression::ArrayFunc(_) => {
22756                // Try to infer element type from first element
22757                "ARRAY(VARCHAR)".to_string()
22758            }
22759            // For nested structs - generate a nested ROW type by inspecting fields
22760            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
22761            Expression::Function(f) => {
22762                if f.name.eq_ignore_ascii_case("STRUCT") {
22763                    "ROW".to_string()
22764                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
22765                    "DATE".to_string()
22766                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
22767                    || f.name.eq_ignore_ascii_case("NOW")
22768                {
22769                    "TIMESTAMP".to_string()
22770                } else {
22771                    "VARCHAR".to_string()
22772                }
22773            }
22774            _ => "VARCHAR".to_string(),
22775        }
22776    }
22777
22778    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
22779        // DuckDB uses STRUCT_EXTRACT function syntax
22780        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22781            self.write_keyword("STRUCT_EXTRACT");
22782            self.write("(");
22783            self.generate_expression(&f.this)?;
22784            self.write(", ");
22785            // Output field name as string literal
22786            self.write("'");
22787            self.write(&f.field.name);
22788            self.write("'");
22789            self.write(")");
22790            return Ok(());
22791        }
22792        self.generate_expression(&f.this)?;
22793        self.write(".");
22794        self.generate_identifier(&f.field)
22795    }
22796
22797    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
22798        if matches!(
22799            self.config.dialect,
22800            Some(DialectType::Spark | DialectType::Databricks)
22801        ) {
22802            self.write_keyword("STRUCT");
22803            self.write("(");
22804            for (i, (name, value)) in f.pairs.iter().enumerate() {
22805                if i > 0 {
22806                    self.write(", ");
22807                }
22808                self.generate_expression(value)?;
22809                self.write(" ");
22810                self.write_keyword("AS");
22811                self.write(" ");
22812                if let Expression::Literal(lit) = name {
22813                    if let Literal::String(field_name) = lit.as_ref() {
22814                        self.generate_identifier(&Identifier::new(field_name))?;
22815                    } else {
22816                        self.generate_expression(name)?;
22817                    }
22818                } else {
22819                    self.generate_expression(name)?;
22820                }
22821            }
22822            self.write(")");
22823            return Ok(());
22824        }
22825
22826        self.write_keyword("NAMED_STRUCT");
22827        self.write("(");
22828        for (i, (name, value)) in f.pairs.iter().enumerate() {
22829            if i > 0 {
22830                self.write(", ");
22831            }
22832            self.generate_expression(name)?;
22833            self.write(", ");
22834            self.generate_expression(value)?;
22835        }
22836        self.write(")");
22837        Ok(())
22838    }
22839
22840    // Map function generators
22841
22842    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
22843        if f.curly_brace_syntax {
22844            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
22845            if f.with_map_keyword {
22846                self.write_keyword("MAP");
22847                self.write(" ");
22848            }
22849            self.write("{");
22850            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
22851                if i > 0 {
22852                    self.write(", ");
22853                }
22854                self.generate_expression(key)?;
22855                self.write(": ");
22856                self.generate_expression(val)?;
22857            }
22858            self.write("}");
22859        } else {
22860            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
22861            self.write_keyword("MAP");
22862            self.write("(");
22863            self.write_keyword("ARRAY");
22864            self.write("[");
22865            for (i, key) in f.keys.iter().enumerate() {
22866                if i > 0 {
22867                    self.write(", ");
22868                }
22869                self.generate_expression(key)?;
22870            }
22871            self.write("], ");
22872            self.write_keyword("ARRAY");
22873            self.write("[");
22874            for (i, val) in f.values.iter().enumerate() {
22875                if i > 0 {
22876                    self.write(", ");
22877                }
22878                self.generate_expression(val)?;
22879            }
22880            self.write("])");
22881        }
22882        Ok(())
22883    }
22884
22885    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
22886        self.write_keyword(name);
22887        self.write("(");
22888        self.generate_expression(&f.this)?;
22889        self.write(", ");
22890        self.generate_expression(&f.transform)?;
22891        self.write(")");
22892        Ok(())
22893    }
22894
22895    // JSON function generators
22896
22897    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
22898        use crate::dialects::DialectType;
22899
22900        // Check if we should use arrow syntax (-> or ->>)
22901        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
22902
22903        if use_arrow {
22904            // Output arrow syntax: expr -> path or expr ->> path
22905            self.generate_expression(&f.this)?;
22906            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
22907                self.write(" ->> ");
22908            } else {
22909                self.write(" -> ");
22910            }
22911            self.generate_expression(&f.path)?;
22912            return Ok(());
22913        }
22914
22915        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
22916        if f.hash_arrow_syntax
22917            && matches!(
22918                self.config.dialect,
22919                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22920            )
22921        {
22922            self.generate_expression(&f.this)?;
22923            self.write(" #>> ");
22924            self.generate_expression(&f.path)?;
22925            return Ok(());
22926        }
22927
22928        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
22929        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
22930        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22931            match name {
22932                "JSON_EXTRACT_SCALAR"
22933                | "JSON_EXTRACT_PATH_TEXT"
22934                | "JSON_EXTRACT"
22935                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
22936                _ => name,
22937            }
22938        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
22939            match name {
22940                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
22941                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
22942                _ => name,
22943            }
22944        } else {
22945            name
22946        };
22947
22948        self.write_keyword(func_name);
22949        self.write("(");
22950        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
22951        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22952            if let Expression::Cast(ref cast) = f.this {
22953                if matches!(cast.to, crate::expressions::DataType::Json) {
22954                    self.generate_expression(&cast.this)?;
22955                } else {
22956                    self.generate_expression(&f.this)?;
22957                }
22958            } else {
22959                self.generate_expression(&f.this)?;
22960            }
22961        } else {
22962            self.generate_expression(&f.this)?;
22963        }
22964        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
22965        // decompose JSON path into separate string arguments
22966        if matches!(
22967            self.config.dialect,
22968            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22969        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
22970        {
22971            if let Expression::Literal(ref lit) = f.path {
22972                if let Literal::String(ref s) = lit.as_ref() {
22973                    let parts = Self::decompose_json_path(s);
22974                    for part in &parts {
22975                        self.write(", '");
22976                        self.write(part);
22977                        self.write("'");
22978                    }
22979                }
22980            } else {
22981                self.write(", ");
22982                self.generate_expression(&f.path)?;
22983            }
22984        } else {
22985            self.write(", ");
22986            self.generate_expression(&f.path)?;
22987        }
22988
22989        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
22990        // These go BEFORE the closing parenthesis
22991        if let Some(ref wrapper) = f.wrapper_option {
22992            self.write_space();
22993            self.write_keyword(wrapper);
22994        }
22995        if let Some(ref quotes) = f.quotes_option {
22996            self.write_space();
22997            self.write_keyword(quotes);
22998            if f.on_scalar_string {
22999                self.write_space();
23000                self.write_keyword("ON SCALAR STRING");
23001            }
23002        }
23003        if let Some(ref on_err) = f.on_error {
23004            self.write_space();
23005            self.write_keyword(on_err);
23006        }
23007        if let Some(ref ret_type) = f.returning {
23008            self.write_space();
23009            self.write_keyword("RETURNING");
23010            self.write_space();
23011            self.generate_data_type(ret_type)?;
23012        }
23013
23014        self.write(")");
23015        Ok(())
23016    }
23017
23018    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
23019    fn dialect_supports_json_arrow(&self) -> bool {
23020        use crate::dialects::DialectType;
23021        match self.config.dialect {
23022            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
23023            Some(DialectType::PostgreSQL) => true,
23024            Some(DialectType::MySQL) => true,
23025            Some(DialectType::DuckDB) => true,
23026            Some(DialectType::CockroachDB) => true,
23027            Some(DialectType::StarRocks) => true,
23028            Some(DialectType::SQLite) => true,
23029            // Other dialects use function syntax
23030            _ => false,
23031        }
23032    }
23033
23034    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
23035        use crate::dialects::DialectType;
23036
23037        // PostgreSQL uses #> operator for JSONB path extraction
23038        if matches!(
23039            self.config.dialect,
23040            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23041        ) && name == "JSON_EXTRACT_PATH"
23042        {
23043            self.generate_expression(&f.this)?;
23044            self.write(" #> ");
23045            if f.paths.len() == 1 {
23046                self.generate_expression(&f.paths[0])?;
23047            } else {
23048                // Multiple paths: ARRAY[path1, path2, ...]
23049                self.write_keyword("ARRAY");
23050                self.write("[");
23051                for (i, path) in f.paths.iter().enumerate() {
23052                    if i > 0 {
23053                        self.write(", ");
23054                    }
23055                    self.generate_expression(path)?;
23056                }
23057                self.write("]");
23058            }
23059            return Ok(());
23060        }
23061
23062        self.write_keyword(name);
23063        self.write("(");
23064        self.generate_expression(&f.this)?;
23065        for path in &f.paths {
23066            self.write(", ");
23067            self.generate_expression(path)?;
23068        }
23069        self.write(")");
23070        Ok(())
23071    }
23072
23073    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
23074        use crate::dialects::DialectType;
23075
23076        self.write_keyword("JSON_OBJECT");
23077        self.write("(");
23078        if f.star {
23079            self.write("*");
23080        } else {
23081            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
23082            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
23083            // Also respect the json_key_value_pair_sep config
23084            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
23085                || matches!(
23086                    self.config.dialect,
23087                    Some(DialectType::BigQuery)
23088                        | Some(DialectType::MySQL)
23089                        | Some(DialectType::SQLite)
23090                );
23091
23092            for (i, (key, value)) in f.pairs.iter().enumerate() {
23093                if i > 0 {
23094                    self.write(", ");
23095                }
23096                self.generate_expression(key)?;
23097                if use_comma_syntax {
23098                    self.write(", ");
23099                } else {
23100                    self.write(": ");
23101                }
23102                self.generate_expression(value)?;
23103            }
23104        }
23105        if let Some(null_handling) = f.null_handling {
23106            self.write_space();
23107            match null_handling {
23108                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23109                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23110            }
23111        }
23112        if f.with_unique_keys {
23113            self.write_space();
23114            self.write_keyword("WITH UNIQUE KEYS");
23115        }
23116        if let Some(ref ret_type) = f.returning_type {
23117            self.write_space();
23118            self.write_keyword("RETURNING");
23119            self.write_space();
23120            self.generate_data_type(ret_type)?;
23121            if f.format_json {
23122                self.write_space();
23123                self.write_keyword("FORMAT JSON");
23124            }
23125            if let Some(ref enc) = f.encoding {
23126                self.write_space();
23127                self.write_keyword("ENCODING");
23128                self.write_space();
23129                self.write(enc);
23130            }
23131        }
23132        self.write(")");
23133        Ok(())
23134    }
23135
23136    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
23137        self.write_keyword(name);
23138        self.write("(");
23139        self.generate_expression(&f.this)?;
23140        for (path, value) in &f.path_values {
23141            self.write(", ");
23142            self.generate_expression(path)?;
23143            self.write(", ");
23144            self.generate_expression(value)?;
23145        }
23146        self.write(")");
23147        Ok(())
23148    }
23149
23150    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
23151        self.write_keyword("JSON_ARRAYAGG");
23152        self.write("(");
23153        self.generate_expression(&f.this)?;
23154        if let Some(ref order_by) = f.order_by {
23155            self.write_space();
23156            self.write_keyword("ORDER BY");
23157            self.write_space();
23158            for (i, ord) in order_by.iter().enumerate() {
23159                if i > 0 {
23160                    self.write(", ");
23161                }
23162                self.generate_ordered(ord)?;
23163            }
23164        }
23165        if let Some(null_handling) = f.null_handling {
23166            self.write_space();
23167            match null_handling {
23168                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23169                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23170            }
23171        }
23172        self.write(")");
23173        if let Some(ref filter) = f.filter {
23174            self.write_space();
23175            self.write_keyword("FILTER");
23176            self.write("(");
23177            self.write_keyword("WHERE");
23178            self.write_space();
23179            self.generate_expression(filter)?;
23180            self.write(")");
23181        }
23182        Ok(())
23183    }
23184
23185    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
23186        self.write_keyword("JSON_OBJECTAGG");
23187        self.write("(");
23188        self.generate_expression(&f.key)?;
23189        self.write(": ");
23190        self.generate_expression(&f.value)?;
23191        if let Some(null_handling) = f.null_handling {
23192            self.write_space();
23193            match null_handling {
23194                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23195                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23196            }
23197        }
23198        self.write(")");
23199        if let Some(ref filter) = f.filter {
23200            self.write_space();
23201            self.write_keyword("FILTER");
23202            self.write("(");
23203            self.write_keyword("WHERE");
23204            self.write_space();
23205            self.generate_expression(filter)?;
23206            self.write(")");
23207        }
23208        Ok(())
23209    }
23210
23211    // Type casting/conversion generators
23212
23213    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
23214        use crate::dialects::DialectType;
23215
23216        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
23217        if self.config.dialect == Some(DialectType::Redshift) {
23218            self.write_keyword("CAST");
23219            self.write("(");
23220            self.generate_expression(&f.this)?;
23221            self.write_space();
23222            self.write_keyword("AS");
23223            self.write_space();
23224            self.generate_data_type(&f.to)?;
23225            self.write(")");
23226            return Ok(());
23227        }
23228
23229        self.write_keyword("CONVERT");
23230        self.write("(");
23231        self.generate_data_type(&f.to)?;
23232        self.write(", ");
23233        self.generate_expression(&f.this)?;
23234        if let Some(ref style) = f.style {
23235            self.write(", ");
23236            self.generate_expression(style)?;
23237        }
23238        self.write(")");
23239        Ok(())
23240    }
23241
23242    // Additional expression generators
23243
23244    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
23245        if f.colon {
23246            // DuckDB syntax: LAMBDA x : expr
23247            self.write_keyword("LAMBDA");
23248            self.write_space();
23249            for (i, param) in f.parameters.iter().enumerate() {
23250                if i > 0 {
23251                    self.write(", ");
23252                }
23253                self.generate_identifier(param)?;
23254            }
23255            self.write(" : ");
23256        } else {
23257            // Standard syntax: x -> expr or (x, y) -> expr
23258            if f.parameters.len() == 1 {
23259                self.generate_identifier(&f.parameters[0])?;
23260            } else {
23261                self.write("(");
23262                for (i, param) in f.parameters.iter().enumerate() {
23263                    if i > 0 {
23264                        self.write(", ");
23265                    }
23266                    self.generate_identifier(param)?;
23267                }
23268                self.write(")");
23269            }
23270            self.write(" -> ");
23271        }
23272        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23273            if let Expression::Lambda(inner) = &f.body {
23274                self.generate_lambda_with_parenthesized_single_param(inner)?;
23275                return Ok(());
23276            }
23277        }
23278
23279        self.generate_expression(&f.body)
23280    }
23281
23282    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
23283        if f.colon {
23284            return self.generate_lambda(f);
23285        }
23286
23287        self.write("(");
23288        for (i, param) in f.parameters.iter().enumerate() {
23289            if i > 0 {
23290                self.write(", ");
23291            }
23292            self.generate_identifier(param)?;
23293        }
23294        self.write(") -> ");
23295        self.generate_expression(&f.body)
23296    }
23297
23298    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
23299        self.generate_identifier(&f.name)?;
23300        match f.separator {
23301            NamedArgSeparator::DArrow => self.write(" => "),
23302            NamedArgSeparator::ColonEq => self.write(" := "),
23303            NamedArgSeparator::Eq => self.write(" = "),
23304        }
23305        self.generate_expression(&f.value)
23306    }
23307
23308    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
23309        self.write_keyword(&f.prefix);
23310        self.write(" ");
23311        self.generate_expression(&f.this)
23312    }
23313
23314    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
23315        match f.style {
23316            ParameterStyle::Question => self.write("?"),
23317            ParameterStyle::Dollar => {
23318                if let Some(idx) = f.index {
23319                    if matches!(
23320                        self.config.dialect,
23321                        Some(DialectType::TSQL) | Some(DialectType::Fabric)
23322                    ) {
23323                        self.write("@P");
23324                        self.write(&idx.to_string());
23325                    } else {
23326                        self.write("$");
23327                        self.write(&idx.to_string());
23328                    }
23329                } else {
23330                    self.write("$");
23331                    if let Some(ref name) = f.name {
23332                        // Session variable like $x or $query_id
23333                        self.write(name);
23334                    }
23335                }
23336            }
23337            ParameterStyle::DollarBrace => {
23338                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
23339                self.write("${");
23340                if let Some(ref name) = f.name {
23341                    self.write(name);
23342                }
23343                if let Some(ref expr) = f.expression {
23344                    self.write(":");
23345                    self.write(expr);
23346                }
23347                self.write("}");
23348            }
23349            ParameterStyle::Colon => {
23350                self.write(":");
23351                if let Some(idx) = f.index {
23352                    self.write(&idx.to_string());
23353                } else if let Some(ref name) = f.name {
23354                    self.write(name);
23355                }
23356            }
23357            ParameterStyle::At => {
23358                self.write("@");
23359                if let Some(ref name) = f.name {
23360                    if f.string_quoted {
23361                        self.write("'");
23362                        self.write(name);
23363                        self.write("'");
23364                    } else if f.quoted {
23365                        self.write("\"");
23366                        self.write(name);
23367                        self.write("\"");
23368                    } else {
23369                        self.write(name);
23370                    }
23371                }
23372            }
23373            ParameterStyle::DoubleAt => {
23374                self.write("@@");
23375                if let Some(ref name) = f.name {
23376                    self.write(name);
23377                }
23378            }
23379            ParameterStyle::DoubleDollar => {
23380                self.write("$$");
23381                if let Some(ref name) = f.name {
23382                    self.write(name);
23383                }
23384            }
23385            ParameterStyle::Percent => {
23386                if let Some(ref name) = f.name {
23387                    // %(name)s format
23388                    self.write("%(");
23389                    self.write(name);
23390                    self.write(")s");
23391                } else {
23392                    // %s format
23393                    self.write("%s");
23394                }
23395            }
23396            ParameterStyle::Brace => {
23397                // Spark/Databricks widget template variable: {name}
23398                // ClickHouse query parameter may include kind: {name: Type}
23399                self.write("{");
23400                if let Some(ref name) = f.name {
23401                    self.write(name);
23402                }
23403                if let Some(ref expr) = f.expression {
23404                    self.write(": ");
23405                    self.write(expr);
23406                }
23407                self.write("}");
23408            }
23409        }
23410        Ok(())
23411    }
23412
23413    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
23414        self.write("?");
23415        if let Some(idx) = f.index {
23416            self.write(&idx.to_string());
23417        }
23418        Ok(())
23419    }
23420
23421    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
23422        if f.is_block {
23423            self.write("/*");
23424            self.write(&f.text);
23425            self.write("*/");
23426        } else {
23427            self.write("--");
23428            self.write(&f.text);
23429        }
23430        Ok(())
23431    }
23432
23433    // Additional predicate generators
23434
23435    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
23436        self.generate_expression(&f.this)?;
23437        if f.not {
23438            self.write_space();
23439            self.write_keyword("NOT");
23440        }
23441        self.write_space();
23442        self.write_keyword("SIMILAR TO");
23443        self.write_space();
23444        self.generate_expression(&f.pattern)?;
23445        if let Some(ref escape) = f.escape {
23446            self.write_space();
23447            self.write_keyword("ESCAPE");
23448            self.write_space();
23449            self.generate_expression(escape)?;
23450        }
23451        Ok(())
23452    }
23453
23454    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
23455        self.generate_expression(&f.this)?;
23456        self.write_space();
23457        // Output comparison operator if present
23458        if let Some(op) = &f.op {
23459            match op {
23460                QuantifiedOp::Eq => self.write("="),
23461                QuantifiedOp::Neq => self.write("<>"),
23462                QuantifiedOp::Lt => self.write("<"),
23463                QuantifiedOp::Lte => self.write("<="),
23464                QuantifiedOp::Gt => self.write(">"),
23465                QuantifiedOp::Gte => self.write(">="),
23466            }
23467            self.write_space();
23468        }
23469        self.write_keyword(name);
23470
23471        // If the child is a Subquery, it provides its own parens — output with space
23472        if matches!(&f.subquery, Expression::Subquery(_)) {
23473            self.write_space();
23474            self.generate_expression(&f.subquery)?;
23475        } else {
23476            let is_statement = matches!(
23477                &f.subquery,
23478                Expression::Select(_)
23479                    | Expression::Union(_)
23480                    | Expression::Intersect(_)
23481                    | Expression::Except(_)
23482            );
23483            if is_statement
23484                && !self.config.quantified_no_paren_space
23485                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
23486            {
23487                self.write_space();
23488            }
23489            self.write("(");
23490
23491            if self.config.pretty && is_statement {
23492                self.write_newline();
23493                self.indent_level += 1;
23494                self.write_indent();
23495            }
23496            self.generate_expression(&f.subquery)?;
23497            if self.config.pretty && is_statement {
23498                self.write_newline();
23499                self.indent_level -= 1;
23500                self.write_indent();
23501            }
23502            self.write(")");
23503        }
23504        Ok(())
23505    }
23506
23507    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
23508        // Check if this is a simple binary form (this OVERLAPS expression)
23509        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
23510            self.generate_expression(this)?;
23511            self.write_space();
23512            self.write_keyword("OVERLAPS");
23513            self.write_space();
23514            self.generate_expression(expr)?;
23515        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
23516            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
23517        {
23518            // Full ANSI form: (a, b) OVERLAPS (c, d)
23519            self.write("(");
23520            self.generate_expression(ls)?;
23521            self.write(", ");
23522            self.generate_expression(le)?;
23523            self.write(")");
23524            self.write_space();
23525            self.write_keyword("OVERLAPS");
23526            self.write_space();
23527            self.write("(");
23528            self.generate_expression(rs)?;
23529            self.write(", ");
23530            self.generate_expression(re)?;
23531            self.write(")");
23532        }
23533        Ok(())
23534    }
23535
23536    // Type conversion generators
23537
23538    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
23539        use crate::dialects::DialectType;
23540
23541        // SingleStore uses !:> syntax for try cast
23542        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23543            self.generate_expression(&cast.this)?;
23544            self.write(" !:> ");
23545            self.generate_data_type(&cast.to)?;
23546            return Ok(());
23547        }
23548
23549        // Teradata uses TRYCAST (no underscore)
23550        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
23551            self.write_keyword("TRYCAST");
23552            self.write("(");
23553            self.generate_expression(&cast.this)?;
23554            self.write_space();
23555            self.write_keyword("AS");
23556            self.write_space();
23557            self.generate_data_type(&cast.to)?;
23558            self.write(")");
23559            return Ok(());
23560        }
23561
23562        // Dialects without TRY_CAST: generate as regular CAST
23563        let keyword = if matches!(
23564            self.config.dialect,
23565            Some(DialectType::Hive)
23566                | Some(DialectType::MySQL)
23567                | Some(DialectType::SQLite)
23568                | Some(DialectType::Oracle)
23569                | Some(DialectType::ClickHouse)
23570                | Some(DialectType::Redshift)
23571                | Some(DialectType::PostgreSQL)
23572                | Some(DialectType::StarRocks)
23573                | Some(DialectType::Doris)
23574        ) {
23575            "CAST"
23576        } else {
23577            "TRY_CAST"
23578        };
23579
23580        self.write_keyword(keyword);
23581        self.write("(");
23582        self.generate_expression(&cast.this)?;
23583        self.write_space();
23584        self.write_keyword("AS");
23585        self.write_space();
23586        self.generate_data_type(&cast.to)?;
23587
23588        // Output FORMAT clause if present
23589        if let Some(format) = &cast.format {
23590            self.write_space();
23591            self.write_keyword("FORMAT");
23592            self.write_space();
23593            self.generate_expression(format)?;
23594        }
23595
23596        self.write(")");
23597        Ok(())
23598    }
23599
23600    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
23601        self.write_keyword("SAFE_CAST");
23602        self.write("(");
23603        self.generate_expression(&cast.this)?;
23604        self.write_space();
23605        self.write_keyword("AS");
23606        self.write_space();
23607        self.generate_data_type(&cast.to)?;
23608
23609        // Output FORMAT clause if present
23610        if let Some(format) = &cast.format {
23611            self.write_space();
23612            self.write_keyword("FORMAT");
23613            self.write_space();
23614            self.generate_expression(format)?;
23615        }
23616
23617        self.write(")");
23618        Ok(())
23619    }
23620
23621    // Array/struct/map access generators
23622
23623    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
23624        // Wrap the base expression in parentheses when it uses arrow syntax (->)
23625        // which has lower precedence than bracket subscript ([]).
23626        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
23627        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
23628        if needs_parens {
23629            self.write("(");
23630        }
23631        self.generate_expression(&s.this)?;
23632        if needs_parens {
23633            self.write(")");
23634        }
23635        self.write("[");
23636        self.generate_expression(&s.index)?;
23637        self.write("]");
23638        Ok(())
23639    }
23640
23641    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
23642        self.generate_expression(&d.this)?;
23643        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
23644        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
23645        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
23646            && matches!(
23647                &d.this,
23648                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
23649            );
23650        if use_colon {
23651            self.write(":");
23652        } else {
23653            self.write(".");
23654        }
23655        self.generate_identifier(&d.field)
23656    }
23657
23658    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
23659        self.generate_expression(&m.this)?;
23660        self.write(".");
23661        // Method names after a dot should not be quoted based on reserved keywords
23662        // Only quote if explicitly marked as quoted in the AST
23663        if m.method.quoted {
23664            let q = self.config.identifier_quote;
23665            self.write(&format!("{}{}{}", q, m.method.name, q));
23666        } else {
23667            self.write(&m.method.name);
23668        }
23669        self.write("(");
23670        for (i, arg) in m.args.iter().enumerate() {
23671            if i > 0 {
23672                self.write(", ");
23673            }
23674            self.generate_expression(arg)?;
23675        }
23676        self.write(")");
23677        Ok(())
23678    }
23679
23680    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
23681        // Check if we need to wrap the inner expression in parentheses
23682        // JSON arrow expressions have lower precedence than array subscript
23683        let needs_parens = matches!(
23684            &s.this,
23685            Expression::JsonExtract(f) if f.arrow_syntax
23686        ) || matches!(
23687            &s.this,
23688            Expression::JsonExtractScalar(f) if f.arrow_syntax
23689        );
23690
23691        if needs_parens {
23692            self.write("(");
23693        }
23694        self.generate_expression(&s.this)?;
23695        if needs_parens {
23696            self.write(")");
23697        }
23698        self.write("[");
23699        if let Some(start) = &s.start {
23700            self.generate_expression(start)?;
23701        }
23702        self.write(":");
23703        if let Some(end) = &s.end {
23704            self.generate_expression(end)?;
23705        }
23706        self.write("]");
23707        Ok(())
23708    }
23709
23710    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23711        // Generate left expression, but skip trailing comments if they're already in left_comments
23712        // to avoid duplication (comments are captured as both expr.trailing_comments
23713        // and BinaryOp.left_comments during parsing)
23714        match &op.left {
23715            Expression::Column(col) => {
23716                // Generate column with trailing comments but skip them if they're
23717                // already captured in BinaryOp.left_comments to avoid duplication
23718                if let Some(table) = &col.table {
23719                    self.generate_identifier(table)?;
23720                    self.write(".");
23721                }
23722                self.generate_identifier(&col.name)?;
23723                // Oracle-style join marker (+)
23724                if col.join_mark && self.config.supports_column_join_marks {
23725                    self.write(" (+)");
23726                }
23727                // Output column trailing comments if they're not already in left_comments
23728                if op.left_comments.is_empty() {
23729                    for comment in &col.trailing_comments {
23730                        self.write_space();
23731                        self.write_formatted_comment(comment);
23732                    }
23733                }
23734            }
23735            Expression::Add(inner_op)
23736            | Expression::Sub(inner_op)
23737            | Expression::Mul(inner_op)
23738            | Expression::Div(inner_op)
23739            | Expression::Concat(inner_op) => {
23740                // Generate binary op without its trailing comments
23741                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23742                    Expression::Add(_) => "+",
23743                    Expression::Sub(_) => "-",
23744                    Expression::Mul(_) => "*",
23745                    Expression::Div(_) => "/",
23746                    Expression::Concat(_) => "||",
23747                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23748                })?;
23749            }
23750            _ => {
23751                self.generate_expression(&op.left)?;
23752            }
23753        }
23754        // Output comments after left operand
23755        for comment in &op.left_comments {
23756            self.write_space();
23757            self.write_formatted_comment(comment);
23758        }
23759        if self.config.pretty
23760            && matches!(self.config.dialect, Some(DialectType::Snowflake))
23761            && (operator == "AND" || operator == "OR")
23762        {
23763            self.write_newline();
23764            self.write_indent();
23765            self.write_keyword(operator);
23766        } else {
23767            self.write_space();
23768            if operator.chars().all(|c| c.is_alphabetic()) {
23769                self.write_keyword(operator);
23770            } else {
23771                self.write(operator);
23772            }
23773        }
23774        // Output comments after operator (before right operand)
23775        for comment in &op.operator_comments {
23776            self.write_space();
23777            self.write_formatted_comment(comment);
23778        }
23779        self.write_space();
23780        self.generate_expression(&op.right)?;
23781        // Output trailing comments after right operand
23782        for comment in &op.trailing_comments {
23783            self.write_space();
23784            self.write_formatted_comment(comment);
23785        }
23786        Ok(())
23787    }
23788
23789    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
23790        let keyword = connector.keyword();
23791        let Some(terms) = self.flatten_connector_terms(op, connector) else {
23792            return self.generate_binary_op(op, keyword);
23793        };
23794
23795        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
23796            let should_wrap = matches!(connector, ConnectorOperator::Or)
23797                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
23798                && matches!(
23799                    generator.config.source_dialect,
23800                    Some(DialectType::ClickHouse)
23801                )
23802                && matches!(term, Expression::And(_));
23803            if should_wrap {
23804                generator.write("(");
23805                generator.generate_expression(term)?;
23806                generator.write(")");
23807            } else {
23808                generator.generate_expression(term)?;
23809            }
23810            Ok(())
23811        };
23812
23813        wrap_clickhouse_or_term(self, terms[0])?;
23814        for term in terms.iter().skip(1) {
23815            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
23816                self.write_newline();
23817                self.write_indent();
23818                self.write_keyword(keyword);
23819            } else {
23820                self.write_space();
23821                self.write_keyword(keyword);
23822            }
23823            self.write_space();
23824            wrap_clickhouse_or_term(self, term)?;
23825        }
23826
23827        Ok(())
23828    }
23829
23830    fn flatten_connector_terms<'a>(
23831        &self,
23832        root: &'a BinaryOp,
23833        connector: ConnectorOperator,
23834    ) -> Option<Vec<&'a Expression>> {
23835        if !root.left_comments.is_empty()
23836            || !root.operator_comments.is_empty()
23837            || !root.trailing_comments.is_empty()
23838        {
23839            return None;
23840        }
23841
23842        let mut terms = Vec::new();
23843        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
23844
23845        while let Some(expr) = stack.pop() {
23846            match (connector, expr) {
23847                (ConnectorOperator::And, Expression::And(inner))
23848                    if inner.left_comments.is_empty()
23849                        && inner.operator_comments.is_empty()
23850                        && inner.trailing_comments.is_empty() =>
23851                {
23852                    stack.push(&inner.right);
23853                    stack.push(&inner.left);
23854                }
23855                (ConnectorOperator::Or, Expression::Or(inner))
23856                    if inner.left_comments.is_empty()
23857                        && inner.operator_comments.is_empty()
23858                        && inner.trailing_comments.is_empty() =>
23859                {
23860                    stack.push(&inner.right);
23861                    stack.push(&inner.left);
23862                }
23863                _ => terms.push(expr),
23864            }
23865        }
23866
23867        if terms.len() > 1 {
23868            Some(terms)
23869        } else {
23870            None
23871        }
23872    }
23873
23874    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
23875        let mut depth = 0usize;
23876        let mut quote: Option<char> = None;
23877        let mut chars = sql.chars().peekable();
23878
23879        while let Some(ch) = chars.next() {
23880            if let Some(quote_char) = quote {
23881                if ch == '\\' {
23882                    chars.next();
23883                } else if ch == quote_char {
23884                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
23885                        chars.next();
23886                    } else {
23887                        quote = None;
23888                    }
23889                }
23890                continue;
23891            }
23892
23893            match ch {
23894                '\'' | '"' | '`' => quote = Some(ch),
23895                '(' => depth += 1,
23896                ')' => depth = depth.saturating_sub(1),
23897                _ => {}
23898            }
23899        }
23900
23901        depth
23902    }
23903
23904    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
23905    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23906        self.generate_like_op_inner(op, operator, false)
23907    }
23908
23909    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23910        self.generate_like_op_inner(op, operator, true)
23911    }
23912
23913    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
23914        if negated
23915            && matches!(
23916                self.config.dialect,
23917                Some(DialectType::ClickHouse)
23918                    | Some(DialectType::DataFusion)
23919                    | Some(DialectType::TSQL)
23920                    | Some(DialectType::Fabric)
23921            )
23922        {
23923            self.write_keyword("NOT");
23924            self.write_space();
23925            return self.generate_like_op_inner(op, operator, false);
23926        }
23927
23928        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23929            if let Expression::Star(star) = &op.left {
23930                if star
23931                    .except
23932                    .as_ref()
23933                    .is_some_and(|except| !except.is_empty())
23934                {
23935                    if let Some(table) = &star.table {
23936                        self.generate_identifier(table)?;
23937                        self.write(".");
23938                    }
23939                    self.write("*");
23940                    self.write_space();
23941                    self.write_keyword(operator);
23942                    if let Some(quantifier) = &op.quantifier {
23943                        self.write_space();
23944                        self.write_keyword(quantifier);
23945                        self.write_space();
23946                    } else {
23947                        self.write_space();
23948                    }
23949                    self.generate_expression(&op.right)?;
23950                    if let Some(escape) = &op.escape {
23951                        self.write_space();
23952                        self.write_keyword("ESCAPE");
23953                        self.write_space();
23954                        self.generate_expression(escape)?;
23955                    }
23956                    if let Some(except) = &star.except {
23957                        self.write_space();
23958                        self.write_keyword("EXCEPT");
23959                        self.write(" (");
23960                        for (i, col) in except.iter().enumerate() {
23961                            if i > 0 {
23962                                self.write(", ");
23963                            }
23964                            self.generate_identifier(col)?;
23965                        }
23966                        self.write(")");
23967                    }
23968                    return Ok(());
23969                }
23970            }
23971        }
23972
23973        self.generate_expression(&op.left)?;
23974        self.write_space();
23975        if negated {
23976            self.write_keyword("NOT");
23977            self.write_space();
23978        }
23979        // Drill backtick-quotes ILIKE
23980        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
23981            self.write("`ILIKE`");
23982        } else {
23983            self.write_keyword(operator);
23984        }
23985        if let Some(quantifier) = &op.quantifier {
23986            self.write_space();
23987            self.write_keyword(quantifier);
23988            // Match Python sqlglot behavior:
23989            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
23990            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
23991            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
23992            let is_any =
23993                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
23994            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
23995                self.write_space();
23996            }
23997        } else {
23998            self.write_space();
23999        }
24000        self.generate_expression(&op.right)?;
24001        if let Some(escape) = &op.escape {
24002            self.write_space();
24003            self.write_keyword("ESCAPE");
24004            self.write_space();
24005            self.generate_expression(escape)?;
24006        }
24007        Ok(())
24008    }
24009
24010    /// Generate null-safe equality
24011    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
24012    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
24013        use crate::dialects::DialectType;
24014        self.generate_expression(&op.left)?;
24015        self.write_space();
24016        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
24017            self.write("<=>");
24018        } else {
24019            self.write_keyword("IS NOT DISTINCT FROM");
24020        }
24021        self.write_space();
24022        self.generate_expression(&op.right)?;
24023        Ok(())
24024    }
24025
24026    /// Generate IS DISTINCT FROM (null-safe inequality)
24027    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
24028        self.generate_expression(&op.left)?;
24029        self.write_space();
24030        self.write_keyword("IS DISTINCT FROM");
24031        self.write_space();
24032        self.generate_expression(&op.right)?;
24033        Ok(())
24034    }
24035
24036    /// Generate binary op without trailing comments (used when nested inside another binary op)
24037    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
24038        // Generate left expression, but skip trailing comments
24039        match &op.left {
24040            Expression::Column(col) => {
24041                if let Some(table) = &col.table {
24042                    self.generate_identifier(table)?;
24043                    self.write(".");
24044                }
24045                self.generate_identifier(&col.name)?;
24046                // Oracle-style join marker (+)
24047                if col.join_mark && self.config.supports_column_join_marks {
24048                    self.write(" (+)");
24049                }
24050            }
24051            Expression::Add(inner_op)
24052            | Expression::Sub(inner_op)
24053            | Expression::Mul(inner_op)
24054            | Expression::Div(inner_op)
24055            | Expression::Concat(inner_op) => {
24056                self.generate_binary_op_no_trailing(inner_op, match &op.left {
24057                    Expression::Add(_) => "+",
24058                    Expression::Sub(_) => "-",
24059                    Expression::Mul(_) => "*",
24060                    Expression::Div(_) => "/",
24061                    Expression::Concat(_) => "||",
24062                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
24063                })?;
24064            }
24065            _ => {
24066                self.generate_expression(&op.left)?;
24067            }
24068        }
24069        // Output left_comments
24070        for comment in &op.left_comments {
24071            self.write_space();
24072            self.write_formatted_comment(comment);
24073        }
24074        self.write_space();
24075        if operator.chars().all(|c| c.is_alphabetic()) {
24076            self.write_keyword(operator);
24077        } else {
24078            self.write(operator);
24079        }
24080        // Output operator_comments
24081        for comment in &op.operator_comments {
24082            self.write_space();
24083            self.write_formatted_comment(comment);
24084        }
24085        self.write_space();
24086        // Generate right expression, but skip trailing comments if it's a Column
24087        // (the parent's left_comments will output them)
24088        match &op.right {
24089            Expression::Column(col) => {
24090                if let Some(table) = &col.table {
24091                    self.generate_identifier(table)?;
24092                    self.write(".");
24093                }
24094                self.generate_identifier(&col.name)?;
24095                // Oracle-style join marker (+)
24096                if col.join_mark && self.config.supports_column_join_marks {
24097                    self.write(" (+)");
24098                }
24099            }
24100            _ => {
24101                self.generate_expression(&op.right)?;
24102            }
24103        }
24104        // Skip trailing_comments - parent will handle them via its left_comments
24105        Ok(())
24106    }
24107
24108    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
24109        if operator.chars().all(|c| c.is_alphabetic()) {
24110            self.write_keyword(operator);
24111            self.write_space();
24112        } else {
24113            self.write(operator);
24114            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
24115            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
24116                self.write_space();
24117            }
24118        }
24119        self.generate_expression(&op.this)
24120    }
24121
24122    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
24123        // Generic mode supports two styles for negated IN:
24124        // - Prefix: NOT a IN (...)
24125        // - Infix:  a NOT IN (...)
24126        let is_generic =
24127            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24128        let use_prefix_not =
24129            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
24130        if use_prefix_not {
24131            self.write_keyword("NOT");
24132            self.write_space();
24133        }
24134        self.generate_expression(&in_expr.this)?;
24135        if in_expr.global {
24136            self.write_space();
24137            self.write_keyword("GLOBAL");
24138        }
24139        if in_expr.not && !use_prefix_not {
24140            self.write_space();
24141            self.write_keyword("NOT");
24142        }
24143        self.write_space();
24144        self.write_keyword("IN");
24145
24146        // BigQuery: IN UNNEST(expr)
24147        if let Some(unnest_expr) = &in_expr.unnest {
24148            self.write_space();
24149            self.write_keyword("UNNEST");
24150            self.write("(");
24151            self.generate_expression(unnest_expr)?;
24152            self.write(")");
24153            return Ok(());
24154        }
24155
24156        if let Some(query) = &in_expr.query {
24157            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
24158            // vs a subquery (col IN (SELECT ...))
24159            let is_bare = in_expr.expressions.is_empty()
24160                && !matches!(
24161                    query,
24162                    Expression::Select(_)
24163                        | Expression::Union(_)
24164                        | Expression::Intersect(_)
24165                        | Expression::Except(_)
24166                        | Expression::Subquery(_)
24167                );
24168            if is_bare {
24169                // Bare identifier: no parentheses
24170                self.write_space();
24171                self.generate_expression(query)?;
24172            } else {
24173                // Subquery: with parentheses
24174                self.write(" (");
24175                let is_statement = matches!(
24176                    query,
24177                    Expression::Select(_)
24178                        | Expression::Union(_)
24179                        | Expression::Intersect(_)
24180                        | Expression::Except(_)
24181                        | Expression::Subquery(_)
24182                );
24183                if self.config.pretty && is_statement {
24184                    self.write_newline();
24185                    self.indent_level += 1;
24186                    self.write_indent();
24187                }
24188                self.generate_expression(query)?;
24189                if self.config.pretty && is_statement {
24190                    self.write_newline();
24191                    self.indent_level -= 1;
24192                    self.write_indent();
24193                }
24194                self.write(")");
24195            }
24196        } else {
24197            // DuckDB: IN without parentheses for single expression that is NOT a literal
24198            // (array/list membership like 'red' IN tbl.flags)
24199            // ClickHouse: IN without parentheses for single non-array expressions
24200            let is_duckdb = matches!(
24201                self.config.dialect,
24202                Some(crate::dialects::DialectType::DuckDB)
24203            );
24204            let is_clickhouse = matches!(
24205                self.config.dialect,
24206                Some(crate::dialects::DialectType::ClickHouse)
24207            );
24208            let single_expr = in_expr.expressions.len() == 1;
24209            if is_clickhouse && single_expr {
24210                if let Expression::Array(arr) = &in_expr.expressions[0] {
24211                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
24212                    self.write(" (");
24213                    for (i, expr) in arr.expressions.iter().enumerate() {
24214                        if i > 0 {
24215                            self.write(", ");
24216                        }
24217                        self.generate_expression(expr)?;
24218                    }
24219                    self.write(")");
24220                } else if in_expr.is_field {
24221                    self.write_space();
24222                    self.generate_expression(&in_expr.expressions[0])?;
24223                } else {
24224                    self.write(" (");
24225                    self.generate_expression(&in_expr.expressions[0])?;
24226                    self.write(")");
24227                }
24228            } else {
24229                let is_bare_ref = single_expr
24230                    && matches!(
24231                        &in_expr.expressions[0],
24232                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
24233                    );
24234                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
24235                    // Bare field reference (no parens in source): IN identifier
24236                    // Also DuckDB: IN without parentheses for array/list membership
24237                    self.write_space();
24238                    self.generate_expression(&in_expr.expressions[0])?;
24239                } else {
24240                    // Standard IN (list)
24241                    self.write(" (");
24242                    for (i, expr) in in_expr.expressions.iter().enumerate() {
24243                        if i > 0 {
24244                            self.write(", ");
24245                        }
24246                        self.generate_expression(expr)?;
24247                    }
24248                    self.write(")");
24249                }
24250            }
24251        }
24252
24253        Ok(())
24254    }
24255
24256    fn generate_between(&mut self, between: &Between) -> Result<()> {
24257        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
24258        let use_prefix_not = between.not
24259            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
24260        if use_prefix_not {
24261            self.write_keyword("NOT");
24262            self.write_space();
24263        }
24264        self.generate_expression(&between.this)?;
24265        if between.not && !use_prefix_not {
24266            self.write_space();
24267            self.write_keyword("NOT");
24268        }
24269        self.write_space();
24270        self.write_keyword("BETWEEN");
24271        // Emit SYMMETRIC/ASYMMETRIC if present
24272        if let Some(sym) = between.symmetric {
24273            if sym {
24274                self.write(" SYMMETRIC");
24275            } else {
24276                self.write(" ASYMMETRIC");
24277            }
24278        }
24279        self.write_space();
24280        self.generate_expression(&between.low)?;
24281        self.write_space();
24282        self.write_keyword("AND");
24283        self.write_space();
24284        self.generate_expression(&between.high)
24285    }
24286
24287    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
24288        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
24289        let use_prefix_not = is_null.not
24290            && (self.config.dialect.is_none()
24291                || self.config.dialect == Some(DialectType::Generic)
24292                || (is_null.postfix_form && self.config.dialect != Some(DialectType::PostgreSQL)));
24293        if use_prefix_not {
24294            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
24295            self.write_keyword("NOT");
24296            self.write_space();
24297            self.generate_expression(&is_null.this)?;
24298            self.write_space();
24299            self.write_keyword("IS");
24300            self.write_space();
24301            self.write_keyword("NULL");
24302        } else {
24303            self.generate_expression(&is_null.this)?;
24304            self.write_space();
24305            self.write_keyword("IS");
24306            if is_null.not {
24307                self.write_space();
24308                self.write_keyword("NOT");
24309            }
24310            self.write_space();
24311            self.write_keyword("NULL");
24312        }
24313        Ok(())
24314    }
24315
24316    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
24317        self.generate_expression(&is_true.this)?;
24318        self.write_space();
24319        self.write_keyword("IS");
24320        if is_true.not {
24321            self.write_space();
24322            self.write_keyword("NOT");
24323        }
24324        self.write_space();
24325        self.write_keyword("TRUE");
24326        Ok(())
24327    }
24328
24329    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
24330        self.generate_expression(&is_false.this)?;
24331        self.write_space();
24332        self.write_keyword("IS");
24333        if is_false.not {
24334            self.write_space();
24335            self.write_keyword("NOT");
24336        }
24337        self.write_space();
24338        self.write_keyword("FALSE");
24339        Ok(())
24340    }
24341
24342    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
24343        self.generate_expression(&is_json.this)?;
24344        self.write_space();
24345        self.write_keyword("IS");
24346        if is_json.negated {
24347            self.write_space();
24348            self.write_keyword("NOT");
24349        }
24350        self.write_space();
24351        self.write_keyword("JSON");
24352
24353        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
24354        if let Some(ref json_type) = is_json.json_type {
24355            self.write_space();
24356            self.write_keyword(json_type);
24357        }
24358
24359        // Output key uniqueness constraint if specified
24360        match &is_json.unique_keys {
24361            Some(JsonUniqueKeys::With) => {
24362                self.write_space();
24363                self.write_keyword("WITH UNIQUE KEYS");
24364            }
24365            Some(JsonUniqueKeys::Without) => {
24366                self.write_space();
24367                self.write_keyword("WITHOUT UNIQUE KEYS");
24368            }
24369            Some(JsonUniqueKeys::Shorthand) => {
24370                self.write_space();
24371                self.write_keyword("UNIQUE KEYS");
24372            }
24373            None => {}
24374        }
24375
24376        Ok(())
24377    }
24378
24379    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
24380        self.generate_expression(&is_expr.left)?;
24381        self.write_space();
24382        self.write_keyword("IS");
24383        self.write_space();
24384        self.generate_expression(&is_expr.right)
24385    }
24386
24387    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
24388        if exists.not {
24389            self.write_keyword("NOT");
24390            self.write_space();
24391        }
24392        self.write_keyword("EXISTS");
24393        self.write("(");
24394        let is_statement = matches!(
24395            &exists.this,
24396            Expression::Select(_)
24397                | Expression::Union(_)
24398                | Expression::Intersect(_)
24399                | Expression::Except(_)
24400        );
24401        if self.config.pretty && is_statement {
24402            self.write_newline();
24403            self.indent_level += 1;
24404            self.write_indent();
24405            self.generate_expression(&exists.this)?;
24406            self.write_newline();
24407            self.indent_level -= 1;
24408            self.write_indent();
24409            self.write(")");
24410        } else {
24411            self.generate_expression(&exists.this)?;
24412            self.write(")");
24413        }
24414        Ok(())
24415    }
24416
24417    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
24418        self.generate_expression(&op.left)?;
24419        self.write_space();
24420        self.write_keyword("MEMBER OF");
24421        self.write("(");
24422        self.generate_expression(&op.right)?;
24423        self.write(")");
24424        Ok(())
24425    }
24426
24427    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
24428        if subquery.lateral {
24429            self.write_keyword("LATERAL");
24430            self.write_space();
24431        }
24432
24433        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
24434        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
24435        // to carry the LIMIT modifier without adding more parens
24436        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
24437            matches!(
24438                &p.this,
24439                Expression::Select(_)
24440                    | Expression::Union(_)
24441                    | Expression::Intersect(_)
24442                    | Expression::Except(_)
24443                    | Expression::Subquery(_)
24444            )
24445        } else {
24446            false
24447        };
24448
24449        // Check if inner expression is a statement for pretty formatting
24450        let is_statement = matches!(
24451            &subquery.this,
24452            Expression::Select(_)
24453                | Expression::Union(_)
24454                | Expression::Intersect(_)
24455                | Expression::Except(_)
24456                | Expression::Merge(_)
24457        );
24458
24459        if !skip_outer_parens {
24460            self.write("(");
24461            if self.config.pretty && is_statement {
24462                self.write_newline();
24463                self.indent_level += 1;
24464                self.write_indent();
24465            }
24466        }
24467        self.generate_expression(&subquery.this)?;
24468
24469        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
24470        if subquery.modifiers_inside {
24471            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
24472            if let Some(order_by) = &subquery.order_by {
24473                self.write_space();
24474                self.write_keyword("ORDER BY");
24475                self.write_space();
24476                for (i, ord) in order_by.expressions.iter().enumerate() {
24477                    if i > 0 {
24478                        self.write(", ");
24479                    }
24480                    self.generate_ordered(ord)?;
24481                }
24482            }
24483
24484            if let Some(limit) = &subquery.limit {
24485                self.write_space();
24486                self.write_keyword("LIMIT");
24487                self.write_space();
24488                self.generate_expression(&limit.this)?;
24489                if limit.percent {
24490                    self.write_space();
24491                    self.write_keyword("PERCENT");
24492                }
24493            }
24494
24495            if let Some(offset) = &subquery.offset {
24496                self.write_space();
24497                self.write_keyword("OFFSET");
24498                self.write_space();
24499                self.generate_expression(&offset.this)?;
24500            }
24501        }
24502
24503        if !skip_outer_parens {
24504            if self.config.pretty && is_statement {
24505                self.write_newline();
24506                self.indent_level -= 1;
24507                self.write_indent();
24508            }
24509            self.write(")");
24510        }
24511
24512        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
24513        if !subquery.modifiers_inside {
24514            if let Some(order_by) = &subquery.order_by {
24515                self.write_space();
24516                self.write_keyword("ORDER BY");
24517                self.write_space();
24518                for (i, ord) in order_by.expressions.iter().enumerate() {
24519                    if i > 0 {
24520                        self.write(", ");
24521                    }
24522                    self.generate_ordered(ord)?;
24523                }
24524            }
24525
24526            if let Some(limit) = &subquery.limit {
24527                self.write_space();
24528                self.write_keyword("LIMIT");
24529                self.write_space();
24530                self.generate_expression(&limit.this)?;
24531                if limit.percent {
24532                    self.write_space();
24533                    self.write_keyword("PERCENT");
24534                }
24535            }
24536
24537            if let Some(offset) = &subquery.offset {
24538                self.write_space();
24539                self.write_keyword("OFFSET");
24540                self.write_space();
24541                self.generate_expression(&offset.this)?;
24542            }
24543
24544            // Generate DISTRIBUTE BY (Hive/Spark)
24545            if let Some(distribute_by) = &subquery.distribute_by {
24546                self.write_space();
24547                self.write_keyword("DISTRIBUTE BY");
24548                self.write_space();
24549                for (i, expr) in distribute_by.expressions.iter().enumerate() {
24550                    if i > 0 {
24551                        self.write(", ");
24552                    }
24553                    self.generate_expression(expr)?;
24554                }
24555            }
24556
24557            // Generate SORT BY (Hive/Spark)
24558            if let Some(sort_by) = &subquery.sort_by {
24559                self.write_space();
24560                self.write_keyword("SORT BY");
24561                self.write_space();
24562                for (i, ord) in sort_by.expressions.iter().enumerate() {
24563                    if i > 0 {
24564                        self.write(", ");
24565                    }
24566                    self.generate_ordered(ord)?;
24567                }
24568            }
24569
24570            // Generate CLUSTER BY (Hive/Spark)
24571            if let Some(cluster_by) = &subquery.cluster_by {
24572                self.write_space();
24573                self.write_keyword("CLUSTER BY");
24574                self.write_space();
24575                for (i, ord) in cluster_by.expressions.iter().enumerate() {
24576                    if i > 0 {
24577                        self.write(", ");
24578                    }
24579                    self.generate_ordered(ord)?;
24580                }
24581            }
24582        }
24583
24584        if let Some(alias) = &subquery.alias {
24585            self.write_space();
24586            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
24587                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
24588                    && !subquery.alias_explicit_as);
24589            if !skip_as {
24590                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24591                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
24592                } else {
24593                    self.write_keyword("AS");
24594                }
24595                self.write_space();
24596            }
24597            self.generate_identifier(alias)?;
24598            if !subquery.column_aliases.is_empty() {
24599                self.write("(");
24600                for (i, col) in subquery.column_aliases.iter().enumerate() {
24601                    if i > 0 {
24602                        self.write(", ");
24603                    }
24604                    self.generate_identifier(col)?;
24605                }
24606                self.write(")");
24607            }
24608        }
24609        // Output trailing comments
24610        for comment in &subquery.trailing_comments {
24611            self.write(" ");
24612            self.write_formatted_comment(comment);
24613        }
24614        Ok(())
24615    }
24616
24617    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
24618        // Generate WITH clause if present
24619        if let Some(ref with) = pivot.with {
24620            self.generate_with(with)?;
24621            self.write_space();
24622        }
24623
24624        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
24625
24626        // Check for Redshift UNPIVOT in FROM clause:
24627        // UNPIVOT expr [AS val AT attr]
24628        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
24629        let is_redshift_unpivot = pivot.unpivot
24630            && pivot.expressions.is_empty()
24631            && pivot.fields.is_empty()
24632            && pivot.using.is_empty()
24633            && pivot.into.is_none()
24634            && !matches!(&pivot.this, Expression::Null(_));
24635
24636        if is_redshift_unpivot {
24637            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
24638            self.write_keyword("UNPIVOT");
24639            self.write_space();
24640            self.generate_expression(&pivot.this)?;
24641            // Alias - for Redshift it can be "val AT attr" format
24642            if let Some(alias) = &pivot.alias {
24643                self.write_space();
24644                self.write_keyword("AS");
24645                self.write_space();
24646                // The alias might contain " AT " for the attr part
24647                self.write(&alias.name);
24648            }
24649            return Ok(());
24650        }
24651
24652        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
24653        let is_simplified = !pivot.using.is_empty()
24654            || pivot.into.is_some()
24655            || (pivot.fields.is_empty()
24656                && !pivot.expressions.is_empty()
24657                && !matches!(&pivot.this, Expression::Null(_)));
24658
24659        if is_simplified {
24660            // DuckDB simplified syntax:
24661            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
24662            //   UNPIVOT table ON cols INTO NAME col VALUE col
24663            self.write_keyword(direction);
24664            self.write_space();
24665            self.generate_expression(&pivot.this)?;
24666
24667            if !pivot.expressions.is_empty() {
24668                self.write_space();
24669                self.write_keyword("ON");
24670                self.write_space();
24671                for (i, expr) in pivot.expressions.iter().enumerate() {
24672                    if i > 0 {
24673                        self.write(", ");
24674                    }
24675                    self.generate_expression(expr)?;
24676                }
24677            }
24678
24679            // INTO (for UNPIVOT)
24680            if let Some(into) = &pivot.into {
24681                self.write_space();
24682                self.write_keyword("INTO");
24683                self.write_space();
24684                self.generate_expression(into)?;
24685            }
24686
24687            // USING (for PIVOT)
24688            if !pivot.using.is_empty() {
24689                self.write_space();
24690                self.write_keyword("USING");
24691                self.write_space();
24692                for (i, expr) in pivot.using.iter().enumerate() {
24693                    if i > 0 {
24694                        self.write(", ");
24695                    }
24696                    self.generate_expression(expr)?;
24697                }
24698            }
24699
24700            // GROUP BY
24701            if let Some(group) = &pivot.group {
24702                self.write_space();
24703                self.generate_expression(group)?;
24704            }
24705        } else {
24706            // Standard syntax:
24707            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
24708            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
24709            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
24710            if !matches!(&pivot.this, Expression::Null(_)) {
24711                self.generate_expression(&pivot.this)?;
24712                self.write_space();
24713            }
24714            self.write_keyword(direction);
24715            self.write("(");
24716
24717            // Aggregation expressions
24718            for (i, expr) in pivot.expressions.iter().enumerate() {
24719                if i > 0 {
24720                    self.write(", ");
24721                }
24722                self.generate_expression(expr)?;
24723            }
24724
24725            // FOR...IN fields
24726            if !pivot.fields.is_empty() {
24727                if !pivot.expressions.is_empty() {
24728                    self.write_space();
24729                }
24730                self.write_keyword("FOR");
24731                self.write_space();
24732                for (i, field) in pivot.fields.iter().enumerate() {
24733                    if i > 0 {
24734                        self.write_space();
24735                    }
24736                    // field is an In expression: column IN (values)
24737                    self.generate_expression(field)?;
24738                }
24739            }
24740
24741            // DEFAULT ON NULL
24742            if let Some(default_val) = &pivot.default_on_null {
24743                self.write_space();
24744                self.write_keyword("DEFAULT ON NULL");
24745                self.write(" (");
24746                self.generate_expression(default_val)?;
24747                self.write(")");
24748            }
24749
24750            // GROUP BY inside PIVOT parens
24751            if let Some(group) = &pivot.group {
24752                self.write_space();
24753                self.generate_expression(group)?;
24754            }
24755
24756            self.write(")");
24757        }
24758
24759        // Alias
24760        if let Some(alias) = &pivot.alias {
24761            self.write_space();
24762            self.write_keyword("AS");
24763            self.write_space();
24764            self.generate_identifier(alias)?;
24765            self.generate_alias_column_list(&pivot.alias_columns)?;
24766        }
24767
24768        Ok(())
24769    }
24770
24771    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
24772        self.generate_expression(&unpivot.this)?;
24773        self.write_space();
24774        self.write_keyword("UNPIVOT");
24775        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
24776        if let Some(include) = unpivot.include_nulls {
24777            self.write_space();
24778            if include {
24779                self.write_keyword("INCLUDE NULLS");
24780            } else {
24781                self.write_keyword("EXCLUDE NULLS");
24782            }
24783            self.write_space();
24784        }
24785        self.write("(");
24786        if unpivot.value_column_parenthesized {
24787            self.write("(");
24788        }
24789        self.generate_identifier(&unpivot.value_column)?;
24790        // Output additional value columns if present
24791        for extra_col in &unpivot.extra_value_columns {
24792            self.write(", ");
24793            self.generate_identifier(extra_col)?;
24794        }
24795        if unpivot.value_column_parenthesized {
24796            self.write(")");
24797        }
24798        self.write_space();
24799        self.write_keyword("FOR");
24800        self.write_space();
24801        self.generate_identifier(&unpivot.name_column)?;
24802        self.write_space();
24803        self.write_keyword("IN");
24804        self.write(" (");
24805        for (i, col) in unpivot.columns.iter().enumerate() {
24806            if i > 0 {
24807                self.write(", ");
24808            }
24809            self.generate_expression(col)?;
24810        }
24811        self.write("))");
24812        if let Some(alias) = &unpivot.alias {
24813            self.write_space();
24814            self.write_keyword("AS");
24815            self.write_space();
24816            self.generate_identifier(alias)?;
24817            self.generate_alias_column_list(&unpivot.alias_columns)?;
24818        }
24819        Ok(())
24820    }
24821
24822    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
24823        if columns.is_empty() {
24824            return Ok(());
24825        }
24826
24827        self.write("(");
24828        for (i, column) in columns.iter().enumerate() {
24829            if i > 0 {
24830                self.write(", ");
24831            }
24832            self.generate_identifier(column)?;
24833        }
24834        self.write(")");
24835        Ok(())
24836    }
24837
24838    fn generate_values(&mut self, values: &Values) -> Result<()> {
24839        self.write_keyword("VALUES");
24840        for (i, row) in values.expressions.iter().enumerate() {
24841            if i > 0 {
24842                self.write(",");
24843            }
24844            self.write(" (");
24845            for (j, expr) in row.expressions.iter().enumerate() {
24846                if j > 0 {
24847                    self.write(", ");
24848                }
24849                self.generate_expression(expr)?;
24850            }
24851            self.write(")");
24852        }
24853        if let Some(alias) = &values.alias {
24854            self.write_space();
24855            self.write_keyword("AS");
24856            self.write_space();
24857            self.generate_identifier(alias)?;
24858            if !values.column_aliases.is_empty() {
24859                self.write("(");
24860                for (i, col) in values.column_aliases.iter().enumerate() {
24861                    if i > 0 {
24862                        self.write(", ");
24863                    }
24864                    self.generate_identifier(col)?;
24865                }
24866                self.write(")");
24867            }
24868        }
24869        Ok(())
24870    }
24871
24872    fn generate_array(&mut self, arr: &Array) -> Result<()> {
24873        // Apply struct name inheritance for target dialects that need it
24874        let needs_inheritance = matches!(
24875            self.config.dialect,
24876            Some(DialectType::DuckDB)
24877                | Some(DialectType::Spark)
24878                | Some(DialectType::Databricks)
24879                | Some(DialectType::Hive)
24880                | Some(DialectType::Snowflake)
24881                | Some(DialectType::Presto)
24882                | Some(DialectType::Trino)
24883        );
24884        let propagated: Vec<Expression>;
24885        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
24886            propagated = Self::inherit_struct_field_names(&arr.expressions);
24887            &propagated
24888        } else {
24889            &arr.expressions
24890        };
24891
24892        // Generic mode: ARRAY(1, 2, 3) with parentheses
24893        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
24894        let use_parens =
24895            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24896        if !self.config.array_bracket_only {
24897            self.write_keyword("ARRAY");
24898        }
24899        if use_parens {
24900            self.write("(");
24901        } else {
24902            self.write("[");
24903        }
24904        for (i, expr) in expressions.iter().enumerate() {
24905            if i > 0 {
24906                self.write(", ");
24907            }
24908            self.generate_expression(expr)?;
24909        }
24910        if use_parens {
24911            self.write(")");
24912        } else {
24913            self.write("]");
24914        }
24915        Ok(())
24916    }
24917
24918    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
24919        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
24920        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
24921        if tuple.expressions.len() == 2 {
24922            if let Expression::TableAlias(_) = &tuple.expressions[1] {
24923                // First element is the function/expression, second is the TableAlias
24924                self.generate_expression(&tuple.expressions[0])?;
24925                self.write_space();
24926                self.write_keyword("AS");
24927                self.write_space();
24928                self.generate_expression(&tuple.expressions[1])?;
24929                return Ok(());
24930            }
24931        }
24932
24933        // In pretty mode, format long tuples with each element on a new line
24934        // Only expand if total width exceeds threshold
24935        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
24936            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
24937            for expr in &tuple.expressions {
24938                expr_strings.push(self.generate_to_string(expr)?);
24939            }
24940            self.too_wide(&expr_strings)
24941        } else {
24942            false
24943        };
24944
24945        if expand_tuple {
24946            self.write("(");
24947            self.write_newline();
24948            self.indent_level += 1;
24949            for (i, expr) in tuple.expressions.iter().enumerate() {
24950                if i > 0 {
24951                    self.write(",");
24952                    self.write_newline();
24953                }
24954                self.write_indent();
24955                self.generate_expression(expr)?;
24956            }
24957            self.indent_level -= 1;
24958            self.write_newline();
24959            self.write_indent();
24960            self.write(")");
24961        } else {
24962            self.write("(");
24963            for (i, expr) in tuple.expressions.iter().enumerate() {
24964                if i > 0 {
24965                    self.write(", ");
24966                }
24967                self.generate_expression(expr)?;
24968            }
24969            self.write(")");
24970        }
24971        Ok(())
24972    }
24973
24974    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
24975        self.generate_expression(&pipe.this)?;
24976        self.write(" |> ");
24977        self.generate_expression(&pipe.expression)?;
24978        Ok(())
24979    }
24980
24981    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
24982        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
24983            && !self.config.null_ordering_supported
24984            && matches!(
24985                self.config.dialect,
24986                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24987            );
24988        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
24989        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
24990            let target_default_nulls_first = !ordered.desc;
24991
24992            unsupported_tsql_null_ordering
24993                && nulls_first != target_default_nulls_first
24994                && !random_ordering
24995        } else {
24996            false
24997        };
24998
24999        if emulate_tsql_null_ordering {
25000            if Self::is_integer_ordering_literal(&ordered.this) {
25001                let nulls_order = if ordered.nulls_first == Some(true) {
25002                    "NULLS FIRST"
25003                } else {
25004                    "NULLS LAST"
25005                };
25006                self.unsupported(format!(
25007                    "'{nulls_order}' translation not supported with positional ordering"
25008                ))?;
25009            } else {
25010                self.write_keyword("CASE WHEN");
25011                self.write_space();
25012                self.generate_expression(&ordered.this)?;
25013                self.write_space();
25014                self.write_keyword("IS NULL THEN 1 ELSE 0 END");
25015                if ordered.nulls_first == Some(true) {
25016                    self.write_space();
25017                    self.write_keyword("DESC");
25018                }
25019                self.write(", ");
25020            }
25021        }
25022
25023        self.generate_expression(&ordered.this)?;
25024        if ordered.desc {
25025            self.write_space();
25026            self.write_keyword("DESC");
25027        } else if ordered.explicit_asc {
25028            self.write_space();
25029            self.write_keyword("ASC");
25030        }
25031        if let Some(nulls_first) = ordered.nulls_first {
25032            if !unsupported_tsql_null_ordering
25033                && (self.config.null_ordering_supported
25034                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
25035            {
25036                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
25037                // for the dialect. Different dialects have different NULL ordering defaults:
25038                //
25039                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
25040                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
25041                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
25042                //
25043                // nulls_are_small (Spark, Hive, BigQuery, most others):
25044                //   - ASC: NULLS FIRST is default
25045                //   - DESC: NULLS LAST is default
25046                //
25047                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
25048                //   - NULLS LAST is always the default regardless of sort direction
25049                let is_asc = !ordered.desc;
25050                let is_nulls_are_large = matches!(
25051                    self.config.dialect,
25052                    Some(DialectType::Oracle)
25053                        | Some(DialectType::PostgreSQL)
25054                        | Some(DialectType::Redshift)
25055                        | Some(DialectType::Snowflake)
25056                );
25057                let is_nulls_are_last = matches!(
25058                    self.config.dialect,
25059                    Some(DialectType::Dremio)
25060                        | Some(DialectType::DuckDB)
25061                        | Some(DialectType::Presto)
25062                        | Some(DialectType::Trino)
25063                        | Some(DialectType::Athena)
25064                        | Some(DialectType::ClickHouse)
25065                        | Some(DialectType::Drill)
25066                        | Some(DialectType::Exasol)
25067                );
25068
25069                // Check if the NULLS ordering matches the default for this dialect
25070                let is_default_nulls = if is_nulls_are_large {
25071                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
25072                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
25073                } else if is_nulls_are_last {
25074                    // For nulls_are_last: NULLS LAST is always default
25075                    !nulls_first
25076                } else {
25077                    false
25078                };
25079
25080                if !is_default_nulls {
25081                    self.write_space();
25082                    self.write_keyword("NULLS");
25083                    self.write_space();
25084                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
25085                }
25086            }
25087        }
25088        // WITH FILL clause (ClickHouse)
25089        if let Some(ref with_fill) = ordered.with_fill {
25090            self.write_space();
25091            self.generate_with_fill(with_fill)?;
25092        }
25093        Ok(())
25094    }
25095
25096    fn is_integer_ordering_literal(expr: &Expression) -> bool {
25097        matches!(
25098            expr,
25099            Expression::Literal(lit)
25100                if matches!(lit.as_ref(), Literal::Number(n) if n.parse::<u64>().is_ok())
25101        )
25102    }
25103
25104    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
25105    fn write_clickhouse_type(&mut self, type_str: &str) {
25106        if self.clickhouse_nullable_depth < 0 {
25107            // Map key context: don't wrap in Nullable
25108            self.write(type_str);
25109        } else {
25110            self.write(&format!("Nullable({})", type_str));
25111        }
25112    }
25113
25114    fn write_type_with_length(&mut self, name: &str, length: Option<u32>) {
25115        self.write_keyword(name);
25116        if let Some(length) = length {
25117            self.write(&format!("({length})"));
25118        }
25119    }
25120
25121    fn write_oracle_number(&mut self, precision: Option<u32>, scale: Option<i32>) -> Result<()> {
25122        use crate::dialects::DialectType;
25123
25124        if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25125            self.write_keyword("NUMBER");
25126            if precision.is_some() || scale.is_some() {
25127                self.write("(");
25128                if let Some(precision) = precision {
25129                    self.write(&precision.to_string());
25130                } else {
25131                    self.write("*");
25132                }
25133                if let Some(scale) = scale {
25134                    self.write(&format!(", {scale}"));
25135                }
25136                self.write(")");
25137            }
25138            return Ok(());
25139        }
25140
25141        let target = self.config.dialect;
25142        match (precision, scale) {
25143            (None, None) => match target {
25144                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25145                    self.write_keyword("NUMERIC");
25146                }
25147                Some(DialectType::MySQL)
25148                | Some(DialectType::SingleStore)
25149                | Some(DialectType::TiDB) => {
25150                    self.unsupported(
25151                        "Oracle NUMBER without precision or scale has no exact MySQL mapping",
25152                    )?;
25153                    self.write_keyword("DECIMAL(65, 30)");
25154                }
25155                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25156                    self.unsupported(
25157                        "Oracle NUMBER without precision or scale has no exact T-SQL mapping",
25158                    )?;
25159                    self.write_keyword("DECIMAL(38, 10)");
25160                }
25161                _ => self.write_keyword("DECIMAL"),
25162            },
25163            (Some(precision), None) | (Some(precision), Some(0)) => {
25164                if precision <= 9 {
25165                    self.write_keyword("INT");
25166                } else if precision <= 18 {
25167                    self.write_keyword("BIGINT");
25168                } else {
25169                    self.write(&format!("DECIMAL({precision}, 0)"));
25170                }
25171            }
25172            (Some(precision), Some(scale)) if scale < 0 => match target {
25173                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25174                    self.write(&format!("NUMERIC({precision}, {scale})"));
25175                }
25176                Some(DialectType::MySQL)
25177                | Some(DialectType::SingleStore)
25178                | Some(DialectType::TiDB) => {
25179                    self.unsupported(format!(
25180                        "Oracle NUMBER({precision}, {scale}) negative-scale rounding cannot be enforced by MySQL"
25181                    ))?;
25182                    let width = precision.saturating_add(scale.unsigned_abs()).min(65);
25183                    self.write(&format!("DECIMAL({width}, 0)"));
25184                }
25185                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25186                    self.unsupported(format!(
25187                        "Oracle NUMBER({precision}, {scale}) negative-scale rounding cannot be enforced by T-SQL"
25188                    ))?;
25189                    let width = precision.saturating_add(scale.unsigned_abs()).min(38);
25190                    self.write(&format!("DECIMAL({width}, 0)"));
25191                }
25192                _ => {
25193                    self.unsupported(format!(
25194                        "Oracle NUMBER({precision}, {scale}) negative-scale rounding may not be supported"
25195                    ))?;
25196                    let width = precision.saturating_add(scale.unsigned_abs());
25197                    self.write(&format!("DECIMAL({width}, 0)"));
25198                }
25199            },
25200            (Some(precision), Some(scale)) => {
25201                let mut target_precision = precision;
25202                let mut target_scale = u32::try_from(scale).unwrap_or_default();
25203                let limits = match target {
25204                    Some(DialectType::MySQL)
25205                    | Some(DialectType::SingleStore)
25206                    | Some(DialectType::TiDB) => Some((65, 30, "MySQL")),
25207                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => Some((38, 38, "T-SQL")),
25208                    _ => None,
25209                };
25210                if let Some((max_precision, max_scale, dialect_name)) = limits {
25211                    let requested_precision = target_precision;
25212                    let requested_scale = target_scale;
25213                    target_scale = target_scale.min(max_scale);
25214                    target_precision = target_precision.max(target_scale).min(max_precision);
25215                    if target_precision != requested_precision || target_scale != requested_scale {
25216                        self.unsupported(format!(
25217                            "Oracle NUMBER({precision}, {scale}) exceeds {dialect_name} DECIMAL limits"
25218                        ))?;
25219                    }
25220                }
25221                self.write(&format!("DECIMAL({target_precision}, {target_scale})"));
25222            }
25223            (None, Some(scale)) => {
25224                self.unsupported(format!(
25225                    "Oracle NUMBER(*, {scale}) has no exact target mapping"
25226                ))?;
25227                self.write_keyword("DECIMAL");
25228            }
25229        }
25230        Ok(())
25231    }
25232
25233    fn write_oracle_character(
25234        &mut self,
25235        kind: OracleCharacterKind,
25236        length: Option<u32>,
25237        semantics: Option<OracleCharacterLengthSemantics>,
25238    ) -> Result<()> {
25239        use crate::dialects::DialectType;
25240
25241        if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25242            let name = match kind {
25243                OracleCharacterKind::Char => "CHAR",
25244                OracleCharacterKind::VarChar => "VARCHAR2",
25245                OracleCharacterKind::NChar => "NCHAR",
25246                OracleCharacterKind::NVarChar => "NVARCHAR2",
25247            };
25248            self.write_keyword(name);
25249            if let Some(length) = length {
25250                self.write(&format!("({length}"));
25251                if let Some(semantics) = semantics {
25252                    self.write(match semantics {
25253                        OracleCharacterLengthSemantics::Byte => " BYTE",
25254                        OracleCharacterLengthSemantics::Char => " CHAR",
25255                    });
25256                }
25257                self.write(")");
25258            }
25259            return Ok(());
25260        }
25261
25262        let mut target_kind = kind;
25263        match self.config.dialect {
25264            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25265                if semantics == Some(OracleCharacterLengthSemantics::Char)
25266                    && matches!(
25267                        kind,
25268                        OracleCharacterKind::Char | OracleCharacterKind::VarChar
25269                    ) =>
25270            {
25271                self.unsupported(
25272                    "Oracle CHAR length semantics require a national-character approximation in T-SQL",
25273                )?;
25274                target_kind = match kind {
25275                    OracleCharacterKind::Char => OracleCharacterKind::NChar,
25276                    OracleCharacterKind::VarChar => OracleCharacterKind::NVarChar,
25277                    _ => kind,
25278                };
25279            }
25280            Some(DialectType::PostgreSQL) | Some(DialectType::Materialize)
25281                if matches!(
25282                    kind,
25283                    OracleCharacterKind::NChar | OracleCharacterKind::NVarChar
25284                ) =>
25285            {
25286                target_kind = match kind {
25287                    OracleCharacterKind::NChar => OracleCharacterKind::Char,
25288                    OracleCharacterKind::NVarChar => OracleCharacterKind::VarChar,
25289                    _ => kind,
25290                };
25291            }
25292            _ => {}
25293        }
25294
25295        if semantics == Some(OracleCharacterLengthSemantics::Byte)
25296            && matches!(
25297                self.config.dialect,
25298                Some(DialectType::MySQL)
25299                    | Some(DialectType::SingleStore)
25300                    | Some(DialectType::TiDB)
25301                    | Some(DialectType::PostgreSQL)
25302                    | Some(DialectType::Materialize)
25303            )
25304        {
25305            self.unsupported(
25306                "Oracle BYTE character length semantics cannot be enforced by the target type",
25307            )?;
25308        }
25309
25310        let name = match target_kind {
25311            OracleCharacterKind::Char => "CHAR",
25312            OracleCharacterKind::VarChar => "VARCHAR",
25313            OracleCharacterKind::NChar => "NCHAR",
25314            OracleCharacterKind::NVarChar => "NVARCHAR",
25315        };
25316        let length = if matches!(
25317            target_kind,
25318            OracleCharacterKind::Char | OracleCharacterKind::NChar
25319        ) {
25320            Some(length.unwrap_or(1))
25321        } else {
25322            length
25323        };
25324        self.write_type_with_length(name, length);
25325        Ok(())
25326    }
25327
25328    fn oracle_timestamp_precision(&mut self, precision: Option<u32>, maximum: u32) -> Result<u32> {
25329        let requested = precision.unwrap_or(6);
25330        if requested > maximum {
25331            self.unsupported(format!(
25332                "Oracle timestamp precision {requested} exceeds the target maximum of {maximum}"
25333            ))?;
25334        }
25335        Ok(requested.min(maximum))
25336    }
25337
25338    fn write_oracle_data_type(&mut self, data_type: &OracleDataType) -> Result<()> {
25339        use crate::dialects::DialectType;
25340
25341        match data_type {
25342            OracleDataType::Number { precision, scale } => {
25343                self.write_oracle_number(*precision, *scale)?;
25344            }
25345            OracleDataType::BinaryFloat => match self.config.dialect {
25346                Some(DialectType::Oracle) | None => self.write_keyword("BINARY_FLOAT"),
25347                Some(DialectType::TSQL)
25348                | Some(DialectType::Fabric)
25349                | Some(DialectType::PostgreSQL)
25350                | Some(DialectType::Materialize) => self.write_keyword("REAL"),
25351                _ => self.write_keyword("FLOAT"),
25352            },
25353            OracleDataType::BinaryDouble => match self.config.dialect {
25354                Some(DialectType::Oracle) | None => self.write_keyword("BINARY_DOUBLE"),
25355                Some(DialectType::TSQL) | Some(DialectType::Fabric) => self.write_keyword("FLOAT"),
25356                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25357                    self.write_keyword("DOUBLE PRECISION")
25358                }
25359                _ => self.write_keyword("DOUBLE"),
25360            },
25361            OracleDataType::Float { precision } => {
25362                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25363                    self.write_keyword("FLOAT");
25364                    if let Some(precision) = precision {
25365                        self.write(&format!("({precision})"));
25366                    }
25367                } else {
25368                    let precision = precision.unwrap_or(126);
25369                    match self.config.dialect {
25370                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25371                            if precision > 53 {
25372                                self.unsupported(format!(
25373                                    "Oracle FLOAT({precision}) exceeds T-SQL FLOAT precision"
25374                                ))?;
25375                            }
25376                            self.write(&format!("FLOAT({})", precision.min(53)));
25377                        }
25378                        Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25379                            self.write_keyword(if precision <= 24 {
25380                                "REAL"
25381                            } else {
25382                                "DOUBLE PRECISION"
25383                            });
25384                        }
25385                        Some(DialectType::MySQL)
25386                        | Some(DialectType::SingleStore)
25387                        | Some(DialectType::TiDB) => {
25388                            self.write_keyword(if precision <= 24 { "FLOAT" } else { "DOUBLE" });
25389                        }
25390                        _ => self.write_keyword("DOUBLE PRECISION"),
25391                    }
25392                }
25393            }
25394            OracleDataType::Character {
25395                kind,
25396                length,
25397                semantics,
25398            } => self.write_oracle_character(*kind, *length, *semantics)?,
25399            OracleDataType::Date => match self.config.dialect {
25400                Some(DialectType::Oracle) | None => self.write_keyword("DATE"),
25401                Some(DialectType::MySQL)
25402                | Some(DialectType::SingleStore)
25403                | Some(DialectType::TiDB) => self.write_keyword("DATETIME"),
25404                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25405                    self.write_keyword("DATETIME2(0)")
25406                }
25407                _ => self.write_keyword("TIMESTAMP(0)"),
25408            },
25409            OracleDataType::Timestamp {
25410                precision,
25411                timezone,
25412            } => {
25413                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25414                    self.write_keyword("TIMESTAMP");
25415                    if let Some(precision) = precision {
25416                        self.write(&format!("({precision})"));
25417                    }
25418                    match timezone {
25419                        OracleTimestampTimeZone::None => {}
25420                        OracleTimestampTimeZone::WithTimeZone => {
25421                            self.write_keyword(" WITH TIME ZONE")
25422                        }
25423                        OracleTimestampTimeZone::WithLocalTimeZone => {
25424                            self.write_keyword(" WITH LOCAL TIME ZONE")
25425                        }
25426                    }
25427                } else {
25428                    match self.config.dialect {
25429                        Some(DialectType::MySQL)
25430                        | Some(DialectType::SingleStore)
25431                        | Some(DialectType::TiDB) => {
25432                            let precision = self.oracle_timestamp_precision(*precision, 6)?;
25433                            match timezone {
25434                                OracleTimestampTimeZone::None => {
25435                                    self.write(&format!("DATETIME({precision})"));
25436                                }
25437                                OracleTimestampTimeZone::WithTimeZone => {
25438                                    self.unsupported(
25439                                        "Oracle TIMESTAMP WITH TIME ZONE loses its time zone in MySQL",
25440                                    )?;
25441                                    self.write(&format!("DATETIME({precision})"));
25442                                }
25443                                OracleTimestampTimeZone::WithLocalTimeZone => {
25444                                    self.unsupported(
25445                                        "Oracle TIMESTAMP WITH LOCAL TIME ZONE has only an approximate MySQL mapping",
25446                                    )?;
25447                                    self.write(&format!("TIMESTAMP({precision})"));
25448                                }
25449                            }
25450                        }
25451                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25452                            let precision = self.oracle_timestamp_precision(*precision, 7)?;
25453                            match timezone {
25454                                OracleTimestampTimeZone::None => {
25455                                    self.write(&format!("DATETIME2({precision})"));
25456                                }
25457                                OracleTimestampTimeZone::WithTimeZone => {
25458                                    self.write(&format!("DATETIMEOFFSET({precision})"));
25459                                }
25460                                OracleTimestampTimeZone::WithLocalTimeZone => {
25461                                    self.unsupported(
25462                                        "Oracle TIMESTAMP WITH LOCAL TIME ZONE loses session time-zone semantics in T-SQL",
25463                                    )?;
25464                                    self.write(&format!("DATETIME2({precision})"));
25465                                }
25466                            }
25467                        }
25468                        Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25469                            let precision = self.oracle_timestamp_precision(*precision, 6)?;
25470                            match timezone {
25471                                OracleTimestampTimeZone::None => {
25472                                    self.write(&format!("TIMESTAMP({precision})"));
25473                                }
25474                                OracleTimestampTimeZone::WithTimeZone => {
25475                                    self.write(&format!("TIMESTAMPTZ({precision})"));
25476                                }
25477                                OracleTimestampTimeZone::WithLocalTimeZone => {
25478                                    self.unsupported(
25479                                        "Oracle TIMESTAMP WITH LOCAL TIME ZONE has only an approximate PostgreSQL mapping",
25480                                    )?;
25481                                    self.write(&format!("TIMESTAMPTZ({precision})"));
25482                                }
25483                            }
25484                        }
25485                        _ => {
25486                            let precision = precision.unwrap_or(6);
25487                            let name = if matches!(timezone, OracleTimestampTimeZone::None) {
25488                                "TIMESTAMP"
25489                            } else {
25490                                "TIMESTAMPTZ"
25491                            };
25492                            self.write(&format!("{name}({precision})"));
25493                        }
25494                    }
25495                }
25496            }
25497            OracleDataType::IntervalYearToMonth { year_precision } => {
25498                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25499                    self.write_keyword("INTERVAL YEAR");
25500                    if let Some(precision) = year_precision {
25501                        self.write(&format!("({precision})"));
25502                    }
25503                    self.write_keyword(" TO MONTH");
25504                } else if matches!(
25505                    self.config.dialect,
25506                    Some(DialectType::MySQL)
25507                        | Some(DialectType::SingleStore)
25508                        | Some(DialectType::TiDB)
25509                        | Some(DialectType::TSQL)
25510                        | Some(DialectType::Fabric)
25511                ) {
25512                    self.unsupported(
25513                        "Oracle INTERVAL YEAR TO MONTH has no native target column type",
25514                    )?;
25515                    self.write_keyword("VARCHAR(30)");
25516                } else {
25517                    if year_precision.is_some() {
25518                        self.unsupported(
25519                            "Oracle INTERVAL YEAR leading precision cannot be preserved",
25520                        )?;
25521                    }
25522                    self.write_keyword("INTERVAL YEAR TO MONTH");
25523                }
25524            }
25525            OracleDataType::IntervalDayToSecond {
25526                day_precision,
25527                fractional_seconds_precision,
25528            } => {
25529                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25530                    self.write_keyword("INTERVAL DAY");
25531                    if let Some(precision) = day_precision {
25532                        self.write(&format!("({precision})"));
25533                    }
25534                    self.write_keyword(" TO SECOND");
25535                    if let Some(precision) = fractional_seconds_precision {
25536                        self.write(&format!("({precision})"));
25537                    }
25538                } else if matches!(
25539                    self.config.dialect,
25540                    Some(DialectType::MySQL)
25541                        | Some(DialectType::SingleStore)
25542                        | Some(DialectType::TiDB)
25543                        | Some(DialectType::TSQL)
25544                        | Some(DialectType::Fabric)
25545                ) {
25546                    self.unsupported(
25547                        "Oracle INTERVAL DAY TO SECOND has no native target column type",
25548                    )?;
25549                    self.write_keyword("VARCHAR(30)");
25550                } else {
25551                    if day_precision.is_some() {
25552                        self.unsupported(
25553                            "Oracle INTERVAL DAY leading precision cannot be preserved",
25554                        )?;
25555                    }
25556                    self.write_keyword("INTERVAL");
25557                    if let Some(precision) = fractional_seconds_precision {
25558                        self.write(&format!("({precision})"));
25559                    }
25560                    self.write_keyword(" DAY TO SECOND");
25561                }
25562            }
25563            OracleDataType::Clob { national } => match self.config.dialect {
25564                Some(DialectType::Oracle) | None => {
25565                    self.write_keyword(if *national { "NCLOB" } else { "CLOB" })
25566                }
25567                Some(DialectType::MySQL)
25568                | Some(DialectType::SingleStore)
25569                | Some(DialectType::TiDB) => self.write_keyword("LONGTEXT"),
25570                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25571                    self.write_keyword(if *national {
25572                        "NVARCHAR(MAX)"
25573                    } else {
25574                        "VARCHAR(MAX)"
25575                    });
25576                }
25577                _ => self.write_keyword("TEXT"),
25578            },
25579            OracleDataType::Blob => match self.config.dialect {
25580                Some(DialectType::Oracle) | None => self.write_keyword("BLOB"),
25581                Some(DialectType::MySQL)
25582                | Some(DialectType::SingleStore)
25583                | Some(DialectType::TiDB) => self.write_keyword("LONGBLOB"),
25584                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25585                    self.write_keyword("VARBINARY(MAX)")
25586                }
25587                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25588                    self.write_keyword("BYTEA")
25589                }
25590                _ => self.write_keyword("BLOB"),
25591            },
25592            OracleDataType::Raw { length } => match self.config.dialect {
25593                Some(DialectType::Oracle) | None => self.write_type_with_length("RAW", *length),
25594                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25595                    if length.is_some() {
25596                        self.unsupported("Oracle RAW length constraint is lost in PostgreSQL")?;
25597                    }
25598                    self.write_keyword("BYTEA");
25599                }
25600                _ => self.write_type_with_length("VARBINARY", *length),
25601            },
25602            OracleDataType::Long { raw } => {
25603                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25604                    self.write_keyword(if *raw { "LONG RAW" } else { "LONG" });
25605                } else if *raw {
25606                    match self.config.dialect {
25607                        Some(DialectType::MySQL)
25608                        | Some(DialectType::SingleStore)
25609                        | Some(DialectType::TiDB) => self.write_keyword("LONGBLOB"),
25610                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25611                            self.write_keyword("VARBINARY(MAX)")
25612                        }
25613                        Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25614                            self.write_keyword("BYTEA")
25615                        }
25616                        _ => self.write_keyword("BLOB"),
25617                    }
25618                } else {
25619                    match self.config.dialect {
25620                        Some(DialectType::MySQL)
25621                        | Some(DialectType::SingleStore)
25622                        | Some(DialectType::TiDB) => self.write_keyword("LONGTEXT"),
25623                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25624                            self.write_keyword("VARCHAR(MAX)")
25625                        }
25626                        _ => self.write_keyword("TEXT"),
25627                    }
25628                }
25629            }
25630            OracleDataType::RowId => {
25631                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25632                    self.write_keyword("ROWID");
25633                } else {
25634                    self.unsupported(
25635                        "Oracle ROWID physical row identity is reduced to its text representation",
25636                    )?;
25637                    self.write_keyword("CHAR(18)");
25638                }
25639            }
25640        }
25641        Ok(())
25642    }
25643
25644    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
25645        use crate::dialects::DialectType;
25646
25647        match dt {
25648            DataType::Boolean => {
25649                // Dialect-specific boolean type mappings
25650                match self.config.dialect {
25651                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
25652                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
25653                    Some(DialectType::Oracle) => {
25654                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
25655                        self.write_keyword("NUMBER(1)")
25656                    }
25657                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
25658                    _ => self.write_keyword("BOOLEAN"),
25659                }
25660            }
25661            DataType::TinyInt { length } => {
25662                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
25663                // Dremio maps TINYINT to INT
25664                // ClickHouse maps TINYINT to Int8
25665                match self.config.dialect {
25666                    Some(DialectType::PostgreSQL)
25667                    | Some(DialectType::Redshift)
25668                    | Some(DialectType::Oracle)
25669                    | Some(DialectType::Exasol) => {
25670                        self.write_keyword("SMALLINT");
25671                    }
25672                    Some(DialectType::Teradata) => {
25673                        // Teradata uses BYTEINT for smallest integer
25674                        self.write_keyword("BYTEINT");
25675                    }
25676                    Some(DialectType::Dremio) => {
25677                        // Dremio maps TINYINT to INT
25678                        self.write_keyword("INT");
25679                    }
25680                    Some(DialectType::ClickHouse) => {
25681                        self.write_clickhouse_type("Int8");
25682                    }
25683                    _ => {
25684                        self.write_keyword("TINYINT");
25685                    }
25686                }
25687                if let Some(n) = length {
25688                    if !matches!(
25689                        self.config.dialect,
25690                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
25691                    ) {
25692                        self.write(&format!("({})", n));
25693                    }
25694                }
25695            }
25696            DataType::SmallInt { length } => {
25697                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
25698                match self.config.dialect {
25699                    Some(DialectType::Dremio) => {
25700                        self.write_keyword("INT");
25701                    }
25702                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
25703                        self.write_keyword("INTEGER");
25704                    }
25705                    Some(DialectType::BigQuery) => {
25706                        self.write_keyword("INT64");
25707                    }
25708                    Some(DialectType::ClickHouse) => {
25709                        self.write_clickhouse_type("Int16");
25710                    }
25711                    _ => {
25712                        self.write_keyword("SMALLINT");
25713                        if let Some(n) = length {
25714                            self.write(&format!("({})", n));
25715                        }
25716                    }
25717                }
25718            }
25719            DataType::Int {
25720                length,
25721                integer_spelling: _,
25722            } => {
25723                // BigQuery uses INT64 for INT
25724                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
25725                    self.write_keyword("INT64");
25726                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25727                    self.write_clickhouse_type("Int32");
25728                } else {
25729                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
25730                    let use_integer = match self.config.dialect {
25731                        Some(DialectType::TSQL)
25732                        | Some(DialectType::Fabric)
25733                        | Some(DialectType::Presto)
25734                        | Some(DialectType::Trino)
25735                        | Some(DialectType::SQLite)
25736                        | Some(DialectType::Redshift) => true,
25737                        _ => false,
25738                    };
25739                    if use_integer {
25740                        self.write_keyword("INTEGER");
25741                    } else {
25742                        self.write_keyword("INT");
25743                    }
25744                    if let Some(n) = length {
25745                        self.write(&format!("({})", n));
25746                    }
25747                }
25748            }
25749            DataType::BigInt { length } => {
25750                // Dialect-specific bigint type mappings
25751                match self.config.dialect {
25752                    Some(DialectType::Oracle) => {
25753                        // Oracle doesn't have BIGINT, uses INT
25754                        self.write_keyword("INT");
25755                    }
25756                    Some(DialectType::ClickHouse) => {
25757                        self.write_clickhouse_type("Int64");
25758                    }
25759                    _ => {
25760                        self.write_keyword("BIGINT");
25761                        if let Some(n) = length {
25762                            self.write(&format!("({})", n));
25763                        }
25764                    }
25765                }
25766            }
25767            DataType::Float {
25768                precision,
25769                scale,
25770                real_spelling,
25771            } => {
25772                // Dialect-specific float type mappings
25773                // If real_spelling is true, preserve REAL; otherwise use dialect default
25774                // Spark/Hive don't support REAL, always use FLOAT
25775                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25776                    self.write_clickhouse_type("Float32");
25777                } else if *real_spelling
25778                    && !matches!(
25779                        self.config.dialect,
25780                        Some(DialectType::Spark)
25781                            | Some(DialectType::Databricks)
25782                            | Some(DialectType::Hive)
25783                            | Some(DialectType::Snowflake)
25784                            | Some(DialectType::MySQL)
25785                            | Some(DialectType::BigQuery)
25786                    )
25787                {
25788                    self.write_keyword("REAL")
25789                } else {
25790                    match self.config.dialect {
25791                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
25792                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25793                        _ => self.write_keyword("FLOAT"),
25794                    }
25795                }
25796                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
25797                // Spark/Hive don't support FLOAT(precision)
25798                if !matches!(
25799                    self.config.dialect,
25800                    Some(DialectType::Spark)
25801                        | Some(DialectType::Databricks)
25802                        | Some(DialectType::Hive)
25803                        | Some(DialectType::Presto)
25804                        | Some(DialectType::Trino)
25805                ) {
25806                    if let Some(p) = precision {
25807                        self.write(&format!("({}", p));
25808                        if let Some(s) = scale {
25809                            self.write(&format!(", {})", s));
25810                        } else {
25811                            self.write(")");
25812                        }
25813                    }
25814                }
25815            }
25816            DataType::Double { precision, scale } => {
25817                // Dialect-specific double type mappings
25818                match self.config.dialect {
25819                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25820                        self.write_keyword("FLOAT")
25821                    } // SQL Server/Fabric FLOAT is double
25822                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
25823                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
25824                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25825                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
25826                    Some(DialectType::PostgreSQL)
25827                    | Some(DialectType::Redshift)
25828                    | Some(DialectType::Teradata)
25829                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
25830                    _ => self.write_keyword("DOUBLE"),
25831                }
25832                // MySQL supports DOUBLE(precision, scale)
25833                if let Some(p) = precision {
25834                    self.write(&format!("({}", p));
25835                    if let Some(s) = scale {
25836                        self.write(&format!(", {})", s));
25837                    } else {
25838                        self.write(")");
25839                    }
25840                }
25841            }
25842            DataType::Decimal { precision, scale } => {
25843                // Dialect-specific decimal type mappings
25844                match self.config.dialect {
25845                    Some(DialectType::ClickHouse) => {
25846                        self.write("Decimal");
25847                        if let Some(p) = precision {
25848                            self.write(&format!("({}", p));
25849                            if let Some(s) = scale {
25850                                self.write(&format!(", {}", s));
25851                            }
25852                            self.write(")");
25853                        }
25854                    }
25855                    Some(DialectType::Oracle) => {
25856                        // Oracle uses NUMBER instead of DECIMAL
25857                        self.write_keyword("NUMBER");
25858                        if let Some(p) = precision {
25859                            self.write(&format!("({}", p));
25860                            if let Some(s) = scale {
25861                                self.write(&format!(", {}", s));
25862                            }
25863                            self.write(")");
25864                        }
25865                    }
25866                    Some(DialectType::BigQuery) => {
25867                        // BigQuery uses NUMERIC instead of DECIMAL
25868                        self.write_keyword("NUMERIC");
25869                        if let Some(p) = precision {
25870                            self.write(&format!("({}", p));
25871                            if let Some(s) = scale {
25872                                self.write(&format!(", {}", s));
25873                            }
25874                            self.write(")");
25875                        }
25876                    }
25877                    _ => {
25878                        self.write_keyword("DECIMAL");
25879                        if let Some(p) = precision {
25880                            self.write(&format!("({}", p));
25881                            if let Some(s) = scale {
25882                                self.write(&format!(", {}", s));
25883                            }
25884                            self.write(")");
25885                        }
25886                    }
25887                }
25888            }
25889            DataType::Oracle { oracle_type } => {
25890                self.write_oracle_data_type(oracle_type)?;
25891            }
25892            DataType::Char { length } => {
25893                // Dialect-specific char type mappings
25894                match self.config.dialect {
25895                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25896                        // DuckDB/SQLite maps CHAR to TEXT
25897                        self.write_keyword("TEXT");
25898                    }
25899                    Some(DialectType::Hive)
25900                    | Some(DialectType::Spark)
25901                    | Some(DialectType::Databricks) => {
25902                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
25903                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
25904                        if length.is_some()
25905                            && !matches!(self.config.dialect, Some(DialectType::Hive))
25906                        {
25907                            self.write_keyword("CHAR");
25908                            if let Some(n) = length {
25909                                self.write(&format!("({})", n));
25910                            }
25911                        } else {
25912                            self.write_keyword("STRING");
25913                        }
25914                    }
25915                    Some(DialectType::Dremio) => {
25916                        // Dremio maps CHAR to VARCHAR
25917                        self.write_keyword("VARCHAR");
25918                        if let Some(n) = length {
25919                            self.write(&format!("({})", n));
25920                        }
25921                    }
25922                    _ => {
25923                        self.write_keyword("CHAR");
25924                        if let Some(n) = length {
25925                            self.write(&format!("({})", n));
25926                        }
25927                    }
25928                }
25929            }
25930            DataType::VarChar {
25931                length,
25932                parenthesized_length,
25933            } => {
25934                // Dialect-specific varchar type mappings
25935                match self.config.dialect {
25936                    Some(DialectType::Oracle) => {
25937                        self.write_keyword("VARCHAR2");
25938                        if let Some(n) = length {
25939                            self.write(&format!("({})", n));
25940                        }
25941                    }
25942                    Some(DialectType::DuckDB) => {
25943                        // DuckDB maps VARCHAR to TEXT, preserving length
25944                        self.write_keyword("TEXT");
25945                        if let Some(n) = length {
25946                            self.write(&format!("({})", n));
25947                        }
25948                    }
25949                    Some(DialectType::SQLite) => {
25950                        // SQLite maps VARCHAR to TEXT, preserving length
25951                        self.write_keyword("TEXT");
25952                        if let Some(n) = length {
25953                            self.write(&format!("({})", n));
25954                        }
25955                    }
25956                    Some(DialectType::MySQL) if length.is_none() => {
25957                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
25958                        self.write_keyword("TEXT");
25959                    }
25960                    Some(DialectType::Hive)
25961                    | Some(DialectType::Spark)
25962                    | Some(DialectType::Databricks)
25963                        if length.is_none() =>
25964                    {
25965                        // Hive/Spark/Databricks: VARCHAR without length → STRING
25966                        self.write_keyword("STRING");
25967                    }
25968                    _ => {
25969                        self.write_keyword("VARCHAR");
25970                        if let Some(n) = length {
25971                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
25972                            if *parenthesized_length {
25973                                self.write(&format!("(({}))", n));
25974                            } else {
25975                                self.write(&format!("({})", n));
25976                            }
25977                        }
25978                    }
25979                }
25980            }
25981            DataType::Text => {
25982                // Dialect-specific text type mappings
25983                match self.config.dialect {
25984                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
25985                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25986                        self.write_keyword("VARCHAR(MAX)")
25987                    }
25988                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25989                    Some(DialectType::Snowflake)
25990                    | Some(DialectType::Dremio)
25991                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
25992                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
25993                    Some(DialectType::Presto)
25994                    | Some(DialectType::Trino)
25995                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25996                    Some(DialectType::Spark)
25997                    | Some(DialectType::Databricks)
25998                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25999                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
26000                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
26001                        self.write_keyword("STRING")
26002                    }
26003                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
26004                    _ => self.write_keyword("TEXT"),
26005                }
26006            }
26007            DataType::TextWithLength { length } => {
26008                // TEXT(n) - dialect-specific type with length
26009                match self.config.dialect {
26010                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
26011                    Some(DialectType::Hive)
26012                    | Some(DialectType::Spark)
26013                    | Some(DialectType::Databricks) => {
26014                        self.write(&format!("VARCHAR({})", length));
26015                    }
26016                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
26017                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
26018                    Some(DialectType::Snowflake)
26019                    | Some(DialectType::Presto)
26020                    | Some(DialectType::Trino)
26021                    | Some(DialectType::Athena)
26022                    | Some(DialectType::Drill)
26023                    | Some(DialectType::Dremio) => {
26024                        self.write(&format!("VARCHAR({})", length));
26025                    }
26026                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
26027                        self.write(&format!("VARCHAR({})", length))
26028                    }
26029                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
26030                        self.write(&format!("STRING({})", length))
26031                    }
26032                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
26033                    _ => self.write(&format!("TEXT({})", length)),
26034                }
26035            }
26036            DataType::String { length } => {
26037                // STRING type with optional length (BigQuery STRING(n))
26038                match self.config.dialect {
26039                    Some(DialectType::ClickHouse) => {
26040                        // ClickHouse uses String with specific casing
26041                        self.write("String");
26042                        if let Some(n) = length {
26043                            self.write(&format!("({})", n));
26044                        }
26045                    }
26046                    Some(DialectType::BigQuery)
26047                    | Some(DialectType::Hive)
26048                    | Some(DialectType::Spark)
26049                    | Some(DialectType::Databricks)
26050                    | Some(DialectType::StarRocks)
26051                    | Some(DialectType::Doris) => {
26052                        self.write_keyword("STRING");
26053                        if let Some(n) = length {
26054                            self.write(&format!("({})", n));
26055                        }
26056                    }
26057                    Some(DialectType::PostgreSQL) => {
26058                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
26059                        if let Some(n) = length {
26060                            self.write_keyword("VARCHAR");
26061                            self.write(&format!("({})", n));
26062                        } else {
26063                            self.write_keyword("TEXT");
26064                        }
26065                    }
26066                    Some(DialectType::Redshift) => {
26067                        // Redshift: STRING -> VARCHAR(MAX)
26068                        if let Some(n) = length {
26069                            self.write_keyword("VARCHAR");
26070                            self.write(&format!("({})", n));
26071                        } else {
26072                            self.write_keyword("VARCHAR(MAX)");
26073                        }
26074                    }
26075                    Some(DialectType::MySQL) => {
26076                        // MySQL doesn't have STRING - use VARCHAR or TEXT
26077                        if let Some(n) = length {
26078                            self.write_keyword("VARCHAR");
26079                            self.write(&format!("({})", n));
26080                        } else {
26081                            self.write_keyword("TEXT");
26082                        }
26083                    }
26084                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
26085                        // TSQL: STRING -> VARCHAR(MAX)
26086                        if let Some(n) = length {
26087                            self.write_keyword("VARCHAR");
26088                            self.write(&format!("({})", n));
26089                        } else {
26090                            self.write_keyword("VARCHAR(MAX)");
26091                        }
26092                    }
26093                    Some(DialectType::Oracle) => {
26094                        // Oracle: STRING -> CLOB
26095                        self.write_keyword("CLOB");
26096                    }
26097                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
26098                        // DuckDB/Materialize uses TEXT for string types
26099                        self.write_keyword("TEXT");
26100                        if let Some(n) = length {
26101                            self.write(&format!("({})", n));
26102                        }
26103                    }
26104                    Some(DialectType::Presto)
26105                    | Some(DialectType::Trino)
26106                    | Some(DialectType::Drill)
26107                    | Some(DialectType::Dremio) => {
26108                        // Presto/Trino/Drill use VARCHAR for string types
26109                        self.write_keyword("VARCHAR");
26110                        if let Some(n) = length {
26111                            self.write(&format!("({})", n));
26112                        }
26113                    }
26114                    Some(DialectType::Snowflake) => {
26115                        // Snowflake: STRING stays as STRING (identity/DDL)
26116                        // CAST context STRING -> VARCHAR is handled in generate_cast
26117                        self.write_keyword("STRING");
26118                        if let Some(n) = length {
26119                            self.write(&format!("({})", n));
26120                        }
26121                    }
26122                    _ => {
26123                        // Default: output STRING with optional length
26124                        self.write_keyword("STRING");
26125                        if let Some(n) = length {
26126                            self.write(&format!("({})", n));
26127                        }
26128                    }
26129                }
26130            }
26131            DataType::Binary { length } => {
26132                // Dialect-specific binary type mappings
26133                match self.config.dialect {
26134                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
26135                        self.write_keyword("BYTEA");
26136                        if let Some(n) = length {
26137                            self.write(&format!("({})", n));
26138                        }
26139                    }
26140                    Some(DialectType::Redshift) => {
26141                        self.write_keyword("VARBYTE");
26142                        if let Some(n) = length {
26143                            self.write(&format!("({})", n));
26144                        }
26145                    }
26146                    Some(DialectType::DuckDB)
26147                    | Some(DialectType::SQLite)
26148                    | Some(DialectType::Oracle) => {
26149                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
26150                        self.write_keyword("BLOB");
26151                        if let Some(n) = length {
26152                            self.write(&format!("({})", n));
26153                        }
26154                    }
26155                    Some(DialectType::Presto)
26156                    | Some(DialectType::Trino)
26157                    | Some(DialectType::Athena)
26158                    | Some(DialectType::Drill)
26159                    | Some(DialectType::Dremio) => {
26160                        // These dialects map BINARY to VARBINARY
26161                        self.write_keyword("VARBINARY");
26162                        if let Some(n) = length {
26163                            self.write(&format!("({})", n));
26164                        }
26165                    }
26166                    Some(DialectType::ClickHouse) => {
26167                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
26168                        if self.clickhouse_nullable_depth < 0 {
26169                            self.write("BINARY");
26170                        } else {
26171                            self.write("Nullable(BINARY");
26172                        }
26173                        if let Some(n) = length {
26174                            self.write(&format!("({})", n));
26175                        }
26176                        if self.clickhouse_nullable_depth >= 0 {
26177                            self.write(")");
26178                        }
26179                    }
26180                    _ => {
26181                        self.write_keyword("BINARY");
26182                        if let Some(n) = length {
26183                            self.write(&format!("({})", n));
26184                        }
26185                    }
26186                }
26187            }
26188            DataType::VarBinary { length } => {
26189                // Dialect-specific varbinary type mappings
26190                match self.config.dialect {
26191                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
26192                        self.write_keyword("BYTEA");
26193                        if let Some(n) = length {
26194                            self.write(&format!("({})", n));
26195                        }
26196                    }
26197                    Some(DialectType::Redshift) => {
26198                        self.write_keyword("VARBYTE");
26199                        if let Some(n) = length {
26200                            self.write(&format!("({})", n));
26201                        }
26202                    }
26203                    Some(DialectType::DuckDB)
26204                    | Some(DialectType::SQLite)
26205                    | Some(DialectType::Oracle) => {
26206                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
26207                        self.write_keyword("BLOB");
26208                        if let Some(n) = length {
26209                            self.write(&format!("({})", n));
26210                        }
26211                    }
26212                    Some(DialectType::Exasol) => {
26213                        // Exasol maps VARBINARY to VARCHAR
26214                        self.write_keyword("VARCHAR");
26215                    }
26216                    Some(DialectType::Spark)
26217                    | Some(DialectType::Hive)
26218                    | Some(DialectType::Databricks) => {
26219                        // Spark/Hive use BINARY instead of VARBINARY
26220                        self.write_keyword("BINARY");
26221                        if let Some(n) = length {
26222                            self.write(&format!("({})", n));
26223                        }
26224                    }
26225                    Some(DialectType::ClickHouse) => {
26226                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
26227                        self.write_clickhouse_type("String");
26228                    }
26229                    _ => {
26230                        self.write_keyword("VARBINARY");
26231                        if let Some(n) = length {
26232                            self.write(&format!("({})", n));
26233                        }
26234                    }
26235                }
26236            }
26237            DataType::Blob => {
26238                // Dialect-specific blob type mappings
26239                match self.config.dialect {
26240                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
26241                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
26242                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
26243                        self.write_keyword("VARBINARY")
26244                    }
26245                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
26246                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
26247                    Some(DialectType::Presto)
26248                    | Some(DialectType::Trino)
26249                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
26250                    Some(DialectType::DuckDB) => {
26251                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
26252                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
26253                        self.write_keyword("VARBINARY");
26254                    }
26255                    Some(DialectType::Spark)
26256                    | Some(DialectType::Databricks)
26257                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
26258                    Some(DialectType::ClickHouse) => {
26259                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
26260                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
26261                        // This matches Python sqlglot behavior.
26262                        self.write("Nullable(String)");
26263                    }
26264                    _ => self.write_keyword("BLOB"),
26265                }
26266            }
26267            DataType::Bit { length } => {
26268                // Dialect-specific bit type mappings
26269                match self.config.dialect {
26270                    Some(DialectType::Dremio)
26271                    | Some(DialectType::Spark)
26272                    | Some(DialectType::Databricks)
26273                    | Some(DialectType::Hive)
26274                    | Some(DialectType::Snowflake)
26275                    | Some(DialectType::BigQuery)
26276                    | Some(DialectType::Presto)
26277                    | Some(DialectType::Trino)
26278                    | Some(DialectType::ClickHouse)
26279                    | Some(DialectType::Redshift) => {
26280                        // These dialects don't support BIT type, use BOOLEAN
26281                        self.write_keyword("BOOLEAN");
26282                    }
26283                    _ => {
26284                        self.write_keyword("BIT");
26285                        if let Some(n) = length {
26286                            self.write(&format!("({})", n));
26287                        }
26288                    }
26289                }
26290            }
26291            DataType::VarBit { length } => {
26292                self.write_keyword("VARBIT");
26293                if let Some(n) = length {
26294                    self.write(&format!("({})", n));
26295                }
26296            }
26297            DataType::Date => self.write_keyword("DATE"),
26298            DataType::Time {
26299                precision,
26300                timezone,
26301            } => {
26302                if *timezone {
26303                    // Dialect-specific TIME WITH TIME ZONE output
26304                    match self.config.dialect {
26305                        Some(DialectType::DuckDB) => {
26306                            // DuckDB: TIMETZ (drops precision)
26307                            self.write_keyword("TIMETZ");
26308                        }
26309                        Some(DialectType::PostgreSQL) => {
26310                            // PostgreSQL: TIMETZ or TIMETZ(p)
26311                            self.write_keyword("TIMETZ");
26312                            if let Some(p) = precision {
26313                                self.write(&format!("({})", p));
26314                            }
26315                        }
26316                        _ => {
26317                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
26318                            self.write_keyword("TIME");
26319                            if let Some(p) = precision {
26320                                self.write(&format!("({})", p));
26321                            }
26322                            self.write_keyword(" WITH TIME ZONE");
26323                        }
26324                    }
26325                } else {
26326                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
26327                    if matches!(
26328                        self.config.dialect,
26329                        Some(DialectType::Spark)
26330                            | Some(DialectType::Databricks)
26331                            | Some(DialectType::Hive)
26332                    ) {
26333                        self.write_keyword("TIMESTAMP");
26334                    } else {
26335                        self.write_keyword("TIME");
26336                        if let Some(p) = precision {
26337                            self.write(&format!("({})", p));
26338                        }
26339                    }
26340                }
26341            }
26342            DataType::Timestamp {
26343                precision,
26344                timezone,
26345            } => {
26346                // Dialect-specific timestamp type mappings
26347                match self.config.dialect {
26348                    Some(DialectType::Snowflake) if *timezone => {
26349                        self.write_keyword("TIMESTAMPTZ");
26350                        if let Some(p) = precision {
26351                            self.write(&format!("({})", p));
26352                        }
26353                    }
26354                    Some(DialectType::ClickHouse) => {
26355                        self.write("DateTime");
26356                        if let Some(p) = precision {
26357                            self.write(&format!("({})", p));
26358                        }
26359                    }
26360                    Some(DialectType::TSQL) => {
26361                        if *timezone {
26362                            self.write_keyword("DATETIMEOFFSET");
26363                        } else {
26364                            self.write_keyword("DATETIME2");
26365                        }
26366                        if let Some(p) = precision {
26367                            self.write(&format!("({})", p));
26368                        }
26369                    }
26370                    Some(DialectType::MySQL) => {
26371                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
26372                        self.write_keyword("TIMESTAMP");
26373                        if let Some(p) = precision {
26374                            self.write(&format!("({})", p));
26375                        }
26376                    }
26377                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
26378                        // Doris/StarRocks: TIMESTAMP -> DATETIME
26379                        self.write_keyword("DATETIME");
26380                        if let Some(p) = precision {
26381                            self.write(&format!("({})", p));
26382                        }
26383                    }
26384                    Some(DialectType::BigQuery) => {
26385                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
26386                        if *timezone {
26387                            self.write_keyword("TIMESTAMP");
26388                        } else {
26389                            self.write_keyword("DATETIME");
26390                        }
26391                    }
26392                    Some(DialectType::DuckDB) => {
26393                        // DuckDB: TIMESTAMPTZ shorthand
26394                        if *timezone {
26395                            self.write_keyword("TIMESTAMPTZ");
26396                        } else {
26397                            self.write_keyword("TIMESTAMP");
26398                            if let Some(p) = precision {
26399                                self.write(&format!("({})", p));
26400                            }
26401                        }
26402                    }
26403                    _ => {
26404                        if *timezone && !self.config.tz_to_with_time_zone {
26405                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
26406                            self.write_keyword("TIMESTAMPTZ");
26407                            if let Some(p) = precision {
26408                                self.write(&format!("({})", p));
26409                            }
26410                        } else {
26411                            self.write_keyword("TIMESTAMP");
26412                            if let Some(p) = precision {
26413                                self.write(&format!("({})", p));
26414                            }
26415                            if *timezone {
26416                                self.write_space();
26417                                self.write_keyword("WITH TIME ZONE");
26418                            }
26419                        }
26420                    }
26421                }
26422            }
26423            DataType::Interval { unit, to } => {
26424                self.write_keyword("INTERVAL");
26425                if let Some(u) = unit {
26426                    self.write_space();
26427                    self.write_keyword(u);
26428                }
26429                // Handle range intervals like DAY TO HOUR
26430                if let Some(t) = to {
26431                    self.write_space();
26432                    self.write_keyword("TO");
26433                    self.write_space();
26434                    self.write_keyword(t);
26435                }
26436            }
26437            DataType::Json => {
26438                // Dialect-specific JSON type mappings
26439                match self.config.dialect {
26440                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
26441                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
26442                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
26443                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
26444                    _ => self.write_keyword("JSON"),
26445                }
26446            }
26447            DataType::JsonB => {
26448                // JSONB is PostgreSQL specific, but Doris also supports it
26449                match self.config.dialect {
26450                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
26451                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
26452                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
26453                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
26454                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
26455                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
26456                }
26457            }
26458            DataType::Uuid => {
26459                // Dialect-specific UUID type mappings
26460                match self.config.dialect {
26461                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
26462                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
26463                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
26464                    Some(DialectType::BigQuery)
26465                    | Some(DialectType::Spark)
26466                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
26467                    _ => self.write_keyword("UUID"),
26468                }
26469            }
26470            DataType::Array {
26471                element_type,
26472                dimension,
26473            } => {
26474                // Dialect-specific array syntax
26475                match self.config.dialect {
26476                    Some(DialectType::PostgreSQL)
26477                    | Some(DialectType::Redshift)
26478                    | Some(DialectType::DuckDB) => {
26479                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
26480                        self.generate_data_type(element_type)?;
26481                        if let Some(dim) = dimension {
26482                            self.write(&format!("[{}]", dim));
26483                        } else {
26484                            self.write("[]");
26485                        }
26486                    }
26487                    Some(DialectType::BigQuery) => {
26488                        self.write_keyword("ARRAY<");
26489                        self.generate_data_type(element_type)?;
26490                        self.write(">");
26491                    }
26492                    Some(DialectType::Snowflake)
26493                    | Some(DialectType::Presto)
26494                    | Some(DialectType::Trino)
26495                    | Some(DialectType::ClickHouse) => {
26496                        // These dialects use Array(TYPE) parentheses syntax
26497                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26498                            self.write("Array(");
26499                        } else {
26500                            self.write_keyword("ARRAY(");
26501                        }
26502                        self.generate_data_type(element_type)?;
26503                        self.write(")");
26504                    }
26505                    Some(DialectType::TSQL)
26506                    | Some(DialectType::MySQL)
26507                    | Some(DialectType::Oracle) => {
26508                        // These dialects don't have native array types
26509                        // Fall back to JSON or use native workarounds
26510                        match self.config.dialect {
26511                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
26512                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
26513                            _ => self.write_keyword("JSON"),
26514                        }
26515                    }
26516                    _ => {
26517                        // Default: use angle bracket syntax (ARRAY<T>)
26518                        self.write_keyword("ARRAY<");
26519                        self.generate_data_type(element_type)?;
26520                        self.write(">");
26521                    }
26522                }
26523            }
26524            DataType::List { element_type } => {
26525                // Materialize: element_type LIST (postfix syntax)
26526                self.generate_data_type(element_type)?;
26527                self.write_keyword(" LIST");
26528            }
26529            DataType::Map {
26530                key_type,
26531                value_type,
26532            } => {
26533                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
26534                match self.config.dialect {
26535                    Some(DialectType::Materialize) => {
26536                        // Materialize: MAP[key_type => value_type]
26537                        self.write_keyword("MAP[");
26538                        self.generate_data_type(key_type)?;
26539                        self.write(" => ");
26540                        self.generate_data_type(value_type)?;
26541                        self.write("]");
26542                    }
26543                    Some(DialectType::Snowflake)
26544                    | Some(DialectType::RisingWave)
26545                    | Some(DialectType::DuckDB)
26546                    | Some(DialectType::Presto)
26547                    | Some(DialectType::Trino)
26548                    | Some(DialectType::Athena) => {
26549                        self.write_keyword("MAP(");
26550                        self.generate_data_type(key_type)?;
26551                        self.write(", ");
26552                        self.generate_data_type(value_type)?;
26553                        self.write(")");
26554                    }
26555                    Some(DialectType::ClickHouse) => {
26556                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
26557                        // Key types must NOT be wrapped in Nullable
26558                        self.write("Map(");
26559                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
26560                        self.generate_data_type(key_type)?;
26561                        self.clickhouse_nullable_depth = 0;
26562                        self.write(", ");
26563                        self.generate_data_type(value_type)?;
26564                        self.write(")");
26565                    }
26566                    _ => {
26567                        self.write_keyword("MAP<");
26568                        self.generate_data_type(key_type)?;
26569                        self.write(", ");
26570                        self.generate_data_type(value_type)?;
26571                        self.write(">");
26572                    }
26573                }
26574            }
26575            DataType::Vector {
26576                element_type,
26577                dimension,
26578            } => {
26579                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
26580                    // SingleStore format: VECTOR(dimension, type_alias)
26581                    self.write_keyword("VECTOR(");
26582                    if let Some(dim) = dimension {
26583                        self.write(&dim.to_string());
26584                    }
26585                    // Map type back to SingleStore alias
26586                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
26587                        DataType::TinyInt { .. } => Some("I8"),
26588                        DataType::SmallInt { .. } => Some("I16"),
26589                        DataType::Int { .. } => Some("I32"),
26590                        DataType::BigInt { .. } => Some("I64"),
26591                        DataType::Float { .. } => Some("F32"),
26592                        DataType::Double { .. } => Some("F64"),
26593                        _ => None,
26594                    });
26595                    if let Some(alias) = type_alias {
26596                        if dimension.is_some() {
26597                            self.write(", ");
26598                        }
26599                        self.write(alias);
26600                    }
26601                    self.write(")");
26602                } else {
26603                    // Snowflake format: VECTOR(type, dimension)
26604                    self.write_keyword("VECTOR(");
26605                    if let Some(ref et) = element_type {
26606                        self.generate_data_type(et)?;
26607                        if dimension.is_some() {
26608                            self.write(", ");
26609                        }
26610                    }
26611                    if let Some(dim) = dimension {
26612                        self.write(&dim.to_string());
26613                    }
26614                    self.write(")");
26615                }
26616            }
26617            DataType::Object { fields, modifier } => {
26618                self.write_keyword("OBJECT(");
26619                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
26620                    if i > 0 {
26621                        self.write(", ");
26622                    }
26623                    self.write(name);
26624                    self.write(" ");
26625                    self.generate_data_type(dt)?;
26626                    if *not_null {
26627                        self.write_keyword(" NOT NULL");
26628                    }
26629                }
26630                self.write(")");
26631                if let Some(mod_str) = modifier {
26632                    self.write(" ");
26633                    self.write_keyword(mod_str);
26634                }
26635            }
26636            DataType::Struct { fields, nested } => {
26637                // Dialect-specific struct type mappings
26638                match self.config.dialect {
26639                    Some(DialectType::Snowflake) => {
26640                        // Snowflake maps STRUCT to OBJECT
26641                        self.write_keyword("OBJECT(");
26642                        for (i, field) in fields.iter().enumerate() {
26643                            if i > 0 {
26644                                self.write(", ");
26645                            }
26646                            if !field.name.is_empty() {
26647                                self.write(&field.name);
26648                                self.write(" ");
26649                            }
26650                            self.generate_data_type(&field.data_type)?;
26651                        }
26652                        self.write(")");
26653                    }
26654                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
26655                        // Presto/Trino use ROW(name TYPE, ...) syntax
26656                        self.write_keyword("ROW(");
26657                        for (i, field) in fields.iter().enumerate() {
26658                            if i > 0 {
26659                                self.write(", ");
26660                            }
26661                            if !field.name.is_empty() {
26662                                self.write(&field.name);
26663                                self.write(" ");
26664                            }
26665                            self.generate_data_type(&field.data_type)?;
26666                        }
26667                        self.write(")");
26668                    }
26669                    Some(DialectType::DuckDB) => {
26670                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
26671                        self.write_keyword("STRUCT(");
26672                        for (i, field) in fields.iter().enumerate() {
26673                            if i > 0 {
26674                                self.write(", ");
26675                            }
26676                            if !field.name.is_empty() {
26677                                self.write(&field.name);
26678                                self.write(" ");
26679                            }
26680                            self.generate_data_type(&field.data_type)?;
26681                        }
26682                        self.write(")");
26683                    }
26684                    Some(DialectType::ClickHouse) => {
26685                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
26686                        self.write("Tuple(");
26687                        for (i, field) in fields.iter().enumerate() {
26688                            if i > 0 {
26689                                self.write(", ");
26690                            }
26691                            if !field.name.is_empty() {
26692                                self.write(&field.name);
26693                                self.write(" ");
26694                            }
26695                            self.generate_data_type(&field.data_type)?;
26696                        }
26697                        self.write(")");
26698                    }
26699                    Some(DialectType::SingleStore) => {
26700                        // SingleStore uses RECORD(name TYPE, ...) for struct types
26701                        self.write_keyword("RECORD(");
26702                        for (i, field) in fields.iter().enumerate() {
26703                            if i > 0 {
26704                                self.write(", ");
26705                            }
26706                            if !field.name.is_empty() {
26707                                self.write(&field.name);
26708                                self.write(" ");
26709                            }
26710                            self.generate_data_type(&field.data_type)?;
26711                        }
26712                        self.write(")");
26713                    }
26714                    _ => {
26715                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
26716                        let force_angle_brackets = matches!(
26717                            self.config.dialect,
26718                            Some(DialectType::Hive)
26719                                | Some(DialectType::Spark)
26720                                | Some(DialectType::Databricks)
26721                        );
26722                        if *nested && !force_angle_brackets {
26723                            self.write_keyword("STRUCT(");
26724                            for (i, field) in fields.iter().enumerate() {
26725                                if i > 0 {
26726                                    self.write(", ");
26727                                }
26728                                if !field.name.is_empty() {
26729                                    self.write(&field.name);
26730                                    self.write(" ");
26731                                }
26732                                self.generate_data_type(&field.data_type)?;
26733                            }
26734                            self.write(")");
26735                        } else {
26736                            self.write_keyword("STRUCT<");
26737                            for (i, field) in fields.iter().enumerate() {
26738                                if i > 0 {
26739                                    self.write(", ");
26740                                }
26741                                if !field.name.is_empty() {
26742                                    // Named field: name TYPE (with configurable separator for Hive)
26743                                    self.write(&field.name);
26744                                    self.write(self.config.struct_field_sep);
26745                                }
26746                                // For anonymous fields, just output the type
26747                                self.generate_data_type(&field.data_type)?;
26748                                // Spark/Databricks: Output COMMENT clause if present
26749                                if let Some(comment) = &field.comment {
26750                                    self.write(" COMMENT '");
26751                                    self.write(comment);
26752                                    self.write("'");
26753                                }
26754                                // BigQuery: Output OPTIONS clause if present
26755                                if !field.options.is_empty() {
26756                                    self.write(" ");
26757                                    self.generate_options_clause(&field.options)?;
26758                                }
26759                            }
26760                            self.write(">");
26761                        }
26762                    }
26763                }
26764            }
26765            DataType::Enum {
26766                values,
26767                assignments,
26768            } => {
26769                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
26770                // ClickHouse: Enum('hello' = 1, 'world' = 2)
26771                if self.config.dialect == Some(DialectType::ClickHouse) {
26772                    self.write("Enum(");
26773                } else {
26774                    self.write_keyword("ENUM(");
26775                }
26776                for (i, val) in values.iter().enumerate() {
26777                    if i > 0 {
26778                        self.write(", ");
26779                    }
26780                    self.write("'");
26781                    self.write(val);
26782                    self.write("'");
26783                    if let Some(Some(assignment)) = assignments.get(i) {
26784                        self.write(" = ");
26785                        self.write(assignment);
26786                    }
26787                }
26788                self.write(")");
26789            }
26790            DataType::Set { values } => {
26791                // MySQL SET type: SET('a', 'b', 'c')
26792                self.write_keyword("SET(");
26793                for (i, val) in values.iter().enumerate() {
26794                    if i > 0 {
26795                        self.write(", ");
26796                    }
26797                    self.write("'");
26798                    self.write(val);
26799                    self.write("'");
26800                }
26801                self.write(")");
26802            }
26803            DataType::Union { fields } => {
26804                // DuckDB UNION type: UNION(num INT, str TEXT)
26805                self.write_keyword("UNION(");
26806                for (i, (name, dt)) in fields.iter().enumerate() {
26807                    if i > 0 {
26808                        self.write(", ");
26809                    }
26810                    if !name.is_empty() {
26811                        self.write(name);
26812                        self.write(" ");
26813                    }
26814                    self.generate_data_type(dt)?;
26815                }
26816                self.write(")");
26817            }
26818            DataType::Nullable { inner } => {
26819                // ClickHouse: Nullable(T), other dialects: just the inner type
26820                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26821                    self.write("Nullable(");
26822                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
26823                    let saved_depth = self.clickhouse_nullable_depth;
26824                    self.clickhouse_nullable_depth = -1;
26825                    self.generate_data_type(inner)?;
26826                    self.clickhouse_nullable_depth = saved_depth;
26827                    self.write(")");
26828                } else {
26829                    // Map ClickHouse-specific custom type names to standard types
26830                    match inner.as_ref() {
26831                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
26832                            self.generate_data_type(&DataType::Timestamp {
26833                                precision: None,
26834                                timezone: false,
26835                            })?;
26836                        }
26837                        _ => {
26838                            self.generate_data_type(inner)?;
26839                        }
26840                    }
26841                }
26842            }
26843            DataType::Custom { name } => {
26844                // Handle dialect-specific type transformations
26845                let name_upper = name.to_ascii_uppercase();
26846                match self.config.dialect {
26847                    Some(DialectType::ClickHouse) => {
26848                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26849                            (name_upper[..idx].to_string(), &name[idx..])
26850                        } else {
26851                            (name_upper.clone(), "")
26852                        };
26853                        let mapped = match base_upper.as_str() {
26854                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
26855                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
26856                            "DATETIME64" => "DateTime64",
26857                            "DATE32" => "Date32",
26858                            "INT" => "Int32",
26859                            "MEDIUMINT" => "Int32",
26860                            "INT8" => "Int8",
26861                            "INT16" => "Int16",
26862                            "INT32" => "Int32",
26863                            "INT64" => "Int64",
26864                            "INT128" => "Int128",
26865                            "INT256" => "Int256",
26866                            "UINT8" => "UInt8",
26867                            "UINT16" => "UInt16",
26868                            "UINT32" => "UInt32",
26869                            "UINT64" => "UInt64",
26870                            "UINT128" => "UInt128",
26871                            "UINT256" => "UInt256",
26872                            "FLOAT32" => "Float32",
26873                            "FLOAT64" => "Float64",
26874                            "DECIMAL32" => "Decimal32",
26875                            "DECIMAL64" => "Decimal64",
26876                            "DECIMAL128" => "Decimal128",
26877                            "DECIMAL256" => "Decimal256",
26878                            "ENUM" => "Enum",
26879                            "ENUM8" => "Enum8",
26880                            "ENUM16" => "Enum16",
26881                            "FIXEDSTRING" => "FixedString",
26882                            "NESTED" => "Nested",
26883                            "LOWCARDINALITY" => "LowCardinality",
26884                            "NULLABLE" => "Nullable",
26885                            "IPV4" => "IPv4",
26886                            "IPV6" => "IPv6",
26887                            "POINT" => "Point",
26888                            "RING" => "Ring",
26889                            "LINESTRING" => "LineString",
26890                            "MULTILINESTRING" => "MultiLineString",
26891                            "POLYGON" => "Polygon",
26892                            "MULTIPOLYGON" => "MultiPolygon",
26893                            "AGGREGATEFUNCTION" => "AggregateFunction",
26894                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
26895                            "DYNAMIC" => "Dynamic",
26896                            _ => "",
26897                        };
26898                        if mapped.is_empty() {
26899                            self.write(name);
26900                        } else {
26901                            self.write(mapped);
26902                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
26903                                && !suffix.is_empty()
26904                            {
26905                                let escaped_suffix = suffix
26906                                    .replace('\\', "\\\\")
26907                                    .replace('\t', "\\t")
26908                                    .replace('\n', "\\n")
26909                                    .replace('\r', "\\r");
26910                                self.write(&escaped_suffix);
26911                            } else {
26912                                self.write(suffix);
26913                            }
26914                        }
26915                    }
26916                    Some(DialectType::MySQL)
26917                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
26918                    {
26919                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
26920                        self.write_keyword("TIMESTAMP");
26921                    }
26922                    Some(DialectType::Snowflake) => {
26923                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26924                            (name_upper[..idx].to_string(), &name[idx..])
26925                        } else {
26926                            (name_upper.clone(), "")
26927                        };
26928
26929                        match base_upper.as_str() {
26930                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
26931                                self.write_keyword("TIMESTAMPNTZ");
26932                                self.write(suffix);
26933                            }
26934                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
26935                                self.write_keyword("TIMESTAMPLTZ");
26936                                self.write(suffix);
26937                            }
26938                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
26939                                self.write_keyword("TIMESTAMPTZ");
26940                                self.write(suffix);
26941                            }
26942                            _ => self.write(name),
26943                        }
26944                    }
26945                    Some(DialectType::Fabric) => {
26946                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
26947                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26948                        } else {
26949                            (name_upper.clone(), None)
26950                        };
26951
26952                        match base_upper.as_str() {
26953                            "NVARCHAR" => {
26954                                self.write_keyword("VARCHAR");
26955                                if let Some(args) = args_str {
26956                                    self.write(args);
26957                                }
26958                            }
26959                            "NCHAR" => {
26960                                self.write_keyword("CHAR");
26961                                if let Some(args) = args_str {
26962                                    self.write(args);
26963                                }
26964                            }
26965                            _ => self.write(name),
26966                        }
26967                    }
26968                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
26969                        self.write_keyword("SQL_VARIANT");
26970                    }
26971                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
26972                        self.write_keyword("DECIMAL(38, 5)");
26973                    }
26974                    Some(DialectType::Exasol) => {
26975                        // Exasol type mappings for custom types
26976                        match name_upper.as_str() {
26977                            // Binary types → VARCHAR
26978                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
26979                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
26980                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
26981                            // Integer types
26982                            "MEDIUMINT" => self.write_keyword("INT"),
26983                            // Decimal types → DECIMAL
26984                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
26985                                self.write_keyword("DECIMAL")
26986                            }
26987                            // Timestamp types
26988                            "DATETIME" => self.write_keyword("TIMESTAMP"),
26989                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
26990                            _ => self.write(name),
26991                        }
26992                    }
26993                    Some(DialectType::Dremio) => {
26994                        // Dremio type mappings for custom types
26995                        match name_upper.as_str() {
26996                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
26997                            "ARRAY" => self.write_keyword("LIST"),
26998                            "NCHAR" => self.write_keyword("VARCHAR"),
26999                            _ => self.write(name),
27000                        }
27001                    }
27002                    // Map dialect-specific custom types to standard SQL types for other dialects
27003                    _ => {
27004                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
27005                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
27006                            (name_upper[..idx].to_string(), Some(&name[idx..]))
27007                        } else {
27008                            (name_upper.clone(), None)
27009                        };
27010
27011                        match base_upper.as_str() {
27012                            "INT64"
27013                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
27014                            {
27015                                self.write_keyword("BIGINT");
27016                            }
27017                            "FLOAT64"
27018                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
27019                            {
27020                                self.write_keyword("DOUBLE");
27021                            }
27022                            "BOOL"
27023                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
27024                            {
27025                                self.write_keyword("BOOLEAN");
27026                            }
27027                            "BYTES"
27028                                if matches!(
27029                                    self.config.dialect,
27030                                    Some(DialectType::Spark)
27031                                        | Some(DialectType::Hive)
27032                                        | Some(DialectType::Databricks)
27033                                ) =>
27034                            {
27035                                self.write_keyword("BINARY");
27036                            }
27037                            "BYTES"
27038                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
27039                            {
27040                                self.write_keyword("VARBINARY");
27041                            }
27042                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
27043                            "DATETIME2" | "SMALLDATETIME"
27044                                if !matches!(
27045                                    self.config.dialect,
27046                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27047                                ) =>
27048                            {
27049                                // PostgreSQL preserves precision, others don't
27050                                if matches!(
27051                                    self.config.dialect,
27052                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27053                                ) {
27054                                    self.write_keyword("TIMESTAMP");
27055                                    if let Some(args) = _args_str {
27056                                        self.write(args);
27057                                    }
27058                                } else {
27059                                    self.write_keyword("TIMESTAMP");
27060                                }
27061                            }
27062                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
27063                            "DATETIMEOFFSET"
27064                                if !matches!(
27065                                    self.config.dialect,
27066                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27067                                ) =>
27068                            {
27069                                if matches!(
27070                                    self.config.dialect,
27071                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27072                                ) {
27073                                    self.write_keyword("TIMESTAMPTZ");
27074                                    if let Some(args) = _args_str {
27075                                        self.write(args);
27076                                    }
27077                                } else {
27078                                    self.write_keyword("TIMESTAMPTZ");
27079                                }
27080                            }
27081                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
27082                            "UNIQUEIDENTIFIER"
27083                                if !matches!(
27084                                    self.config.dialect,
27085                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27086                                ) =>
27087                            {
27088                                match self.config.dialect {
27089                                    Some(DialectType::Spark)
27090                                    | Some(DialectType::Databricks)
27091                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
27092                                    _ => self.write_keyword("UUID"),
27093                                }
27094                            }
27095                            // TSQL BIT -> BOOLEAN for most dialects
27096                            "BIT"
27097                                if !matches!(
27098                                    self.config.dialect,
27099                                    Some(DialectType::TSQL)
27100                                        | Some(DialectType::Fabric)
27101                                        | Some(DialectType::PostgreSQL)
27102                                        | Some(DialectType::MySQL)
27103                                        | Some(DialectType::DuckDB)
27104                                ) =>
27105                            {
27106                                self.write_keyword("BOOLEAN");
27107                            }
27108                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
27109                            "NVARCHAR"
27110                                if !matches!(
27111                                    self.config.dialect,
27112                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27113                                ) =>
27114                            {
27115                                match self.config.dialect {
27116                                    Some(DialectType::Oracle) => {
27117                                        // Oracle: NVARCHAR -> NVARCHAR2
27118                                        self.write_keyword("NVARCHAR2");
27119                                        if let Some(args) = _args_str {
27120                                            self.write(args);
27121                                        }
27122                                    }
27123                                    Some(DialectType::BigQuery) => {
27124                                        // BigQuery: NVARCHAR -> STRING
27125                                        self.write_keyword("STRING");
27126                                    }
27127                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
27128                                        self.write_keyword("TEXT");
27129                                        if let Some(args) = _args_str {
27130                                            self.write(args);
27131                                        }
27132                                    }
27133                                    Some(DialectType::Hive) => {
27134                                        // Hive: NVARCHAR -> STRING
27135                                        self.write_keyword("STRING");
27136                                    }
27137                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
27138                                        if _args_str.is_some() {
27139                                            self.write_keyword("VARCHAR");
27140                                            self.write(_args_str.unwrap());
27141                                        } else {
27142                                            self.write_keyword("STRING");
27143                                        }
27144                                    }
27145                                    _ => {
27146                                        self.write_keyword("VARCHAR");
27147                                        if let Some(args) = _args_str {
27148                                            self.write(args);
27149                                        }
27150                                    }
27151                                }
27152                            }
27153                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
27154                            "NCHAR"
27155                                if !matches!(
27156                                    self.config.dialect,
27157                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27158                                ) =>
27159                            {
27160                                match self.config.dialect {
27161                                    Some(DialectType::Oracle) => {
27162                                        // Oracle natively supports NCHAR
27163                                        self.write_keyword("NCHAR");
27164                                        if let Some(args) = _args_str {
27165                                            self.write(args);
27166                                        }
27167                                    }
27168                                    Some(DialectType::BigQuery) => {
27169                                        // BigQuery: NCHAR -> STRING
27170                                        self.write_keyword("STRING");
27171                                    }
27172                                    Some(DialectType::Hive) => {
27173                                        // Hive: NCHAR -> STRING
27174                                        self.write_keyword("STRING");
27175                                    }
27176                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
27177                                        self.write_keyword("TEXT");
27178                                        if let Some(args) = _args_str {
27179                                            self.write(args);
27180                                        }
27181                                    }
27182                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
27183                                        if _args_str.is_some() {
27184                                            self.write_keyword("CHAR");
27185                                            self.write(_args_str.unwrap());
27186                                        } else {
27187                                            self.write_keyword("STRING");
27188                                        }
27189                                    }
27190                                    _ => {
27191                                        self.write_keyword("CHAR");
27192                                        if let Some(args) = _args_str {
27193                                            self.write(args);
27194                                        }
27195                                    }
27196                                }
27197                            }
27198                            // MySQL text variant types -> map to appropriate target type
27199                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
27200                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
27201                                Some(DialectType::MySQL)
27202                                | Some(DialectType::SingleStore)
27203                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
27204                                Some(DialectType::Spark)
27205                                | Some(DialectType::Databricks)
27206                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
27207                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
27208                                Some(DialectType::Presto)
27209                                | Some(DialectType::Trino)
27210                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
27211                                Some(DialectType::Snowflake)
27212                                | Some(DialectType::Redshift)
27213                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
27214                                _ => self.write_keyword("TEXT"),
27215                            },
27216                            // MySQL blob variant types -> map to appropriate target type
27217                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
27218                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
27219                                Some(DialectType::MySQL)
27220                                | Some(DialectType::SingleStore)
27221                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
27222                                Some(DialectType::Spark)
27223                                | Some(DialectType::Databricks)
27224                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
27225                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
27226                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
27227                                Some(DialectType::Presto)
27228                                | Some(DialectType::Trino)
27229                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
27230                                Some(DialectType::Snowflake)
27231                                | Some(DialectType::Redshift)
27232                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
27233                                _ => self.write_keyword("BLOB"),
27234                            },
27235                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
27236                            "LONGVARCHAR" => match self.config.dialect {
27237                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
27238                                _ => self.write_keyword("VARCHAR"),
27239                            },
27240                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
27241                            "DATETIME" => {
27242                                match self.config.dialect {
27243                                    Some(DialectType::MySQL)
27244                                    | Some(DialectType::Doris)
27245                                    | Some(DialectType::StarRocks)
27246                                    | Some(DialectType::TSQL)
27247                                    | Some(DialectType::Fabric)
27248                                    | Some(DialectType::BigQuery)
27249                                    | Some(DialectType::SQLite)
27250                                    | Some(DialectType::Snowflake) => {
27251                                        self.write_keyword("DATETIME");
27252                                        if let Some(args) = _args_str {
27253                                            self.write(args);
27254                                        }
27255                                    }
27256                                    Some(_) => {
27257                                        // Only map to TIMESTAMP when we have a specific target dialect
27258                                        self.write_keyword("TIMESTAMP");
27259                                        if let Some(args) = _args_str {
27260                                            self.write(args);
27261                                        }
27262                                    }
27263                                    None => {
27264                                        // No dialect - preserve original
27265                                        self.write(name);
27266                                    }
27267                                }
27268                            }
27269                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
27270                            "VARCHAR2"
27271                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
27272                            {
27273                                match self.config.dialect {
27274                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
27275                                        self.write_keyword("TEXT");
27276                                    }
27277                                    Some(DialectType::Hive)
27278                                    | Some(DialectType::Spark)
27279                                    | Some(DialectType::Databricks)
27280                                    | Some(DialectType::BigQuery)
27281                                    | Some(DialectType::ClickHouse)
27282                                    | Some(DialectType::StarRocks)
27283                                    | Some(DialectType::Doris) => {
27284                                        self.write_keyword("STRING");
27285                                    }
27286                                    _ => {
27287                                        self.write_keyword("VARCHAR");
27288                                        if let Some(args) = _args_str {
27289                                            self.write(args);
27290                                        }
27291                                    }
27292                                }
27293                            }
27294                            "NVARCHAR2"
27295                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
27296                            {
27297                                match self.config.dialect {
27298                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
27299                                        self.write_keyword("TEXT");
27300                                    }
27301                                    Some(DialectType::Hive)
27302                                    | Some(DialectType::Spark)
27303                                    | Some(DialectType::Databricks)
27304                                    | Some(DialectType::BigQuery)
27305                                    | Some(DialectType::ClickHouse)
27306                                    | Some(DialectType::StarRocks)
27307                                    | Some(DialectType::Doris) => {
27308                                        self.write_keyword("STRING");
27309                                    }
27310                                    _ => {
27311                                        self.write_keyword("VARCHAR");
27312                                        if let Some(args) = _args_str {
27313                                            self.write(args);
27314                                        }
27315                                    }
27316                                }
27317                            }
27318                            _ => self.write(name),
27319                        }
27320                    }
27321                }
27322            }
27323            DataType::Geometry { subtype, srid } => {
27324                // Dialect-specific geometry type mappings
27325                match self.config.dialect {
27326                    Some(DialectType::MySQL) => {
27327                        // MySQL uses POINT SRID 4326 syntax for specific types
27328                        if let Some(sub) = subtype {
27329                            self.write_keyword(sub);
27330                            if let Some(s) = srid {
27331                                self.write(" SRID ");
27332                                self.write(&s.to_string());
27333                            }
27334                        } else {
27335                            self.write_keyword("GEOMETRY");
27336                        }
27337                    }
27338                    Some(DialectType::BigQuery) => {
27339                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
27340                        self.write_keyword("GEOGRAPHY");
27341                    }
27342                    Some(DialectType::Teradata) => {
27343                        // Teradata uses ST_GEOMETRY
27344                        self.write_keyword("ST_GEOMETRY");
27345                        if subtype.is_some() || srid.is_some() {
27346                            self.write("(");
27347                            if let Some(sub) = subtype {
27348                                self.write_keyword(sub);
27349                            }
27350                            if let Some(s) = srid {
27351                                if subtype.is_some() {
27352                                    self.write(", ");
27353                                }
27354                                self.write(&s.to_string());
27355                            }
27356                            self.write(")");
27357                        }
27358                    }
27359                    _ => {
27360                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
27361                        self.write_keyword("GEOMETRY");
27362                        if subtype.is_some() || srid.is_some() {
27363                            self.write("(");
27364                            if let Some(sub) = subtype {
27365                                self.write_keyword(sub);
27366                            }
27367                            if let Some(s) = srid {
27368                                if subtype.is_some() {
27369                                    self.write(", ");
27370                                }
27371                                self.write(&s.to_string());
27372                            }
27373                            self.write(")");
27374                        }
27375                    }
27376                }
27377            }
27378            DataType::Geography { subtype, srid } => {
27379                // Dialect-specific geography type mappings
27380                match self.config.dialect {
27381                    Some(DialectType::MySQL) => {
27382                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
27383                        if let Some(sub) = subtype {
27384                            self.write_keyword(sub);
27385                        } else {
27386                            self.write_keyword("GEOMETRY");
27387                        }
27388                        // Geography implies SRID 4326 (WGS84)
27389                        let effective_srid = srid.unwrap_or(4326);
27390                        self.write(" SRID ");
27391                        self.write(&effective_srid.to_string());
27392                    }
27393                    Some(DialectType::BigQuery) => {
27394                        // BigQuery uses simple GEOGRAPHY without parameters
27395                        self.write_keyword("GEOGRAPHY");
27396                    }
27397                    Some(DialectType::Snowflake) => {
27398                        // Snowflake uses GEOGRAPHY without parameters
27399                        self.write_keyword("GEOGRAPHY");
27400                    }
27401                    _ => {
27402                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
27403                        self.write_keyword("GEOGRAPHY");
27404                        if subtype.is_some() || srid.is_some() {
27405                            self.write("(");
27406                            if let Some(sub) = subtype {
27407                                self.write_keyword(sub);
27408                            }
27409                            if let Some(s) = srid {
27410                                if subtype.is_some() {
27411                                    self.write(", ");
27412                                }
27413                                self.write(&s.to_string());
27414                            }
27415                            self.write(")");
27416                        }
27417                    }
27418                }
27419            }
27420            DataType::CharacterSet { name } => {
27421                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
27422                self.write_keyword("CHAR CHARACTER SET ");
27423                self.write(name);
27424            }
27425            _ => self.write("UNKNOWN"),
27426        }
27427        Ok(())
27428    }
27429
27430    // === Helper methods ===
27431
27432    #[inline]
27433    fn write(&mut self, s: &str) {
27434        self.output.push_str(s);
27435    }
27436
27437    #[inline]
27438    fn write_space(&mut self) {
27439        self.output.push(' ');
27440    }
27441
27442    #[inline]
27443    fn write_keyword(&mut self, keyword: &str) {
27444        if self.config.uppercase_keywords {
27445            self.output.push_str(keyword);
27446        } else {
27447            for b in keyword.bytes() {
27448                self.output.push(b.to_ascii_lowercase() as char);
27449            }
27450        }
27451    }
27452
27453    /// Write a function name respecting the normalize_functions config setting
27454    fn write_func_name(&mut self, name: &str) {
27455        let normalized = self.normalize_func_name(name);
27456        self.output.push_str(normalized.as_ref());
27457    }
27458
27459    /// Convert strptime format string to Exasol format string
27460    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
27461    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
27462    fn convert_strptime_to_exasol_format(format: &str) -> String {
27463        let mut result = String::new();
27464        let chars: Vec<char> = format.chars().collect();
27465        let mut i = 0;
27466        while i < chars.len() {
27467            if chars[i] == '%' && i + 1 < chars.len() {
27468                let spec = chars[i + 1];
27469                let exasol_spec = match spec {
27470                    'Y' => "YYYY",
27471                    'y' => "YY",
27472                    'm' => "MM",
27473                    'd' => "DD",
27474                    'H' => "HH",
27475                    'M' => "MI",
27476                    'S' => "SS",
27477                    'a' => "DY",    // abbreviated weekday name
27478                    'A' => "DAY",   // full weekday name
27479                    'b' => "MON",   // abbreviated month name
27480                    'B' => "MONTH", // full month name
27481                    'I' => "H12",   // 12-hour format
27482                    'u' => "ID",    // ISO weekday (1-7)
27483                    'V' => "IW",    // ISO week number
27484                    'G' => "IYYY",  // ISO year
27485                    'W' => "UW",    // Week number (Monday as first day)
27486                    'U' => "UW",    // Week number (Sunday as first day)
27487                    'z' => "Z",     // timezone offset
27488                    _ => {
27489                        // Unknown specifier, keep as-is
27490                        result.push('%');
27491                        result.push(spec);
27492                        i += 2;
27493                        continue;
27494                    }
27495                };
27496                result.push_str(exasol_spec);
27497                i += 2;
27498            } else {
27499                result.push(chars[i]);
27500                i += 1;
27501            }
27502        }
27503        result
27504    }
27505
27506    /// Convert strptime format string to PostgreSQL/Redshift format string
27507    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
27508    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
27509    fn convert_strptime_to_postgres_format(format: &str) -> String {
27510        let mut result = String::new();
27511        let chars: Vec<char> = format.chars().collect();
27512        let mut i = 0;
27513        while i < chars.len() {
27514            if chars[i] == '%' && i + 1 < chars.len() {
27515                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
27516                if chars[i + 1] == '-' && i + 2 < chars.len() {
27517                    let spec = chars[i + 2];
27518                    let pg_spec = match spec {
27519                        'd' => "FMDD",
27520                        'm' => "FMMM",
27521                        'H' => "FMHH24",
27522                        'M' => "FMMI",
27523                        'S' => "FMSS",
27524                        _ => {
27525                            result.push('%');
27526                            result.push('-');
27527                            result.push(spec);
27528                            i += 3;
27529                            continue;
27530                        }
27531                    };
27532                    result.push_str(pg_spec);
27533                    i += 3;
27534                    continue;
27535                }
27536                let spec = chars[i + 1];
27537                let pg_spec = match spec {
27538                    'Y' => "YYYY",
27539                    'y' => "YY",
27540                    'm' => "MM",
27541                    'd' => "DD",
27542                    'H' => "HH24",
27543                    'I' => "HH12",
27544                    'M' => "MI",
27545                    'S' => "SS",
27546                    'f' => "US",      // microseconds
27547                    'u' => "D",       // day of week (1=Monday)
27548                    'j' => "DDD",     // day of year
27549                    'z' => "OF",      // UTC offset
27550                    'Z' => "TZ",      // timezone name
27551                    'A' => "TMDay",   // full weekday name
27552                    'a' => "TMDy",    // abbreviated weekday name
27553                    'b' => "TMMon",   // abbreviated month name
27554                    'B' => "TMMonth", // full month name
27555                    'U' => "WW",      // week number
27556                    _ => {
27557                        // Unknown specifier, keep as-is
27558                        result.push('%');
27559                        result.push(spec);
27560                        i += 2;
27561                        continue;
27562                    }
27563                };
27564                result.push_str(pg_spec);
27565                i += 2;
27566            } else {
27567                result.push(chars[i]);
27568                i += 1;
27569            }
27570        }
27571        result
27572    }
27573
27574    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
27575    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
27576        if self.config.limit_only_literals {
27577            if let Some(value) = Self::try_evaluate_constant(expr) {
27578                self.write(&value.to_string());
27579                return Ok(());
27580            }
27581        }
27582        self.generate_expression(expr)
27583    }
27584
27585    fn is_noop_limit_expr(expr: &Expression) -> bool {
27586        match expr {
27587            Expression::Null(_) => true,
27588            Expression::Identifier(identifier) => identifier.name.eq_ignore_ascii_case("ALL"),
27589            Expression::Column(column) => {
27590                column.table.is_none() && column.name.name.eq_ignore_ascii_case("ALL")
27591            }
27592            Expression::Var(var) => var.this.eq_ignore_ascii_case("ALL"),
27593            Expression::Paren(paren) => Self::is_noop_limit_expr(&paren.this),
27594            _ => false,
27595        }
27596    }
27597
27598    /// Format a comment with proper spacing.
27599    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
27600    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
27601    fn write_formatted_comment(&mut self, comment: &str) {
27602        // Normalize all comments to block comment format /* ... */
27603        // This matches Python sqlglot behavior which always outputs block comments
27604        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
27605            // Already block comment - extract inner content
27606            // Preserve internal whitespace, but ensure at least one space padding
27607            &comment[2..comment.len() - 2]
27608        } else if comment.starts_with("--") {
27609            // Line comment - extract content after --
27610            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
27611            &comment[2..]
27612        } else {
27613            // Raw content (no delimiters)
27614            comment
27615        };
27616        // Skip empty comments (e.g., bare "--" with no content)
27617        if content.trim().is_empty() {
27618            return;
27619        }
27620        // Escape nested block comment markers to prevent premature closure or unintended nesting.
27621        // This matches Python sqlglot's sanitize_comment behavior.
27622        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
27623        let content = &sanitized;
27624        // Ensure at least one space after /* and before */
27625        self.output.push_str("/*");
27626        if !content.starts_with(' ') {
27627            self.output.push(' ');
27628        }
27629        self.output.push_str(content);
27630        if !content.ends_with(' ') {
27631            self.output.push(' ');
27632        }
27633        self.output.push_str("*/");
27634    }
27635
27636    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
27637    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
27638    fn escape_block_for_single_quote(&self, block: &str) -> String {
27639        let escape_backslash = matches!(
27640            self.config.dialect,
27641            Some(crate::dialects::DialectType::Snowflake)
27642        );
27643        let mut escaped = String::with_capacity(block.len() + 4);
27644        for ch in block.chars() {
27645            if ch == '\'' {
27646                escaped.push('\\');
27647                escaped.push('\'');
27648            } else if escape_backslash && ch == '\\' {
27649                escaped.push('\\');
27650                escaped.push('\\');
27651            } else {
27652                escaped.push(ch);
27653            }
27654        }
27655        escaped
27656    }
27657
27658    fn write_newline(&mut self) {
27659        self.output.push('\n');
27660    }
27661
27662    fn write_indent(&mut self) {
27663        for _ in 0..self.indent_level {
27664            self.output.push_str(self.config.indent);
27665        }
27666    }
27667
27668    // === SQLGlot-style pretty printing helpers ===
27669
27670    /// Returns the separator string for pretty printing.
27671    /// Check if the total length of arguments exceeds max_text_width.
27672    /// Used for dynamic line breaking in expressions() formatting.
27673    fn too_wide(&self, args: &[String]) -> bool {
27674        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
27675    }
27676
27677    /// Generate an expression to a string using a temporary non-pretty generator.
27678    /// Useful for width calculations before deciding on formatting.
27679    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
27680        let config = GeneratorConfig {
27681            pretty: false,
27682            dialect: self.config.dialect,
27683            ..Default::default()
27684        };
27685        let mut gen = Generator::with_config(config);
27686        gen.generate_expression(expr)?;
27687        Ok(gen.output)
27688    }
27689
27690    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
27691    /// In pretty mode: newline + indented keyword + newline + indented condition
27692    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
27693        if self.config.pretty {
27694            self.write_newline();
27695            self.write_indent();
27696            self.write_keyword(keyword);
27697            self.write_newline();
27698            self.indent_level += 1;
27699            self.write_indent();
27700            self.generate_expression(condition)?;
27701            self.indent_level -= 1;
27702        } else {
27703            self.write_space();
27704            self.write_keyword(keyword);
27705            self.write_space();
27706            self.generate_expression(condition)?;
27707        }
27708        Ok(())
27709    }
27710
27711    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
27712    /// In pretty mode: each expression on new line with indentation
27713    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
27714        if exprs.is_empty() {
27715            return Ok(());
27716        }
27717
27718        if self.config.pretty {
27719            self.write_newline();
27720            self.write_indent();
27721            self.write_keyword(keyword);
27722            self.write_newline();
27723            self.indent_level += 1;
27724            for (i, expr) in exprs.iter().enumerate() {
27725                if i > 0 {
27726                    self.write(",");
27727                    self.write_newline();
27728                }
27729                self.write_indent();
27730                self.generate_expression(expr)?;
27731            }
27732            self.indent_level -= 1;
27733        } else {
27734            self.write_space();
27735            self.write_keyword(keyword);
27736            self.write_space();
27737            for (i, expr) in exprs.iter().enumerate() {
27738                if i > 0 {
27739                    self.write(", ");
27740                }
27741                self.generate_expression(expr)?;
27742            }
27743        }
27744        Ok(())
27745    }
27746
27747    /// Writes ORDER BY / SORT BY clause with Ordered expressions
27748    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
27749        if orderings.is_empty() {
27750            return Ok(());
27751        }
27752
27753        if self.config.pretty {
27754            self.write_newline();
27755            self.write_indent();
27756            self.write_keyword(keyword);
27757            self.write_newline();
27758            self.indent_level += 1;
27759            for (i, ordered) in orderings.iter().enumerate() {
27760                if i > 0 {
27761                    self.write(",");
27762                    self.write_newline();
27763                }
27764                self.write_indent();
27765                self.generate_ordered(ordered)?;
27766            }
27767            self.indent_level -= 1;
27768        } else {
27769            self.write_space();
27770            self.write_keyword(keyword);
27771            self.write_space();
27772            for (i, ordered) in orderings.iter().enumerate() {
27773                if i > 0 {
27774                    self.write(", ");
27775                }
27776                self.generate_ordered(ordered)?;
27777            }
27778        }
27779        Ok(())
27780    }
27781
27782    /// Writes WINDOW clause with named window definitions
27783    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
27784        if windows.is_empty() {
27785            return Ok(());
27786        }
27787
27788        if self.config.pretty {
27789            self.write_newline();
27790            self.write_indent();
27791            self.write_keyword("WINDOW");
27792            self.write_newline();
27793            self.indent_level += 1;
27794            for (i, named_window) in windows.iter().enumerate() {
27795                if i > 0 {
27796                    self.write(",");
27797                    self.write_newline();
27798                }
27799                self.write_indent();
27800                self.generate_identifier(&named_window.name)?;
27801                self.write_space();
27802                self.write_keyword("AS");
27803                self.write(" (");
27804                self.generate_over(&named_window.spec)?;
27805                self.write(")");
27806            }
27807            self.indent_level -= 1;
27808        } else {
27809            self.write_space();
27810            self.write_keyword("WINDOW");
27811            self.write_space();
27812            for (i, named_window) in windows.iter().enumerate() {
27813                if i > 0 {
27814                    self.write(", ");
27815                }
27816                self.generate_identifier(&named_window.name)?;
27817                self.write_space();
27818                self.write_keyword("AS");
27819                self.write(" (");
27820                self.generate_over(&named_window.spec)?;
27821                self.write(")");
27822            }
27823        }
27824        Ok(())
27825    }
27826
27827    // === BATCH-GENERATED STUB METHODS (481 variants) ===
27828    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
27829        // AI_AGG(this, expression)
27830        self.write_keyword("AI_AGG");
27831        self.write("(");
27832        self.generate_expression(&e.this)?;
27833        self.write(", ");
27834        self.generate_expression(&e.expression)?;
27835        self.write(")");
27836        Ok(())
27837    }
27838
27839    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
27840        // AI_CLASSIFY(input, [categories], [config])
27841        self.write_keyword("AI_CLASSIFY");
27842        self.write("(");
27843        self.generate_expression(&e.this)?;
27844        if let Some(categories) = &e.categories {
27845            self.write(", ");
27846            self.generate_expression(categories)?;
27847        }
27848        if let Some(config) = &e.config {
27849            self.write(", ");
27850            self.generate_expression(config)?;
27851        }
27852        self.write(")");
27853        Ok(())
27854    }
27855
27856    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
27857        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
27858        self.write_keyword("ADD");
27859        self.write_space();
27860        if e.exists {
27861            self.write_keyword("IF NOT EXISTS");
27862            self.write_space();
27863        }
27864        self.generate_expression(&e.this)?;
27865        if let Some(location) = &e.location {
27866            self.write_space();
27867            self.generate_expression(location)?;
27868        }
27869        Ok(())
27870    }
27871
27872    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
27873        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
27874        self.write_keyword("ALGORITHM");
27875        self.write("=");
27876        self.generate_expression(&e.this)?;
27877        Ok(())
27878    }
27879
27880    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
27881        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
27882        self.generate_expression(&e.this)?;
27883        self.write_space();
27884        self.write_keyword("AS");
27885        self.write(" (");
27886        for (i, expr) in e.expressions.iter().enumerate() {
27887            if i > 0 {
27888                self.write(", ");
27889            }
27890            self.generate_expression(expr)?;
27891        }
27892        self.write(")");
27893        Ok(())
27894    }
27895
27896    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
27897        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
27898        self.write_keyword("ALLOWED_VALUES");
27899        self.write_space();
27900        for (i, expr) in e.expressions.iter().enumerate() {
27901            if i > 0 {
27902                self.write(", ");
27903            }
27904            self.generate_expression(expr)?;
27905        }
27906        Ok(())
27907    }
27908
27909    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
27910        // Python: complex logic based on dtype, default, comment, visible, etc.
27911        self.write_keyword("ALTER COLUMN");
27912        self.write_space();
27913        self.generate_expression(&e.this)?;
27914
27915        if let Some(dtype) = &e.dtype {
27916            self.write_space();
27917            self.write_keyword("SET DATA TYPE");
27918            self.write_space();
27919            self.generate_expression(dtype)?;
27920            if let Some(collate) = &e.collate {
27921                self.write_space();
27922                self.write_keyword("COLLATE");
27923                self.write_space();
27924                self.generate_expression(collate)?;
27925            }
27926            if let Some(using) = &e.using {
27927                self.write_space();
27928                self.write_keyword("USING");
27929                self.write_space();
27930                self.generate_expression(using)?;
27931            }
27932        } else if let Some(default) = &e.default {
27933            self.write_space();
27934            self.write_keyword("SET DEFAULT");
27935            self.write_space();
27936            self.generate_expression(default)?;
27937        } else if let Some(comment) = &e.comment {
27938            self.write_space();
27939            self.write_keyword("COMMENT");
27940            self.write_space();
27941            self.generate_expression(comment)?;
27942        } else if let Some(drop) = &e.drop {
27943            self.write_space();
27944            self.write_keyword("DROP");
27945            self.write_space();
27946            self.generate_expression(drop)?;
27947        } else if let Some(visible) = &e.visible {
27948            self.write_space();
27949            self.generate_expression(visible)?;
27950        } else if let Some(rename_to) = &e.rename_to {
27951            self.write_space();
27952            self.write_keyword("RENAME TO");
27953            self.write_space();
27954            self.generate_expression(rename_to)?;
27955        } else if let Some(allow_null) = &e.allow_null {
27956            self.write_space();
27957            self.generate_expression(allow_null)?;
27958        }
27959        Ok(())
27960    }
27961
27962    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
27963        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
27964        self.write_keyword("ALTER SESSION");
27965        self.write_space();
27966        if e.unset.is_some() {
27967            self.write_keyword("UNSET");
27968        } else {
27969            self.write_keyword("SET");
27970        }
27971        self.write_space();
27972        for (i, expr) in e.expressions.iter().enumerate() {
27973            if i > 0 {
27974                self.write(", ");
27975            }
27976            self.generate_expression(expr)?;
27977        }
27978        Ok(())
27979    }
27980
27981    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
27982        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
27983        self.write_keyword("SET");
27984
27985        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
27986        if let Some(opt) = &e.option {
27987            self.write_space();
27988            self.generate_expression(opt)?;
27989        }
27990
27991        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
27992        // Check if expressions look like property assignments
27993        if !e.expressions.is_empty() {
27994            // Check if this looks like property assignments (for SET PROPERTIES)
27995            let is_properties = e
27996                .expressions
27997                .iter()
27998                .any(|expr| matches!(expr, Expression::Eq(_)));
27999            if is_properties && e.option.is_none() {
28000                self.write_space();
28001                self.write_keyword("PROPERTIES");
28002            }
28003            self.write_space();
28004            for (i, expr) in e.expressions.iter().enumerate() {
28005                if i > 0 {
28006                    self.write(", ");
28007                }
28008                self.generate_expression(expr)?;
28009            }
28010        }
28011
28012        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
28013        if let Some(file_format) = &e.file_format {
28014            self.write(" ");
28015            self.write_keyword("STAGE_FILE_FORMAT");
28016            self.write(" = (");
28017            self.generate_space_separated_properties(file_format)?;
28018            self.write(")");
28019        }
28020
28021        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
28022        if let Some(copy_options) = &e.copy_options {
28023            self.write(" ");
28024            self.write_keyword("STAGE_COPY_OPTIONS");
28025            self.write(" = (");
28026            self.generate_space_separated_properties(copy_options)?;
28027            self.write(")");
28028        }
28029
28030        // Generate TAG ...
28031        if let Some(tag) = &e.tag {
28032            self.write(" ");
28033            self.write_keyword("TAG");
28034            self.write(" ");
28035            self.generate_expression(tag)?;
28036        }
28037
28038        Ok(())
28039    }
28040
28041    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
28042    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
28043        match expr {
28044            Expression::Tuple(t) => {
28045                for (i, prop) in t.expressions.iter().enumerate() {
28046                    if i > 0 {
28047                        self.write(" ");
28048                    }
28049                    self.generate_expression(prop)?;
28050                }
28051            }
28052            _ => {
28053                self.generate_expression(expr)?;
28054            }
28055        }
28056        Ok(())
28057    }
28058
28059    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
28060        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
28061        self.write_keyword("ALTER");
28062        if e.compound.is_some() {
28063            self.write_space();
28064            self.write_keyword("COMPOUND");
28065        }
28066        self.write_space();
28067        self.write_keyword("SORTKEY");
28068        self.write_space();
28069        if let Some(this) = &e.this {
28070            self.generate_expression(this)?;
28071        } else if !e.expressions.is_empty() {
28072            self.write("(");
28073            for (i, expr) in e.expressions.iter().enumerate() {
28074                if i > 0 {
28075                    self.write(", ");
28076                }
28077                self.generate_expression(expr)?;
28078            }
28079            self.write(")");
28080        }
28081        Ok(())
28082    }
28083
28084    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
28085        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
28086        self.write_keyword("ANALYZE");
28087        if !e.options.is_empty() {
28088            self.write_space();
28089            for (i, opt) in e.options.iter().enumerate() {
28090                if i > 0 {
28091                    self.write_space();
28092                }
28093                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
28094                if let Expression::Identifier(id) = opt {
28095                    self.write_keyword(&id.name);
28096                } else {
28097                    self.generate_expression(opt)?;
28098                }
28099            }
28100        }
28101        if let Some(kind) = &e.kind {
28102            self.write_space();
28103            self.write_keyword(kind);
28104        }
28105        if let Some(this) = &e.this {
28106            self.write_space();
28107            self.generate_expression(this)?;
28108        }
28109        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
28110        if !e.columns.is_empty() {
28111            self.write("(");
28112            for (i, col) in e.columns.iter().enumerate() {
28113                if i > 0 {
28114                    self.write(", ");
28115                }
28116                self.write(col);
28117            }
28118            self.write(")");
28119        }
28120        if let Some(partition) = &e.partition {
28121            self.write_space();
28122            self.generate_expression(partition)?;
28123        }
28124        if let Some(mode) = &e.mode {
28125            self.write_space();
28126            self.generate_expression(mode)?;
28127        }
28128        if let Some(expression) = &e.expression {
28129            self.write_space();
28130            self.generate_expression(expression)?;
28131        }
28132        if !e.properties.is_empty() {
28133            self.write_space();
28134            self.write_keyword(self.config.with_properties_prefix);
28135            self.write(" (");
28136            for (i, prop) in e.properties.iter().enumerate() {
28137                if i > 0 {
28138                    self.write(", ");
28139                }
28140                self.generate_expression(prop)?;
28141            }
28142            self.write(")");
28143        }
28144        Ok(())
28145    }
28146
28147    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
28148        // Python: return f"DELETE{kind} STATISTICS"
28149        self.write_keyword("DELETE");
28150        if let Some(kind) = &e.kind {
28151            self.write_space();
28152            self.write_keyword(kind);
28153        }
28154        self.write_space();
28155        self.write_keyword("STATISTICS");
28156        Ok(())
28157    }
28158
28159    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
28160        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
28161        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
28162        if let Expression::Identifier(id) = e.this.as_ref() {
28163            self.write_keyword(&id.name);
28164        } else {
28165            self.generate_expression(&e.this)?;
28166        }
28167        self.write_space();
28168        self.write_keyword("HISTOGRAM ON");
28169        self.write_space();
28170        for (i, expr) in e.expressions.iter().enumerate() {
28171            if i > 0 {
28172                self.write(", ");
28173            }
28174            self.generate_expression(expr)?;
28175        }
28176        if let Some(expression) = &e.expression {
28177            self.write_space();
28178            self.generate_expression(expression)?;
28179        }
28180        if let Some(update_options) = &e.update_options {
28181            self.write_space();
28182            self.generate_expression(update_options)?;
28183            self.write_space();
28184            self.write_keyword("UPDATE");
28185        }
28186        Ok(())
28187    }
28188
28189    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
28190        // Python: return f"LIST CHAINED ROWS{inner_expression}"
28191        self.write_keyword("LIST CHAINED ROWS");
28192        if let Some(expression) = &e.expression {
28193            self.write_space();
28194            self.write_keyword("INTO");
28195            self.write_space();
28196            self.generate_expression(expression)?;
28197        }
28198        Ok(())
28199    }
28200
28201    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
28202        // Python: return f"SAMPLE {sample} {kind}"
28203        self.write_keyword("SAMPLE");
28204        self.write_space();
28205        if let Some(sample) = &e.sample {
28206            self.generate_expression(sample)?;
28207            self.write_space();
28208        }
28209        self.write_keyword(&e.kind);
28210        Ok(())
28211    }
28212
28213    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
28214        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
28215        self.write_keyword(&e.kind);
28216        if let Some(option) = &e.option {
28217            self.write_space();
28218            self.generate_expression(option)?;
28219        }
28220        self.write_space();
28221        self.write_keyword("STATISTICS");
28222        if let Some(this) = &e.this {
28223            self.write_space();
28224            self.generate_expression(this)?;
28225        }
28226        if !e.expressions.is_empty() {
28227            self.write_space();
28228            for (i, expr) in e.expressions.iter().enumerate() {
28229                if i > 0 {
28230                    self.write(", ");
28231                }
28232                self.generate_expression(expr)?;
28233            }
28234        }
28235        Ok(())
28236    }
28237
28238    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
28239        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
28240        self.write_keyword("VALIDATE");
28241        self.write_space();
28242        self.write_keyword(&e.kind);
28243        if let Some(this) = &e.this {
28244            self.write_space();
28245            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
28246            if let Expression::Identifier(id) = this.as_ref() {
28247                self.write_keyword(&id.name);
28248            } else {
28249                self.generate_expression(this)?;
28250            }
28251        }
28252        if let Some(expression) = &e.expression {
28253            self.write_space();
28254            self.write_keyword("INTO");
28255            self.write_space();
28256            self.generate_expression(expression)?;
28257        }
28258        Ok(())
28259    }
28260
28261    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
28262        // Python: return f"WITH {expressions}"
28263        self.write_keyword("WITH");
28264        self.write_space();
28265        for (i, expr) in e.expressions.iter().enumerate() {
28266            if i > 0 {
28267                self.write(", ");
28268            }
28269            self.generate_expression(expr)?;
28270        }
28271        Ok(())
28272    }
28273
28274    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
28275        // Anonymous represents a generic function call: FUNC_NAME(args...)
28276        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
28277        self.generate_expression(&e.this)?;
28278        self.write("(");
28279        for (i, arg) in e.expressions.iter().enumerate() {
28280            if i > 0 {
28281                self.write(", ");
28282            }
28283            self.generate_expression(arg)?;
28284        }
28285        self.write(")");
28286        Ok(())
28287    }
28288
28289    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
28290        // Same as Anonymous but for aggregate functions
28291        self.generate_expression(&e.this)?;
28292        self.write("(");
28293        for (i, arg) in e.expressions.iter().enumerate() {
28294            if i > 0 {
28295                self.write(", ");
28296            }
28297            self.generate_expression(arg)?;
28298        }
28299        self.write(")");
28300        Ok(())
28301    }
28302
28303    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
28304        // Python: return f"{this} APPLY({expr})"
28305        self.generate_expression(&e.this)?;
28306        self.write_space();
28307        self.write_keyword("APPLY");
28308        self.write("(");
28309        self.generate_expression(&e.expression)?;
28310        self.write(")");
28311        Ok(())
28312    }
28313
28314    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
28315        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
28316        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
28317        self.write("(");
28318        self.generate_expression(&e.this)?;
28319        if let Some(percentile) = &e.percentile {
28320            self.write(", ");
28321            self.generate_expression(percentile)?;
28322        }
28323        self.write(")");
28324        Ok(())
28325    }
28326
28327    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
28328        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
28329        self.write_keyword("APPROX_QUANTILE");
28330        self.write("(");
28331        self.generate_expression(&e.this)?;
28332        if let Some(quantile) = &e.quantile {
28333            self.write(", ");
28334            self.generate_expression(quantile)?;
28335        }
28336        if let Some(accuracy) = &e.accuracy {
28337            self.write(", ");
28338            self.generate_expression(accuracy)?;
28339        }
28340        if let Some(weight) = &e.weight {
28341            self.write(", ");
28342            self.generate_expression(weight)?;
28343        }
28344        self.write(")");
28345        Ok(())
28346    }
28347
28348    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
28349        // APPROX_QUANTILES(this, expression)
28350        self.write_keyword("APPROX_QUANTILES");
28351        self.write("(");
28352        self.generate_expression(&e.this)?;
28353        if let Some(expression) = &e.expression {
28354            self.write(", ");
28355            self.generate_expression(expression)?;
28356        }
28357        self.write(")");
28358        Ok(())
28359    }
28360
28361    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
28362        // APPROX_TOP_K(this[, expression][, counters])
28363        self.write_keyword("APPROX_TOP_K");
28364        self.write("(");
28365        self.generate_expression(&e.this)?;
28366        if let Some(expression) = &e.expression {
28367            self.write(", ");
28368            self.generate_expression(expression)?;
28369        }
28370        if let Some(counters) = &e.counters {
28371            self.write(", ");
28372            self.generate_expression(counters)?;
28373        }
28374        self.write(")");
28375        Ok(())
28376    }
28377
28378    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
28379        // APPROX_TOP_K_ACCUMULATE(this[, expression])
28380        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
28381        self.write("(");
28382        self.generate_expression(&e.this)?;
28383        if let Some(expression) = &e.expression {
28384            self.write(", ");
28385            self.generate_expression(expression)?;
28386        }
28387        self.write(")");
28388        Ok(())
28389    }
28390
28391    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
28392        // APPROX_TOP_K_COMBINE(this[, expression])
28393        self.write_keyword("APPROX_TOP_K_COMBINE");
28394        self.write("(");
28395        self.generate_expression(&e.this)?;
28396        if let Some(expression) = &e.expression {
28397            self.write(", ");
28398            self.generate_expression(expression)?;
28399        }
28400        self.write(")");
28401        Ok(())
28402    }
28403
28404    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
28405        // APPROX_TOP_K_ESTIMATE(this[, expression])
28406        self.write_keyword("APPROX_TOP_K_ESTIMATE");
28407        self.write("(");
28408        self.generate_expression(&e.this)?;
28409        if let Some(expression) = &e.expression {
28410            self.write(", ");
28411            self.generate_expression(expression)?;
28412        }
28413        self.write(")");
28414        Ok(())
28415    }
28416
28417    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
28418        // APPROX_TOP_SUM(this, expression[, count])
28419        self.write_keyword("APPROX_TOP_SUM");
28420        self.write("(");
28421        self.generate_expression(&e.this)?;
28422        self.write(", ");
28423        self.generate_expression(&e.expression)?;
28424        if let Some(count) = &e.count {
28425            self.write(", ");
28426            self.generate_expression(count)?;
28427        }
28428        self.write(")");
28429        Ok(())
28430    }
28431
28432    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
28433        // ARG_MAX(this, expression[, count])
28434        self.write_keyword("ARG_MAX");
28435        self.write("(");
28436        self.generate_expression(&e.this)?;
28437        self.write(", ");
28438        self.generate_expression(&e.expression)?;
28439        if let Some(count) = &e.count {
28440            self.write(", ");
28441            self.generate_expression(count)?;
28442        }
28443        self.write(")");
28444        Ok(())
28445    }
28446
28447    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
28448        // ARG_MIN(this, expression[, count])
28449        self.write_keyword("ARG_MIN");
28450        self.write("(");
28451        self.generate_expression(&e.this)?;
28452        self.write(", ");
28453        self.generate_expression(&e.expression)?;
28454        if let Some(count) = &e.count {
28455            self.write(", ");
28456            self.generate_expression(count)?;
28457        }
28458        self.write(")");
28459        Ok(())
28460    }
28461
28462    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
28463        // ARRAY_ALL(this, expression)
28464        self.write_keyword("ARRAY_ALL");
28465        self.write("(");
28466        self.generate_expression(&e.this)?;
28467        self.write(", ");
28468        self.generate_expression(&e.expression)?;
28469        self.write(")");
28470        Ok(())
28471    }
28472
28473    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
28474        // ARRAY_ANY(this, expression) - fallback implementation
28475        self.write_keyword("ARRAY_ANY");
28476        self.write("(");
28477        self.generate_expression(&e.this)?;
28478        self.write(", ");
28479        self.generate_expression(&e.expression)?;
28480        self.write(")");
28481        Ok(())
28482    }
28483
28484    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
28485        // ARRAY_CONSTRUCT_COMPACT(expressions...)
28486        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
28487        self.write("(");
28488        for (i, expr) in e.expressions.iter().enumerate() {
28489            if i > 0 {
28490                self.write(", ");
28491            }
28492            self.generate_expression(expr)?;
28493        }
28494        self.write(")");
28495        Ok(())
28496    }
28497
28498    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
28499        // ARRAY_SUM(this[, expression])
28500        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28501            self.write("arraySum");
28502        } else {
28503            self.write_keyword("ARRAY_SUM");
28504        }
28505        self.write("(");
28506        self.generate_expression(&e.this)?;
28507        if let Some(expression) = &e.expression {
28508            self.write(", ");
28509            self.generate_expression(expression)?;
28510        }
28511        self.write(")");
28512        Ok(())
28513    }
28514
28515    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
28516        // Python: return f"{this} AT {index}"
28517        self.generate_expression(&e.this)?;
28518        self.write_space();
28519        self.write_keyword("AT");
28520        self.write_space();
28521        self.generate_expression(&e.expression)?;
28522        Ok(())
28523    }
28524
28525    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
28526        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
28527        self.write_keyword("ATTACH");
28528        if e.exists {
28529            self.write_space();
28530            self.write_keyword("IF NOT EXISTS");
28531        }
28532        self.write_space();
28533        self.generate_expression(&e.this)?;
28534        if !e.expressions.is_empty() {
28535            self.write(" (");
28536            for (i, expr) in e.expressions.iter().enumerate() {
28537                if i > 0 {
28538                    self.write(", ");
28539                }
28540                self.generate_expression(expr)?;
28541            }
28542            self.write(")");
28543        }
28544        Ok(())
28545    }
28546
28547    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
28548        // AttachOption: this [expression]
28549        // Python sqlglot: no equals sign, just space-separated
28550        self.generate_expression(&e.this)?;
28551        if let Some(expression) = &e.expression {
28552            self.write_space();
28553            self.generate_expression(expression)?;
28554        }
28555        Ok(())
28556    }
28557
28558    /// Generate the auto_increment keyword and options for a column definition.
28559    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
28560    /// GENERATED AS IDENTITY, etc.
28561    fn generate_auto_increment_keyword(
28562        &mut self,
28563        col: &crate::expressions::ColumnDef,
28564    ) -> Result<()> {
28565        use crate::dialects::DialectType;
28566        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
28567            self.write_keyword("IDENTITY");
28568            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28569                self.write("(");
28570                if let Some(ref start) = col.auto_increment_start {
28571                    self.generate_expression(start)?;
28572                } else {
28573                    self.write("0");
28574                }
28575                self.write(", ");
28576                if let Some(ref inc) = col.auto_increment_increment {
28577                    self.generate_expression(inc)?;
28578                } else {
28579                    self.write("1");
28580                }
28581                self.write(")");
28582            }
28583        } else if matches!(
28584            self.config.dialect,
28585            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
28586        ) {
28587            self.write_keyword("AUTOINCREMENT");
28588            if let Some(ref start) = col.auto_increment_start {
28589                self.write_space();
28590                self.write_keyword("START");
28591                self.write_space();
28592                self.generate_expression(start)?;
28593            }
28594            if let Some(ref inc) = col.auto_increment_increment {
28595                self.write_space();
28596                self.write_keyword("INCREMENT");
28597                self.write_space();
28598                self.generate_expression(inc)?;
28599            }
28600            if let Some(order) = col.auto_increment_order {
28601                self.write_space();
28602                if order {
28603                    self.write_keyword("ORDER");
28604                } else {
28605                    self.write_keyword("NOORDER");
28606                }
28607            }
28608        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
28609            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
28610            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28611                self.write(" (");
28612                let mut first = true;
28613                if let Some(ref start) = col.auto_increment_start {
28614                    self.write_keyword("START WITH");
28615                    self.write_space();
28616                    self.generate_expression(start)?;
28617                    first = false;
28618                }
28619                if let Some(ref inc) = col.auto_increment_increment {
28620                    if !first {
28621                        self.write_space();
28622                    }
28623                    self.write_keyword("INCREMENT BY");
28624                    self.write_space();
28625                    self.generate_expression(inc)?;
28626                }
28627                self.write(")");
28628            }
28629        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28630            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
28631            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
28632            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28633                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
28634            } else {
28635                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
28636            }
28637            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28638                self.write(" (");
28639                let mut first = true;
28640                if let Some(ref start) = col.auto_increment_start {
28641                    self.write_keyword("START WITH");
28642                    self.write_space();
28643                    self.generate_expression(start)?;
28644                    first = false;
28645                }
28646                if let Some(ref inc) = col.auto_increment_increment {
28647                    if !first {
28648                        self.write_space();
28649                    }
28650                    self.write_keyword("INCREMENT BY");
28651                    self.write_space();
28652                    self.generate_expression(inc)?;
28653                }
28654                self.write(")");
28655            }
28656        } else if matches!(
28657            self.config.dialect,
28658            Some(DialectType::TSQL) | Some(DialectType::Fabric)
28659        ) {
28660            self.write_keyword("IDENTITY");
28661            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28662                self.write("(");
28663                if let Some(ref start) = col.auto_increment_start {
28664                    self.generate_expression(start)?;
28665                } else {
28666                    self.write("0");
28667                }
28668                self.write(", ");
28669                if let Some(ref inc) = col.auto_increment_increment {
28670                    self.generate_expression(inc)?;
28671                } else {
28672                    self.write("1");
28673                }
28674                self.write(")");
28675            }
28676        } else {
28677            self.write_keyword("AUTO_INCREMENT");
28678            if let Some(ref start) = col.auto_increment_start {
28679                self.write_space();
28680                self.write_keyword("START");
28681                self.write_space();
28682                self.generate_expression(start)?;
28683            }
28684            if let Some(ref inc) = col.auto_increment_increment {
28685                self.write_space();
28686                self.write_keyword("INCREMENT");
28687                self.write_space();
28688                self.generate_expression(inc)?;
28689            }
28690            if let Some(order) = col.auto_increment_order {
28691                self.write_space();
28692                if order {
28693                    self.write_keyword("ORDER");
28694                } else {
28695                    self.write_keyword("NOORDER");
28696                }
28697            }
28698        }
28699        Ok(())
28700    }
28701
28702    fn generate_tidb_auto_random(&mut self, auto_random: &TiDBAutoRandom) -> Result<()> {
28703        let is_tidb = matches!(self.config.dialect, Some(DialectType::TiDB));
28704        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
28705
28706        if !is_tidb && !is_mysql {
28707            return self
28708                .write_unsupported_comment("AUTO_RANDOM is only supported by the TiDB dialect");
28709        }
28710        if is_mysql {
28711            self.unsupported("MySQL ignores TiDB AUTO_RANDOM executable comments")?;
28712        }
28713
28714        let use_comment = auto_random.executable_comment || is_mysql;
28715        if use_comment {
28716            self.write("/*T![auto_rand] ");
28717        }
28718        self.write_keyword("AUTO_RANDOM");
28719        if let Some(shard_bits) = auto_random.shard_bits {
28720            self.write("(");
28721            self.write(&shard_bits.to_string());
28722            if let Some(range_bits) = auto_random.range_bits {
28723                self.write(", ");
28724                self.write(&range_bits.to_string());
28725            }
28726            self.write(")");
28727        }
28728        if use_comment {
28729            self.write(" */");
28730        }
28731        Ok(())
28732    }
28733
28734    fn generate_tidb_table_option(&mut self, option: &TiDBTableOption, force: bool) -> Result<()> {
28735        let is_tidb = matches!(self.config.dialect, Some(DialectType::TiDB));
28736        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
28737        if !is_tidb && !is_mysql {
28738            return self.write_unsupported_comment(
28739                "TiDB table options are not supported by the target dialect",
28740            );
28741        }
28742        if is_mysql {
28743            self.unsupported("MySQL ignores TiDB table-option executable comments")?;
28744        }
28745
28746        let use_comment = option.executable_comment || is_mysql;
28747        if use_comment {
28748            match option.kind {
28749                TiDBTableOptionKind::Ttl { .. }
28750                | TiDBTableOptionKind::TtlEnable { .. }
28751                | TiDBTableOptionKind::TtlJobInterval { .. } => self.write("/*T![ttl] "),
28752                TiDBTableOptionKind::PlacementPolicy { .. } => self.write("/*T![placement] "),
28753                _ => self.write("/*T! "),
28754            }
28755        }
28756        if force {
28757            self.write_keyword("FORCE");
28758            self.write_space();
28759        }
28760        match &option.kind {
28761            TiDBTableOptionKind::ShardRowIdBits { bits } => {
28762                self.write_keyword("SHARD_ROW_ID_BITS");
28763                self.write("=");
28764                self.write(&bits.to_string());
28765            }
28766            TiDBTableOptionKind::PreSplitRegions { regions } => {
28767                self.write_keyword("PRE_SPLIT_REGIONS");
28768                self.write("=");
28769                self.write(&regions.to_string());
28770            }
28771            TiDBTableOptionKind::AutoRandomBase { value } => {
28772                self.write_keyword("AUTO_RANDOM_BASE");
28773                self.write("=");
28774                self.write(&value.to_string());
28775            }
28776            TiDBTableOptionKind::PlacementPolicy { policy } => {
28777                self.write_keyword("PLACEMENT POLICY");
28778                self.write("=");
28779                if let Some(policy) = policy {
28780                    self.generate_identifier(policy)?;
28781                } else {
28782                    self.write_keyword("DEFAULT");
28783                }
28784            }
28785            TiDBTableOptionKind::Ttl {
28786                column,
28787                interval,
28788                enabled,
28789            } => {
28790                self.write_keyword("TTL");
28791                self.write("=");
28792                self.generate_identifier(column)?;
28793                self.write(" + ");
28794                self.generate_tidb_ttl_interval(interval)?;
28795                if let Some(enabled) = enabled {
28796                    self.write_space();
28797                    self.write_keyword("TTL_ENABLE");
28798                    self.write("='");
28799                    self.write(if *enabled { "ON" } else { "OFF" });
28800                    self.write("'");
28801                }
28802            }
28803            TiDBTableOptionKind::TtlEnable { enabled } => {
28804                self.write_keyword("TTL_ENABLE");
28805                self.write("='");
28806                self.write(if *enabled { "ON" } else { "OFF" });
28807                self.write("'");
28808            }
28809            TiDBTableOptionKind::TtlJobInterval { interval } => {
28810                self.write_keyword("TTL_JOB_INTERVAL");
28811                self.write("=");
28812                self.generate_string_literal(interval)?;
28813            }
28814        }
28815        if use_comment {
28816            self.write(" */");
28817        }
28818        Ok(())
28819    }
28820
28821    fn generate_tidb_ttl_interval(&mut self, interval: &Interval) -> Result<()> {
28822        self.write_keyword("INTERVAL");
28823        if let Some(value) = &interval.this {
28824            self.write_space();
28825            if let Expression::Literal(literal) = value {
28826                if let Literal::String(value) = literal.as_ref() {
28827                    if value.parse::<f64>().is_ok() {
28828                        self.write(value);
28829                    } else {
28830                        self.generate_string_literal(value)?;
28831                    }
28832                } else {
28833                    self.generate_expression(value)?;
28834                }
28835            } else {
28836                self.generate_expression(value)?;
28837            }
28838        }
28839        if let Some(unit) = &interval.unit {
28840            self.write_space();
28841            self.write_interval_unit_spec(unit)?;
28842        }
28843        Ok(())
28844    }
28845
28846    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
28847        // AUTO_INCREMENT=value
28848        self.write_keyword("AUTO_INCREMENT");
28849        self.write("=");
28850        self.generate_expression(&e.this)?;
28851        Ok(())
28852    }
28853
28854    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
28855        // AUTO_REFRESH=value
28856        self.write_keyword("AUTO_REFRESH");
28857        self.write("=");
28858        self.generate_expression(&e.this)?;
28859        Ok(())
28860    }
28861
28862    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
28863        // BACKUP YES|NO (Redshift syntax uses space, not equals)
28864        self.write_keyword("BACKUP");
28865        self.write_space();
28866        self.generate_expression(&e.this)?;
28867        Ok(())
28868    }
28869
28870    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
28871        // BASE64_DECODE_BINARY(this[, alphabet])
28872        self.write_keyword("BASE64_DECODE_BINARY");
28873        self.write("(");
28874        self.generate_expression(&e.this)?;
28875        if let Some(alphabet) = &e.alphabet {
28876            self.write(", ");
28877            self.generate_expression(alphabet)?;
28878        }
28879        self.write(")");
28880        Ok(())
28881    }
28882
28883    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
28884        // BASE64_DECODE_STRING(this[, alphabet])
28885        self.write_keyword("BASE64_DECODE_STRING");
28886        self.write("(");
28887        self.generate_expression(&e.this)?;
28888        if let Some(alphabet) = &e.alphabet {
28889            self.write(", ");
28890            self.generate_expression(alphabet)?;
28891        }
28892        self.write(")");
28893        Ok(())
28894    }
28895
28896    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
28897        // BASE64_ENCODE(this[, max_line_length][, alphabet])
28898        self.write_keyword("BASE64_ENCODE");
28899        self.write("(");
28900        self.generate_expression(&e.this)?;
28901        if let Some(max_line_length) = &e.max_line_length {
28902            self.write(", ");
28903            self.generate_expression(max_line_length)?;
28904        }
28905        if let Some(alphabet) = &e.alphabet {
28906            self.write(", ");
28907            self.generate_expression(alphabet)?;
28908        }
28909        self.write(")");
28910        Ok(())
28911    }
28912
28913    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
28914        // BLOCKCOMPRESSION=... (complex Teradata property)
28915        self.write_keyword("BLOCKCOMPRESSION");
28916        self.write("=");
28917        if let Some(autotemp) = &e.autotemp {
28918            self.write_keyword("AUTOTEMP");
28919            self.write("(");
28920            self.generate_expression(autotemp)?;
28921            self.write(")");
28922        }
28923        if let Some(always) = &e.always {
28924            self.generate_expression(always)?;
28925        }
28926        if let Some(default) = &e.default {
28927            self.generate_expression(default)?;
28928        }
28929        if let Some(manual) = &e.manual {
28930            self.generate_expression(manual)?;
28931        }
28932        if let Some(never) = &e.never {
28933            self.generate_expression(never)?;
28934        }
28935        Ok(())
28936    }
28937
28938    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
28939        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
28940        self.write("((");
28941        self.generate_expression(&e.this)?;
28942        self.write(") ");
28943        self.write_keyword("AND");
28944        self.write(" (");
28945        self.generate_expression(&e.expression)?;
28946        self.write("))");
28947        Ok(())
28948    }
28949
28950    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
28951        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
28952        self.write("((");
28953        self.generate_expression(&e.this)?;
28954        self.write(") ");
28955        self.write_keyword("OR");
28956        self.write(" (");
28957        self.generate_expression(&e.expression)?;
28958        self.write("))");
28959        Ok(())
28960    }
28961
28962    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
28963        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
28964        self.write_keyword("BUILD");
28965        self.write_space();
28966        self.generate_expression(&e.this)?;
28967        Ok(())
28968    }
28969
28970    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
28971        // Byte string literal like B'...' or X'...'
28972        self.generate_expression(&e.this)?;
28973        Ok(())
28974    }
28975
28976    fn generate_case_specific_column_constraint(
28977        &mut self,
28978        e: &CaseSpecificColumnConstraint,
28979    ) -> Result<()> {
28980        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
28981        if e.not_.is_some() {
28982            self.write_keyword("NOT");
28983            self.write_space();
28984        }
28985        self.write_keyword("CASESPECIFIC");
28986        Ok(())
28987    }
28988
28989    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
28990        // Cast to string type (dialect-specific)
28991        self.write_keyword("CAST");
28992        self.write("(");
28993        self.generate_expression(&e.this)?;
28994        if self.config.dialect == Some(DialectType::ClickHouse) {
28995            // ClickHouse: CAST(expr, 'type_string')
28996            self.write(", ");
28997        } else {
28998            self.write_space();
28999            self.write_keyword("AS");
29000            self.write_space();
29001        }
29002        if let Some(to) = &e.to {
29003            self.generate_expression(to)?;
29004        }
29005        self.write(")");
29006        Ok(())
29007    }
29008
29009    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
29010        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
29011        // Python: f"CHANGES ({information}){at_before}{end}"
29012        self.write_keyword("CHANGES");
29013        self.write(" (");
29014        if let Some(information) = &e.information {
29015            self.write_keyword("INFORMATION");
29016            self.write(" => ");
29017            self.generate_expression(information)?;
29018        }
29019        self.write(")");
29020        // at_before and end are HistoricalData expressions that generate their own keywords
29021        if let Some(at_before) = &e.at_before {
29022            self.write(" ");
29023            self.generate_expression(at_before)?;
29024        }
29025        if let Some(end) = &e.end {
29026            self.write(" ");
29027            self.generate_expression(end)?;
29028        }
29029        Ok(())
29030    }
29031
29032    fn generate_character_set_column_constraint(
29033        &mut self,
29034        e: &CharacterSetColumnConstraint,
29035    ) -> Result<()> {
29036        // CHARACTER SET charset_name
29037        self.write_keyword("CHARACTER SET");
29038        self.write_space();
29039        self.generate_expression(&e.this)?;
29040        Ok(())
29041    }
29042
29043    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
29044        // [DEFAULT] CHARACTER SET=value
29045        if e.default.is_some() {
29046            self.write_keyword("DEFAULT");
29047            self.write_space();
29048        }
29049        self.write_keyword("CHARACTER SET");
29050        self.write("=");
29051        self.generate_expression(&e.this)?;
29052        Ok(())
29053    }
29054
29055    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
29056        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
29057        self.write_keyword("CHECK");
29058        self.write(" (");
29059        self.generate_expression(&e.this)?;
29060        self.write(")");
29061        if e.enforced.is_some() {
29062            self.write_space();
29063            self.write_keyword("ENFORCED");
29064        }
29065        Ok(())
29066    }
29067
29068    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
29069        // Python: return f"ASSUME ({self.sql(e, 'this')})"
29070        self.write_keyword("ASSUME");
29071        self.write(" (");
29072        self.generate_expression(&e.this)?;
29073        self.write(")");
29074        Ok(())
29075    }
29076
29077    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
29078        // CHECK_JSON(this)
29079        self.write_keyword("CHECK_JSON");
29080        self.write("(");
29081        self.generate_expression(&e.this)?;
29082        self.write(")");
29083        Ok(())
29084    }
29085
29086    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
29087        // CHECK_XML(this)
29088        self.write_keyword("CHECK_XML");
29089        self.write("(");
29090        self.generate_expression(&e.this)?;
29091        self.write(")");
29092        Ok(())
29093    }
29094
29095    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
29096        // CHECKSUM=[ON|OFF|DEFAULT]
29097        self.write_keyword("CHECKSUM");
29098        self.write("=");
29099        if e.on.is_some() {
29100            self.write_keyword("ON");
29101        } else if e.default.is_some() {
29102            self.write_keyword("DEFAULT");
29103        } else {
29104            self.write_keyword("OFF");
29105        }
29106        Ok(())
29107    }
29108
29109    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
29110        // Python: return f"{shallow}{keyword} {this}"
29111        if e.shallow.is_some() {
29112            self.write_keyword("SHALLOW");
29113            self.write_space();
29114        }
29115        if e.copy.is_some() {
29116            self.write_keyword("COPY");
29117        } else {
29118            self.write_keyword("CLONE");
29119        }
29120        self.write_space();
29121        self.generate_expression(&e.this)?;
29122        Ok(())
29123    }
29124
29125    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
29126        // CLUSTER BY (expressions)
29127        self.write_keyword("CLUSTER BY");
29128        self.write(" (");
29129        for (i, ord) in e.expressions.iter().enumerate() {
29130            if i > 0 {
29131                self.write(", ");
29132            }
29133            self.generate_ordered(ord)?;
29134        }
29135        self.write(")");
29136        Ok(())
29137    }
29138
29139    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
29140        // BigQuery table property: CLUSTER BY col1, col2
29141        self.write_keyword("CLUSTER BY");
29142        self.write_space();
29143        for (i, col) in e.columns.iter().enumerate() {
29144            if i > 0 {
29145                self.write(", ");
29146            }
29147            self.generate_identifier(col)?;
29148        }
29149        Ok(())
29150    }
29151
29152    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
29153        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
29154        self.write_keyword("CLUSTERED BY");
29155        self.write(" (");
29156        for (i, expr) in e.expressions.iter().enumerate() {
29157            if i > 0 {
29158                self.write(", ");
29159            }
29160            self.generate_expression(expr)?;
29161        }
29162        self.write(")");
29163        if let Some(sorted_by) = &e.sorted_by {
29164            self.write_space();
29165            self.write_keyword("SORTED BY");
29166            self.write(" (");
29167            // Unwrap Tuple to avoid double parentheses
29168            if let Expression::Tuple(t) = sorted_by.as_ref() {
29169                for (i, expr) in t.expressions.iter().enumerate() {
29170                    if i > 0 {
29171                        self.write(", ");
29172                    }
29173                    self.generate_expression(expr)?;
29174                }
29175            } else {
29176                self.generate_expression(sorted_by)?;
29177            }
29178            self.write(")");
29179        }
29180        if let Some(buckets) = &e.buckets {
29181            self.write_space();
29182            self.write_keyword("INTO");
29183            self.write_space();
29184            self.generate_expression(buckets)?;
29185            self.write_space();
29186            self.write_keyword("BUCKETS");
29187        }
29188        Ok(())
29189    }
29190
29191    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
29192        // [DEFAULT] COLLATE [=] value
29193        // BigQuery uses space: DEFAULT COLLATE 'en'
29194        // Others use equals: COLLATE='en'
29195        if e.default.is_some() {
29196            self.write_keyword("DEFAULT");
29197            self.write_space();
29198        }
29199        self.write_keyword("COLLATE");
29200        // BigQuery uses space between COLLATE and value
29201        match self.config.dialect {
29202            Some(DialectType::BigQuery) => self.write_space(),
29203            _ => self.write("="),
29204        }
29205        self.generate_expression(&e.this)?;
29206        Ok(())
29207    }
29208
29209    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
29210        // ColumnConstraint is an enum
29211        match e {
29212            ColumnConstraint::NotNull => {
29213                self.write_keyword("NOT NULL");
29214            }
29215            ColumnConstraint::Null => {
29216                self.write_keyword("NULL");
29217            }
29218            ColumnConstraint::Unique => {
29219                self.write_keyword("UNIQUE");
29220            }
29221            ColumnConstraint::PrimaryKey => {
29222                self.write_keyword("PRIMARY KEY");
29223            }
29224            ColumnConstraint::Default(expr) => {
29225                self.write_keyword("DEFAULT");
29226                self.write_space();
29227                self.generate_expression(expr)?;
29228            }
29229            ColumnConstraint::Check(expr) => {
29230                self.write_keyword("CHECK");
29231                self.write(" (");
29232                self.generate_expression(expr)?;
29233                self.write(")");
29234            }
29235            ColumnConstraint::References(fk_ref) => {
29236                if fk_ref.has_foreign_key_keywords {
29237                    self.write_keyword("FOREIGN KEY");
29238                    self.write_space();
29239                }
29240                self.write_keyword("REFERENCES");
29241                self.write_space();
29242                self.generate_table(&fk_ref.table)?;
29243                if !fk_ref.columns.is_empty() {
29244                    self.write(" (");
29245                    for (i, col) in fk_ref.columns.iter().enumerate() {
29246                        if i > 0 {
29247                            self.write(", ");
29248                        }
29249                        self.generate_identifier(col)?;
29250                    }
29251                    self.write(")");
29252                }
29253            }
29254            ColumnConstraint::GeneratedAsIdentity(gen) => {
29255                self.write_keyword("GENERATED");
29256                self.write_space();
29257                if gen.always {
29258                    self.write_keyword("ALWAYS");
29259                } else {
29260                    self.write_keyword("BY DEFAULT");
29261                    if gen.on_null {
29262                        self.write_space();
29263                        self.write_keyword("ON NULL");
29264                    }
29265                }
29266                self.write_space();
29267                self.write_keyword("AS IDENTITY");
29268            }
29269            ColumnConstraint::Collate(collation) => {
29270                self.write_keyword("COLLATE");
29271                self.write_space();
29272                self.generate_identifier(collation)?;
29273            }
29274            ColumnConstraint::Comment(comment) => {
29275                self.write_keyword("COMMENT");
29276                self.write(" '");
29277                self.write(comment);
29278                self.write("'");
29279            }
29280            ColumnConstraint::ComputedColumn(cc) => {
29281                self.generate_computed_column_inline(cc)?;
29282            }
29283            ColumnConstraint::GeneratedAsRow(gar) => {
29284                self.generate_generated_as_row_inline(gar)?;
29285            }
29286            ColumnConstraint::Tags(tags) => {
29287                self.write_keyword("TAG");
29288                self.write(" (");
29289                for (i, expr) in tags.expressions.iter().enumerate() {
29290                    if i > 0 {
29291                        self.write(", ");
29292                    }
29293                    self.generate_expression(expr)?;
29294                }
29295                self.write(")");
29296            }
29297            ColumnConstraint::Path(path_expr) => {
29298                self.write_keyword("PATH");
29299                self.write_space();
29300                self.generate_expression(path_expr)?;
29301            }
29302        }
29303        Ok(())
29304    }
29305
29306    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
29307        // ColumnPosition is an enum
29308        match e {
29309            ColumnPosition::First => {
29310                self.write_keyword("FIRST");
29311            }
29312            ColumnPosition::After(ident) => {
29313                self.write_keyword("AFTER");
29314                self.write_space();
29315                self.generate_identifier(ident)?;
29316            }
29317        }
29318        Ok(())
29319    }
29320
29321    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
29322        // column(prefix)
29323        self.generate_expression(&e.this)?;
29324        self.write("(");
29325        self.generate_expression(&e.expression)?;
29326        self.write(")");
29327        Ok(())
29328    }
29329
29330    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
29331        // If unpack is true, this came from * COLUMNS(pattern)
29332        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
29333        if let Some(ref unpack) = e.unpack {
29334            if let Expression::Boolean(b) = unpack.as_ref() {
29335                if b.value {
29336                    self.write("*");
29337                }
29338            }
29339        }
29340        self.write_keyword("COLUMNS");
29341        self.write("(");
29342        self.generate_expression(&e.this)?;
29343        self.write(")");
29344        Ok(())
29345    }
29346
29347    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
29348        // Combined aggregate: FUNC(args) combined
29349        self.generate_expression(&e.this)?;
29350        self.write("(");
29351        for (i, expr) in e.expressions.iter().enumerate() {
29352            if i > 0 {
29353                self.write(", ");
29354            }
29355            self.generate_expression(expr)?;
29356        }
29357        self.write(")");
29358        Ok(())
29359    }
29360
29361    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
29362        // Combined parameterized aggregate: FUNC(params)(expressions)
29363        self.generate_expression(&e.this)?;
29364        self.write("(");
29365        for (i, param) in e.params.iter().enumerate() {
29366            if i > 0 {
29367                self.write(", ");
29368            }
29369            self.generate_expression(param)?;
29370        }
29371        self.write(")(");
29372        for (i, expr) in e.expressions.iter().enumerate() {
29373            if i > 0 {
29374                self.write(", ");
29375            }
29376            self.generate_expression(expr)?;
29377        }
29378        self.write(")");
29379        Ok(())
29380    }
29381
29382    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
29383        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
29384        self.write_keyword("COMMIT");
29385
29386        // TSQL always uses COMMIT TRANSACTION
29387        if e.this.is_none()
29388            && matches!(
29389                self.config.dialect,
29390                Some(DialectType::TSQL) | Some(DialectType::Fabric)
29391            )
29392        {
29393            self.write_space();
29394            self.write_keyword("TRANSACTION");
29395        }
29396
29397        // Check if this has TRANSACTION keyword or transaction name
29398        if let Some(this) = &e.this {
29399            // Check if it's just the "TRANSACTION" marker or an actual transaction name
29400            let is_transaction_marker = matches!(
29401                this.as_ref(),
29402                Expression::Identifier(id) if id.name == "TRANSACTION"
29403            );
29404
29405            self.write_space();
29406            self.write_keyword("TRANSACTION");
29407
29408            // If it's a real transaction name, output it
29409            if !is_transaction_marker {
29410                self.write_space();
29411                self.generate_expression(this)?;
29412            }
29413        }
29414
29415        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
29416        if let Some(durability) = &e.durability {
29417            self.write_space();
29418            self.write_keyword("WITH");
29419            self.write(" (");
29420            self.write_keyword("DELAYED_DURABILITY");
29421            self.write(" = ");
29422            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
29423                self.write_keyword("ON");
29424            } else {
29425                self.write_keyword("OFF");
29426            }
29427            self.write(")");
29428        }
29429
29430        // Output AND [NO] CHAIN
29431        if let Some(chain) = &e.chain {
29432            self.write_space();
29433            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
29434                self.write_keyword("AND NO CHAIN");
29435            } else {
29436                self.write_keyword("AND CHAIN");
29437            }
29438        }
29439        Ok(())
29440    }
29441
29442    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
29443        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
29444        self.write("[");
29445        self.generate_expression(&e.this)?;
29446        self.write_space();
29447        self.write_keyword("FOR");
29448        self.write_space();
29449        self.generate_expression(&e.expression)?;
29450        // Handle optional position variable (for enumerate-like syntax)
29451        if let Some(pos) = &e.position {
29452            self.write(", ");
29453            self.generate_expression(pos)?;
29454        }
29455        if let Some(iterator) = &e.iterator {
29456            self.write_space();
29457            self.write_keyword("IN");
29458            self.write_space();
29459            self.generate_expression(iterator)?;
29460        }
29461        if let Some(condition) = &e.condition {
29462            self.write_space();
29463            self.write_keyword("IF");
29464            self.write_space();
29465            self.generate_expression(condition)?;
29466        }
29467        self.write("]");
29468        Ok(())
29469    }
29470
29471    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
29472        // COMPRESS(this[, method])
29473        self.write_keyword("COMPRESS");
29474        self.write("(");
29475        self.generate_expression(&e.this)?;
29476        if let Some(method) = &e.method {
29477            self.write(", '");
29478            self.write(method);
29479            self.write("'");
29480        }
29481        self.write(")");
29482        Ok(())
29483    }
29484
29485    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
29486        // Python: return f"COMPRESS {this}"
29487        self.write_keyword("COMPRESS");
29488        if let Some(this) = &e.this {
29489            self.write_space();
29490            self.generate_expression(this)?;
29491        }
29492        Ok(())
29493    }
29494
29495    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
29496        // Python: return f"AS {this}{persisted}"
29497        self.write_keyword("AS");
29498        self.write_space();
29499        self.generate_expression(&e.this)?;
29500        if e.not_null.is_some() {
29501            self.write_space();
29502            self.write_keyword("PERSISTED NOT NULL");
29503        } else if e.persisted.is_some() {
29504            self.write_space();
29505            self.write_keyword("PERSISTED");
29506        }
29507        Ok(())
29508    }
29509
29510    /// Generate a ComputedColumn constraint inline within a column definition.
29511    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
29512    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
29513    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
29514        let computed_expr = if matches!(
29515            self.config.dialect,
29516            Some(DialectType::TSQL) | Some(DialectType::Fabric)
29517        ) {
29518            match &*cc.expression {
29519                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
29520                {
29521                    let wrapped = Expression::Cast(Box::new(Cast {
29522                        this: y.this.clone(),
29523                        to: DataType::Date,
29524                        trailing_comments: Vec::new(),
29525                        double_colon_syntax: false,
29526                        format: None,
29527                        default: None,
29528                        inferred_type: None,
29529                    }));
29530                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
29531                }
29532                Expression::Function(f)
29533                    if f.name.eq_ignore_ascii_case("YEAR")
29534                        && f.args.len() == 1
29535                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
29536                {
29537                    let wrapped = Expression::Cast(Box::new(Cast {
29538                        this: f.args[0].clone(),
29539                        to: DataType::Date,
29540                        trailing_comments: Vec::new(),
29541                        double_colon_syntax: false,
29542                        format: None,
29543                        default: None,
29544                        inferred_type: None,
29545                    }));
29546                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
29547                }
29548                _ => *cc.expression.clone(),
29549            }
29550        } else {
29551            *cc.expression.clone()
29552        };
29553
29554        match cc.persistence_kind.as_deref() {
29555            Some("STORED") | Some("VIRTUAL") => {
29556                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
29557                self.write_keyword("GENERATED ALWAYS AS");
29558                self.write(" (");
29559                self.generate_expression(&computed_expr)?;
29560                self.write(")");
29561                self.write_space();
29562                if cc.persisted {
29563                    self.write_keyword("STORED");
29564                } else {
29565                    self.write_keyword("VIRTUAL");
29566                }
29567            }
29568            Some("PERSISTED") => {
29569                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
29570                self.write_keyword("AS");
29571                self.write(" (");
29572                self.generate_expression(&computed_expr)?;
29573                self.write(")");
29574                self.write_space();
29575                self.write_keyword("PERSISTED");
29576                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
29577                if let Some(ref dt) = cc.data_type {
29578                    self.write_space();
29579                    self.generate_data_type(dt)?;
29580                }
29581                if cc.not_null {
29582                    self.write_space();
29583                    self.write_keyword("NOT NULL");
29584                }
29585            }
29586            _ => {
29587                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
29588                // TSQL computed column without PERSISTED: AS (expr)
29589                if matches!(
29590                    self.config.dialect,
29591                    Some(DialectType::Spark)
29592                        | Some(DialectType::Databricks)
29593                        | Some(DialectType::Hive)
29594                ) {
29595                    self.write_keyword("GENERATED ALWAYS AS");
29596                    self.write(" (");
29597                    self.generate_expression(&computed_expr)?;
29598                    self.write(")");
29599                } else if matches!(
29600                    self.config.dialect,
29601                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
29602                ) {
29603                    self.write_keyword("AS");
29604                    let omit_parens = matches!(computed_expr, Expression::Year(_))
29605                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
29606                    if omit_parens {
29607                        self.write_space();
29608                        self.generate_expression(&computed_expr)?;
29609                    } else {
29610                        self.write(" (");
29611                        self.generate_expression(&computed_expr)?;
29612                        self.write(")");
29613                    }
29614                } else {
29615                    self.write_keyword("AS");
29616                    self.write(" (");
29617                    self.generate_expression(&computed_expr)?;
29618                    self.write(")");
29619                }
29620            }
29621        }
29622        Ok(())
29623    }
29624
29625    /// Generate a GeneratedAsRow constraint inline within a column definition.
29626    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29627    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
29628        self.write_keyword("GENERATED ALWAYS AS ROW ");
29629        if gar.start {
29630            self.write_keyword("START");
29631        } else {
29632            self.write_keyword("END");
29633        }
29634        if gar.hidden {
29635            self.write_space();
29636            self.write_keyword("HIDDEN");
29637        }
29638        Ok(())
29639    }
29640
29641    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
29642    fn generate_system_versioning_content(
29643        &mut self,
29644        e: &WithSystemVersioningProperty,
29645    ) -> Result<()> {
29646        let mut parts = Vec::new();
29647
29648        if let Some(this) = &e.this {
29649            let mut s = String::from("HISTORY_TABLE=");
29650            let mut gen = Generator::with_arc_config(self.config.clone());
29651            gen.generate_expression(this)?;
29652            s.push_str(&gen.output);
29653            parts.push(s);
29654        }
29655
29656        if let Some(data_consistency) = &e.data_consistency {
29657            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
29658            let mut gen = Generator::with_arc_config(self.config.clone());
29659            gen.generate_expression(data_consistency)?;
29660            s.push_str(&gen.output);
29661            parts.push(s);
29662        }
29663
29664        if let Some(retention_period) = &e.retention_period {
29665            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
29666            let mut gen = Generator::with_arc_config(self.config.clone());
29667            gen.generate_expression(retention_period)?;
29668            s.push_str(&gen.output);
29669            parts.push(s);
29670        }
29671
29672        self.write_keyword("SYSTEM_VERSIONING");
29673        self.write("=");
29674
29675        if !parts.is_empty() {
29676            self.write_keyword("ON");
29677            self.write("(");
29678            self.write(&parts.join(", "));
29679            self.write(")");
29680        } else if e.on.is_some() {
29681            self.write_keyword("ON");
29682        } else {
29683            self.write_keyword("OFF");
29684        }
29685
29686        Ok(())
29687    }
29688
29689    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
29690        // Conditional INSERT for multi-table inserts
29691        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
29692        if e.else_.is_some() {
29693            self.write_keyword("ELSE");
29694            self.write_space();
29695        } else if let Some(expression) = &e.expression {
29696            self.write_keyword("WHEN");
29697            self.write_space();
29698            self.generate_expression(expression)?;
29699            self.write_space();
29700            self.write_keyword("THEN");
29701            self.write_space();
29702        }
29703
29704        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
29705        // without the "INSERT " prefix
29706        if let Expression::Insert(insert) = e.this.as_ref() {
29707            self.write_keyword("INTO");
29708            self.write_space();
29709            self.generate_table(&insert.table)?;
29710
29711            // Optional column list
29712            if !insert.columns.is_empty() {
29713                self.write(" (");
29714                for (i, col) in insert.columns.iter().enumerate() {
29715                    if i > 0 {
29716                        self.write(", ");
29717                    }
29718                    self.generate_identifier(col)?;
29719                }
29720                self.write(")");
29721            }
29722
29723            // Optional VALUES clause
29724            if !insert.values.is_empty() {
29725                self.write_space();
29726                self.write_keyword("VALUES");
29727                for (row_idx, row) in insert.values.iter().enumerate() {
29728                    if row_idx > 0 {
29729                        self.write(", ");
29730                    }
29731                    self.write(" (");
29732                    for (i, val) in row.iter().enumerate() {
29733                        if i > 0 {
29734                            self.write(", ");
29735                        }
29736                        self.generate_expression(val)?;
29737                    }
29738                    self.write(")");
29739                }
29740            }
29741        } else {
29742            // Fallback for non-Insert expressions
29743            self.generate_expression(&e.this)?;
29744        }
29745        Ok(())
29746    }
29747
29748    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
29749        // Python: return f"CONSTRAINT {this} {expressions}"
29750        self.write_keyword("CONSTRAINT");
29751        self.write_space();
29752        self.generate_expression(&e.this)?;
29753        if !e.expressions.is_empty() {
29754            self.write_space();
29755            for (i, expr) in e.expressions.iter().enumerate() {
29756                if i > 0 {
29757                    self.write_space();
29758                }
29759                self.generate_expression(expr)?;
29760            }
29761        }
29762        Ok(())
29763    }
29764
29765    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
29766        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
29767        self.write_keyword("CONVERT_TIMEZONE");
29768        self.write("(");
29769        let mut first = true;
29770        if let Some(source_tz) = &e.source_tz {
29771            self.generate_expression(source_tz)?;
29772            first = false;
29773        }
29774        if let Some(target_tz) = &e.target_tz {
29775            if !first {
29776                self.write(", ");
29777            }
29778            self.generate_expression(target_tz)?;
29779            first = false;
29780        }
29781        if let Some(timestamp) = &e.timestamp {
29782            if !first {
29783                self.write(", ");
29784            }
29785            self.generate_expression(timestamp)?;
29786        }
29787        self.write(")");
29788        Ok(())
29789    }
29790
29791    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
29792        // CONVERT(this USING dest)
29793        self.write_keyword("CONVERT");
29794        self.write("(");
29795        self.generate_expression(&e.this)?;
29796        if let Some(dest) = &e.dest {
29797            self.write_space();
29798            self.write_keyword("USING");
29799            self.write_space();
29800            self.generate_expression(dest)?;
29801        }
29802        self.write(")");
29803        Ok(())
29804    }
29805
29806    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
29807        self.write_keyword("COPY");
29808        if e.is_into {
29809            self.write_space();
29810            self.write_keyword("INTO");
29811        }
29812        self.write_space();
29813
29814        // Generate target table or query (or stage for COPY INTO @stage)
29815        if let Expression::Literal(lit) = &e.this {
29816            if let Literal::String(s) = lit.as_ref() {
29817                if s.starts_with('@') {
29818                    self.write(s);
29819                } else {
29820                    self.generate_expression(&e.this)?;
29821                }
29822            }
29823        } else {
29824            self.generate_expression(&e.this)?;
29825        }
29826
29827        // FROM or TO based on kind
29828        if e.kind {
29829            // kind=true means FROM (loading into table)
29830            if self.config.pretty {
29831                self.write_newline();
29832            } else {
29833                self.write_space();
29834            }
29835            self.write_keyword("FROM");
29836            self.write_space();
29837        } else if !e.files.is_empty() {
29838            // kind=false means TO (exporting)
29839            if self.config.pretty {
29840                self.write_newline();
29841            } else {
29842                self.write_space();
29843            }
29844            self.write_keyword("TO");
29845            self.write_space();
29846        }
29847
29848        // Generate source/destination files
29849        for (i, file) in e.files.iter().enumerate() {
29850            if i > 0 {
29851                self.write_space();
29852            }
29853            // For stage references (strings starting with @), output without quotes
29854            if let Expression::Literal(lit) = file {
29855                if let Literal::String(s) = lit.as_ref() {
29856                    if s.starts_with('@') {
29857                        self.write(s);
29858                    } else {
29859                        self.generate_expression(file)?;
29860                    }
29861                }
29862            } else if let Expression::Identifier(id) = file {
29863                // Backtick-quoted file path (Databricks style: `s3://link`)
29864                if id.quoted {
29865                    self.write("`");
29866                    self.write(&id.name);
29867                    self.write("`");
29868                } else {
29869                    self.generate_expression(file)?;
29870                }
29871            } else {
29872                self.generate_expression(file)?;
29873            }
29874        }
29875
29876        // Generate credentials if present (Snowflake style - not wrapped in WITH)
29877        if !e.with_wrapped {
29878            if let Some(ref creds) = e.credentials {
29879                if let Some(ref storage) = creds.storage {
29880                    if self.config.pretty {
29881                        self.write_newline();
29882                    } else {
29883                        self.write_space();
29884                    }
29885                    self.write_keyword("STORAGE_INTEGRATION");
29886                    self.write(" = ");
29887                    self.write(storage);
29888                }
29889                if creds.credentials.is_empty() {
29890                    // Empty credentials: CREDENTIALS = ()
29891                    if self.config.pretty {
29892                        self.write_newline();
29893                    } else {
29894                        self.write_space();
29895                    }
29896                    self.write_keyword("CREDENTIALS");
29897                    self.write(" = ()");
29898                } else {
29899                    if self.config.pretty {
29900                        self.write_newline();
29901                    } else {
29902                        self.write_space();
29903                    }
29904                    self.write_keyword("CREDENTIALS");
29905                    // Check if this is Redshift-style (single value with empty key)
29906                    // vs Snowflake-style (multiple key=value pairs)
29907                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
29908                        // Redshift style: CREDENTIALS 'value'
29909                        self.write(" '");
29910                        self.write(&creds.credentials[0].1);
29911                        self.write("'");
29912                    } else {
29913                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
29914                        self.write(" = (");
29915                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
29916                            if i > 0 {
29917                                self.write_space();
29918                            }
29919                            self.write(k);
29920                            self.write("='");
29921                            self.write(v);
29922                            self.write("'");
29923                        }
29924                        self.write(")");
29925                    }
29926                }
29927                if let Some(ref encryption) = creds.encryption {
29928                    self.write_space();
29929                    self.write_keyword("ENCRYPTION");
29930                    self.write(" = ");
29931                    self.write(encryption);
29932                }
29933            }
29934        }
29935
29936        // Generate parameters
29937        if !e.params.is_empty() {
29938            if e.with_wrapped {
29939                // DuckDB/PostgreSQL/TSQL WITH (...) format
29940                self.write_space();
29941                self.write_keyword("WITH");
29942                self.write(" (");
29943                for (i, param) in e.params.iter().enumerate() {
29944                    if i > 0 {
29945                        self.write(", ");
29946                    }
29947                    self.generate_copy_param_with_format(param)?;
29948                }
29949                self.write(")");
29950            } else {
29951                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
29952                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
29953                // For Snowflake: KEY = VALUE
29954                for param in &e.params {
29955                    if self.config.pretty {
29956                        self.write_newline();
29957                    } else {
29958                        self.write_space();
29959                    }
29960                    // Preserve original case of parameter name (important for Redshift COPY options)
29961                    self.write(&param.name);
29962                    if let Some(ref value) = param.value {
29963                        // Use = only if it was present in the original (param.eq)
29964                        if param.eq {
29965                            self.write(" = ");
29966                        } else {
29967                            self.write(" ");
29968                        }
29969                        if !param.values.is_empty() {
29970                            self.write("(");
29971                            for (i, v) in param.values.iter().enumerate() {
29972                                if i > 0 {
29973                                    self.write_space();
29974                                }
29975                                self.generate_copy_nested_param(v)?;
29976                            }
29977                            self.write(")");
29978                        } else {
29979                            // For COPY parameter values, output identifiers without quoting
29980                            self.generate_copy_param_value(value)?;
29981                        }
29982                    } else if !param.values.is_empty() {
29983                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
29984                        if param.eq {
29985                            self.write(" = (");
29986                        } else {
29987                            self.write(" (");
29988                        }
29989                        // Determine separator for values inside parentheses:
29990                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
29991                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
29992                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
29993                        let is_key_value_pairs = param
29994                            .values
29995                            .first()
29996                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
29997                        let sep = if is_key_value_pairs && param.eq {
29998                            " "
29999                        } else {
30000                            ", "
30001                        };
30002                        for (i, v) in param.values.iter().enumerate() {
30003                            if i > 0 {
30004                                self.write(sep);
30005                            }
30006                            self.generate_copy_nested_param(v)?;
30007                        }
30008                        self.write(")");
30009                    }
30010                }
30011            }
30012        }
30013
30014        Ok(())
30015    }
30016
30017    /// Generate a COPY parameter in WITH (...) format
30018    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
30019    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
30020        self.write_keyword(&param.name);
30021        if !param.values.is_empty() {
30022            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
30023            self.write(" = (");
30024            for (i, v) in param.values.iter().enumerate() {
30025                if i > 0 {
30026                    self.write(", ");
30027                }
30028                self.generate_copy_nested_param(v)?;
30029            }
30030            self.write(")");
30031        } else if let Some(ref value) = param.value {
30032            if param.eq {
30033                self.write(" = ");
30034            } else {
30035                self.write(" ");
30036            }
30037            self.generate_expression(value)?;
30038        }
30039        Ok(())
30040    }
30041
30042    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
30043    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
30044        match expr {
30045            Expression::Eq(eq) => {
30046                // Generate key
30047                match &eq.left {
30048                    Expression::Column(c) => self.write(&c.name.name),
30049                    _ => self.generate_expression(&eq.left)?,
30050                }
30051                self.write("=");
30052                // Generate value
30053                match &eq.right {
30054                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30055                        let Literal::String(s) = lit.as_ref() else {
30056                            unreachable!()
30057                        };
30058                        self.write("'");
30059                        self.write(s);
30060                        self.write("'");
30061                    }
30062                    Expression::Tuple(t) => {
30063                        // For lists like NULL_IF=('', 'str1')
30064                        self.write("(");
30065                        if self.config.pretty {
30066                            self.write_newline();
30067                            self.indent_level += 1;
30068                            for (i, item) in t.expressions.iter().enumerate() {
30069                                if i > 0 {
30070                                    self.write(", ");
30071                                }
30072                                self.write_indent();
30073                                self.generate_expression(item)?;
30074                            }
30075                            self.write_newline();
30076                            self.indent_level -= 1;
30077                        } else {
30078                            for (i, item) in t.expressions.iter().enumerate() {
30079                                if i > 0 {
30080                                    self.write(", ");
30081                                }
30082                                self.generate_expression(item)?;
30083                            }
30084                        }
30085                        self.write(")");
30086                    }
30087                    _ => self.generate_expression(&eq.right)?,
30088                }
30089                Ok(())
30090            }
30091            Expression::Column(c) => {
30092                // Standalone keyword like COMPRESSION
30093                self.write(&c.name.name);
30094                Ok(())
30095            }
30096            _ => self.generate_expression(expr),
30097        }
30098    }
30099
30100    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
30101    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
30102    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
30103        match expr {
30104            Expression::Column(c) => {
30105                // Output identifier, preserving quotes if originally quoted
30106                if c.name.quoted {
30107                    self.write("\"");
30108                    self.write(&c.name.name);
30109                    self.write("\"");
30110                } else {
30111                    self.write(&c.name.name);
30112                }
30113                Ok(())
30114            }
30115            Expression::Identifier(id) => {
30116                // Output identifier, preserving quotes if originally quoted
30117                if id.quoted {
30118                    self.write("\"");
30119                    self.write(&id.name);
30120                    self.write("\"");
30121                } else {
30122                    self.write(&id.name);
30123                }
30124                Ok(())
30125            }
30126            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30127                let Literal::String(s) = lit.as_ref() else {
30128                    unreachable!()
30129                };
30130                // Output string with quotes
30131                self.write("'");
30132                self.write(s);
30133                self.write("'");
30134                Ok(())
30135            }
30136            _ => self.generate_expression(expr),
30137        }
30138    }
30139
30140    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
30141        self.write_keyword(&e.name);
30142        if let Some(ref value) = e.value {
30143            if e.eq {
30144                self.write(" = ");
30145            } else {
30146                self.write(" ");
30147            }
30148            self.generate_expression(value)?;
30149        }
30150        if !e.values.is_empty() {
30151            if e.eq {
30152                self.write(" = ");
30153            } else {
30154                self.write(" ");
30155            }
30156            self.write("(");
30157            for (i, v) in e.values.iter().enumerate() {
30158                if i > 0 {
30159                    self.write(", ");
30160                }
30161                self.generate_expression(v)?;
30162            }
30163            self.write(")");
30164        }
30165        Ok(())
30166    }
30167
30168    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
30169        // CORR(this, expression)
30170        self.write_keyword("CORR");
30171        self.write("(");
30172        self.generate_expression(&e.this)?;
30173        self.write(", ");
30174        self.generate_expression(&e.expression)?;
30175        self.write(")");
30176        Ok(())
30177    }
30178
30179    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
30180        // COSINE_DISTANCE(this, expression)
30181        self.write_keyword("COSINE_DISTANCE");
30182        self.write("(");
30183        self.generate_expression(&e.this)?;
30184        self.write(", ");
30185        self.generate_expression(&e.expression)?;
30186        self.write(")");
30187        Ok(())
30188    }
30189
30190    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
30191        // COVAR_POP(this, expression)
30192        self.write_keyword("COVAR_POP");
30193        self.write("(");
30194        self.generate_expression(&e.this)?;
30195        self.write(", ");
30196        self.generate_expression(&e.expression)?;
30197        self.write(")");
30198        Ok(())
30199    }
30200
30201    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
30202        // COVAR_SAMP(this, expression)
30203        self.write_keyword("COVAR_SAMP");
30204        self.write("(");
30205        self.generate_expression(&e.this)?;
30206        self.write(", ");
30207        self.generate_expression(&e.expression)?;
30208        self.write(")");
30209        Ok(())
30210    }
30211
30212    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
30213        // CREDENTIALS (key1='value1', key2='value2')
30214        self.write_keyword("CREDENTIALS");
30215        self.write(" (");
30216        for (i, (key, value)) in e.credentials.iter().enumerate() {
30217            if i > 0 {
30218                self.write(", ");
30219            }
30220            self.write(key);
30221            self.write("='");
30222            self.write(value);
30223            self.write("'");
30224        }
30225        self.write(")");
30226        Ok(())
30227    }
30228
30229    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
30230        // CREDENTIALS=(expressions)
30231        self.write_keyword("CREDENTIALS");
30232        self.write("=(");
30233        for (i, expr) in e.expressions.iter().enumerate() {
30234            if i > 0 {
30235                self.write(", ");
30236            }
30237            self.generate_expression(expr)?;
30238        }
30239        self.write(")");
30240        Ok(())
30241    }
30242
30243    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
30244        use crate::dialects::DialectType;
30245
30246        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
30247        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
30248        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
30249            self.generate_expression(&e.this)?;
30250            self.write_space();
30251            self.write_keyword("AS");
30252            self.write_space();
30253            self.generate_identifier(&e.alias)?;
30254            return Ok(());
30255        }
30256        self.write(&e.alias.name);
30257
30258        // BigQuery doesn't support column aliases in CTE definitions
30259        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
30260
30261        if !e.columns.is_empty() && !skip_cte_columns {
30262            self.write("(");
30263            for (i, col) in e.columns.iter().enumerate() {
30264                if i > 0 {
30265                    self.write(", ");
30266                }
30267                self.write(&col.name);
30268            }
30269            self.write(")");
30270        }
30271        // USING KEY (columns) for DuckDB recursive CTEs
30272        if !e.key_expressions.is_empty() {
30273            self.write_space();
30274            self.write_keyword("USING KEY");
30275            self.write(" (");
30276            for (i, key) in e.key_expressions.iter().enumerate() {
30277                if i > 0 {
30278                    self.write(", ");
30279                }
30280                self.write(&key.name);
30281            }
30282            self.write(")");
30283        }
30284        self.write_space();
30285        self.write_keyword("AS");
30286        self.write_space();
30287        if let Some(materialized) = e.materialized {
30288            if materialized {
30289                self.write_keyword("MATERIALIZED");
30290            } else {
30291                self.write_keyword("NOT MATERIALIZED");
30292            }
30293            self.write_space();
30294        }
30295        self.write("(");
30296        self.generate_expression(&e.this)?;
30297        self.write(")");
30298        Ok(())
30299    }
30300
30301    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
30302        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
30303        if e.expressions.is_empty() {
30304            self.write_keyword("WITH CUBE");
30305        } else {
30306            self.write_keyword("CUBE");
30307            self.write("(");
30308            for (i, expr) in e.expressions.iter().enumerate() {
30309                if i > 0 {
30310                    self.write(", ");
30311                }
30312                self.generate_expression(expr)?;
30313            }
30314            self.write(")");
30315        }
30316        Ok(())
30317    }
30318
30319    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
30320        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
30321        self.write_keyword("CURRENT_DATETIME");
30322        if let Some(this) = &e.this {
30323            self.write("(");
30324            self.generate_expression(this)?;
30325            self.write(")");
30326        }
30327        Ok(())
30328    }
30329
30330    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
30331        // CURRENT_SCHEMA - no arguments
30332        self.write_keyword("CURRENT_SCHEMA");
30333        Ok(())
30334    }
30335
30336    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
30337        // CURRENT_SCHEMAS(include_implicit)
30338        self.write_keyword("CURRENT_SCHEMAS");
30339        self.write("(");
30340        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
30341        if !matches!(
30342            self.config.dialect,
30343            Some(crate::dialects::DialectType::Snowflake)
30344        ) {
30345            if let Some(this) = &e.this {
30346                self.generate_expression(this)?;
30347            }
30348        }
30349        self.write(")");
30350        Ok(())
30351    }
30352
30353    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
30354        // CURRENT_USER or CURRENT_USER()
30355        self.write_keyword("CURRENT_USER");
30356        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
30357        let needs_parens = e.this.is_some()
30358            || matches!(
30359                self.config.dialect,
30360                Some(DialectType::Snowflake)
30361                    | Some(DialectType::Spark)
30362                    | Some(DialectType::Hive)
30363                    | Some(DialectType::DuckDB)
30364                    | Some(DialectType::BigQuery)
30365                    | Some(DialectType::MySQL)
30366                    | Some(DialectType::Databricks)
30367            );
30368        if needs_parens {
30369            self.write("()");
30370        }
30371        Ok(())
30372    }
30373
30374    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
30375        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
30376        if self.config.dialect == Some(DialectType::Solr) {
30377            self.generate_expression(&e.this)?;
30378            self.write(" ");
30379            self.write_keyword("OR");
30380            self.write(" ");
30381            self.generate_expression(&e.expression)?;
30382        } else if self.config.dialect == Some(DialectType::MySQL) {
30383            self.generate_mysql_concat_from_dpipe(e)?;
30384        } else {
30385            // String concatenation: this || expression
30386            self.generate_expression(&e.this)?;
30387            self.write(" || ");
30388            self.generate_expression(&e.expression)?;
30389        }
30390        Ok(())
30391    }
30392
30393    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
30394        // DATABLOCKSIZE=... (Teradata)
30395        self.write_keyword("DATABLOCKSIZE");
30396        self.write("=");
30397        if let Some(size) = e.size {
30398            self.write(&size.to_string());
30399            if let Some(units) = &e.units {
30400                self.write_space();
30401                self.generate_expression(units)?;
30402            }
30403        } else if e.minimum.is_some() {
30404            self.write_keyword("MINIMUM");
30405        } else if e.maximum.is_some() {
30406            self.write_keyword("MAXIMUM");
30407        } else if e.default.is_some() {
30408            self.write_keyword("DEFAULT");
30409        }
30410        Ok(())
30411    }
30412
30413    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
30414        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
30415        self.write_keyword("DATA_DELETION");
30416        self.write("=");
30417
30418        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
30419        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
30420
30421        if is_on {
30422            self.write_keyword("ON");
30423            if has_options {
30424                self.write("(");
30425                let mut first = true;
30426                if let Some(filter_column) = &e.filter_column {
30427                    self.write_keyword("FILTER_COLUMN");
30428                    self.write("=");
30429                    self.generate_expression(filter_column)?;
30430                    first = false;
30431                }
30432                if let Some(retention_period) = &e.retention_period {
30433                    if !first {
30434                        self.write(", ");
30435                    }
30436                    self.write_keyword("RETENTION_PERIOD");
30437                    self.write("=");
30438                    self.generate_expression(retention_period)?;
30439                }
30440                self.write(")");
30441            }
30442        } else {
30443            self.write_keyword("OFF");
30444        }
30445        Ok(())
30446    }
30447
30448    /// Generate a Date function expression
30449    /// For Exasol: {d'value'} -> TO_DATE('value')
30450    /// For other dialects: DATE('value')
30451    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
30452        use crate::dialects::DialectType;
30453        use crate::expressions::Literal;
30454
30455        match self.config.dialect {
30456            // Exasol uses TO_DATE for Date expressions
30457            Some(DialectType::Exasol) => {
30458                self.write_keyword("TO_DATE");
30459                self.write("(");
30460                // Extract the string value from the expression if it's a string literal
30461                match &e.this {
30462                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30463                        let Literal::String(s) = lit.as_ref() else {
30464                            unreachable!()
30465                        };
30466                        self.write("'");
30467                        self.write(s);
30468                        self.write("'");
30469                    }
30470                    _ => {
30471                        self.generate_expression(&e.this)?;
30472                    }
30473                }
30474                self.write(")");
30475            }
30476            // Standard: DATE(value)
30477            _ => {
30478                self.write_keyword("DATE");
30479                self.write("(");
30480                self.generate_expression(&e.this)?;
30481                self.write(")");
30482            }
30483        }
30484        Ok(())
30485    }
30486
30487    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
30488        // DATE_BIN(interval, timestamp[, origin])
30489        self.write_keyword("DATE_BIN");
30490        self.write("(");
30491        self.generate_expression(&e.this)?;
30492        self.write(", ");
30493        self.generate_expression(&e.expression)?;
30494        if let Some(origin) = &e.origin {
30495            self.write(", ");
30496            self.generate_expression(origin)?;
30497        }
30498        self.write(")");
30499        Ok(())
30500    }
30501
30502    fn generate_date_format_column_constraint(
30503        &mut self,
30504        e: &DateFormatColumnConstraint,
30505    ) -> Result<()> {
30506        // FORMAT 'format_string' (Teradata)
30507        self.write_keyword("FORMAT");
30508        self.write_space();
30509        self.generate_expression(&e.this)?;
30510        Ok(())
30511    }
30512
30513    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
30514        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
30515        self.write_keyword("DATE_FROM_PARTS");
30516        self.write("(");
30517        let mut first = true;
30518        if let Some(year) = &e.year {
30519            self.generate_expression(year)?;
30520            first = false;
30521        }
30522        if let Some(month) = &e.month {
30523            if !first {
30524                self.write(", ");
30525            }
30526            self.generate_expression(month)?;
30527            first = false;
30528        }
30529        if let Some(day) = &e.day {
30530            if !first {
30531                self.write(", ");
30532            }
30533            self.generate_expression(day)?;
30534        }
30535        self.write(")");
30536        Ok(())
30537    }
30538
30539    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
30540        // DATETIME(this) or DATETIME(this, expression)
30541        self.write_keyword("DATETIME");
30542        self.write("(");
30543        self.generate_expression(&e.this)?;
30544        if let Some(expr) = &e.expression {
30545            self.write(", ");
30546            self.generate_expression(expr)?;
30547        }
30548        self.write(")");
30549        Ok(())
30550    }
30551
30552    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
30553        // DATETIME_ADD(this, expression, unit)
30554        self.write_keyword("DATETIME_ADD");
30555        self.write("(");
30556        self.generate_expression(&e.this)?;
30557        self.write(", ");
30558        self.generate_expression(&e.expression)?;
30559        if let Some(unit) = &e.unit {
30560            self.write(", ");
30561            self.write_keyword(unit);
30562        }
30563        self.write(")");
30564        Ok(())
30565    }
30566
30567    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
30568        // DATETIME_DIFF(this, expression, unit)
30569        self.write_keyword("DATETIME_DIFF");
30570        self.write("(");
30571        self.generate_expression(&e.this)?;
30572        self.write(", ");
30573        self.generate_expression(&e.expression)?;
30574        if let Some(unit) = &e.unit {
30575            self.write(", ");
30576            self.write_keyword(unit);
30577        }
30578        self.write(")");
30579        Ok(())
30580    }
30581
30582    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
30583        // DATETIME_SUB(this, expression, unit)
30584        self.write_keyword("DATETIME_SUB");
30585        self.write("(");
30586        self.generate_expression(&e.this)?;
30587        self.write(", ");
30588        self.generate_expression(&e.expression)?;
30589        if let Some(unit) = &e.unit {
30590            self.write(", ");
30591            self.write_keyword(unit);
30592        }
30593        self.write(")");
30594        Ok(())
30595    }
30596
30597    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
30598        // DATETIME_TRUNC(this, unit, zone)
30599        self.write_keyword("DATETIME_TRUNC");
30600        self.write("(");
30601        self.generate_expression(&e.this)?;
30602        self.write(", ");
30603        self.write_keyword(&e.unit);
30604        if let Some(zone) = &e.zone {
30605            self.write(", ");
30606            self.generate_expression(zone)?;
30607        }
30608        self.write(")");
30609        Ok(())
30610    }
30611
30612    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
30613        // DAYNAME(this)
30614        self.write_keyword("DAYNAME");
30615        self.write("(");
30616        self.generate_expression(&e.this)?;
30617        self.write(")");
30618        Ok(())
30619    }
30620
30621    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
30622        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
30623        self.write_keyword("DECLARE");
30624        self.write_space();
30625        if e.replace {
30626            self.write_keyword("OR");
30627            self.write_space();
30628            self.write_keyword("REPLACE");
30629            self.write_space();
30630        }
30631        for (i, expr) in e.expressions.iter().enumerate() {
30632            if i > 0 {
30633                self.write(", ");
30634            }
30635            self.generate_expression(expr)?;
30636        }
30637        Ok(())
30638    }
30639
30640    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
30641        use crate::dialects::DialectType;
30642
30643        // variable TYPE [DEFAULT default]
30644        self.generate_expression(&e.this)?;
30645        // BigQuery multi-variable: DECLARE X, Y, Z INT64
30646        for name in &e.additional_names {
30647            self.write(", ");
30648            self.generate_expression(name)?;
30649        }
30650        if let Some(kind) = &e.kind {
30651            self.write_space();
30652            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
30653            // TSQL: Always includes AS (normalization)
30654            // Others: Include AS if present in original
30655            match self.config.dialect {
30656                Some(DialectType::BigQuery) => {
30657                    self.write(kind);
30658                }
30659                Some(DialectType::TSQL) => {
30660                    // TSQL DECLARE: no AS keyword (sqlglot convention)
30661                    // Normalize INT to INTEGER for simple declarations
30662                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
30663                    let is_complex_table = kind.starts_with("TABLE")
30664                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
30665                    if is_complex_table {
30666                        self.write(kind);
30667                    } else if kind == "INT" {
30668                        self.write("INTEGER");
30669                    } else if kind.starts_with("TABLE") {
30670                        // Normalize INT to INTEGER inside simple TABLE column definitions
30671                        let normalized = kind
30672                            .replace(" INT ", " INTEGER ")
30673                            .replace(" INT,", " INTEGER,")
30674                            .replace(" INT)", " INTEGER)")
30675                            .replace("(INT ", "(INTEGER ");
30676                        self.write(&normalized);
30677                    } else {
30678                        self.write(kind);
30679                    }
30680                }
30681                _ => {
30682                    if e.has_as {
30683                        self.write_keyword("AS");
30684                        self.write_space();
30685                    }
30686                    self.write(kind);
30687                }
30688            }
30689        }
30690        if let Some(default) = &e.default {
30691            // BigQuery uses DEFAULT, others use =
30692            match self.config.dialect {
30693                Some(DialectType::BigQuery) => {
30694                    self.write_space();
30695                    self.write_keyword("DEFAULT");
30696                    self.write_space();
30697                }
30698                _ => {
30699                    self.write(" = ");
30700                }
30701            }
30702            self.generate_expression(default)?;
30703        }
30704        Ok(())
30705    }
30706
30707    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
30708        // DECODE(expr, search1, result1, search2, result2, ..., default)
30709        self.write_keyword("DECODE");
30710        self.write("(");
30711        for (i, expr) in e.expressions.iter().enumerate() {
30712            if i > 0 {
30713                self.write(", ");
30714            }
30715            self.generate_expression(expr)?;
30716        }
30717        self.write(")");
30718        Ok(())
30719    }
30720
30721    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
30722        // DECOMPRESS(expr, 'method')
30723        self.write_keyword("DECOMPRESS");
30724        self.write("(");
30725        self.generate_expression(&e.this)?;
30726        self.write(", '");
30727        self.write(&e.method);
30728        self.write("')");
30729        Ok(())
30730    }
30731
30732    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
30733        // DECOMPRESS(expr, 'method')
30734        self.write_keyword("DECOMPRESS");
30735        self.write("(");
30736        self.generate_expression(&e.this)?;
30737        self.write(", '");
30738        self.write(&e.method);
30739        self.write("')");
30740        Ok(())
30741    }
30742
30743    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
30744        // DECRYPT(value, passphrase [, aad [, algorithm]])
30745        self.write_keyword("DECRYPT");
30746        self.write("(");
30747        self.generate_expression(&e.this)?;
30748        if let Some(passphrase) = &e.passphrase {
30749            self.write(", ");
30750            self.generate_expression(passphrase)?;
30751        }
30752        if let Some(aad) = &e.aad {
30753            self.write(", ");
30754            self.generate_expression(aad)?;
30755        }
30756        if let Some(method) = &e.encryption_method {
30757            self.write(", ");
30758            self.generate_expression(method)?;
30759        }
30760        self.write(")");
30761        Ok(())
30762    }
30763
30764    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
30765        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
30766        self.write_keyword("DECRYPT_RAW");
30767        self.write("(");
30768        self.generate_expression(&e.this)?;
30769        if let Some(key) = &e.key {
30770            self.write(", ");
30771            self.generate_expression(key)?;
30772        }
30773        if let Some(iv) = &e.iv {
30774            self.write(", ");
30775            self.generate_expression(iv)?;
30776        }
30777        if let Some(aad) = &e.aad {
30778            self.write(", ");
30779            self.generate_expression(aad)?;
30780        }
30781        if let Some(method) = &e.encryption_method {
30782            self.write(", ");
30783            self.generate_expression(method)?;
30784        }
30785        self.write(")");
30786        Ok(())
30787    }
30788
30789    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
30790        // DEFINER = user
30791        self.write_keyword("DEFINER");
30792        self.write(" = ");
30793        self.generate_expression(&e.this)?;
30794        Ok(())
30795    }
30796
30797    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
30798        // Python: DETACH[DATABASE IF EXISTS] this
30799        self.write_keyword("DETACH");
30800        if e.exists {
30801            self.write_keyword(" DATABASE IF EXISTS");
30802        }
30803        self.write_space();
30804        self.generate_expression(&e.this)?;
30805        Ok(())
30806    }
30807
30808    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
30809        let property_name = match e.this.as_ref() {
30810            Expression::Identifier(id) => id.name.as_str(),
30811            Expression::Var(v) => v.this.as_str(),
30812            _ => "DICTIONARY",
30813        };
30814        self.write_keyword(property_name);
30815        self.write("(");
30816        self.write(&e.kind);
30817        if let Some(settings) = &e.settings {
30818            self.write("(");
30819            if let Expression::Tuple(t) = settings.as_ref() {
30820                if self.config.pretty && !t.expressions.is_empty() {
30821                    self.write_newline();
30822                    self.indent_level += 1;
30823                    for (i, pair) in t.expressions.iter().enumerate() {
30824                        if i > 0 {
30825                            self.write(",");
30826                            self.write_newline();
30827                        }
30828                        self.write_indent();
30829                        if let Expression::Tuple(pair_tuple) = pair {
30830                            if let Some(k) = pair_tuple.expressions.first() {
30831                                self.generate_expression(k)?;
30832                            }
30833                            if let Some(v) = pair_tuple.expressions.get(1) {
30834                                self.write(" ");
30835                                self.generate_expression(v)?;
30836                            }
30837                        } else {
30838                            self.generate_expression(pair)?;
30839                        }
30840                    }
30841                    self.indent_level -= 1;
30842                    self.write_newline();
30843                    self.write_indent();
30844                } else {
30845                    for (i, pair) in t.expressions.iter().enumerate() {
30846                        if i > 0 {
30847                            // ClickHouse dict properties are space-separated, not comma-separated
30848                            self.write(" ");
30849                        }
30850                        if let Expression::Tuple(pair_tuple) = pair {
30851                            if let Some(k) = pair_tuple.expressions.first() {
30852                                self.generate_expression(k)?;
30853                            }
30854                            if let Some(v) = pair_tuple.expressions.get(1) {
30855                                self.write(" ");
30856                                self.generate_expression(v)?;
30857                            }
30858                        } else {
30859                            self.generate_expression(pair)?;
30860                        }
30861                    }
30862                }
30863            } else {
30864                self.generate_expression(settings)?;
30865            }
30866            self.write(")");
30867        } else {
30868            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
30869            self.write("()");
30870        }
30871        self.write(")");
30872        Ok(())
30873    }
30874
30875    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
30876        let property_name = match e.this.as_ref() {
30877            Expression::Identifier(id) => id.name.as_str(),
30878            Expression::Var(v) => v.this.as_str(),
30879            _ => "RANGE",
30880        };
30881        self.write_keyword(property_name);
30882        self.write("(");
30883        if let Some(min) = &e.min {
30884            self.write_keyword("MIN");
30885            self.write_space();
30886            self.generate_expression(min)?;
30887        }
30888        if let Some(max) = &e.max {
30889            self.write_space();
30890            self.write_keyword("MAX");
30891            self.write_space();
30892            self.generate_expression(max)?;
30893        }
30894        self.write(")");
30895        Ok(())
30896    }
30897
30898    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
30899        // Python: {local}DIRECTORY {this}{row_format}
30900        if e.local.is_some() {
30901            self.write_keyword("LOCAL ");
30902        }
30903        self.write_keyword("DIRECTORY");
30904        self.write_space();
30905        self.generate_expression(&e.this)?;
30906        if let Some(row_format) = &e.row_format {
30907            self.write_space();
30908            self.generate_expression(row_format)?;
30909        }
30910        Ok(())
30911    }
30912
30913    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
30914        // Redshift: DISTKEY(column)
30915        self.write_keyword("DISTKEY");
30916        self.write("(");
30917        self.generate_expression(&e.this)?;
30918        self.write(")");
30919        Ok(())
30920    }
30921
30922    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
30923        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
30924        self.write_keyword("DISTSTYLE");
30925        self.write_space();
30926        self.generate_expression(&e.this)?;
30927        Ok(())
30928    }
30929
30930    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
30931        // Python: "DISTRIBUTE BY" expressions
30932        self.write_keyword("DISTRIBUTE BY");
30933        self.write_space();
30934        for (i, expr) in e.expressions.iter().enumerate() {
30935            if i > 0 {
30936                self.write(", ");
30937            }
30938            self.generate_expression(expr)?;
30939        }
30940        Ok(())
30941    }
30942
30943    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
30944        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
30945        self.write_keyword("DISTRIBUTED BY");
30946        self.write_space();
30947        self.write(&e.kind);
30948        if !e.expressions.is_empty() {
30949            self.write(" (");
30950            for (i, expr) in e.expressions.iter().enumerate() {
30951                if i > 0 {
30952                    self.write(", ");
30953                }
30954                self.generate_expression(expr)?;
30955            }
30956            self.write(")");
30957        }
30958        if let Some(buckets) = &e.buckets {
30959            self.write_space();
30960            self.write_keyword("BUCKETS");
30961            self.write_space();
30962            self.generate_expression(buckets)?;
30963        }
30964        if let Some(order) = &e.order {
30965            self.write_space();
30966            self.generate_expression(order)?;
30967        }
30968        Ok(())
30969    }
30970
30971    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
30972        // DOT_PRODUCT(vector1, vector2)
30973        self.write_keyword("DOT_PRODUCT");
30974        self.write("(");
30975        self.generate_expression(&e.this)?;
30976        self.write(", ");
30977        self.generate_expression(&e.expression)?;
30978        self.write(")");
30979        Ok(())
30980    }
30981
30982    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
30983        // Python: DROP{IF EXISTS }expressions
30984        self.write_keyword("DROP");
30985        if e.exists {
30986            self.write_keyword(" IF EXISTS ");
30987        } else {
30988            self.write_space();
30989        }
30990        for (i, expr) in e.expressions.iter().enumerate() {
30991            if i > 0 {
30992                self.write(", ");
30993            }
30994            self.generate_expression(expr)?;
30995        }
30996        Ok(())
30997    }
30998
30999    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
31000        // Python: DUPLICATE KEY (expressions)
31001        self.write_keyword("DUPLICATE KEY");
31002        self.write(" (");
31003        for (i, expr) in e.expressions.iter().enumerate() {
31004            if i > 0 {
31005                self.write(", ");
31006            }
31007            self.generate_expression(expr)?;
31008        }
31009        self.write(")");
31010        Ok(())
31011    }
31012
31013    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
31014        // ELT(index, str1, str2, ...)
31015        self.write_keyword("ELT");
31016        self.write("(");
31017        self.generate_expression(&e.this)?;
31018        for expr in &e.expressions {
31019            self.write(", ");
31020            self.generate_expression(expr)?;
31021        }
31022        self.write(")");
31023        Ok(())
31024    }
31025
31026    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
31027        // ENCODE(string, charset)
31028        self.write_keyword("ENCODE");
31029        self.write("(");
31030        self.generate_expression(&e.this)?;
31031        if let Some(charset) = &e.charset {
31032            self.write(", ");
31033            self.generate_expression(charset)?;
31034        }
31035        self.write(")");
31036        Ok(())
31037    }
31038
31039    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
31040        // Python: [KEY ]ENCODE this [properties]
31041        if e.key.is_some() {
31042            self.write_keyword("KEY ");
31043        }
31044        self.write_keyword("ENCODE");
31045        self.write_space();
31046        self.generate_expression(&e.this)?;
31047        if !e.properties.is_empty() {
31048            self.write(" (");
31049            for (i, prop) in e.properties.iter().enumerate() {
31050                if i > 0 {
31051                    self.write(", ");
31052                }
31053                self.generate_expression(prop)?;
31054            }
31055            self.write(")");
31056        }
31057        Ok(())
31058    }
31059
31060    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
31061        // ENCRYPT(value, passphrase [, aad [, algorithm]])
31062        self.write_keyword("ENCRYPT");
31063        self.write("(");
31064        self.generate_expression(&e.this)?;
31065        if let Some(passphrase) = &e.passphrase {
31066            self.write(", ");
31067            self.generate_expression(passphrase)?;
31068        }
31069        if let Some(aad) = &e.aad {
31070            self.write(", ");
31071            self.generate_expression(aad)?;
31072        }
31073        if let Some(method) = &e.encryption_method {
31074            self.write(", ");
31075            self.generate_expression(method)?;
31076        }
31077        self.write(")");
31078        Ok(())
31079    }
31080
31081    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
31082        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
31083        self.write_keyword("ENCRYPT_RAW");
31084        self.write("(");
31085        self.generate_expression(&e.this)?;
31086        if let Some(key) = &e.key {
31087            self.write(", ");
31088            self.generate_expression(key)?;
31089        }
31090        if let Some(iv) = &e.iv {
31091            self.write(", ");
31092            self.generate_expression(iv)?;
31093        }
31094        if let Some(aad) = &e.aad {
31095            self.write(", ");
31096            self.generate_expression(aad)?;
31097        }
31098        if let Some(method) = &e.encryption_method {
31099            self.write(", ");
31100            self.generate_expression(method)?;
31101        }
31102        self.write(")");
31103        Ok(())
31104    }
31105
31106    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
31107        // MySQL: ENGINE = InnoDB
31108        self.write_keyword("ENGINE");
31109        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
31110            self.write("=");
31111        } else {
31112            self.write(" = ");
31113        }
31114        self.generate_expression(&e.this)?;
31115        Ok(())
31116    }
31117
31118    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
31119        // ENVIRONMENT (expressions)
31120        self.write_keyword("ENVIRONMENT");
31121        self.write(" (");
31122        for (i, expr) in e.expressions.iter().enumerate() {
31123            if i > 0 {
31124                self.write(", ");
31125            }
31126            self.generate_expression(expr)?;
31127        }
31128        self.write(")");
31129        Ok(())
31130    }
31131
31132    fn generate_ephemeral_column_constraint(
31133        &mut self,
31134        e: &EphemeralColumnConstraint,
31135    ) -> Result<()> {
31136        // MySQL: EPHEMERAL [expr]
31137        self.write_keyword("EPHEMERAL");
31138        if let Some(this) = &e.this {
31139            self.write_space();
31140            self.generate_expression(this)?;
31141        }
31142        Ok(())
31143    }
31144
31145    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
31146        // Snowflake: EQUAL_NULL(a, b)
31147        self.write_keyword("EQUAL_NULL");
31148        self.write("(");
31149        self.generate_expression(&e.this)?;
31150        self.write(", ");
31151        self.generate_expression(&e.expression)?;
31152        self.write(")");
31153        Ok(())
31154    }
31155
31156    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
31157        use crate::dialects::DialectType;
31158
31159        // PostgreSQL uses <-> operator syntax
31160        match self.config.dialect {
31161            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
31162                self.generate_expression(&e.this)?;
31163                self.write(" <-> ");
31164                self.generate_expression(&e.expression)?;
31165            }
31166            _ => {
31167                // Other dialects use EUCLIDEAN_DISTANCE function
31168                self.write_keyword("EUCLIDEAN_DISTANCE");
31169                self.write("(");
31170                self.generate_expression(&e.this)?;
31171                self.write(", ");
31172                self.generate_expression(&e.expression)?;
31173                self.write(")");
31174            }
31175        }
31176        Ok(())
31177    }
31178
31179    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
31180        // EXECUTE AS CALLER|OWNER|user
31181        self.write_keyword("EXECUTE AS");
31182        self.write_space();
31183        self.generate_expression(&e.this)?;
31184        Ok(())
31185    }
31186
31187    fn generate_export(&mut self, e: &Export) -> Result<()> {
31188        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
31189        self.write_keyword("EXPORT DATA");
31190        if let Some(connection) = &e.connection {
31191            self.write_space();
31192            self.write_keyword("WITH CONNECTION");
31193            self.write_space();
31194            self.generate_expression(connection)?;
31195        }
31196        if !e.options.is_empty() {
31197            self.write_space();
31198            self.generate_options_clause(&e.options)?;
31199        }
31200        self.write_space();
31201        self.write_keyword("AS");
31202        self.write_space();
31203        self.generate_expression(&e.this)?;
31204        Ok(())
31205    }
31206
31207    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
31208        // EXTERNAL [this]
31209        self.write_keyword("EXTERNAL");
31210        if let Some(this) = &e.this {
31211            self.write_space();
31212            self.generate_expression(this)?;
31213        }
31214        Ok(())
31215    }
31216
31217    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
31218        // Python: {no}FALLBACK{protection}
31219        if e.no.is_some() {
31220            self.write_keyword("NO ");
31221        }
31222        self.write_keyword("FALLBACK");
31223        if e.protection.is_some() {
31224            self.write_keyword(" PROTECTION");
31225        }
31226        Ok(())
31227    }
31228
31229    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
31230        // BigQuery: FARM_FINGERPRINT(value)
31231        self.write_keyword("FARM_FINGERPRINT");
31232        self.write("(");
31233        for (i, expr) in e.expressions.iter().enumerate() {
31234            if i > 0 {
31235                self.write(", ");
31236            }
31237            self.generate_expression(expr)?;
31238        }
31239        self.write(")");
31240        Ok(())
31241    }
31242
31243    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
31244        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
31245        self.write_keyword("FEATURES_AT_TIME");
31246        self.write("(");
31247        self.generate_expression(&e.this)?;
31248        if let Some(time) = &e.time {
31249            self.write(", ");
31250            self.generate_expression(time)?;
31251        }
31252        if let Some(num_rows) = &e.num_rows {
31253            self.write(", ");
31254            self.generate_expression(num_rows)?;
31255        }
31256        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
31257            self.write(", ");
31258            self.generate_expression(ignore_nulls)?;
31259        }
31260        self.write(")");
31261        Ok(())
31262    }
31263
31264    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
31265        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
31266        let use_limit = !e.percent
31267            && !e.with_ties
31268            && e.count.is_some()
31269            && matches!(
31270                self.config.dialect,
31271                Some(DialectType::Spark)
31272                    | Some(DialectType::Hive)
31273                    | Some(DialectType::DuckDB)
31274                    | Some(DialectType::SQLite)
31275                    | Some(DialectType::MySQL)
31276                    | Some(DialectType::BigQuery)
31277                    | Some(DialectType::Databricks)
31278                    | Some(DialectType::StarRocks)
31279                    | Some(DialectType::Doris)
31280                    | Some(DialectType::Athena)
31281                    | Some(DialectType::ClickHouse)
31282            );
31283
31284        if use_limit {
31285            self.write_keyword("LIMIT");
31286            self.write_space();
31287            self.generate_expression(e.count.as_ref().unwrap())?;
31288            return Ok(());
31289        }
31290
31291        // Python: FETCH direction count limit_options
31292        self.write_keyword("FETCH");
31293        if !e.direction.is_empty() {
31294            self.write_space();
31295            self.write_keyword(&e.direction);
31296        }
31297        if let Some(count) = &e.count {
31298            self.write_space();
31299            self.generate_expression(count)?;
31300        }
31301        // Generate PERCENT, ROWS, WITH TIES/ONLY
31302        if e.percent {
31303            self.write_keyword(" PERCENT");
31304        }
31305        if e.rows {
31306            self.write_keyword(" ROWS");
31307        }
31308        if e.with_ties {
31309            self.write_keyword(" WITH TIES");
31310        } else if e.rows {
31311            self.write_keyword(" ONLY");
31312        } else {
31313            self.write_keyword(" ROWS ONLY");
31314        }
31315        Ok(())
31316    }
31317
31318    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
31319        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
31320        // For Spark/Databricks without hive_format: USING this
31321        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
31322        if e.hive_format.is_some() {
31323            // Hive format: STORED AS ...
31324            self.write_keyword("STORED AS");
31325            self.write_space();
31326            if let Some(this) = &e.this {
31327                // Uppercase the format name (e.g., parquet -> PARQUET)
31328                if let Expression::Identifier(id) = this.as_ref() {
31329                    self.write_keyword(&id.name.to_ascii_uppercase());
31330                } else {
31331                    self.generate_expression(this)?;
31332                }
31333            }
31334        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
31335            // Hive: STORED AS format
31336            self.write_keyword("STORED AS");
31337            self.write_space();
31338            if let Some(this) = &e.this {
31339                if let Expression::Identifier(id) = this.as_ref() {
31340                    self.write_keyword(&id.name.to_ascii_uppercase());
31341                } else {
31342                    self.generate_expression(this)?;
31343                }
31344            }
31345        } else if matches!(
31346            self.config.dialect,
31347            Some(DialectType::Spark) | Some(DialectType::Databricks)
31348        ) {
31349            // Spark/Databricks: USING format (e.g., USING DELTA)
31350            self.write_keyword("USING");
31351            self.write_space();
31352            if let Some(this) = &e.this {
31353                self.generate_expression(this)?;
31354            }
31355        } else {
31356            // Snowflake/standard format
31357            self.write_keyword("FILE_FORMAT");
31358            self.write(" = ");
31359            if let Some(this) = &e.this {
31360                self.generate_expression(this)?;
31361            } else if !e.expressions.is_empty() {
31362                self.write("(");
31363                for (i, expr) in e.expressions.iter().enumerate() {
31364                    if i > 0 {
31365                        self.write(", ");
31366                    }
31367                    self.generate_expression(expr)?;
31368                }
31369                self.write(")");
31370            }
31371        }
31372        Ok(())
31373    }
31374
31375    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
31376        // agg_func FILTER(WHERE condition)
31377        self.generate_expression(&e.this)?;
31378        self.write_space();
31379        self.write_keyword("FILTER");
31380        self.write("(");
31381        self.write_keyword("WHERE");
31382        self.write_space();
31383        self.generate_expression(&e.expression)?;
31384        self.write(")");
31385        Ok(())
31386    }
31387
31388    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
31389        // FLOAT64(this) or FLOAT64(this, expression)
31390        self.write_keyword("FLOAT64");
31391        self.write("(");
31392        self.generate_expression(&e.this)?;
31393        if let Some(expr) = &e.expression {
31394            self.write(", ");
31395            self.generate_expression(expr)?;
31396        }
31397        self.write(")");
31398        Ok(())
31399    }
31400
31401    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
31402        // FOR this DO expression
31403        self.write_keyword("FOR");
31404        self.write_space();
31405        self.generate_expression(&e.this)?;
31406        self.write_space();
31407        self.write_keyword("DO");
31408        self.write_space();
31409        self.generate_expression(&e.expression)?;
31410        Ok(())
31411    }
31412
31413    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
31414        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
31415        self.write_keyword("FOREIGN KEY");
31416        if !e.expressions.is_empty() {
31417            self.write(" (");
31418            for (i, expr) in e.expressions.iter().enumerate() {
31419                if i > 0 {
31420                    self.write(", ");
31421                }
31422                self.generate_expression(expr)?;
31423            }
31424            self.write(")");
31425        }
31426        if let Some(reference) = &e.reference {
31427            self.write_space();
31428            self.generate_expression(reference)?;
31429        }
31430        if let Some(delete) = &e.delete {
31431            self.write_space();
31432            self.write_keyword("ON DELETE");
31433            self.write_space();
31434            self.generate_expression(delete)?;
31435        }
31436        if let Some(update) = &e.update {
31437            self.write_space();
31438            self.write_keyword("ON UPDATE");
31439            self.write_space();
31440            self.generate_expression(update)?;
31441        }
31442        if !e.options.is_empty() {
31443            self.write_space();
31444            for (i, opt) in e.options.iter().enumerate() {
31445                if i > 0 {
31446                    self.write_space();
31447                }
31448                self.generate_expression(opt)?;
31449            }
31450        }
31451        Ok(())
31452    }
31453
31454    fn generate_format(&mut self, e: &Format) -> Result<()> {
31455        // FORMAT(this, expressions...)
31456        self.write_keyword("FORMAT");
31457        self.write("(");
31458        self.generate_expression(&e.this)?;
31459        for expr in &e.expressions {
31460            self.write(", ");
31461            self.generate_expression(expr)?;
31462        }
31463        self.write(")");
31464        Ok(())
31465    }
31466
31467    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
31468        // Teradata: column (FORMAT 'format_string')
31469        self.generate_expression(&e.this)?;
31470        self.write(" (");
31471        self.write_keyword("FORMAT");
31472        self.write(" '");
31473        self.write(&e.format);
31474        self.write("')");
31475        Ok(())
31476    }
31477
31478    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
31479        // Python: FREESPACE=this[PERCENT]
31480        self.write_keyword("FREESPACE");
31481        self.write("=");
31482        self.generate_expression(&e.this)?;
31483        if e.percent.is_some() {
31484            self.write_keyword(" PERCENT");
31485        }
31486        Ok(())
31487    }
31488
31489    fn generate_from(&mut self, e: &From) -> Result<()> {
31490        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
31491        self.write_keyword("FROM");
31492        self.write_space();
31493
31494        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
31495        // But keep commas when TABLESAMPLE is present
31496        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
31497        use crate::dialects::DialectType;
31498        let has_tablesample = e
31499            .expressions
31500            .iter()
31501            .any(|expr| matches!(expr, Expression::TableSample(_)));
31502        let is_cross_join_dialect = matches!(
31503            self.config.dialect,
31504            Some(DialectType::BigQuery)
31505                | Some(DialectType::Hive)
31506                | Some(DialectType::Spark)
31507                | Some(DialectType::Databricks)
31508                | Some(DialectType::SQLite)
31509                | Some(DialectType::ClickHouse)
31510        );
31511        let source_is_same_as_target2 = self.config.source_dialect.is_some()
31512            && self.config.source_dialect == self.config.dialect;
31513        let source_is_cross_join_dialect2 = matches!(
31514            self.config.source_dialect,
31515            Some(DialectType::BigQuery)
31516                | Some(DialectType::Hive)
31517                | Some(DialectType::Spark)
31518                | Some(DialectType::Databricks)
31519                | Some(DialectType::SQLite)
31520                | Some(DialectType::ClickHouse)
31521        );
31522        let use_cross_join = !has_tablesample
31523            && is_cross_join_dialect
31524            && (source_is_same_as_target2
31525                || source_is_cross_join_dialect2
31526                || self.config.source_dialect.is_none());
31527
31528        // Generic SQL and VALUES-as-derived-table dialects wrap standalone VALUES
31529        // table constructors in parentheses.
31530        let wrap_values_in_parens = self.config.dialect.is_none()
31531            || matches!(
31532                self.config.dialect,
31533                Some(
31534                    DialectType::Generic
31535                        | DialectType::Snowflake
31536                        | DialectType::Presto
31537                        | DialectType::Trino
31538                        | DialectType::Athena
31539                )
31540            );
31541
31542        for (i, expr) in e.expressions.iter().enumerate() {
31543            if i > 0 {
31544                if use_cross_join {
31545                    self.write(" CROSS JOIN ");
31546                } else {
31547                    self.write(", ");
31548                }
31549            }
31550            self.generate_from_source(expr, wrap_values_in_parens)?;
31551            // Output leading comments that were on the table name before FROM
31552            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
31553            let leading = Self::extract_table_leading_comments(expr);
31554            for comment in &leading {
31555                self.write_space();
31556                self.write_formatted_comment(comment);
31557            }
31558        }
31559        Ok(())
31560    }
31561
31562    fn generate_from_source(&mut self, expr: &Expression, wrap_values: bool) -> Result<()> {
31563        if wrap_values {
31564            if let Expression::Values(values) = expr {
31565                let mut inner_values = values.as_ref().clone();
31566                let alias = inner_values.alias.take();
31567                let column_aliases = std::mem::take(&mut inner_values.column_aliases);
31568
31569                self.write("(");
31570                self.generate_values(&inner_values)?;
31571                self.write(")");
31572
31573                if let Some(alias) = alias {
31574                    self.write_space();
31575                    self.write_keyword("AS");
31576                    self.write_space();
31577                    self.generate_identifier(&alias)?;
31578                    self.generate_alias_column_list(&column_aliases)?;
31579                }
31580                return Ok(());
31581            }
31582        }
31583
31584        self.generate_expression(expr)
31585    }
31586
31587    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
31588    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
31589        match expr {
31590            Expression::Table(t) => t.leading_comments.clone(),
31591            Expression::Pivot(p) => {
31592                if let Expression::Table(t) = &p.this {
31593                    t.leading_comments.clone()
31594                } else {
31595                    Vec::new()
31596                }
31597            }
31598            _ => Vec::new(),
31599        }
31600    }
31601
31602    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
31603        // FROM_BASE(this, expression) - convert from base N
31604        self.write_keyword("FROM_BASE");
31605        self.write("(");
31606        self.generate_expression(&e.this)?;
31607        self.write(", ");
31608        self.generate_expression(&e.expression)?;
31609        self.write(")");
31610        Ok(())
31611    }
31612
31613    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
31614        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
31615        self.generate_expression(&e.this)?;
31616        if let Some(zone) = &e.zone {
31617            self.write_space();
31618            self.write_keyword("AT TIME ZONE");
31619            self.write_space();
31620            self.generate_expression(zone)?;
31621            self.write_space();
31622            self.write_keyword("AT TIME ZONE");
31623            self.write(" 'UTC'");
31624        }
31625        Ok(())
31626    }
31627
31628    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
31629        // GAP_FILL(this, ts_column, bucket_width, ...)
31630        self.write_keyword("GAP_FILL");
31631        self.write("(");
31632        self.generate_expression(&e.this)?;
31633        if let Some(ts_column) = &e.ts_column {
31634            self.write(", ");
31635            self.generate_expression(ts_column)?;
31636        }
31637        if let Some(bucket_width) = &e.bucket_width {
31638            self.write(", ");
31639            self.generate_expression(bucket_width)?;
31640        }
31641        if let Some(partitioning_columns) = &e.partitioning_columns {
31642            self.write(", ");
31643            self.generate_expression(partitioning_columns)?;
31644        }
31645        if let Some(value_columns) = &e.value_columns {
31646            self.write(", ");
31647            self.generate_expression(value_columns)?;
31648        }
31649        self.write(")");
31650        Ok(())
31651    }
31652
31653    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
31654        // GENERATE_DATE_ARRAY(start, end, step)
31655        self.write_keyword("GENERATE_DATE_ARRAY");
31656        self.write("(");
31657        let mut first = true;
31658        if let Some(start) = &e.start {
31659            self.generate_expression(start)?;
31660            first = false;
31661        }
31662        if let Some(end) = &e.end {
31663            if !first {
31664                self.write(", ");
31665            }
31666            self.generate_expression(end)?;
31667            first = false;
31668        }
31669        if let Some(step) = &e.step {
31670            if !first {
31671                self.write(", ");
31672            }
31673            self.generate_expression(step)?;
31674        }
31675        self.write(")");
31676        Ok(())
31677    }
31678
31679    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
31680        // ML.GENERATE_EMBEDDING(model, content, params)
31681        self.write_keyword("ML.GENERATE_EMBEDDING");
31682        self.write("(");
31683        self.generate_expression(&e.this)?;
31684        self.write(", ");
31685        self.generate_expression(&e.expression)?;
31686        if let Some(params) = &e.params_struct {
31687            self.write(", ");
31688            self.generate_expression(params)?;
31689        }
31690        self.write(")");
31691        Ok(())
31692    }
31693
31694    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
31695        // Dialect-specific function name
31696        let fn_name = match self.config.dialect {
31697            Some(DialectType::Presto)
31698            | Some(DialectType::Trino)
31699            | Some(DialectType::Athena)
31700            | Some(DialectType::Spark)
31701            | Some(DialectType::Databricks)
31702            | Some(DialectType::Hive) => "SEQUENCE",
31703            _ => "GENERATE_SERIES",
31704        };
31705        self.write_keyword(fn_name);
31706        self.write("(");
31707        let mut first = true;
31708        if let Some(start) = &e.start {
31709            self.generate_expression(start)?;
31710            first = false;
31711        }
31712        if let Some(end) = &e.end {
31713            if !first {
31714                self.write(", ");
31715            }
31716            self.generate_expression(end)?;
31717            first = false;
31718        }
31719        if let Some(step) = &e.step {
31720            if !first {
31721                self.write(", ");
31722            }
31723            // For Presto/Trino: convert WEEK intervals to DAY multiples
31724            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
31725            if matches!(
31726                self.config.dialect,
31727                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
31728            ) {
31729                if let Some(converted) = self.convert_week_interval_to_day(step) {
31730                    self.generate_expression(&converted)?;
31731                } else {
31732                    self.generate_expression(step)?;
31733                }
31734            } else {
31735                self.generate_expression(step)?;
31736            }
31737        }
31738        self.write(")");
31739        Ok(())
31740    }
31741
31742    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
31743    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
31744    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
31745        use crate::expressions::*;
31746        if let Expression::Interval(ref iv) = expr {
31747            // Check for structured WEEK unit
31748            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
31749                unit: IntervalUnit::Week,
31750                ..
31751            }) = &iv.unit
31752            {
31753                // Value is in iv.this
31754                let count = match &iv.this {
31755                    Some(Expression::Literal(lit)) => match lit.as_ref() {
31756                        Literal::String(s) | Literal::Number(s) => s.clone(),
31757                        _ => return None,
31758                    },
31759                    _ => return None,
31760                };
31761                (true, count)
31762            } else if iv.unit.is_none() {
31763                // Check for string-encoded interval like "1 WEEK"
31764                if let Some(Expression::Literal(lit)) = &iv.this {
31765                    if let Literal::String(s) = lit.as_ref() {
31766                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
31767                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
31768                            (true, parts[0].to_string())
31769                        } else {
31770                            (false, String::new())
31771                        }
31772                    } else {
31773                        (false, String::new())
31774                    }
31775                } else {
31776                    (false, String::new())
31777                }
31778            } else {
31779                (false, String::new())
31780            };
31781
31782            if is_week {
31783                // Build: (N * INTERVAL '7' DAY)
31784                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
31785                let day_interval = Expression::Interval(Box::new(Interval {
31786                    this: Some(Expression::Literal(Box::new(Literal::String(
31787                        "7".to_string(),
31788                    )))),
31789                    unit: Some(IntervalUnitSpec::Simple {
31790                        unit: IntervalUnit::Day,
31791                        use_plural: false,
31792                    }),
31793                }));
31794                let mul = Expression::Mul(Box::new(BinaryOp {
31795                    left: count_expr,
31796                    right: day_interval,
31797                    left_comments: vec![],
31798                    operator_comments: vec![],
31799                    trailing_comments: vec![],
31800                    inferred_type: None,
31801                }));
31802                return Some(Expression::Paren(Box::new(Paren {
31803                    this: mul,
31804                    trailing_comments: vec![],
31805                })));
31806            }
31807        }
31808        None
31809    }
31810
31811    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
31812        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
31813        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
31814        self.write("(");
31815        let mut first = true;
31816        if let Some(start) = &e.start {
31817            self.generate_expression(start)?;
31818            first = false;
31819        }
31820        if let Some(end) = &e.end {
31821            if !first {
31822                self.write(", ");
31823            }
31824            self.generate_expression(end)?;
31825            first = false;
31826        }
31827        if let Some(step) = &e.step {
31828            if !first {
31829                self.write(", ");
31830            }
31831            self.generate_expression(step)?;
31832        }
31833        self.write(")");
31834        Ok(())
31835    }
31836
31837    fn generate_generated_as_identity_column_constraint(
31838        &mut self,
31839        e: &GeneratedAsIdentityColumnConstraint,
31840    ) -> Result<()> {
31841        use crate::dialects::DialectType;
31842
31843        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
31844        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31845            self.write_keyword("AUTOINCREMENT");
31846            if let Some(start) = &e.start {
31847                self.write_keyword(" START ");
31848                self.generate_expression(start)?;
31849            }
31850            if let Some(increment) = &e.increment {
31851                self.write_keyword(" INCREMENT ");
31852                self.generate_expression(increment)?;
31853            }
31854            return Ok(());
31855        }
31856
31857        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
31858        self.write_keyword("GENERATED");
31859        if let Some(this) = &e.this {
31860            // Check if it's a truthy boolean expression
31861            if let Expression::Boolean(b) = this.as_ref() {
31862                if b.value {
31863                    self.write_keyword(" ALWAYS");
31864                } else {
31865                    self.write_keyword(" BY DEFAULT");
31866                    if e.on_null.is_some() {
31867                        self.write_keyword(" ON NULL");
31868                    }
31869                }
31870            } else {
31871                self.write_keyword(" ALWAYS");
31872            }
31873        }
31874        self.write_keyword(" AS IDENTITY");
31875        // Add sequence options if any
31876        let has_options = e.start.is_some()
31877            || e.increment.is_some()
31878            || e.minvalue.is_some()
31879            || e.maxvalue.is_some();
31880        if has_options {
31881            self.write(" (");
31882            let mut first = true;
31883            if let Some(start) = &e.start {
31884                self.write_keyword("START WITH ");
31885                self.generate_expression(start)?;
31886                first = false;
31887            }
31888            if let Some(increment) = &e.increment {
31889                if !first {
31890                    self.write(" ");
31891                }
31892                self.write_keyword("INCREMENT BY ");
31893                self.generate_expression(increment)?;
31894                first = false;
31895            }
31896            if let Some(minvalue) = &e.minvalue {
31897                if !first {
31898                    self.write(" ");
31899                }
31900                self.write_keyword("MINVALUE ");
31901                self.generate_expression(minvalue)?;
31902                first = false;
31903            }
31904            if let Some(maxvalue) = &e.maxvalue {
31905                if !first {
31906                    self.write(" ");
31907                }
31908                self.write_keyword("MAXVALUE ");
31909                self.generate_expression(maxvalue)?;
31910            }
31911            self.write(")");
31912        }
31913        Ok(())
31914    }
31915
31916    fn generate_generated_as_row_column_constraint(
31917        &mut self,
31918        e: &GeneratedAsRowColumnConstraint,
31919    ) -> Result<()> {
31920        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
31921        self.write_keyword("GENERATED ALWAYS AS ROW ");
31922        if e.start.is_some() {
31923            self.write_keyword("START");
31924        } else {
31925            self.write_keyword("END");
31926        }
31927        if e.hidden.is_some() {
31928            self.write_keyword(" HIDDEN");
31929        }
31930        Ok(())
31931    }
31932
31933    fn generate_get(&mut self, e: &Get) -> Result<()> {
31934        // GET this target properties
31935        self.write_keyword("GET");
31936        self.write_space();
31937        self.generate_expression(&e.this)?;
31938        if let Some(target) = &e.target {
31939            self.write_space();
31940            self.generate_expression(target)?;
31941        }
31942        for prop in &e.properties {
31943            self.write_space();
31944            self.generate_expression(prop)?;
31945        }
31946        Ok(())
31947    }
31948
31949    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
31950        // GetExtract generates bracket access: this[expression]
31951        self.generate_expression(&e.this)?;
31952        self.write("[");
31953        self.generate_expression(&e.expression)?;
31954        self.write("]");
31955        Ok(())
31956    }
31957
31958    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
31959        // GETBIT(this, expression) or GET_BIT(this, expression)
31960        self.write_keyword("GETBIT");
31961        self.write("(");
31962        self.generate_expression(&e.this)?;
31963        self.write(", ");
31964        self.generate_expression(&e.expression)?;
31965        self.write(")");
31966        Ok(())
31967    }
31968
31969    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
31970        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
31971        if e.is_role {
31972            self.write_keyword("ROLE");
31973            self.write_space();
31974        } else if e.is_group {
31975            self.write_keyword("GROUP");
31976            self.write_space();
31977        } else if e.is_share {
31978            self.write_keyword("SHARE");
31979            self.write_space();
31980        }
31981        self.write(&e.name.name);
31982        Ok(())
31983    }
31984
31985    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
31986        // privilege(columns) or just privilege
31987        self.generate_expression(&e.this)?;
31988        if !e.expressions.is_empty() {
31989            self.write("(");
31990            for (i, expr) in e.expressions.iter().enumerate() {
31991                if i > 0 {
31992                    self.write(", ");
31993                }
31994                self.generate_expression(expr)?;
31995            }
31996            self.write(")");
31997        }
31998        Ok(())
31999    }
32000
32001    fn generate_group(&mut self, e: &Group) -> Result<()> {
32002        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
32003        self.write_keyword("GROUP BY");
32004        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
32005        match e.all {
32006            Some(true) => {
32007                self.write_space();
32008                self.write_keyword("ALL");
32009            }
32010            Some(false) => {
32011                self.write_space();
32012                self.write_keyword("DISTINCT");
32013            }
32014            None => {}
32015        }
32016        if !e.expressions.is_empty() {
32017            self.write_space();
32018            for (i, expr) in e.expressions.iter().enumerate() {
32019                if i > 0 {
32020                    self.write(", ");
32021                }
32022                self.generate_expression(expr)?;
32023            }
32024        }
32025        // Handle CUBE, ROLLUP, GROUPING SETS
32026        if let Some(cube) = &e.cube {
32027            if !e.expressions.is_empty() {
32028                self.write(", ");
32029            } else {
32030                self.write_space();
32031            }
32032            self.generate_expression(cube)?;
32033        }
32034        if let Some(rollup) = &e.rollup {
32035            if !e.expressions.is_empty() || e.cube.is_some() {
32036                self.write(", ");
32037            } else {
32038                self.write_space();
32039            }
32040            self.generate_expression(rollup)?;
32041        }
32042        if let Some(grouping_sets) = &e.grouping_sets {
32043            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
32044                self.write(", ");
32045            } else {
32046                self.write_space();
32047            }
32048            self.generate_expression(grouping_sets)?;
32049        }
32050        if let Some(totals) = &e.totals {
32051            self.write_space();
32052            self.write_keyword("WITH TOTALS");
32053            self.generate_expression(totals)?;
32054        }
32055        Ok(())
32056    }
32057
32058    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
32059        // GROUP BY expressions
32060        self.write_keyword("GROUP BY");
32061        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
32062        match e.all {
32063            Some(true) => {
32064                self.write_space();
32065                self.write_keyword("ALL");
32066            }
32067            Some(false) => {
32068                self.write_space();
32069                self.write_keyword("DISTINCT");
32070            }
32071            None => {}
32072        }
32073
32074        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
32075        // These are represented as Cube/Rollup expressions with empty expressions at the end
32076        let mut trailing_cube = false;
32077        let mut trailing_rollup = false;
32078        let mut regular_expressions: Vec<&Expression> = Vec::new();
32079
32080        for expr in &e.expressions {
32081            match expr {
32082                Expression::Cube(c) if c.expressions.is_empty() => {
32083                    trailing_cube = true;
32084                }
32085                Expression::Rollup(r) if r.expressions.is_empty() => {
32086                    trailing_rollup = true;
32087                }
32088                _ => {
32089                    regular_expressions.push(expr);
32090                }
32091            }
32092        }
32093
32094        // In pretty mode, put columns on separate lines
32095        if self.config.pretty {
32096            self.write_newline();
32097            self.indent_level += 1;
32098            for (i, expr) in regular_expressions.iter().enumerate() {
32099                if i > 0 {
32100                    self.write(",");
32101                    self.write_newline();
32102                }
32103                self.write_indent();
32104                self.generate_expression(expr)?;
32105            }
32106            self.indent_level -= 1;
32107        } else {
32108            self.write_space();
32109            for (i, expr) in regular_expressions.iter().enumerate() {
32110                if i > 0 {
32111                    self.write(", ");
32112                }
32113                self.generate_expression(expr)?;
32114            }
32115        }
32116
32117        // Output trailing WITH CUBE or WITH ROLLUP
32118        if trailing_cube {
32119            self.write_space();
32120            self.write_keyword("WITH CUBE");
32121        } else if trailing_rollup {
32122            self.write_space();
32123            self.write_keyword("WITH ROLLUP");
32124        }
32125
32126        // ClickHouse: WITH TOTALS
32127        if e.totals {
32128            self.write_space();
32129            self.write_keyword("WITH TOTALS");
32130        }
32131
32132        Ok(())
32133    }
32134
32135    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
32136        let function_name = if e.expressions.len() > 1
32137            && matches!(
32138                self.config.dialect,
32139                Some(DialectType::TSQL | DialectType::Fabric)
32140            ) {
32141            "GROUPING_ID"
32142        } else {
32143            "GROUPING"
32144        };
32145        self.write_keyword(function_name);
32146        self.write("(");
32147        for (i, expr) in e.expressions.iter().enumerate() {
32148            if i > 0 {
32149                self.write(", ");
32150            }
32151            self.generate_expression(expr)?;
32152        }
32153        self.write(")");
32154        Ok(())
32155    }
32156
32157    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
32158        // GROUPING_ID(col1, col2, ...)
32159        self.write_keyword("GROUPING_ID");
32160        self.write("(");
32161        for (i, expr) in e.expressions.iter().enumerate() {
32162            if i > 0 {
32163                self.write(", ");
32164            }
32165            self.generate_expression(expr)?;
32166        }
32167        self.write(")");
32168        Ok(())
32169    }
32170
32171    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
32172        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
32173        self.write_keyword("GROUPING SETS");
32174        self.write(" (");
32175        for (i, expr) in e.expressions.iter().enumerate() {
32176            if i > 0 {
32177                self.write(", ");
32178            }
32179            self.generate_expression(expr)?;
32180        }
32181        self.write(")");
32182        Ok(())
32183    }
32184
32185    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
32186        // HASH_AGG(this, expressions...)
32187        self.write_keyword("HASH_AGG");
32188        self.write("(");
32189        self.generate_expression(&e.this)?;
32190        for expr in &e.expressions {
32191            self.write(", ");
32192            self.generate_expression(expr)?;
32193        }
32194        self.write(")");
32195        Ok(())
32196    }
32197
32198    fn generate_having(&mut self, e: &Having) -> Result<()> {
32199        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
32200        self.write_keyword("HAVING");
32201        self.write_space();
32202        self.generate_expression(&e.this)?;
32203        Ok(())
32204    }
32205
32206    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
32207        // Python: this HAVING MAX|MIN expression
32208        self.generate_expression(&e.this)?;
32209        self.write_space();
32210        self.write_keyword("HAVING");
32211        self.write_space();
32212        if e.max.is_some() {
32213            self.write_keyword("MAX");
32214        } else {
32215            self.write_keyword("MIN");
32216        }
32217        self.write_space();
32218        self.generate_expression(&e.expression)?;
32219        Ok(())
32220    }
32221
32222    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
32223        use crate::dialects::DialectType;
32224        // DuckDB: convert dollar-tagged strings to single-quoted
32225        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
32226            // Extract the string content and output as single-quoted
32227            if let Expression::Literal(ref lit) = *e.this {
32228                if let Literal::String(ref s) = lit.as_ref() {
32229                    return self.generate_string_literal(s);
32230                }
32231            }
32232        }
32233        // PostgreSQL: preserve dollar-quoting
32234        if matches!(
32235            self.config.dialect,
32236            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32237        ) {
32238            self.write("$");
32239            if let Some(tag) = &e.tag {
32240                self.generate_expression(tag)?;
32241            }
32242            self.write("$");
32243            self.generate_expression(&e.this)?;
32244            self.write("$");
32245            if let Some(tag) = &e.tag {
32246                self.generate_expression(tag)?;
32247            }
32248            self.write("$");
32249            return Ok(());
32250        }
32251        // Default: output as dollar-tagged
32252        self.write("$");
32253        if let Some(tag) = &e.tag {
32254            self.generate_expression(tag)?;
32255        }
32256        self.write("$");
32257        self.generate_expression(&e.this)?;
32258        self.write("$");
32259        if let Some(tag) = &e.tag {
32260            self.generate_expression(tag)?;
32261        }
32262        self.write("$");
32263        Ok(())
32264    }
32265
32266    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
32267        // HEX_ENCODE(this)
32268        self.write_keyword("HEX_ENCODE");
32269        self.write("(");
32270        self.generate_expression(&e.this)?;
32271        self.write(")");
32272        Ok(())
32273    }
32274
32275    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
32276        // Python: this (kind => expression)
32277        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
32278        match e.this.as_ref() {
32279            Expression::Identifier(id) => self.write(&id.name),
32280            other => self.generate_expression(other)?,
32281        }
32282        self.write(" (");
32283        self.write(&e.kind);
32284        self.write(" => ");
32285        self.generate_expression(&e.expression)?;
32286        self.write(")");
32287        Ok(())
32288    }
32289
32290    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
32291        // HLL(this, expressions...)
32292        self.write_keyword("HLL");
32293        self.write("(");
32294        self.generate_expression(&e.this)?;
32295        for expr in &e.expressions {
32296            self.write(", ");
32297            self.generate_expression(expr)?;
32298        }
32299        self.write(")");
32300        Ok(())
32301    }
32302
32303    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
32304        // Python: IN|OUT|IN OUT
32305        if e.input_.is_some() && e.output.is_some() {
32306            self.write_keyword("IN OUT");
32307        } else if e.input_.is_some() {
32308            self.write_keyword("IN");
32309        } else if e.output.is_some() {
32310            self.write_keyword("OUT");
32311        }
32312        Ok(())
32313    }
32314
32315    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
32316        // Python: INCLUDE this [column_def] [AS alias]
32317        self.write_keyword("INCLUDE");
32318        self.write_space();
32319        self.generate_expression(&e.this)?;
32320        if let Some(column_def) = &e.column_def {
32321            self.write_space();
32322            self.generate_expression(column_def)?;
32323        }
32324        if let Some(alias) = &e.alias {
32325            self.write_space();
32326            self.write_keyword("AS");
32327            self.write_space();
32328            self.write(alias);
32329        }
32330        Ok(())
32331    }
32332
32333    fn generate_index(&mut self, e: &Index) -> Result<()> {
32334        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
32335        if e.unique {
32336            self.write_keyword("UNIQUE");
32337            self.write_space();
32338        }
32339        if e.primary.is_some() {
32340            self.write_keyword("PRIMARY");
32341            self.write_space();
32342        }
32343        if e.amp.is_some() {
32344            self.write_keyword("AMP");
32345            self.write_space();
32346        }
32347        if e.table.is_none() {
32348            self.write_keyword("INDEX");
32349            self.write_space();
32350        }
32351        if let Some(name) = &e.this {
32352            self.generate_expression(name)?;
32353            self.write_space();
32354        }
32355        if let Some(table) = &e.table {
32356            self.write_keyword("ON");
32357            self.write_space();
32358            self.generate_expression(table)?;
32359        }
32360        if !e.params.is_empty() {
32361            self.write("(");
32362            for (i, param) in e.params.iter().enumerate() {
32363                if i > 0 {
32364                    self.write(", ");
32365                }
32366                self.generate_expression(param)?;
32367            }
32368            self.write(")");
32369        }
32370        Ok(())
32371    }
32372
32373    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
32374        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
32375        if let Some(kind) = &e.kind {
32376            self.write(kind);
32377            self.write_space();
32378        }
32379        self.write_keyword("INDEX");
32380        if let Some(this) = &e.this {
32381            self.write_space();
32382            self.generate_expression(this)?;
32383        }
32384        if let Some(index_type) = &e.index_type {
32385            self.write_space();
32386            self.write_keyword("USING");
32387            self.write_space();
32388            self.generate_expression(index_type)?;
32389        }
32390        if !e.expressions.is_empty() {
32391            self.write(" (");
32392            for (i, expr) in e.expressions.iter().enumerate() {
32393                if i > 0 {
32394                    self.write(", ");
32395                }
32396                self.generate_expression(expr)?;
32397            }
32398            self.write(")");
32399        }
32400        for opt in &e.options {
32401            self.write_space();
32402            self.generate_expression(opt)?;
32403        }
32404        Ok(())
32405    }
32406
32407    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
32408        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
32409        if let Some(key_block_size) = &e.key_block_size {
32410            self.write_keyword("KEY_BLOCK_SIZE");
32411            self.write(" = ");
32412            self.generate_expression(key_block_size)?;
32413        } else if let Some(using) = &e.using {
32414            self.write_keyword("USING");
32415            self.write_space();
32416            self.generate_expression(using)?;
32417        } else if let Some(parser) = &e.parser {
32418            self.write_keyword("WITH PARSER");
32419            self.write_space();
32420            self.generate_expression(parser)?;
32421        } else if let Some(comment) = &e.comment {
32422            self.write_keyword("COMMENT");
32423            self.write_space();
32424            self.generate_expression(comment)?;
32425        } else if let Some(visible) = &e.visible {
32426            self.generate_expression(visible)?;
32427        } else if let Some(engine_attr) = &e.engine_attr {
32428            self.write_keyword("ENGINE_ATTRIBUTE");
32429            self.write(" = ");
32430            self.generate_expression(engine_attr)?;
32431        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
32432            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
32433            self.write(" = ");
32434            self.generate_expression(secondary_engine_attr)?;
32435        }
32436        Ok(())
32437    }
32438
32439    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
32440        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
32441        if let Some(using) = &e.using {
32442            self.write_keyword("USING");
32443            self.write_space();
32444            self.generate_expression(using)?;
32445        }
32446        if !e.columns.is_empty() {
32447            self.write("(");
32448            for (i, col) in e.columns.iter().enumerate() {
32449                if i > 0 {
32450                    self.write(", ");
32451                }
32452                self.generate_expression(col)?;
32453            }
32454            self.write(")");
32455        }
32456        if let Some(partition_by) = &e.partition_by {
32457            self.write_space();
32458            self.write_keyword("PARTITION BY");
32459            self.write_space();
32460            self.generate_expression(partition_by)?;
32461        }
32462        if let Some(where_) = &e.where_ {
32463            self.write_space();
32464            self.generate_expression(where_)?;
32465        }
32466        if let Some(include) = &e.include {
32467            self.write_space();
32468            self.write_keyword("INCLUDE");
32469            self.write(" (");
32470            self.generate_expression(include)?;
32471            self.write(")");
32472        }
32473        if let Some(with_storage) = &e.with_storage {
32474            self.write_space();
32475            self.write_keyword("WITH");
32476            self.write(" (");
32477            self.generate_expression(with_storage)?;
32478            self.write(")");
32479        }
32480        if let Some(tablespace) = &e.tablespace {
32481            self.write_space();
32482            self.write_keyword("USING INDEX TABLESPACE");
32483            self.write_space();
32484            self.generate_expression(tablespace)?;
32485        }
32486        Ok(())
32487    }
32488
32489    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
32490        // Python: this INDEX [FOR target] (expressions)
32491        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
32492        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
32493        if let Expression::Identifier(id) = &*e.this {
32494            self.write_keyword(&id.name);
32495        } else {
32496            self.generate_expression(&e.this)?;
32497        }
32498        self.write_space();
32499        self.write_keyword("INDEX");
32500        if let Some(target) = &e.target {
32501            self.write_space();
32502            self.write_keyword("FOR");
32503            self.write_space();
32504            if let Expression::Identifier(id) = &**target {
32505                self.write_keyword(&id.name);
32506            } else {
32507                self.generate_expression(target)?;
32508            }
32509        }
32510        // Always output parentheses (even if empty, e.g. USE INDEX ())
32511        self.write(" (");
32512        for (i, expr) in e.expressions.iter().enumerate() {
32513            if i > 0 {
32514                self.write(", ");
32515            }
32516            self.generate_expression(expr)?;
32517        }
32518        self.write(")");
32519        Ok(())
32520    }
32521
32522    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
32523        // INHERITS (table1, table2, ...)
32524        self.write_keyword("INHERITS");
32525        self.write(" (");
32526        for (i, expr) in e.expressions.iter().enumerate() {
32527            if i > 0 {
32528                self.write(", ");
32529            }
32530            self.generate_expression(expr)?;
32531        }
32532        self.write(")");
32533        Ok(())
32534    }
32535
32536    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
32537        // INPUT(model)
32538        self.write_keyword("INPUT");
32539        self.write("(");
32540        self.generate_expression(&e.this)?;
32541        self.write(")");
32542        Ok(())
32543    }
32544
32545    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
32546        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
32547        if let Some(input_format) = &e.input_format {
32548            self.write_keyword("INPUTFORMAT");
32549            self.write_space();
32550            self.generate_expression(input_format)?;
32551        }
32552        if let Some(output_format) = &e.output_format {
32553            if e.input_format.is_some() {
32554                self.write(" ");
32555            }
32556            self.write_keyword("OUTPUTFORMAT");
32557            self.write_space();
32558            self.generate_expression(output_format)?;
32559        }
32560        Ok(())
32561    }
32562
32563    fn generate_install(&mut self, e: &Install) -> Result<()> {
32564        // [FORCE] INSTALL extension [FROM source]
32565        if e.force.is_some() {
32566            self.write_keyword("FORCE");
32567            self.write_space();
32568        }
32569        self.write_keyword("INSTALL");
32570        self.write_space();
32571        self.generate_expression(&e.this)?;
32572        if let Some(from) = &e.from_ {
32573            self.write_space();
32574            self.write_keyword("FROM");
32575            self.write_space();
32576            self.generate_expression(from)?;
32577        }
32578        Ok(())
32579    }
32580
32581    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
32582        // INTERVAL 'expression' unit
32583        self.write_keyword("INTERVAL");
32584        self.write_space();
32585        // When a unit is specified and the expression is a number,
32586        self.generate_expression(&e.expression)?;
32587        if let Some(unit) = &e.unit {
32588            self.write_space();
32589            self.write(unit);
32590        }
32591        Ok(())
32592    }
32593
32594    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
32595        // unit TO unit (e.g., HOUR TO SECOND)
32596        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
32597        self.write_space();
32598        self.write_keyword("TO");
32599        self.write_space();
32600        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
32601        Ok(())
32602    }
32603
32604    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
32605        // INTO [TEMPORARY|UNLOGGED] table
32606        self.write_keyword("INTO");
32607        if e.temporary {
32608            self.write_keyword(" TEMPORARY");
32609        }
32610        if e.unlogged.is_some() {
32611            self.write_keyword(" UNLOGGED");
32612        }
32613        if let Some(this) = &e.this {
32614            self.write_space();
32615            self.generate_expression(this)?;
32616        }
32617        if !e.expressions.is_empty() {
32618            self.write(" (");
32619            for (i, expr) in e.expressions.iter().enumerate() {
32620                if i > 0 {
32621                    self.write(", ");
32622                }
32623                self.generate_expression(expr)?;
32624            }
32625            self.write(")");
32626        }
32627        Ok(())
32628    }
32629
32630    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
32631        // Python: this expression (e.g., _utf8 'string')
32632        self.generate_expression(&e.this)?;
32633        self.write_space();
32634        self.generate_expression(&e.expression)?;
32635        Ok(())
32636    }
32637
32638    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
32639        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
32640        self.write_keyword("WITH");
32641        if e.no.is_some() {
32642            self.write_keyword(" NO");
32643        }
32644        if e.concurrent.is_some() {
32645            self.write_keyword(" CONCURRENT");
32646        }
32647        self.write_keyword(" ISOLATED LOADING");
32648        if let Some(target) = &e.target {
32649            self.write_space();
32650            self.generate_expression(target)?;
32651        }
32652        Ok(())
32653    }
32654
32655    fn generate_json(&mut self, e: &JSON) -> Result<()> {
32656        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
32657        self.write_keyword("JSON");
32658        if let Some(this) = &e.this {
32659            self.write_space();
32660            self.generate_expression(this)?;
32661        }
32662        if let Some(with_) = &e.with_ {
32663            // Check if it's a truthy boolean
32664            if let Expression::Boolean(b) = with_.as_ref() {
32665                if b.value {
32666                    self.write_keyword(" WITH");
32667                } else {
32668                    self.write_keyword(" WITHOUT");
32669                }
32670            }
32671        }
32672        if e.unique {
32673            self.write_keyword(" UNIQUE KEYS");
32674        }
32675        Ok(())
32676    }
32677
32678    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
32679        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
32680        self.write_keyword("JSON_ARRAY");
32681        self.write("(");
32682        for (i, expr) in e.expressions.iter().enumerate() {
32683            if i > 0 {
32684                self.write(", ");
32685            }
32686            self.generate_expression(expr)?;
32687        }
32688        if let Some(null_handling) = &e.null_handling {
32689            self.write_space();
32690            self.generate_expression(null_handling)?;
32691        }
32692        if let Some(return_type) = &e.return_type {
32693            self.write_space();
32694            self.write_keyword("RETURNING");
32695            self.write_space();
32696            self.generate_expression(return_type)?;
32697        }
32698        if e.strict.is_some() {
32699            self.write_space();
32700            self.write_keyword("STRICT");
32701        }
32702        self.write(")");
32703        Ok(())
32704    }
32705
32706    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
32707        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
32708        self.write_keyword("JSON_ARRAYAGG");
32709        self.write("(");
32710        self.generate_expression(&e.this)?;
32711        if let Some(order) = &e.order {
32712            self.write_space();
32713            // Order is stored as an OrderBy expression
32714            if let Expression::OrderBy(ob) = order.as_ref() {
32715                self.write_keyword("ORDER BY");
32716                self.write_space();
32717                for (i, ord) in ob.expressions.iter().enumerate() {
32718                    if i > 0 {
32719                        self.write(", ");
32720                    }
32721                    self.generate_ordered(ord)?;
32722                }
32723            } else {
32724                // Fallback: generate the expression directly
32725                self.generate_expression(order)?;
32726            }
32727        }
32728        if let Some(null_handling) = &e.null_handling {
32729            self.write_space();
32730            self.generate_expression(null_handling)?;
32731        }
32732        if let Some(return_type) = &e.return_type {
32733            self.write_space();
32734            self.write_keyword("RETURNING");
32735            self.write_space();
32736            self.generate_expression(return_type)?;
32737        }
32738        if e.strict.is_some() {
32739            self.write_space();
32740            self.write_keyword("STRICT");
32741        }
32742        self.write(")");
32743        Ok(())
32744    }
32745
32746    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
32747        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
32748        self.write_keyword("JSON_OBJECTAGG");
32749        self.write("(");
32750        for (i, expr) in e.expressions.iter().enumerate() {
32751            if i > 0 {
32752                self.write(", ");
32753            }
32754            self.generate_expression(expr)?;
32755        }
32756        if let Some(null_handling) = &e.null_handling {
32757            self.write_space();
32758            self.generate_expression(null_handling)?;
32759        }
32760        if let Some(unique_keys) = &e.unique_keys {
32761            self.write_space();
32762            if let Expression::Boolean(b) = unique_keys.as_ref() {
32763                if b.value {
32764                    self.write_keyword("WITH UNIQUE KEYS");
32765                } else {
32766                    self.write_keyword("WITHOUT UNIQUE KEYS");
32767                }
32768            }
32769        }
32770        if let Some(return_type) = &e.return_type {
32771            self.write_space();
32772            self.write_keyword("RETURNING");
32773            self.write_space();
32774            self.generate_expression(return_type)?;
32775        }
32776        self.write(")");
32777        Ok(())
32778    }
32779
32780    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
32781        // JSON_ARRAY_APPEND(this, path, value, ...)
32782        self.write_keyword("JSON_ARRAY_APPEND");
32783        self.write("(");
32784        self.generate_expression(&e.this)?;
32785        for expr in &e.expressions {
32786            self.write(", ");
32787            self.generate_expression(expr)?;
32788        }
32789        self.write(")");
32790        Ok(())
32791    }
32792
32793    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
32794        // JSON_ARRAY_CONTAINS(this, expression)
32795        self.write_keyword("JSON_ARRAY_CONTAINS");
32796        self.write("(");
32797        self.generate_expression(&e.this)?;
32798        self.write(", ");
32799        self.generate_expression(&e.expression)?;
32800        self.write(")");
32801        Ok(())
32802    }
32803
32804    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
32805        // JSON_ARRAY_INSERT(this, path, value, ...)
32806        self.write_keyword("JSON_ARRAY_INSERT");
32807        self.write("(");
32808        self.generate_expression(&e.this)?;
32809        for expr in &e.expressions {
32810            self.write(", ");
32811            self.generate_expression(expr)?;
32812        }
32813        self.write(")");
32814        Ok(())
32815    }
32816
32817    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
32818        // JSONB_EXISTS(this, path)
32819        self.write_keyword("JSONB_EXISTS");
32820        self.write("(");
32821        self.generate_expression(&e.this)?;
32822        if let Some(path) = &e.path {
32823            self.write(", ");
32824            self.generate_expression(path)?;
32825        }
32826        self.write(")");
32827        Ok(())
32828    }
32829
32830    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
32831        // JSONB_EXTRACT_SCALAR(this, expression)
32832        self.write_keyword("JSONB_EXTRACT_SCALAR");
32833        self.write("(");
32834        self.generate_expression(&e.this)?;
32835        self.write(", ");
32836        self.generate_expression(&e.expression)?;
32837        self.write(")");
32838        Ok(())
32839    }
32840
32841    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
32842        // JSONB_OBJECT_AGG(this, expression)
32843        self.write_keyword("JSONB_OBJECT_AGG");
32844        self.write("(");
32845        self.generate_expression(&e.this)?;
32846        self.write(", ");
32847        self.generate_expression(&e.expression)?;
32848        self.write(")");
32849        Ok(())
32850    }
32851
32852    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
32853        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
32854        if let Some(nested_schema) = &e.nested_schema {
32855            self.write_keyword("NESTED");
32856            if let Some(path) = &e.path {
32857                self.write_space();
32858                self.write_keyword("PATH");
32859                self.write_space();
32860                self.generate_expression(path)?;
32861            }
32862            self.write_space();
32863            self.generate_expression(nested_schema)?;
32864        } else {
32865            if let Some(this) = &e.this {
32866                self.generate_expression(this)?;
32867            }
32868            if let Some(kind) = &e.kind {
32869                self.write_space();
32870                self.write(kind);
32871            }
32872            if e.format_json {
32873                self.write_space();
32874                self.write_keyword("FORMAT JSON");
32875            }
32876            if let Some(path) = &e.path {
32877                self.write_space();
32878                self.write_keyword("PATH");
32879                self.write_space();
32880                self.generate_expression(path)?;
32881            }
32882            if e.ordinality.is_some() {
32883                self.write_keyword(" FOR ORDINALITY");
32884            }
32885        }
32886        Ok(())
32887    }
32888
32889    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
32890        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
32891        self.write_keyword("JSON_EXISTS");
32892        self.write("(");
32893        self.generate_expression(&e.this)?;
32894        if let Some(path) = &e.path {
32895            self.write(", ");
32896            self.generate_expression(path)?;
32897        }
32898        if let Some(passing) = &e.passing {
32899            self.write_space();
32900            self.write_keyword("PASSING");
32901            self.write_space();
32902            self.generate_expression(passing)?;
32903        }
32904        if let Some(on_condition) = &e.on_condition {
32905            self.write_space();
32906            self.generate_expression(on_condition)?;
32907        }
32908        self.write(")");
32909        Ok(())
32910    }
32911
32912    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
32913        self.generate_expression(&e.this)?;
32914        self.write(".:");
32915        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
32916        // wrap the entire type string in double quotes.
32917        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
32918        if Self::data_type_has_nested_expressions(&e.to) {
32919            // Generate the data type to a temporary string buffer, then wrap in quotes
32920            let saved = std::mem::take(&mut self.output);
32921            self.generate_data_type(&e.to)?;
32922            let type_sql = std::mem::replace(&mut self.output, saved);
32923            self.write("\"");
32924            self.write(&type_sql);
32925            self.write("\"");
32926        } else {
32927            self.generate_data_type(&e.to)?;
32928        }
32929        Ok(())
32930    }
32931
32932    /// Check if a DataType has nested type expressions (sub-types).
32933    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
32934    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
32935        matches!(
32936            dt,
32937            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
32938        )
32939    }
32940
32941    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
32942        // JSON_EXTRACT_ARRAY(this, expression)
32943        self.write_keyword("JSON_EXTRACT_ARRAY");
32944        self.write("(");
32945        self.generate_expression(&e.this)?;
32946        if let Some(expr) = &e.expression {
32947            self.write(", ");
32948            self.generate_expression(expr)?;
32949        }
32950        self.write(")");
32951        Ok(())
32952    }
32953
32954    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
32955        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
32956        if let Some(option) = &e.option {
32957            self.generate_expression(option)?;
32958            self.write_space();
32959        }
32960        self.write_keyword("QUOTES");
32961        if e.scalar.is_some() {
32962            self.write_keyword(" SCALAR_ONLY");
32963        }
32964        Ok(())
32965    }
32966
32967    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
32968        // JSON_EXTRACT_SCALAR(this, expression)
32969        self.write_keyword("JSON_EXTRACT_SCALAR");
32970        self.write("(");
32971        self.generate_expression(&e.this)?;
32972        self.write(", ");
32973        self.generate_expression(&e.expression)?;
32974        self.write(")");
32975        Ok(())
32976    }
32977
32978    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
32979        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
32980        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
32981        // Otherwise output JSON_EXTRACT(this, expression)
32982        if e.variant_extract.is_some() {
32983            use crate::dialects::DialectType;
32984            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
32985                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
32986                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
32987                // bracket notation: c:["x-y"] instead of c:x-y
32988                self.generate_expression(&e.this)?;
32989                self.write(":");
32990                match e.expression.as_ref() {
32991                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32992                        let Literal::String(s) = lit.as_ref() else {
32993                            unreachable!()
32994                        };
32995                        self.write_databricks_json_path(s);
32996                    }
32997                    _ => {
32998                        // Fallback: generate as-is (shouldn't happen in typical cases)
32999                        self.generate_expression(&e.expression)?;
33000                    }
33001                }
33002            } else {
33003                // Snowflake and others: use GET_PATH(col, 'path')
33004                self.write_keyword("GET_PATH");
33005                self.write("(");
33006                self.generate_expression(&e.this)?;
33007                self.write(", ");
33008                self.generate_expression(&e.expression)?;
33009                self.write(")");
33010            }
33011        } else {
33012            self.write_keyword("JSON_EXTRACT");
33013            self.write("(");
33014            self.generate_expression(&e.this)?;
33015            self.write(", ");
33016            self.generate_expression(&e.expression)?;
33017            for expr in &e.expressions {
33018                self.write(", ");
33019                self.generate_expression(expr)?;
33020            }
33021            self.write(")");
33022        }
33023        Ok(())
33024    }
33025
33026    /// Write a Databricks JSON colon-path, using bracket notation for keys
33027    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
33028    /// Safe identifier regex: ^[_a-zA-Z]\w*$
33029    fn write_databricks_json_path(&mut self, path: &str) {
33030        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
33031        // it was already formatted by the parser - output as-is
33032        if path.starts_with("[\"") || path.starts_with("['") {
33033            self.write(path);
33034            return;
33035        }
33036        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
33037        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
33038        // e.g., "x-y" -> ["x-y"]
33039        let mut first = true;
33040        for segment in path.split('.') {
33041            if !first {
33042                self.write(".");
33043            }
33044            first = false;
33045            // Check if there's a bracket subscript in this segment: "items[0]"
33046            if let Some(bracket_pos) = segment.find('[') {
33047                let key = &segment[..bracket_pos];
33048                let subscript = &segment[bracket_pos..];
33049                if key.is_empty() {
33050                    // Bracket notation at start of segment (e.g., already formatted)
33051                    self.write(segment);
33052                } else if Self::is_safe_json_path_key(key) {
33053                    self.write(key);
33054                    self.write(subscript);
33055                } else {
33056                    self.write("[\"");
33057                    self.write(key);
33058                    self.write("\"]");
33059                    self.write(subscript);
33060                }
33061            } else if Self::is_safe_json_path_key(segment) {
33062                self.write(segment);
33063            } else {
33064                self.write("[\"");
33065                self.write(segment);
33066                self.write("\"]");
33067            }
33068        }
33069    }
33070
33071    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
33072    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
33073    fn is_safe_json_path_key(key: &str) -> bool {
33074        if key.is_empty() {
33075            return false;
33076        }
33077        let mut chars = key.chars();
33078        let first = chars.next().unwrap();
33079        if first != '_' && !first.is_ascii_alphabetic() {
33080            return false;
33081        }
33082        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
33083    }
33084
33085    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
33086        // Output: {expr} FORMAT JSON
33087        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
33088        if let Some(this) = &e.this {
33089            self.generate_expression(this)?;
33090            self.write_space();
33091        }
33092        self.write_keyword("FORMAT JSON");
33093        Ok(())
33094    }
33095
33096    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
33097        // key: value (for JSON objects)
33098        self.generate_expression(&e.this)?;
33099        self.write(": ");
33100        self.generate_expression(&e.expression)?;
33101        Ok(())
33102    }
33103
33104    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
33105        // JSON_KEYS(this, expression, expressions...)
33106        self.write_keyword("JSON_KEYS");
33107        self.write("(");
33108        self.generate_expression(&e.this)?;
33109        if let Some(expr) = &e.expression {
33110            self.write(", ");
33111            self.generate_expression(expr)?;
33112        }
33113        for expr in &e.expressions {
33114            self.write(", ");
33115            self.generate_expression(expr)?;
33116        }
33117        self.write(")");
33118        Ok(())
33119    }
33120
33121    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
33122        // JSON_KEYS(this, expression)
33123        self.write_keyword("JSON_KEYS");
33124        self.write("(");
33125        self.generate_expression(&e.this)?;
33126        if let Some(expr) = &e.expression {
33127            self.write(", ");
33128            self.generate_expression(expr)?;
33129        }
33130        self.write(")");
33131        Ok(())
33132    }
33133
33134    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
33135        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
33136        // The path components are concatenated without spaces
33137        let mut path_str = String::new();
33138        for expr in &e.expressions {
33139            match expr {
33140                Expression::JSONPathRoot(_) => {
33141                    path_str.push('$');
33142                }
33143                Expression::JSONPathKey(k) => {
33144                    // .key or ."key" (quote if key has special characters)
33145                    if let Expression::Literal(lit) = k.this.as_ref() {
33146                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
33147                            path_str.push('.');
33148                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
33149                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
33150                            if needs_quoting {
33151                                path_str.push('"');
33152                                path_str.push_str(s);
33153                                path_str.push('"');
33154                            } else {
33155                                path_str.push_str(s);
33156                            }
33157                        }
33158                    }
33159                }
33160                Expression::JSONPathSubscript(s) => {
33161                    // [index]
33162                    if let Expression::Literal(lit) = s.this.as_ref() {
33163                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
33164                            path_str.push('[');
33165                            path_str.push_str(n);
33166                            path_str.push(']');
33167                        }
33168                    }
33169                }
33170                _ => {
33171                    // For other path parts, try to generate them
33172                    let mut temp_gen = Self::with_arc_config(self.config.clone());
33173                    temp_gen.generate_expression(expr)?;
33174                    path_str.push_str(&temp_gen.output);
33175                }
33176            }
33177        }
33178        // Output as quoted string
33179        self.write("'");
33180        self.write(&path_str);
33181        self.write("'");
33182        Ok(())
33183    }
33184
33185    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
33186        // JSON path filter: ?(predicate)
33187        self.write("?(");
33188        self.generate_expression(&e.this)?;
33189        self.write(")");
33190        Ok(())
33191    }
33192
33193    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
33194        // JSON path key: .key or ["key"]
33195        self.write(".");
33196        self.generate_expression(&e.this)?;
33197        Ok(())
33198    }
33199
33200    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
33201        // JSON path recursive descent: ..
33202        self.write("..");
33203        if let Some(this) = &e.this {
33204            self.generate_expression(this)?;
33205        }
33206        Ok(())
33207    }
33208
33209    fn generate_json_path_root(&mut self) -> Result<()> {
33210        // JSON path root: $
33211        self.write("$");
33212        Ok(())
33213    }
33214
33215    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
33216        // JSON path script: (expression)
33217        self.write("(");
33218        self.generate_expression(&e.this)?;
33219        self.write(")");
33220        Ok(())
33221    }
33222
33223    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
33224        // JSON path selector: *
33225        self.generate_expression(&e.this)?;
33226        Ok(())
33227    }
33228
33229    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
33230        // JSON path slice: [start:end:step]
33231        self.write("[");
33232        if let Some(start) = &e.start {
33233            self.generate_expression(start)?;
33234        }
33235        self.write(":");
33236        if let Some(end) = &e.end {
33237            self.generate_expression(end)?;
33238        }
33239        if let Some(step) = &e.step {
33240            self.write(":");
33241            self.generate_expression(step)?;
33242        }
33243        self.write("]");
33244        Ok(())
33245    }
33246
33247    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
33248        // JSON path subscript: [index] or [*]
33249        self.write("[");
33250        self.generate_expression(&e.this)?;
33251        self.write("]");
33252        Ok(())
33253    }
33254
33255    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
33256        // JSON path union: [key1, key2, ...]
33257        self.write("[");
33258        for (i, expr) in e.expressions.iter().enumerate() {
33259            if i > 0 {
33260                self.write(", ");
33261            }
33262            self.generate_expression(expr)?;
33263        }
33264        self.write("]");
33265        Ok(())
33266    }
33267
33268    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
33269        // JSON_REMOVE(this, path1, path2, ...)
33270        self.write_keyword("JSON_REMOVE");
33271        self.write("(");
33272        self.generate_expression(&e.this)?;
33273        for expr in &e.expressions {
33274            self.write(", ");
33275            self.generate_expression(expr)?;
33276        }
33277        self.write(")");
33278        Ok(())
33279    }
33280
33281    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
33282        // COLUMNS(col1 type, col2 type, ...)
33283        // When pretty printing and content is too wide, format with each column on a separate line
33284        self.write_keyword("COLUMNS");
33285        self.write("(");
33286
33287        if self.config.pretty && !e.expressions.is_empty() {
33288            // First, generate all expressions into strings to check width
33289            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
33290            for expr in &e.expressions {
33291                let mut temp_gen = Generator::with_arc_config(self.config.clone());
33292                temp_gen.generate_expression(expr)?;
33293                expr_strings.push(temp_gen.output);
33294            }
33295
33296            // Check if total width exceeds max_text_width
33297            if self.too_wide(&expr_strings) {
33298                // Pretty print: each column on its own line
33299                self.write_newline();
33300                self.indent_level += 1;
33301                for (i, expr_str) in expr_strings.iter().enumerate() {
33302                    if i > 0 {
33303                        self.write(",");
33304                        self.write_newline();
33305                    }
33306                    self.write_indent();
33307                    self.write(expr_str);
33308                }
33309                self.write_newline();
33310                self.indent_level -= 1;
33311                self.write_indent();
33312            } else {
33313                // Compact: all on one line
33314                for (i, expr_str) in expr_strings.iter().enumerate() {
33315                    if i > 0 {
33316                        self.write(", ");
33317                    }
33318                    self.write(expr_str);
33319                }
33320            }
33321        } else {
33322            // Non-pretty mode: compact format
33323            for (i, expr) in e.expressions.iter().enumerate() {
33324                if i > 0 {
33325                    self.write(", ");
33326                }
33327                self.generate_expression(expr)?;
33328            }
33329        }
33330        self.write(")");
33331        Ok(())
33332    }
33333
33334    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
33335        // JSON_SET(this, path, value, ...)
33336        self.write_keyword("JSON_SET");
33337        self.write("(");
33338        self.generate_expression(&e.this)?;
33339        for expr in &e.expressions {
33340            self.write(", ");
33341            self.generate_expression(expr)?;
33342        }
33343        self.write(")");
33344        Ok(())
33345    }
33346
33347    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
33348        // JSON_STRIP_NULLS(this, expression)
33349        self.write_keyword("JSON_STRIP_NULLS");
33350        self.write("(");
33351        self.generate_expression(&e.this)?;
33352        if let Some(expr) = &e.expression {
33353            self.write(", ");
33354            self.generate_expression(expr)?;
33355        }
33356        self.write(")");
33357        Ok(())
33358    }
33359
33360    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
33361        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
33362        self.write_keyword("JSON_TABLE");
33363        self.write("(");
33364        self.generate_expression(&e.this)?;
33365        if let Some(path) = &e.path {
33366            self.write(", ");
33367            self.generate_expression(path)?;
33368        }
33369        if let Some(error_handling) = &e.error_handling {
33370            self.write_space();
33371            self.generate_expression(error_handling)?;
33372        }
33373        if let Some(empty_handling) = &e.empty_handling {
33374            self.write_space();
33375            self.generate_expression(empty_handling)?;
33376        }
33377        if let Some(schema) = &e.schema {
33378            self.write_space();
33379            self.generate_expression(schema)?;
33380        }
33381        self.write(")");
33382        Ok(())
33383    }
33384
33385    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
33386        // JSON_TYPE(this)
33387        self.write_keyword("JSON_TYPE");
33388        self.write("(");
33389        self.generate_expression(&e.this)?;
33390        self.write(")");
33391        Ok(())
33392    }
33393
33394    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
33395        // JSON_VALUE(this, path RETURNING type ON condition)
33396        self.write_keyword("JSON_VALUE");
33397        self.write("(");
33398        self.generate_expression(&e.this)?;
33399        if let Some(path) = &e.path {
33400            self.write(", ");
33401            self.generate_expression(path)?;
33402        }
33403        if let Some(returning) = &e.returning {
33404            self.write_space();
33405            self.write_keyword("RETURNING");
33406            self.write_space();
33407            self.generate_expression(returning)?;
33408        }
33409        if let Some(on_condition) = &e.on_condition {
33410            self.write_space();
33411            self.generate_expression(on_condition)?;
33412        }
33413        self.write(")");
33414        Ok(())
33415    }
33416
33417    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
33418        // JSON_VALUE_ARRAY(this)
33419        self.write_keyword("JSON_VALUE_ARRAY");
33420        self.write("(");
33421        self.generate_expression(&e.this)?;
33422        self.write(")");
33423        Ok(())
33424    }
33425
33426    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
33427        // JAROWINKLER_SIMILARITY(str1, str2)
33428        self.write_keyword("JAROWINKLER_SIMILARITY");
33429        self.write("(");
33430        self.generate_expression(&e.this)?;
33431        self.write(", ");
33432        self.generate_expression(&e.expression)?;
33433        self.write(")");
33434        Ok(())
33435    }
33436
33437    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
33438        // Python: this(expressions)
33439        self.generate_expression(&e.this)?;
33440        self.write("(");
33441        for (i, expr) in e.expressions.iter().enumerate() {
33442            if i > 0 {
33443                self.write(", ");
33444            }
33445            self.generate_expression(expr)?;
33446        }
33447        self.write(")");
33448        Ok(())
33449    }
33450
33451    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
33452        // Python: {no}{local}{dual}{before}{after}JOURNAL
33453        if e.no.is_some() {
33454            self.write_keyword("NO ");
33455        }
33456        if let Some(local) = &e.local {
33457            self.generate_expression(local)?;
33458            self.write_space();
33459        }
33460        if e.dual.is_some() {
33461            self.write_keyword("DUAL ");
33462        }
33463        if e.before.is_some() {
33464            self.write_keyword("BEFORE ");
33465        }
33466        if e.after.is_some() {
33467            self.write_keyword("AFTER ");
33468        }
33469        self.write_keyword("JOURNAL");
33470        Ok(())
33471    }
33472
33473    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
33474        // LANGUAGE language_name
33475        self.write_keyword("LANGUAGE");
33476        self.write_space();
33477        self.generate_expression(&e.this)?;
33478        Ok(())
33479    }
33480
33481    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
33482        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
33483        if e.view.is_some() {
33484            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
33485            self.write_keyword("LATERAL VIEW");
33486            if e.outer.is_some() {
33487                self.write_space();
33488                self.write_keyword("OUTER");
33489            }
33490            self.write_space();
33491            self.generate_expression(&e.this)?;
33492            if let Some(alias) = &e.alias {
33493                self.write_space();
33494                self.write(alias);
33495            }
33496        } else {
33497            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
33498            self.write_keyword("LATERAL");
33499            self.write_space();
33500            self.generate_expression(&e.this)?;
33501            if e.ordinality.is_some() {
33502                self.write_space();
33503                self.write_keyword("WITH ORDINALITY");
33504            }
33505            if let Some(alias) = &e.alias {
33506                self.write_space();
33507                self.write_keyword("AS");
33508                self.write_space();
33509                self.write(alias);
33510                if !e.column_aliases.is_empty() {
33511                    self.write("(");
33512                    for (i, col) in e.column_aliases.iter().enumerate() {
33513                        if i > 0 {
33514                            self.write(", ");
33515                        }
33516                        self.write(col);
33517                    }
33518                    self.write(")");
33519                }
33520            }
33521        }
33522        Ok(())
33523    }
33524
33525    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
33526        // Python: LIKE this [options]
33527        self.write_keyword("LIKE");
33528        self.write_space();
33529        self.generate_expression(&e.this)?;
33530        for expr in &e.expressions {
33531            self.write_space();
33532            self.generate_expression(expr)?;
33533        }
33534        Ok(())
33535    }
33536
33537    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
33538        self.write_keyword("LIMIT");
33539        self.write_space();
33540        self.write_limit_expr(&e.this)?;
33541        if e.percent {
33542            self.write_space();
33543            self.write_keyword("PERCENT");
33544        }
33545        // Emit any comments that were captured from before the LIMIT keyword
33546        for comment in &e.comments {
33547            self.write(" ");
33548            self.write_formatted_comment(comment);
33549        }
33550        Ok(())
33551    }
33552
33553    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
33554        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
33555        if e.percent.is_some() {
33556            self.write_keyword(" PERCENT");
33557        }
33558        if e.rows.is_some() {
33559            self.write_keyword(" ROWS");
33560        }
33561        if e.with_ties.is_some() {
33562            self.write_keyword(" WITH TIES");
33563        } else if e.rows.is_some() {
33564            self.write_keyword(" ONLY");
33565        }
33566        Ok(())
33567    }
33568
33569    fn generate_list(&mut self, e: &List) -> Result<()> {
33570        use crate::dialects::DialectType;
33571        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
33572
33573        // Check if this is a subquery-based list (LIST(SELECT ...))
33574        if e.expressions.len() == 1 {
33575            if let Expression::Select(_) = &e.expressions[0] {
33576                self.write_keyword("LIST");
33577                self.write("(");
33578                self.generate_expression(&e.expressions[0])?;
33579                self.write(")");
33580                return Ok(());
33581            }
33582        }
33583
33584        // For Materialize, output as LIST[expr, expr, ...]
33585        if is_materialize {
33586            self.write_keyword("LIST");
33587            self.write("[");
33588            for (i, expr) in e.expressions.iter().enumerate() {
33589                if i > 0 {
33590                    self.write(", ");
33591                }
33592                self.generate_expression(expr)?;
33593            }
33594            self.write("]");
33595        } else {
33596            // For other dialects, output as LIST(expr, expr, ...)
33597            self.write_keyword("LIST");
33598            self.write("(");
33599            for (i, expr) in e.expressions.iter().enumerate() {
33600                if i > 0 {
33601                    self.write(", ");
33602                }
33603                self.generate_expression(expr)?;
33604            }
33605            self.write(")");
33606        }
33607        Ok(())
33608    }
33609
33610    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
33611        // Check if this is a subquery-based map (MAP(SELECT ...))
33612        if let Expression::Select(_) = &*e.this {
33613            self.write_keyword("MAP");
33614            self.write("(");
33615            self.generate_expression(&e.this)?;
33616            self.write(")");
33617            return Ok(());
33618        }
33619
33620        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
33621
33622        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
33623        self.write_keyword("MAP");
33624        if is_duckdb {
33625            self.write(" {");
33626        } else {
33627            self.write("[");
33628        }
33629        if let Expression::Struct(s) = &*e.this {
33630            for (i, (_, expr)) in s.fields.iter().enumerate() {
33631                if i > 0 {
33632                    self.write(", ");
33633                }
33634                if let Expression::PropertyEQ(op) = expr {
33635                    self.generate_expression(&op.left)?;
33636                    if is_duckdb {
33637                        self.write(": ");
33638                    } else {
33639                        self.write(" => ");
33640                    }
33641                    self.generate_expression(&op.right)?;
33642                } else {
33643                    self.generate_expression(expr)?;
33644                }
33645            }
33646        }
33647        if is_duckdb {
33648            self.write("}");
33649        } else {
33650            self.write("]");
33651        }
33652        Ok(())
33653    }
33654
33655    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
33656        // Python: LOCALTIME or LOCALTIME(precision)
33657        self.write_keyword("LOCALTIME");
33658        if let Some(precision) = &e.this {
33659            self.write("(");
33660            self.generate_expression(precision)?;
33661            self.write(")");
33662        }
33663        Ok(())
33664    }
33665
33666    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
33667        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
33668        self.write_keyword("LOCALTIMESTAMP");
33669        if let Some(precision) = &e.this {
33670            self.write("(");
33671            self.generate_expression(precision)?;
33672            self.write(")");
33673        }
33674        Ok(())
33675    }
33676
33677    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
33678        // LOCATION 'path'
33679        self.write_keyword("LOCATION");
33680        self.write_space();
33681        self.generate_expression(&e.this)?;
33682        Ok(())
33683    }
33684
33685    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
33686        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
33687        if e.update.is_some() {
33688            if e.key.is_some() {
33689                self.write_keyword("FOR NO KEY UPDATE");
33690            } else {
33691                self.write_keyword("FOR UPDATE");
33692            }
33693        } else {
33694            if e.key.is_some() {
33695                self.write_keyword("FOR KEY SHARE");
33696            } else {
33697                self.write_keyword("FOR SHARE");
33698            }
33699        }
33700        if !e.expressions.is_empty() {
33701            self.write_keyword(" OF ");
33702            for (i, expr) in e.expressions.iter().enumerate() {
33703                if i > 0 {
33704                    self.write(", ");
33705                }
33706                self.generate_expression(expr)?;
33707            }
33708        }
33709        // Handle wait option following Python sqlglot convention:
33710        // - Boolean(true) -> NOWAIT
33711        // - Boolean(false) -> SKIP LOCKED
33712        // - Literal (number) -> WAIT n
33713        if let Some(wait) = &e.wait {
33714            match wait.as_ref() {
33715                Expression::Boolean(b) => {
33716                    if b.value {
33717                        self.write_keyword(" NOWAIT");
33718                    } else {
33719                        self.write_keyword(" SKIP LOCKED");
33720                    }
33721                }
33722                _ => {
33723                    // It's a literal (number), output WAIT n
33724                    self.write_keyword(" WAIT ");
33725                    self.generate_expression(wait)?;
33726                }
33727            }
33728        }
33729        Ok(())
33730    }
33731
33732    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
33733        // LOCK property
33734        self.write_keyword("LOCK");
33735        self.write_space();
33736        self.generate_expression(&e.this)?;
33737        Ok(())
33738    }
33739
33740    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
33741        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
33742        self.write_keyword("LOCKING");
33743        self.write_space();
33744        self.write(&e.kind);
33745        if let Some(this) = &e.this {
33746            self.write_space();
33747            self.generate_expression(this)?;
33748        }
33749        if let Some(for_or_in) = &e.for_or_in {
33750            self.write_space();
33751            self.generate_expression(for_or_in)?;
33752        }
33753        if let Some(lock_type) = &e.lock_type {
33754            self.write_space();
33755            self.generate_expression(lock_type)?;
33756        }
33757        if e.override_.is_some() {
33758            self.write_keyword(" OVERRIDE");
33759        }
33760        Ok(())
33761    }
33762
33763    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
33764        // this expression
33765        self.generate_expression(&e.this)?;
33766        self.write_space();
33767        self.generate_expression(&e.expression)?;
33768        Ok(())
33769    }
33770
33771    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
33772        // [NO] LOG
33773        if e.no.is_some() {
33774            self.write_keyword("NO ");
33775        }
33776        self.write_keyword("LOG");
33777        Ok(())
33778    }
33779
33780    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
33781        // MD5(this, expressions...)
33782        self.write_keyword("MD5");
33783        self.write("(");
33784        self.generate_expression(&e.this)?;
33785        for expr in &e.expressions {
33786            self.write(", ");
33787            self.generate_expression(expr)?;
33788        }
33789        self.write(")");
33790        Ok(())
33791    }
33792
33793    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
33794        // ML.FORECAST(model, [params])
33795        self.write_keyword("ML.FORECAST");
33796        self.write("(");
33797        self.generate_expression(&e.this)?;
33798        if let Some(expression) = &e.expression {
33799            self.write(", ");
33800            self.generate_expression(expression)?;
33801        }
33802        if let Some(params) = &e.params_struct {
33803            self.write(", ");
33804            self.generate_expression(params)?;
33805        }
33806        self.write(")");
33807        Ok(())
33808    }
33809
33810    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
33811        // ML.TRANSLATE(model, input, [params])
33812        self.write_keyword("ML.TRANSLATE");
33813        self.write("(");
33814        self.generate_expression(&e.this)?;
33815        self.write(", ");
33816        self.generate_expression(&e.expression)?;
33817        if let Some(params) = &e.params_struct {
33818            self.write(", ");
33819            self.generate_expression(params)?;
33820        }
33821        self.write(")");
33822        Ok(())
33823    }
33824
33825    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
33826        // MAKE_INTERVAL(years => x, months => y, ...)
33827        self.write_keyword("MAKE_INTERVAL");
33828        self.write("(");
33829        let mut first = true;
33830        if let Some(year) = &e.year {
33831            self.write("years => ");
33832            self.generate_expression(year)?;
33833            first = false;
33834        }
33835        if let Some(month) = &e.month {
33836            if !first {
33837                self.write(", ");
33838            }
33839            self.write("months => ");
33840            self.generate_expression(month)?;
33841            first = false;
33842        }
33843        if let Some(week) = &e.week {
33844            if !first {
33845                self.write(", ");
33846            }
33847            self.write("weeks => ");
33848            self.generate_expression(week)?;
33849            first = false;
33850        }
33851        if let Some(day) = &e.day {
33852            if !first {
33853                self.write(", ");
33854            }
33855            self.write("days => ");
33856            self.generate_expression(day)?;
33857            first = false;
33858        }
33859        if let Some(hour) = &e.hour {
33860            if !first {
33861                self.write(", ");
33862            }
33863            self.write("hours => ");
33864            self.generate_expression(hour)?;
33865            first = false;
33866        }
33867        if let Some(minute) = &e.minute {
33868            if !first {
33869                self.write(", ");
33870            }
33871            self.write("mins => ");
33872            self.generate_expression(minute)?;
33873            first = false;
33874        }
33875        if let Some(second) = &e.second {
33876            if !first {
33877                self.write(", ");
33878            }
33879            self.write("secs => ");
33880            self.generate_expression(second)?;
33881        }
33882        self.write(")");
33883        Ok(())
33884    }
33885
33886    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
33887        // MANHATTAN_DISTANCE(vector1, vector2)
33888        self.write_keyword("MANHATTAN_DISTANCE");
33889        self.write("(");
33890        self.generate_expression(&e.this)?;
33891        self.write(", ");
33892        self.generate_expression(&e.expression)?;
33893        self.write(")");
33894        Ok(())
33895    }
33896
33897    fn generate_map(&mut self, e: &Map) -> Result<()> {
33898        // MAP(key1, value1, key2, value2, ...)
33899        self.write_keyword("MAP");
33900        self.write("(");
33901        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
33902            if i > 0 {
33903                self.write(", ");
33904            }
33905            self.generate_expression(key)?;
33906            self.write(", ");
33907            self.generate_expression(value)?;
33908        }
33909        self.write(")");
33910        Ok(())
33911    }
33912
33913    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
33914        // MAP_CAT(map1, map2)
33915        self.write_keyword("MAP_CAT");
33916        self.write("(");
33917        self.generate_expression(&e.this)?;
33918        self.write(", ");
33919        self.generate_expression(&e.expression)?;
33920        self.write(")");
33921        Ok(())
33922    }
33923
33924    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
33925        // MAP_DELETE(map, key1, key2, ...)
33926        self.write_keyword("MAP_DELETE");
33927        self.write("(");
33928        self.generate_expression(&e.this)?;
33929        for expr in &e.expressions {
33930            self.write(", ");
33931            self.generate_expression(expr)?;
33932        }
33933        self.write(")");
33934        Ok(())
33935    }
33936
33937    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
33938        // MAP_INSERT(map, key, value, [update_flag])
33939        self.write_keyword("MAP_INSERT");
33940        self.write("(");
33941        self.generate_expression(&e.this)?;
33942        if let Some(key) = &e.key {
33943            self.write(", ");
33944            self.generate_expression(key)?;
33945        }
33946        if let Some(value) = &e.value {
33947            self.write(", ");
33948            self.generate_expression(value)?;
33949        }
33950        if let Some(update_flag) = &e.update_flag {
33951            self.write(", ");
33952            self.generate_expression(update_flag)?;
33953        }
33954        self.write(")");
33955        Ok(())
33956    }
33957
33958    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
33959        // MAP_PICK(map, key1, key2, ...)
33960        self.write_keyword("MAP_PICK");
33961        self.write("(");
33962        self.generate_expression(&e.this)?;
33963        for expr in &e.expressions {
33964            self.write(", ");
33965            self.generate_expression(expr)?;
33966        }
33967        self.write(")");
33968        Ok(())
33969    }
33970
33971    fn generate_masking_policy_column_constraint(
33972        &mut self,
33973        e: &MaskingPolicyColumnConstraint,
33974    ) -> Result<()> {
33975        // Python: MASKING POLICY name [USING (cols)]
33976        self.write_keyword("MASKING POLICY");
33977        self.write_space();
33978        self.generate_expression(&e.this)?;
33979        if !e.expressions.is_empty() {
33980            self.write_keyword(" USING");
33981            self.write(" (");
33982            for (i, expr) in e.expressions.iter().enumerate() {
33983                if i > 0 {
33984                    self.write(", ");
33985                }
33986                self.generate_expression(expr)?;
33987            }
33988            self.write(")");
33989        }
33990        Ok(())
33991    }
33992
33993    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
33994        if matches!(
33995            self.config.dialect,
33996            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33997        ) {
33998            if e.expressions.len() > 1 {
33999                self.write("(");
34000            }
34001            for (i, expr) in e.expressions.iter().enumerate() {
34002                if i > 0 {
34003                    self.write_keyword(" OR ");
34004                }
34005                self.generate_expression(expr)?;
34006                self.write_space();
34007                self.write("@@");
34008                self.write_space();
34009                self.generate_expression(&e.this)?;
34010            }
34011            if e.expressions.len() > 1 {
34012                self.write(")");
34013            }
34014            return Ok(());
34015        }
34016
34017        // MATCH(columns) AGAINST(expr [modifier])
34018        self.write_keyword("MATCH");
34019        self.write("(");
34020        for (i, expr) in e.expressions.iter().enumerate() {
34021            if i > 0 {
34022                self.write(", ");
34023            }
34024            self.generate_expression(expr)?;
34025        }
34026        self.write(")");
34027        self.write_keyword(" AGAINST");
34028        self.write("(");
34029        self.generate_expression(&e.this)?;
34030        if let Some(modifier) = &e.modifier {
34031            self.write_space();
34032            self.generate_expression(modifier)?;
34033        }
34034        self.write(")");
34035        Ok(())
34036    }
34037
34038    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
34039        // Python: [window_frame] this
34040        if let Some(window_frame) = &e.window_frame {
34041            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
34042            self.write_space();
34043        }
34044        self.generate_expression(&e.this)?;
34045        Ok(())
34046    }
34047
34048    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
34049        // MATERIALIZED [this]
34050        self.write_keyword("MATERIALIZED");
34051        if let Some(this) = &e.this {
34052            self.write_space();
34053            self.generate_expression(this)?;
34054        }
34055        Ok(())
34056    }
34057
34058    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
34059        // MERGE INTO target USING source ON condition WHEN ...
34060        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
34061        if let Some(with_) = &e.with_ {
34062            if let Expression::With(with_clause) = with_.as_ref() {
34063                self.generate_with(with_clause)?;
34064                self.write_space();
34065            } else {
34066                self.generate_expression(with_)?;
34067                self.write_space();
34068            }
34069        }
34070        self.write_keyword("MERGE INTO");
34071        self.write_space();
34072        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
34073            if let Expression::Alias(alias) = e.this.as_ref() {
34074                self.generate_expression(&alias.this)?;
34075                self.write_space();
34076                self.generate_identifier(&alias.alias)?;
34077            } else {
34078                self.generate_expression(&e.this)?;
34079            }
34080        } else {
34081            self.generate_expression(&e.this)?;
34082        }
34083
34084        // USING clause - newline before in pretty mode
34085        if self.config.pretty {
34086            self.write_newline();
34087            self.write_indent();
34088        } else {
34089            self.write_space();
34090        }
34091        self.write_keyword("USING");
34092        self.write_space();
34093        self.generate_expression(&e.using)?;
34094
34095        // ON clause - newline before in pretty mode
34096        if let Some(on) = &e.on {
34097            if self.config.pretty {
34098                self.write_newline();
34099                self.write_indent();
34100            } else {
34101                self.write_space();
34102            }
34103            self.write_keyword("ON");
34104            self.write_space();
34105            self.generate_expression(on)?;
34106        }
34107        // DuckDB USING (key_columns) clause
34108        if let Some(using_cond) = &e.using_cond {
34109            self.write_space();
34110            self.write_keyword("USING");
34111            self.write_space();
34112            self.write("(");
34113            // using_cond is a Tuple containing the column identifiers
34114            if let Expression::Tuple(tuple) = using_cond.as_ref() {
34115                for (i, col) in tuple.expressions.iter().enumerate() {
34116                    if i > 0 {
34117                        self.write(", ");
34118                    }
34119                    self.generate_expression(col)?;
34120                }
34121            } else {
34122                self.generate_expression(using_cond)?;
34123            }
34124            self.write(")");
34125        }
34126        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
34127        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
34128        if matches!(
34129            self.config.dialect,
34130            Some(crate::DialectType::PostgreSQL)
34131                | Some(crate::DialectType::Redshift)
34132                | Some(crate::DialectType::Trino)
34133                | Some(crate::DialectType::Presto)
34134                | Some(crate::DialectType::Athena)
34135        ) {
34136            let mut names = Vec::new();
34137            match e.this.as_ref() {
34138                Expression::Alias(a) => {
34139                    // e.g., "x AS z" -> strip both "x" and "z"
34140                    if let Expression::Table(t) = &a.this {
34141                        names.push(t.name.name.clone());
34142                    } else if let Expression::Identifier(id) = &a.this {
34143                        names.push(id.name.clone());
34144                    }
34145                    names.push(a.alias.name.clone());
34146                }
34147                Expression::Table(t) => {
34148                    names.push(t.name.name.clone());
34149                }
34150                Expression::Identifier(id) => {
34151                    names.push(id.name.clone());
34152                }
34153                _ => {}
34154            }
34155            self.merge_strip_qualifiers = names;
34156        }
34157
34158        // WHEN clauses - newline before each in pretty mode
34159        if let Some(whens) = &e.whens {
34160            if self.config.pretty {
34161                self.write_newline();
34162                self.write_indent();
34163            } else {
34164                self.write_space();
34165            }
34166            self.generate_expression(whens)?;
34167        }
34168
34169        // Restore merge_strip_qualifiers
34170        self.merge_strip_qualifiers = saved_merge_strip;
34171
34172        // OUTPUT/RETURNING clause - newline before in pretty mode
34173        if let Some(returning) = &e.returning {
34174            if self.config.pretty {
34175                self.write_newline();
34176                self.write_indent();
34177            } else {
34178                self.write_space();
34179            }
34180            self.generate_expression(returning)?;
34181        }
34182        Ok(())
34183    }
34184
34185    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
34186        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
34187        if e.no.is_some() {
34188            self.write_keyword("NO MERGEBLOCKRATIO");
34189        } else if e.default.is_some() {
34190            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
34191        } else {
34192            self.write_keyword("MERGEBLOCKRATIO");
34193            self.write("=");
34194            if let Some(this) = &e.this {
34195                self.generate_expression(this)?;
34196            }
34197            if e.percent.is_some() {
34198                self.write_keyword(" PERCENT");
34199            }
34200        }
34201        Ok(())
34202    }
34203
34204    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
34205        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
34206        self.write_keyword("TTL");
34207        let pretty_clickhouse = self.config.pretty
34208            && matches!(
34209                self.config.dialect,
34210                Some(crate::dialects::DialectType::ClickHouse)
34211            );
34212
34213        if pretty_clickhouse {
34214            self.write_newline();
34215            self.indent_level += 1;
34216            for (i, expr) in e.expressions.iter().enumerate() {
34217                if i > 0 {
34218                    self.write(",");
34219                    self.write_newline();
34220                }
34221                self.write_indent();
34222                self.generate_expression(expr)?;
34223            }
34224            self.indent_level -= 1;
34225        } else {
34226            self.write_space();
34227            for (i, expr) in e.expressions.iter().enumerate() {
34228                if i > 0 {
34229                    self.write(", ");
34230                }
34231                self.generate_expression(expr)?;
34232            }
34233        }
34234
34235        if let Some(where_) = &e.where_ {
34236            if pretty_clickhouse {
34237                self.write_newline();
34238                if let Expression::Where(w) = where_.as_ref() {
34239                    self.write_indent();
34240                    self.write_keyword("WHERE");
34241                    self.write_newline();
34242                    self.indent_level += 1;
34243                    self.write_indent();
34244                    self.generate_expression(&w.this)?;
34245                    self.indent_level -= 1;
34246                } else {
34247                    self.write_indent();
34248                    self.generate_expression(where_)?;
34249                }
34250            } else {
34251                self.write_space();
34252                self.generate_expression(where_)?;
34253            }
34254        }
34255        if let Some(group) = &e.group {
34256            if pretty_clickhouse {
34257                self.write_newline();
34258                if let Expression::Group(g) = group.as_ref() {
34259                    self.write_indent();
34260                    self.write_keyword("GROUP BY");
34261                    self.write_newline();
34262                    self.indent_level += 1;
34263                    for (i, expr) in g.expressions.iter().enumerate() {
34264                        if i > 0 {
34265                            self.write(",");
34266                            self.write_newline();
34267                        }
34268                        self.write_indent();
34269                        self.generate_expression(expr)?;
34270                    }
34271                    self.indent_level -= 1;
34272                } else {
34273                    self.write_indent();
34274                    self.generate_expression(group)?;
34275                }
34276            } else {
34277                self.write_space();
34278                self.generate_expression(group)?;
34279            }
34280        }
34281        if let Some(aggregates) = &e.aggregates {
34282            if pretty_clickhouse {
34283                self.write_newline();
34284                self.write_indent();
34285                self.write_keyword("SET");
34286                self.write_newline();
34287                self.indent_level += 1;
34288                if let Expression::Tuple(t) = aggregates.as_ref() {
34289                    for (i, agg) in t.expressions.iter().enumerate() {
34290                        if i > 0 {
34291                            self.write(",");
34292                            self.write_newline();
34293                        }
34294                        self.write_indent();
34295                        self.generate_expression(agg)?;
34296                    }
34297                } else {
34298                    self.write_indent();
34299                    self.generate_expression(aggregates)?;
34300                }
34301                self.indent_level -= 1;
34302            } else {
34303                self.write_space();
34304                self.write_keyword("SET");
34305                self.write_space();
34306                if let Expression::Tuple(t) = aggregates.as_ref() {
34307                    for (i, agg) in t.expressions.iter().enumerate() {
34308                        if i > 0 {
34309                            self.write(", ");
34310                        }
34311                        self.generate_expression(agg)?;
34312                    }
34313                } else {
34314                    self.generate_expression(aggregates)?;
34315                }
34316            }
34317        }
34318        Ok(())
34319    }
34320
34321    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
34322        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
34323        self.generate_expression(&e.this)?;
34324        if e.delete.is_some() {
34325            self.write_keyword(" DELETE");
34326        }
34327        if let Some(recompress) = &e.recompress {
34328            self.write_keyword(" RECOMPRESS ");
34329            self.generate_expression(recompress)?;
34330        }
34331        if let Some(to_disk) = &e.to_disk {
34332            self.write_keyword(" TO DISK ");
34333            self.generate_expression(to_disk)?;
34334        }
34335        if let Some(to_volume) = &e.to_volume {
34336            self.write_keyword(" TO VOLUME ");
34337            self.generate_expression(to_volume)?;
34338        }
34339        Ok(())
34340    }
34341
34342    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
34343        // MINHASH(this, expressions...)
34344        self.write_keyword("MINHASH");
34345        self.write("(");
34346        self.generate_expression(&e.this)?;
34347        for expr in &e.expressions {
34348            self.write(", ");
34349            self.generate_expression(expr)?;
34350        }
34351        self.write(")");
34352        Ok(())
34353    }
34354
34355    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
34356        // model!attribute - Snowflake syntax
34357        self.generate_expression(&e.this)?;
34358        self.write("!");
34359        self.generate_expression(&e.expression)?;
34360        Ok(())
34361    }
34362
34363    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
34364        // MONTHNAME(this)
34365        self.write_keyword("MONTHNAME");
34366        self.write("(");
34367        self.generate_expression(&e.this)?;
34368        self.write(")");
34369        Ok(())
34370    }
34371
34372    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
34373        // Output leading comments
34374        for comment in &e.leading_comments {
34375            self.write_formatted_comment(comment);
34376            if self.config.pretty {
34377                self.write_newline();
34378                self.write_indent();
34379            } else {
34380                self.write_space();
34381            }
34382        }
34383        // Python: INSERT [OVERWRITE] kind expressions source
34384        self.write_keyword("INSERT");
34385        if e.overwrite {
34386            self.write_space();
34387            self.write_keyword("OVERWRITE");
34388        }
34389        self.write_space();
34390        self.write(&e.kind);
34391        if self.config.pretty {
34392            self.indent_level += 1;
34393            for expr in &e.expressions {
34394                self.write_newline();
34395                self.write_indent();
34396                self.generate_expression(expr)?;
34397            }
34398            self.indent_level -= 1;
34399        } else {
34400            for expr in &e.expressions {
34401                self.write_space();
34402                self.generate_expression(expr)?;
34403            }
34404        }
34405        if let Some(source) = &e.source {
34406            if self.config.pretty {
34407                self.write_newline();
34408                self.write_indent();
34409            } else {
34410                self.write_space();
34411            }
34412            self.generate_expression(source)?;
34413        }
34414        Ok(())
34415    }
34416
34417    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
34418        // Python: NEXT VALUE FOR this [OVER (order)]
34419        self.write_keyword("NEXT VALUE FOR");
34420        self.write_space();
34421        self.generate_expression(&e.this)?;
34422        if let Some(order) = &e.order {
34423            self.write_space();
34424            self.write_keyword("OVER");
34425            self.write(" (");
34426            self.generate_expression(order)?;
34427            self.write(")");
34428        }
34429        Ok(())
34430    }
34431
34432    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
34433        // NORMAL(mean, stddev, gen)
34434        self.write_keyword("NORMAL");
34435        self.write("(");
34436        self.generate_expression(&e.this)?;
34437        if let Some(stddev) = &e.stddev {
34438            self.write(", ");
34439            self.generate_expression(stddev)?;
34440        }
34441        if let Some(gen) = &e.gen {
34442            self.write(", ");
34443            self.generate_expression(gen)?;
34444        }
34445        self.write(")");
34446        Ok(())
34447    }
34448
34449    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
34450        // NORMALIZE(this, form) or CASEFOLD version
34451        if e.is_casefold.is_some() {
34452            self.write_keyword("NORMALIZE_AND_CASEFOLD");
34453        } else {
34454            self.write_keyword("NORMALIZE");
34455        }
34456        self.write("(");
34457        self.generate_expression(&e.this)?;
34458        if let Some(form) = &e.form {
34459            self.write(", ");
34460            self.generate_expression(form)?;
34461        }
34462        self.write(")");
34463        Ok(())
34464    }
34465
34466    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
34467        // Python: [NOT ]NULL
34468        if e.allow_null.is_none() {
34469            self.write_keyword("NOT ");
34470        }
34471        self.write_keyword("NULL");
34472        Ok(())
34473    }
34474
34475    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
34476        // NULLIF(this, expression)
34477        self.write_keyword("NULLIF");
34478        self.write("(");
34479        self.generate_expression(&e.this)?;
34480        self.write(", ");
34481        self.generate_expression(&e.expression)?;
34482        self.write(")");
34483        Ok(())
34484    }
34485
34486    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
34487        // FORMAT(this, format, culture)
34488        self.write_keyword("FORMAT");
34489        self.write("(");
34490        self.generate_expression(&e.this)?;
34491        self.write(", '");
34492        self.write(&e.format);
34493        self.write("'");
34494        if let Some(culture) = &e.culture {
34495            self.write(", ");
34496            self.generate_expression(culture)?;
34497        }
34498        self.write(")");
34499        Ok(())
34500    }
34501
34502    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
34503        // OBJECT_AGG(key, value)
34504        self.write_keyword("OBJECT_AGG");
34505        self.write("(");
34506        self.generate_expression(&e.this)?;
34507        self.write(", ");
34508        self.generate_expression(&e.expression)?;
34509        self.write(")");
34510        Ok(())
34511    }
34512
34513    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
34514        // Python: Just returns the name
34515        self.generate_expression(&e.this)?;
34516        Ok(())
34517    }
34518
34519    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
34520        // OBJECT_INSERT(obj, key, value, [update_flag])
34521        self.write_keyword("OBJECT_INSERT");
34522        self.write("(");
34523        self.generate_expression(&e.this)?;
34524        if let Some(key) = &e.key {
34525            self.write(", ");
34526            self.generate_expression(key)?;
34527        }
34528        if let Some(value) = &e.value {
34529            self.write(", ");
34530            self.generate_expression(value)?;
34531        }
34532        if let Some(update_flag) = &e.update_flag {
34533            self.write(", ");
34534            self.generate_expression(update_flag)?;
34535        }
34536        self.write(")");
34537        Ok(())
34538    }
34539
34540    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
34541        // OFFSET value [ROW|ROWS]
34542        self.write_keyword("OFFSET");
34543        self.write_space();
34544        self.generate_expression(&e.this)?;
34545        // Output ROWS keyword only for TSQL/Oracle targets
34546        if e.rows == Some(true)
34547            && matches!(
34548                self.config.dialect,
34549                Some(crate::dialects::DialectType::TSQL)
34550                    | Some(crate::dialects::DialectType::Oracle)
34551            )
34552        {
34553            self.write_space();
34554            self.write_keyword("ROWS");
34555        }
34556        Ok(())
34557    }
34558
34559    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
34560        // QUALIFY condition (Snowflake/BigQuery)
34561        self.write_keyword("QUALIFY");
34562        self.write_space();
34563        self.generate_expression(&e.this)?;
34564        Ok(())
34565    }
34566
34567    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
34568        // ON CLUSTER cluster_name
34569        self.write_keyword("ON CLUSTER");
34570        self.write_space();
34571        self.generate_expression(&e.this)?;
34572        Ok(())
34573    }
34574
34575    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
34576        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
34577        self.write_keyword("ON COMMIT");
34578        if e.delete.is_some() {
34579            self.write_keyword(" DELETE ROWS");
34580        } else {
34581            self.write_keyword(" PRESERVE ROWS");
34582        }
34583        Ok(())
34584    }
34585
34586    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
34587        // Python: error/empty/null handling
34588        if let Some(empty) = &e.empty {
34589            self.generate_expression(empty)?;
34590            self.write_keyword(" ON EMPTY");
34591        }
34592        if let Some(error) = &e.error {
34593            if e.empty.is_some() {
34594                self.write_space();
34595            }
34596            self.generate_expression(error)?;
34597            self.write_keyword(" ON ERROR");
34598        }
34599        if let Some(null) = &e.null {
34600            if e.empty.is_some() || e.error.is_some() {
34601                self.write_space();
34602            }
34603            self.generate_expression(null)?;
34604            self.write_keyword(" ON NULL");
34605        }
34606        Ok(())
34607    }
34608
34609    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
34610        // Materialize doesn't support ON CONFLICT - skip entirely
34611        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
34612            return Ok(());
34613        }
34614        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
34615        if e.duplicate.is_some() {
34616            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
34617            self.write_keyword("ON DUPLICATE KEY UPDATE");
34618            for (i, expr) in e.expressions.iter().enumerate() {
34619                if i > 0 {
34620                    self.write(",");
34621                }
34622                self.write_space();
34623                self.generate_expression(expr)?;
34624            }
34625            return Ok(());
34626        } else {
34627            self.write_keyword("ON CONFLICT");
34628        }
34629        if let Some(constraint) = &e.constraint {
34630            self.write_keyword(" ON CONSTRAINT ");
34631            self.generate_expression(constraint)?;
34632        }
34633        if let Some(conflict_keys) = &e.conflict_keys {
34634            // conflict_keys can be a Tuple containing expressions
34635            if let Expression::Tuple(t) = conflict_keys.as_ref() {
34636                self.write("(");
34637                for (i, expr) in t.expressions.iter().enumerate() {
34638                    if i > 0 {
34639                        self.write(", ");
34640                    }
34641                    self.generate_expression(expr)?;
34642                }
34643                self.write(")");
34644            } else {
34645                self.write("(");
34646                self.generate_expression(conflict_keys)?;
34647                self.write(")");
34648            }
34649        }
34650        if let Some(index_predicate) = &e.index_predicate {
34651            self.write_keyword(" WHERE ");
34652            self.generate_expression(index_predicate)?;
34653        }
34654        if let Some(action) = &e.action {
34655            // Check if action is "NOTHING" or an UPDATE set
34656            if let Expression::Identifier(id) = action.as_ref() {
34657                if id.name.eq_ignore_ascii_case("NOTHING") {
34658                    self.write_keyword(" DO NOTHING");
34659                } else {
34660                    self.write_keyword(" DO ");
34661                    self.generate_expression(action)?;
34662                }
34663            } else if let Expression::Tuple(t) = action.as_ref() {
34664                // DO UPDATE SET col1 = val1, col2 = val2
34665                self.write_keyword(" DO UPDATE SET ");
34666                for (i, expr) in t.expressions.iter().enumerate() {
34667                    if i > 0 {
34668                        self.write(", ");
34669                    }
34670                    self.generate_expression(expr)?;
34671                }
34672            } else {
34673                self.write_keyword(" DO ");
34674                self.generate_expression(action)?;
34675            }
34676        }
34677        // WHERE clause for the UPDATE action
34678        if let Some(where_) = &e.where_ {
34679            self.write_keyword(" WHERE ");
34680            self.generate_expression(where_)?;
34681        }
34682        Ok(())
34683    }
34684
34685    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
34686        // ON property_value
34687        self.write_keyword("ON");
34688        self.write_space();
34689        self.generate_expression(&e.this)?;
34690        Ok(())
34691    }
34692
34693    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
34694        // Python: this expression (e.g., column opclass)
34695        self.generate_expression(&e.this)?;
34696        self.write_space();
34697        self.generate_expression(&e.expression)?;
34698        Ok(())
34699    }
34700
34701    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
34702        // Python: OPENJSON(this[, path]) [WITH (columns)]
34703        self.write_keyword("OPENJSON");
34704        self.write("(");
34705        self.generate_expression(&e.this)?;
34706        if let Some(path) = &e.path {
34707            self.write(", ");
34708            self.generate_expression(path)?;
34709        }
34710        self.write(")");
34711        if !e.expressions.is_empty() {
34712            self.write_keyword(" WITH");
34713            if self.config.pretty {
34714                self.write(" (\n");
34715                self.indent_level += 2;
34716                for (i, expr) in e.expressions.iter().enumerate() {
34717                    if i > 0 {
34718                        self.write(",\n");
34719                    }
34720                    self.write_indent();
34721                    self.generate_expression(expr)?;
34722                }
34723                self.write("\n");
34724                self.indent_level -= 2;
34725                self.write(")");
34726            } else {
34727                self.write(" (");
34728                for (i, expr) in e.expressions.iter().enumerate() {
34729                    if i > 0 {
34730                        self.write(", ");
34731                    }
34732                    self.generate_expression(expr)?;
34733                }
34734                self.write(")");
34735            }
34736        }
34737        Ok(())
34738    }
34739
34740    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
34741        // Python: this kind [path] [AS JSON]
34742        self.generate_expression(&e.this)?;
34743        self.write_space();
34744        // Use parsed data_type if available, otherwise fall back to kind string
34745        if let Some(ref dt) = e.data_type {
34746            self.generate_data_type(dt)?;
34747        } else if !e.kind.is_empty() {
34748            self.write(&e.kind);
34749        }
34750        if let Some(path) = &e.path {
34751            self.write_space();
34752            self.generate_expression(path)?;
34753        }
34754        if e.as_json.is_some() {
34755            self.write_keyword(" AS JSON");
34756        }
34757        Ok(())
34758    }
34759
34760    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
34761        // this OPERATOR(op) expression
34762        self.generate_expression(&e.this)?;
34763        self.write_space();
34764        if let Some(op) = &e.operator {
34765            self.write_keyword("OPERATOR");
34766            self.write("(");
34767            self.generate_expression(op)?;
34768            self.write(")");
34769        }
34770        // Emit inline comments between OPERATOR() and the RHS
34771        for comment in &e.comments {
34772            self.write_space();
34773            self.write_formatted_comment(comment);
34774        }
34775        self.write_space();
34776        self.generate_expression(&e.expression)?;
34777        Ok(())
34778    }
34779
34780    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
34781        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
34782        self.write_keyword("ORDER BY");
34783        let pretty_clickhouse_single_paren = self.config.pretty
34784            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
34785            && e.expressions.len() == 1
34786            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
34787        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
34788            && e.expressions.len() == 1
34789            && matches!(e.expressions[0].this, Expression::Tuple(_))
34790            && !e.expressions[0].desc
34791            && e.expressions[0].nulls_first.is_none();
34792
34793        if pretty_clickhouse_single_paren {
34794            self.write_space();
34795            if let Expression::Paren(p) = &e.expressions[0].this {
34796                self.write("(");
34797                self.write_newline();
34798                self.indent_level += 1;
34799                self.write_indent();
34800                self.generate_expression(&p.this)?;
34801                self.indent_level -= 1;
34802                self.write_newline();
34803                self.write(")");
34804            }
34805            return Ok(());
34806        }
34807
34808        if clickhouse_single_tuple {
34809            self.write_space();
34810            if let Expression::Tuple(t) = &e.expressions[0].this {
34811                self.write("(");
34812                for (i, expr) in t.expressions.iter().enumerate() {
34813                    if i > 0 {
34814                        self.write(", ");
34815                    }
34816                    self.generate_expression(expr)?;
34817                }
34818                self.write(")");
34819            }
34820            return Ok(());
34821        }
34822
34823        self.write_space();
34824        for (i, ordered) in e.expressions.iter().enumerate() {
34825            if i > 0 {
34826                self.write(", ");
34827            }
34828            self.generate_expression(&ordered.this)?;
34829            if ordered.desc {
34830                self.write_space();
34831                self.write_keyword("DESC");
34832            } else if ordered.explicit_asc {
34833                self.write_space();
34834                self.write_keyword("ASC");
34835            }
34836            if let Some(nulls_first) = ordered.nulls_first {
34837                // In Dremio, NULLS LAST is the default, so skip generating it
34838                let skip_nulls_last =
34839                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
34840                if !skip_nulls_last {
34841                    self.write_space();
34842                    self.write_keyword("NULLS");
34843                    self.write_space();
34844                    if nulls_first {
34845                        self.write_keyword("FIRST");
34846                    } else {
34847                        self.write_keyword("LAST");
34848                    }
34849                }
34850            }
34851        }
34852        Ok(())
34853    }
34854
34855    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
34856        // OUTPUT(model)
34857        self.write_keyword("OUTPUT");
34858        self.write("(");
34859        if self.config.pretty {
34860            self.indent_level += 1;
34861            self.write_newline();
34862            self.write_indent();
34863            self.generate_expression(&e.this)?;
34864            self.indent_level -= 1;
34865            self.write_newline();
34866        } else {
34867            self.generate_expression(&e.this)?;
34868        }
34869        self.write(")");
34870        Ok(())
34871    }
34872
34873    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
34874        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
34875        self.write_keyword("TRUNCATE");
34876        if let Some(this) = &e.this {
34877            self.write_space();
34878            self.generate_expression(this)?;
34879        }
34880        if e.with_count.is_some() {
34881            self.write_keyword(" WITH COUNT");
34882        } else {
34883            self.write_keyword(" WITHOUT COUNT");
34884        }
34885        Ok(())
34886    }
34887
34888    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
34889        // Python: name(expressions)(params)
34890        self.generate_expression(&e.this)?;
34891        self.write("(");
34892        for (i, expr) in e.expressions.iter().enumerate() {
34893            if i > 0 {
34894                self.write(", ");
34895            }
34896            self.generate_expression(expr)?;
34897        }
34898        self.write(")(");
34899        for (i, param) in e.params.iter().enumerate() {
34900            if i > 0 {
34901                self.write(", ");
34902            }
34903            self.generate_expression(param)?;
34904        }
34905        self.write(")");
34906        Ok(())
34907    }
34908
34909    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
34910        // PARSE_DATETIME(format, this) or similar
34911        self.write_keyword("PARSE_DATETIME");
34912        self.write("(");
34913        if let Some(format) = &e.format {
34914            self.write("'");
34915            self.write(format);
34916            self.write("', ");
34917        }
34918        self.generate_expression(&e.this)?;
34919        if let Some(zone) = &e.zone {
34920            self.write(", ");
34921            self.generate_expression(zone)?;
34922        }
34923        self.write(")");
34924        Ok(())
34925    }
34926
34927    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
34928        // PARSE_IP(this, type, permissive)
34929        self.write_keyword("PARSE_IP");
34930        self.write("(");
34931        self.generate_expression(&e.this)?;
34932        if let Some(type_) = &e.type_ {
34933            self.write(", ");
34934            self.generate_expression(type_)?;
34935        }
34936        if let Some(permissive) = &e.permissive {
34937            self.write(", ");
34938            self.generate_expression(permissive)?;
34939        }
34940        self.write(")");
34941        Ok(())
34942    }
34943
34944    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
34945        // PARSE_JSON(this, [expression])
34946        self.write_keyword("PARSE_JSON");
34947        self.write("(");
34948        self.generate_expression(&e.this)?;
34949        if let Some(expression) = &e.expression {
34950            self.write(", ");
34951            self.generate_expression(expression)?;
34952        }
34953        self.write(")");
34954        Ok(())
34955    }
34956
34957    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
34958        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
34959        self.write_keyword("PARSE_TIME");
34960        self.write("(");
34961        self.write(&format!("'{}'", e.format));
34962        self.write(", ");
34963        self.generate_expression(&e.this)?;
34964        self.write(")");
34965        Ok(())
34966    }
34967
34968    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
34969        // PARSE_URL(this, [part_to_extract], [key], [permissive])
34970        self.write_keyword("PARSE_URL");
34971        self.write("(");
34972        self.generate_expression(&e.this)?;
34973        if let Some(part) = &e.part_to_extract {
34974            self.write(", ");
34975            self.generate_expression(part)?;
34976        }
34977        if let Some(key) = &e.key {
34978            self.write(", ");
34979            self.generate_expression(key)?;
34980        }
34981        if let Some(permissive) = &e.permissive {
34982            self.write(", ");
34983            self.generate_expression(permissive)?;
34984        }
34985        self.write(")");
34986        Ok(())
34987    }
34988
34989    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
34990        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
34991        if e.subpartition {
34992            self.write_keyword("SUBPARTITION");
34993        } else {
34994            self.write_keyword("PARTITION");
34995        }
34996        self.write("(");
34997        for (i, expr) in e.expressions.iter().enumerate() {
34998            if i > 0 {
34999                self.write(", ");
35000            }
35001            self.generate_expression(expr)?;
35002        }
35003        self.write(")");
35004        Ok(())
35005    }
35006
35007    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
35008        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
35009        if let Some(this) = &e.this {
35010            if let Some(expression) = &e.expression {
35011                // WITH (MODULUS this, REMAINDER expression)
35012                self.write_keyword("WITH");
35013                self.write(" (");
35014                self.write_keyword("MODULUS");
35015                self.write_space();
35016                self.generate_expression(this)?;
35017                self.write(", ");
35018                self.write_keyword("REMAINDER");
35019                self.write_space();
35020                self.generate_expression(expression)?;
35021                self.write(")");
35022            } else {
35023                // IN (this) - this could be a list
35024                self.write_keyword("IN");
35025                self.write(" (");
35026                self.generate_partition_bound_values(this)?;
35027                self.write(")");
35028            }
35029        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
35030            // FROM (from_expressions) TO (to_expressions)
35031            self.write_keyword("FROM");
35032            self.write(" (");
35033            self.generate_partition_bound_values(from)?;
35034            self.write(") ");
35035            self.write_keyword("TO");
35036            self.write(" (");
35037            self.generate_partition_bound_values(to)?;
35038            self.write(")");
35039        }
35040        Ok(())
35041    }
35042
35043    /// Generate partition bound values - handles Tuple expressions by outputting
35044    /// contents without wrapping parens (since caller provides the parens)
35045    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
35046        if let Expression::Tuple(t) = expr {
35047            for (i, e) in t.expressions.iter().enumerate() {
35048                if i > 0 {
35049                    self.write(", ");
35050                }
35051                self.generate_expression(e)?;
35052            }
35053            Ok(())
35054        } else {
35055            self.generate_expression(expr)
35056        }
35057    }
35058
35059    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
35060        // PARTITION BY LIST (partition_expressions) (create_expressions)
35061        self.write_keyword("PARTITION BY LIST");
35062        if let Some(partition_exprs) = &e.partition_expressions {
35063            self.write(" (");
35064            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
35065            self.generate_doris_partition_expressions(partition_exprs)?;
35066            self.write(")");
35067        }
35068        if let Some(create_exprs) = &e.create_expressions {
35069            self.write(" (");
35070            // Unwrap Tuple for partition definitions
35071            self.generate_doris_partition_definitions(create_exprs)?;
35072            self.write(")");
35073        }
35074        Ok(())
35075    }
35076
35077    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
35078        // PARTITION BY RANGE (partition_expressions) (create_expressions)
35079        self.write_keyword("PARTITION BY RANGE");
35080        if let Some(partition_exprs) = &e.partition_expressions {
35081            self.write(" (");
35082            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
35083            self.generate_doris_partition_expressions(partition_exprs)?;
35084            self.write(")");
35085        }
35086        if let Some(create_exprs) = &e.create_expressions {
35087            self.write(" (");
35088            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
35089            self.generate_doris_partition_definitions(create_exprs)?;
35090            self.write(")");
35091        }
35092        Ok(())
35093    }
35094
35095    /// Generate Doris partition column expressions (unwrap Tuple)
35096    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
35097        if let Expression::Tuple(t) = expr {
35098            for (i, e) in t.expressions.iter().enumerate() {
35099                if i > 0 {
35100                    self.write(", ");
35101                }
35102                self.generate_expression(e)?;
35103            }
35104        } else {
35105            self.generate_expression(expr)?;
35106        }
35107        Ok(())
35108    }
35109
35110    /// Generate Doris partition definitions (comma-separated Partition expressions)
35111    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
35112        match expr {
35113            Expression::Tuple(t) => {
35114                // Multiple partitions, comma-separated
35115                for (i, part) in t.expressions.iter().enumerate() {
35116                    if i > 0 {
35117                        self.write(", ");
35118                    }
35119                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
35120                    if let Expression::Partition(p) = part {
35121                        for (j, inner) in p.expressions.iter().enumerate() {
35122                            if j > 0 {
35123                                self.write(", ");
35124                            }
35125                            self.generate_expression(inner)?;
35126                        }
35127                    } else {
35128                        self.generate_expression(part)?;
35129                    }
35130                }
35131            }
35132            Expression::PartitionByRangePropertyDynamic(_) => {
35133                // Dynamic partition - FROM/TO/INTERVAL
35134                self.generate_expression(expr)?;
35135            }
35136            _ => {
35137                self.generate_expression(expr)?;
35138            }
35139        }
35140        Ok(())
35141    }
35142
35143    fn generate_partition_by_range_property_dynamic(
35144        &mut self,
35145        e: &PartitionByRangePropertyDynamic,
35146    ) -> Result<()> {
35147        if e.use_start_end {
35148            // StarRocks: START ('val') END ('val') EVERY (expr)
35149            if let Some(start) = &e.start {
35150                self.write_keyword("START");
35151                self.write(" (");
35152                self.generate_expression(start)?;
35153                self.write(")");
35154            }
35155            if let Some(end) = &e.end {
35156                self.write_space();
35157                self.write_keyword("END");
35158                self.write(" (");
35159                self.generate_expression(end)?;
35160                self.write(")");
35161            }
35162            if let Some(every) = &e.every {
35163                self.write_space();
35164                self.write_keyword("EVERY");
35165                self.write(" (");
35166                // Use unquoted interval format for StarRocks
35167                self.generate_doris_interval(every)?;
35168                self.write(")");
35169            }
35170        } else {
35171            // Doris: FROM (start) TO (end) INTERVAL n UNIT
35172            if let Some(start) = &e.start {
35173                self.write_keyword("FROM");
35174                self.write(" (");
35175                self.generate_expression(start)?;
35176                self.write(")");
35177            }
35178            if let Some(end) = &e.end {
35179                self.write_space();
35180                self.write_keyword("TO");
35181                self.write(" (");
35182                self.generate_expression(end)?;
35183                self.write(")");
35184            }
35185            if let Some(every) = &e.every {
35186                self.write_space();
35187                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
35188                self.generate_doris_interval(every)?;
35189            }
35190        }
35191        Ok(())
35192    }
35193
35194    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
35195    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
35196        if let Expression::Interval(interval) = expr {
35197            self.write_keyword("INTERVAL");
35198            if let Some(ref value) = interval.this {
35199                self.write_space();
35200                // If the value is a string literal that looks like a number,
35201                // output it without quotes (matching Python sqlglot's
35202                // partitionbyrangepropertydynamic_sql which converts back to number)
35203                match value {
35204                    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()) => {
35205                        if let Literal::String(s) = lit.as_ref() {
35206                            self.write(s);
35207                        }
35208                    }
35209                    _ => {
35210                        self.generate_expression(value)?;
35211                    }
35212                }
35213            }
35214            if let Some(ref unit_spec) = interval.unit {
35215                self.write_space();
35216                self.write_interval_unit_spec(unit_spec)?;
35217            }
35218            Ok(())
35219        } else {
35220            self.generate_expression(expr)
35221        }
35222    }
35223
35224    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
35225        // TRUNCATE(expression, this)
35226        self.write_keyword("TRUNCATE");
35227        self.write("(");
35228        self.generate_expression(&e.expression)?;
35229        self.write(", ");
35230        self.generate_expression(&e.this)?;
35231        self.write(")");
35232        Ok(())
35233    }
35234
35235    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
35236        // Doris: PARTITION name VALUES IN (val1, val2)
35237        self.write_keyword("PARTITION");
35238        self.write_space();
35239        self.generate_expression(&e.this)?;
35240        self.write_space();
35241        self.write_keyword("VALUES IN");
35242        self.write(" (");
35243        for (i, expr) in e.expressions.iter().enumerate() {
35244            if i > 0 {
35245                self.write(", ");
35246            }
35247            self.generate_expression(expr)?;
35248        }
35249        self.write(")");
35250        Ok(())
35251    }
35252
35253    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
35254        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
35255        // TSQL ranges have no expressions and just use `this TO expression`
35256        if e.expressions.is_empty() && e.expression.is_some() {
35257            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
35258            self.generate_expression(&e.this)?;
35259            self.write_space();
35260            self.write_keyword("TO");
35261            self.write_space();
35262            self.generate_expression(e.expression.as_ref().unwrap())?;
35263            return Ok(());
35264        }
35265
35266        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
35267        self.write_keyword("PARTITION");
35268        self.write_space();
35269        self.generate_expression(&e.this)?;
35270        self.write_space();
35271
35272        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
35273        if e.expressions.len() == 1 {
35274            // Single value: VALUES LESS THAN (val)
35275            self.write_keyword("VALUES LESS THAN");
35276            self.write(" (");
35277            self.generate_expression(&e.expressions[0])?;
35278            self.write(")");
35279        } else if !e.expressions.is_empty() {
35280            // Multiple values with Tuple: VALUES [(val1), (val2))
35281            self.write_keyword("VALUES");
35282            self.write(" [");
35283            for (i, expr) in e.expressions.iter().enumerate() {
35284                if i > 0 {
35285                    self.write(", ");
35286                }
35287                // If the expr is a Tuple, generate its contents wrapped in parens
35288                if let Expression::Tuple(t) = expr {
35289                    self.write("(");
35290                    for (j, inner) in t.expressions.iter().enumerate() {
35291                        if j > 0 {
35292                            self.write(", ");
35293                        }
35294                        self.generate_expression(inner)?;
35295                    }
35296                    self.write(")");
35297                } else {
35298                    self.write("(");
35299                    self.generate_expression(expr)?;
35300                    self.write(")");
35301                }
35302            }
35303            self.write(")");
35304        }
35305        Ok(())
35306    }
35307
35308    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
35309        // BUCKET(this, expression)
35310        self.write_keyword("BUCKET");
35311        self.write("(");
35312        self.generate_expression(&e.this)?;
35313        self.write(", ");
35314        self.generate_expression(&e.expression)?;
35315        self.write(")");
35316        Ok(())
35317    }
35318
35319    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
35320        // BigQuery table property: PARTITION BY expression [, expression ...]
35321        self.write_keyword("PARTITION BY");
35322        self.write_space();
35323        for (i, expr) in e.expressions.iter().enumerate() {
35324            if i > 0 {
35325                self.write(", ");
35326            }
35327            self.generate_expression(expr)?;
35328        }
35329        Ok(())
35330    }
35331
35332    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
35333        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
35334        if matches!(
35335            self.config.dialect,
35336            Some(crate::dialects::DialectType::Teradata)
35337                | Some(crate::dialects::DialectType::ClickHouse)
35338        ) {
35339            self.write_keyword("PARTITION BY");
35340        } else {
35341            self.write_keyword("PARTITIONED BY");
35342        }
35343        self.write_space();
35344        // In pretty mode, always use multiline tuple format for PARTITIONED BY
35345        if self.config.pretty {
35346            if let Expression::Tuple(ref tuple) = *e.this {
35347                self.write("(");
35348                self.write_newline();
35349                self.indent_level += 1;
35350                for (i, expr) in tuple.expressions.iter().enumerate() {
35351                    if i > 0 {
35352                        self.write(",");
35353                        self.write_newline();
35354                    }
35355                    self.write_indent();
35356                    self.generate_expression(expr)?;
35357                }
35358                self.indent_level -= 1;
35359                self.write_newline();
35360                self.write(")");
35361            } else {
35362                self.generate_expression(&e.this)?;
35363            }
35364        } else {
35365            self.generate_expression(&e.this)?;
35366        }
35367        Ok(())
35368    }
35369
35370    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
35371        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
35372        self.write_keyword("PARTITION OF");
35373        self.write_space();
35374        self.generate_expression(&e.this)?;
35375        // Check if expression is a PartitionBoundSpec
35376        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
35377            self.write_space();
35378            self.write_keyword("FOR VALUES");
35379            self.write_space();
35380            self.generate_expression(&e.expression)?;
35381        } else {
35382            self.write_space();
35383            self.write_keyword("DEFAULT");
35384        }
35385        Ok(())
35386    }
35387
35388    fn generate_period_for_system_time_constraint(
35389        &mut self,
35390        e: &PeriodForSystemTimeConstraint,
35391    ) -> Result<()> {
35392        // PERIOD FOR SYSTEM_TIME (this, expression)
35393        self.write_keyword("PERIOD FOR SYSTEM_TIME");
35394        self.write(" (");
35395        self.generate_expression(&e.this)?;
35396        self.write(", ");
35397        self.generate_expression(&e.expression)?;
35398        self.write(")");
35399        Ok(())
35400    }
35401
35402    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
35403        // value AS alias
35404        // The alias can be an identifier or an expression (e.g., string concatenation)
35405        self.generate_expression(&e.this)?;
35406        self.write_space();
35407        self.write_keyword("AS");
35408        self.write_space();
35409        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
35410        if self.config.unpivot_aliases_are_identifiers {
35411            match &e.alias {
35412                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35413                    let Literal::String(s) = lit.as_ref() else {
35414                        unreachable!()
35415                    };
35416                    // Convert string literal to identifier
35417                    self.generate_identifier(&Identifier::new(s.clone()))?;
35418                }
35419                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
35420                    let Literal::Number(n) = lit.as_ref() else {
35421                        unreachable!()
35422                    };
35423                    // Convert number literal to quoted identifier
35424                    let mut id = Identifier::new(n.clone());
35425                    id.quoted = true;
35426                    self.generate_identifier(&id)?;
35427                }
35428                other => {
35429                    self.generate_expression(other)?;
35430                }
35431            }
35432        } else {
35433            self.generate_expression(&e.alias)?;
35434        }
35435        Ok(())
35436    }
35437
35438    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
35439        // ANY or ANY [expression]
35440        self.write_keyword("ANY");
35441        if let Some(this) = &e.this {
35442            self.write_space();
35443            self.generate_expression(this)?;
35444        }
35445        Ok(())
35446    }
35447
35448    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
35449        // ML.PREDICT(MODEL this, expression, [params_struct])
35450        self.write_keyword("ML.PREDICT");
35451        self.write("(");
35452        self.write_keyword("MODEL");
35453        self.write_space();
35454        self.generate_expression(&e.this)?;
35455        self.write(", ");
35456        self.generate_expression(&e.expression)?;
35457        if let Some(params) = &e.params_struct {
35458            self.write(", ");
35459            self.generate_expression(params)?;
35460        }
35461        self.write(")");
35462        Ok(())
35463    }
35464
35465    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
35466        // PREVIOUS_DAY(this, expression)
35467        self.write_keyword("PREVIOUS_DAY");
35468        self.write("(");
35469        self.generate_expression(&e.this)?;
35470        self.write(", ");
35471        self.generate_expression(&e.expression)?;
35472        self.write(")");
35473        Ok(())
35474    }
35475
35476    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
35477        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
35478        self.write_keyword("PRIMARY KEY");
35479        if let Some(name) = &e.this {
35480            self.write_space();
35481            self.generate_expression(name)?;
35482        }
35483        if !e.expressions.is_empty() {
35484            self.write(" (");
35485            for (i, expr) in e.expressions.iter().enumerate() {
35486                if i > 0 {
35487                    self.write(", ");
35488                }
35489                self.generate_expression(expr)?;
35490            }
35491            self.write(")");
35492        }
35493        if let Some(include) = &e.include {
35494            self.write_space();
35495            self.generate_expression(include)?;
35496        }
35497        if !e.options.is_empty() {
35498            self.write_space();
35499            for (i, opt) in e.options.iter().enumerate() {
35500                if i > 0 {
35501                    self.write_space();
35502                }
35503                self.generate_expression(opt)?;
35504            }
35505        }
35506        Ok(())
35507    }
35508
35509    fn generate_primary_key_column_constraint(
35510        &mut self,
35511        _e: &PrimaryKeyColumnConstraint,
35512    ) -> Result<()> {
35513        // PRIMARY KEY constraint at column level
35514        self.write_keyword("PRIMARY KEY");
35515        Ok(())
35516    }
35517
35518    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
35519        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
35520        self.write_keyword("PATH");
35521        self.write_space();
35522        self.generate_expression(&e.this)?;
35523        Ok(())
35524    }
35525
35526    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
35527        // PROJECTION this (expression)
35528        self.write_keyword("PROJECTION");
35529        self.write_space();
35530        self.generate_expression(&e.this)?;
35531        self.write(" (");
35532        self.generate_expression(&e.expression)?;
35533        self.write(")");
35534        Ok(())
35535    }
35536
35537    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
35538        // Properties list
35539        for (i, prop) in e.expressions.iter().enumerate() {
35540            if i > 0 {
35541                self.write(", ");
35542            }
35543            self.generate_expression(prop)?;
35544        }
35545        Ok(())
35546    }
35547
35548    fn generate_property(&mut self, e: &Property) -> Result<()> {
35549        // name=value
35550        self.generate_expression(&e.this)?;
35551        if let Some(value) = &e.value {
35552            self.write("=");
35553            self.generate_expression(value)?;
35554        }
35555        Ok(())
35556    }
35557
35558    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
35559        self.write_keyword("OPTIONS");
35560        if e.entries.is_empty() {
35561            self.write(" ()");
35562            return Ok(());
35563        }
35564
35565        if self.config.pretty {
35566            self.write(" (");
35567            self.write_newline();
35568            self.indent_level += 1;
35569            for (i, entry) in e.entries.iter().enumerate() {
35570                if i > 0 {
35571                    self.write(",");
35572                    self.write_newline();
35573                }
35574                self.write_indent();
35575                self.generate_identifier(&entry.key)?;
35576                self.write("=");
35577                self.generate_expression(&entry.value)?;
35578            }
35579            self.indent_level -= 1;
35580            self.write_newline();
35581            self.write(")");
35582        } else {
35583            self.write(" (");
35584            for (i, entry) in e.entries.iter().enumerate() {
35585                if i > 0 {
35586                    self.write(", ");
35587                }
35588                self.generate_identifier(&entry.key)?;
35589                self.write("=");
35590                self.generate_expression(&entry.value)?;
35591            }
35592            self.write(")");
35593        }
35594        Ok(())
35595    }
35596
35597    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
35598    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
35599        self.write_keyword("OPTIONS");
35600        self.write(" (");
35601        for (i, opt) in options.iter().enumerate() {
35602            if i > 0 {
35603                self.write(", ");
35604            }
35605            self.generate_option_expression(opt)?;
35606        }
35607        self.write(")");
35608        Ok(())
35609    }
35610
35611    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
35612    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
35613        self.write_keyword("PROPERTIES");
35614        self.write(" (");
35615        for (i, prop) in properties.iter().enumerate() {
35616            if i > 0 {
35617                self.write(", ");
35618            }
35619            self.generate_option_expression(prop)?;
35620        }
35621        self.write(")");
35622        Ok(())
35623    }
35624
35625    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
35626    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
35627        self.write_keyword("ENVIRONMENT");
35628        self.write(" (");
35629        for (i, env_item) in environment.iter().enumerate() {
35630            if i > 0 {
35631                self.write(", ");
35632            }
35633            self.generate_environment_expression(env_item)?;
35634        }
35635        self.write(")");
35636        Ok(())
35637    }
35638
35639    /// Generate an environment expression with spaces around =
35640    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
35641        match expr {
35642            Expression::Eq(eq) => {
35643                // Generate key = value with spaces (Databricks ENVIRONMENT style)
35644                self.generate_expression(&eq.left)?;
35645                self.write(" = ");
35646                self.generate_expression(&eq.right)?;
35647                Ok(())
35648            }
35649            _ => self.generate_expression(expr),
35650        }
35651    }
35652
35653    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
35654    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
35655        self.write_keyword("TBLPROPERTIES");
35656        if self.config.pretty {
35657            self.write(" (");
35658            self.write_newline();
35659            self.indent_level += 1;
35660            for (i, opt) in options.iter().enumerate() {
35661                if i > 0 {
35662                    self.write(",");
35663                    self.write_newline();
35664                }
35665                self.write_indent();
35666                self.generate_option_expression(opt)?;
35667            }
35668            self.indent_level -= 1;
35669            self.write_newline();
35670            self.write(")");
35671        } else {
35672            self.write(" (");
35673            for (i, opt) in options.iter().enumerate() {
35674                if i > 0 {
35675                    self.write(", ");
35676                }
35677                self.generate_option_expression(opt)?;
35678            }
35679            self.write(")");
35680        }
35681        Ok(())
35682    }
35683
35684    /// Generate an option expression without spaces around =
35685    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
35686        match expr {
35687            Expression::Eq(eq) => {
35688                // Generate key=value without spaces
35689                self.generate_expression(&eq.left)?;
35690                self.write("=");
35691                self.generate_expression(&eq.right)?;
35692                Ok(())
35693            }
35694            _ => self.generate_expression(expr),
35695        }
35696    }
35697
35698    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
35699        // Just output the name
35700        self.generate_expression(&e.this)?;
35701        Ok(())
35702    }
35703
35704    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
35705        // PUT source_file @stage [options]
35706        self.write_keyword("PUT");
35707        self.write_space();
35708
35709        // Source file path - preserve original quoting
35710        if e.source_quoted {
35711            self.write("'");
35712            self.write(&e.source);
35713            self.write("'");
35714        } else {
35715            self.write(&e.source);
35716        }
35717
35718        self.write_space();
35719
35720        // Target stage reference - output the string directly (includes @)
35721        if let Expression::Literal(lit) = &e.target {
35722            if let Literal::String(s) = lit.as_ref() {
35723                self.write(s);
35724            }
35725        } else {
35726            self.generate_expression(&e.target)?;
35727        }
35728
35729        // Optional parameters: KEY=VALUE
35730        for param in &e.params {
35731            self.write_space();
35732            self.write(&param.name);
35733            if let Some(ref value) = param.value {
35734                self.write("=");
35735                self.generate_expression(value)?;
35736            }
35737        }
35738
35739        Ok(())
35740    }
35741
35742    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
35743        // QUANTILE(this, quantile)
35744        self.write_keyword("QUANTILE");
35745        self.write("(");
35746        self.generate_expression(&e.this)?;
35747        if let Some(quantile) = &e.quantile {
35748            self.write(", ");
35749            self.generate_expression(quantile)?;
35750        }
35751        self.write(")");
35752        Ok(())
35753    }
35754
35755    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
35756        // QUERY_BAND = this [UPDATE] [FOR scope]
35757        if matches!(
35758            self.config.dialect,
35759            Some(crate::dialects::DialectType::Teradata)
35760        ) {
35761            self.write_keyword("SET");
35762            self.write_space();
35763        }
35764        self.write_keyword("QUERY_BAND");
35765        self.write(" = ");
35766        self.generate_expression(&e.this)?;
35767        if e.update.is_some() {
35768            self.write_space();
35769            self.write_keyword("UPDATE");
35770        }
35771        if let Some(scope) = &e.scope {
35772            self.write_space();
35773            self.write_keyword("FOR");
35774            self.write_space();
35775            self.generate_expression(scope)?;
35776        }
35777        Ok(())
35778    }
35779
35780    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
35781        // this = expression
35782        self.generate_expression(&e.this)?;
35783        if let Some(expression) = &e.expression {
35784            self.write(" = ");
35785            self.generate_expression(expression)?;
35786        }
35787        Ok(())
35788    }
35789
35790    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
35791        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
35792        self.write_keyword("TRANSFORM");
35793        self.write("(");
35794        for (i, expr) in e.expressions.iter().enumerate() {
35795            if i > 0 {
35796                self.write(", ");
35797            }
35798            self.generate_expression(expr)?;
35799        }
35800        self.write(")");
35801        if let Some(row_format_before) = &e.row_format_before {
35802            self.write_space();
35803            self.generate_expression(row_format_before)?;
35804        }
35805        if let Some(record_writer) = &e.record_writer {
35806            self.write_space();
35807            self.write_keyword("RECORDWRITER");
35808            self.write_space();
35809            self.generate_expression(record_writer)?;
35810        }
35811        if let Some(command_script) = &e.command_script {
35812            self.write_space();
35813            self.write_keyword("USING");
35814            self.write_space();
35815            self.generate_expression(command_script)?;
35816        }
35817        if let Some(schema) = &e.schema {
35818            self.write_space();
35819            self.write_keyword("AS");
35820            self.write_space();
35821            self.generate_expression(schema)?;
35822        }
35823        if let Some(row_format_after) = &e.row_format_after {
35824            self.write_space();
35825            self.generate_expression(row_format_after)?;
35826        }
35827        if let Some(record_reader) = &e.record_reader {
35828            self.write_space();
35829            self.write_keyword("RECORDREADER");
35830            self.write_space();
35831            self.generate_expression(record_reader)?;
35832        }
35833        Ok(())
35834    }
35835
35836    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
35837        // RANDN([seed])
35838        self.write_keyword("RANDN");
35839        self.write("(");
35840        if let Some(this) = &e.this {
35841            self.generate_expression(this)?;
35842        }
35843        self.write(")");
35844        Ok(())
35845    }
35846
35847    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
35848        // RANDSTR(this, [generator])
35849        self.write_keyword("RANDSTR");
35850        self.write("(");
35851        self.generate_expression(&e.this)?;
35852        if let Some(generator) = &e.generator {
35853            self.write(", ");
35854            self.generate_expression(generator)?;
35855        }
35856        self.write(")");
35857        Ok(())
35858    }
35859
35860    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
35861        // RANGE_BUCKET(this, expression)
35862        self.write_keyword("RANGE_BUCKET");
35863        self.write("(");
35864        self.generate_expression(&e.this)?;
35865        self.write(", ");
35866        self.generate_expression(&e.expression)?;
35867        self.write(")");
35868        Ok(())
35869    }
35870
35871    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
35872        // RANGE_N(this BETWEEN expressions [EACH each])
35873        self.write_keyword("RANGE_N");
35874        self.write("(");
35875        self.generate_expression(&e.this)?;
35876        self.write_space();
35877        self.write_keyword("BETWEEN");
35878        self.write_space();
35879        for (i, expr) in e.expressions.iter().enumerate() {
35880            if i > 0 {
35881                self.write(", ");
35882            }
35883            self.generate_expression(expr)?;
35884        }
35885        if let Some(each) = &e.each {
35886            self.write_space();
35887            self.write_keyword("EACH");
35888            self.write_space();
35889            self.generate_expression(each)?;
35890        }
35891        self.write(")");
35892        Ok(())
35893    }
35894
35895    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
35896        // READ_CSV(this, expressions...)
35897        self.write_keyword("READ_CSV");
35898        self.write("(");
35899        self.generate_expression(&e.this)?;
35900        for expr in &e.expressions {
35901            self.write(", ");
35902            self.generate_expression(expr)?;
35903        }
35904        self.write(")");
35905        Ok(())
35906    }
35907
35908    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
35909        // READ_PARQUET(expressions...)
35910        self.write_keyword("READ_PARQUET");
35911        self.write("(");
35912        for (i, expr) in e.expressions.iter().enumerate() {
35913            if i > 0 {
35914                self.write(", ");
35915            }
35916            self.generate_expression(expr)?;
35917        }
35918        self.write(")");
35919        Ok(())
35920    }
35921
35922    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
35923        // SEARCH kind FIRST BY this SET expression [USING using]
35924        // or CYCLE this SET expression [USING using]
35925        if e.kind == "CYCLE" {
35926            self.write_keyword("CYCLE");
35927        } else {
35928            self.write_keyword("SEARCH");
35929            self.write_space();
35930            self.write(&e.kind);
35931            self.write_space();
35932            self.write_keyword("FIRST BY");
35933        }
35934        self.write_space();
35935        self.generate_expression(&e.this)?;
35936        self.write_space();
35937        self.write_keyword("SET");
35938        self.write_space();
35939        self.generate_expression(&e.expression)?;
35940        if let Some(using) = &e.using {
35941            self.write_space();
35942            self.write_keyword("USING");
35943            self.write_space();
35944            self.generate_expression(using)?;
35945        }
35946        Ok(())
35947    }
35948
35949    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
35950        // REDUCE(this, initial, merge, [finish])
35951        self.write_keyword("REDUCE");
35952        self.write("(");
35953        self.generate_expression(&e.this)?;
35954        if let Some(initial) = &e.initial {
35955            self.write(", ");
35956            self.generate_expression(initial)?;
35957        }
35958        if let Some(merge) = &e.merge {
35959            self.write(", ");
35960            self.generate_expression(merge)?;
35961        }
35962        if let Some(finish) = &e.finish {
35963            self.write(", ");
35964            self.generate_expression(finish)?;
35965        }
35966        self.write(")");
35967        Ok(())
35968    }
35969
35970    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
35971        // REFERENCES this (expressions) [options]
35972        self.write_keyword("REFERENCES");
35973        self.write_space();
35974        self.generate_expression(&e.this)?;
35975        if !e.expressions.is_empty() {
35976            self.write(" (");
35977            for (i, expr) in e.expressions.iter().enumerate() {
35978                if i > 0 {
35979                    self.write(", ");
35980                }
35981                self.generate_expression(expr)?;
35982            }
35983            self.write(")");
35984        }
35985        for opt in &e.options {
35986            self.write_space();
35987            self.generate_expression(opt)?;
35988        }
35989        Ok(())
35990    }
35991
35992    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
35993        // REFRESH [kind] this
35994        self.write_keyword("REFRESH");
35995        if !e.kind.is_empty() {
35996            self.write_space();
35997            self.write_keyword(&e.kind);
35998        }
35999        self.write_space();
36000        self.generate_expression(&e.this)?;
36001        Ok(())
36002    }
36003
36004    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
36005        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
36006        self.write_keyword("REFRESH");
36007        self.write_space();
36008        self.write_keyword(&e.method);
36009
36010        if let Some(ref kind) = e.kind {
36011            self.write_space();
36012            self.write_keyword("ON");
36013            self.write_space();
36014            self.write_keyword(kind);
36015
36016            // EVERY n UNIT
36017            if let Some(ref every) = e.every {
36018                self.write_space();
36019                self.write_keyword("EVERY");
36020                self.write_space();
36021                self.generate_expression(every)?;
36022                if let Some(ref unit) = e.unit {
36023                    self.write_space();
36024                    self.write_keyword(unit);
36025                }
36026            }
36027
36028            // STARTS 'datetime'
36029            if let Some(ref starts) = e.starts {
36030                self.write_space();
36031                self.write_keyword("STARTS");
36032                self.write_space();
36033                self.generate_expression(starts)?;
36034            }
36035        }
36036        Ok(())
36037    }
36038
36039    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
36040        // REGEXP_COUNT(this, expression, position, parameters)
36041        self.write_keyword("REGEXP_COUNT");
36042        self.write("(");
36043        self.generate_expression(&e.this)?;
36044        self.write(", ");
36045        self.generate_expression(&e.expression)?;
36046        if let Some(position) = &e.position {
36047            self.write(", ");
36048            self.generate_expression(position)?;
36049        }
36050        if let Some(parameters) = &e.parameters {
36051            self.write(", ");
36052            self.generate_expression(parameters)?;
36053        }
36054        self.write(")");
36055        Ok(())
36056    }
36057
36058    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
36059        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
36060        self.write_keyword("REGEXP_EXTRACT_ALL");
36061        self.write("(");
36062        self.generate_expression(&e.this)?;
36063        self.write(", ");
36064        self.generate_expression(&e.expression)?;
36065        if let Some(group) = &e.group {
36066            self.write(", ");
36067            self.generate_expression(group)?;
36068        }
36069        self.write(")");
36070        Ok(())
36071    }
36072
36073    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
36074        // REGEXP_FULL_MATCH(this, expression)
36075        self.write_keyword("REGEXP_FULL_MATCH");
36076        self.write("(");
36077        self.generate_expression(&e.this)?;
36078        self.write(", ");
36079        self.generate_expression(&e.expression)?;
36080        self.write(")");
36081        Ok(())
36082    }
36083
36084    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
36085        use crate::dialects::DialectType;
36086        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
36087        if matches!(
36088            self.config.dialect,
36089            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
36090        ) && e.flag.is_none()
36091        {
36092            self.generate_expression(&e.this)?;
36093            self.write(" ~* ");
36094            self.generate_expression(&e.expression)?;
36095        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && e.flag.is_none() {
36096            // ClickHouse has no case-insensitive regex operator; use match() with an inline
36097            // (?i) flag. Inline it into a string-literal pattern for readable output, and fall
36098            // back to concat() so the flag also applies to a non-literal pattern expression.
36099            self.write("match(");
36100            self.generate_expression(&e.this)?;
36101            self.write(", ");
36102            if let Expression::Literal(lit) = e.expression.as_ref() {
36103                if let Literal::String(s) = lit.as_ref() {
36104                    let insensitive =
36105                        Expression::Literal(Box::new(Literal::String(format!("(?i){s}"))));
36106                    self.generate_expression(&insensitive)?;
36107                    self.write(")");
36108                    return Ok(());
36109                }
36110            }
36111            self.write("concat('(?i)', ");
36112            self.generate_expression(&e.expression)?;
36113            self.write("))");
36114            return Ok(());
36115        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36116            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
36117            self.write_keyword("REGEXP_LIKE");
36118            self.write("(");
36119            self.generate_expression(&e.this)?;
36120            self.write(", ");
36121            self.generate_expression(&e.expression)?;
36122            self.write(", ");
36123            if let Some(flag) = &e.flag {
36124                self.generate_expression(flag)?;
36125            } else {
36126                self.write("'i'");
36127            }
36128            self.write(")");
36129        } else {
36130            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
36131            self.generate_expression(&e.this)?;
36132            self.write_space();
36133            self.write_keyword("REGEXP_ILIKE");
36134            self.write_space();
36135            self.generate_expression(&e.expression)?;
36136            if let Some(flag) = &e.flag {
36137                self.write(", ");
36138                self.generate_expression(flag)?;
36139            }
36140        }
36141        Ok(())
36142    }
36143
36144    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
36145        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
36146        self.write_keyword("REGEXP_INSTR");
36147        self.write("(");
36148        self.generate_expression(&e.this)?;
36149        self.write(", ");
36150        self.generate_expression(&e.expression)?;
36151        if let Some(position) = &e.position {
36152            self.write(", ");
36153            self.generate_expression(position)?;
36154        }
36155        if let Some(occurrence) = &e.occurrence {
36156            self.write(", ");
36157            self.generate_expression(occurrence)?;
36158        }
36159        if let Some(option) = &e.option {
36160            self.write(", ");
36161            self.generate_expression(option)?;
36162        }
36163        if let Some(parameters) = &e.parameters {
36164            self.write(", ");
36165            self.generate_expression(parameters)?;
36166        }
36167        if let Some(group) = &e.group {
36168            self.write(", ");
36169            self.generate_expression(group)?;
36170        }
36171        self.write(")");
36172        Ok(())
36173    }
36174
36175    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
36176        // REGEXP_SPLIT(this, expression, limit)
36177        self.write_keyword("REGEXP_SPLIT");
36178        self.write("(");
36179        self.generate_expression(&e.this)?;
36180        self.write(", ");
36181        self.generate_expression(&e.expression)?;
36182        if let Some(limit) = &e.limit {
36183            self.write(", ");
36184            self.generate_expression(limit)?;
36185        }
36186        self.write(")");
36187        Ok(())
36188    }
36189
36190    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
36191        // REGR_AVGX(this, expression)
36192        self.write_keyword("REGR_AVGX");
36193        self.write("(");
36194        self.generate_expression(&e.this)?;
36195        self.write(", ");
36196        self.generate_expression(&e.expression)?;
36197        self.write(")");
36198        Ok(())
36199    }
36200
36201    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
36202        // REGR_AVGY(this, expression)
36203        self.write_keyword("REGR_AVGY");
36204        self.write("(");
36205        self.generate_expression(&e.this)?;
36206        self.write(", ");
36207        self.generate_expression(&e.expression)?;
36208        self.write(")");
36209        Ok(())
36210    }
36211
36212    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
36213        // REGR_COUNT(this, expression)
36214        self.write_keyword("REGR_COUNT");
36215        self.write("(");
36216        self.generate_expression(&e.this)?;
36217        self.write(", ");
36218        self.generate_expression(&e.expression)?;
36219        self.write(")");
36220        Ok(())
36221    }
36222
36223    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
36224        // REGR_INTERCEPT(this, expression)
36225        self.write_keyword("REGR_INTERCEPT");
36226        self.write("(");
36227        self.generate_expression(&e.this)?;
36228        self.write(", ");
36229        self.generate_expression(&e.expression)?;
36230        self.write(")");
36231        Ok(())
36232    }
36233
36234    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
36235        // REGR_R2(this, expression)
36236        self.write_keyword("REGR_R2");
36237        self.write("(");
36238        self.generate_expression(&e.this)?;
36239        self.write(", ");
36240        self.generate_expression(&e.expression)?;
36241        self.write(")");
36242        Ok(())
36243    }
36244
36245    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
36246        // REGR_SLOPE(this, expression)
36247        self.write_keyword("REGR_SLOPE");
36248        self.write("(");
36249        self.generate_expression(&e.this)?;
36250        self.write(", ");
36251        self.generate_expression(&e.expression)?;
36252        self.write(")");
36253        Ok(())
36254    }
36255
36256    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
36257        // REGR_SXX(this, expression)
36258        self.write_keyword("REGR_SXX");
36259        self.write("(");
36260        self.generate_expression(&e.this)?;
36261        self.write(", ");
36262        self.generate_expression(&e.expression)?;
36263        self.write(")");
36264        Ok(())
36265    }
36266
36267    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
36268        // REGR_SXY(this, expression)
36269        self.write_keyword("REGR_SXY");
36270        self.write("(");
36271        self.generate_expression(&e.this)?;
36272        self.write(", ");
36273        self.generate_expression(&e.expression)?;
36274        self.write(")");
36275        Ok(())
36276    }
36277
36278    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
36279        // REGR_SYY(this, expression)
36280        self.write_keyword("REGR_SYY");
36281        self.write("(");
36282        self.generate_expression(&e.this)?;
36283        self.write(", ");
36284        self.generate_expression(&e.expression)?;
36285        self.write(")");
36286        Ok(())
36287    }
36288
36289    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
36290        // REGR_VALX(this, expression)
36291        self.write_keyword("REGR_VALX");
36292        self.write("(");
36293        self.generate_expression(&e.this)?;
36294        self.write(", ");
36295        self.generate_expression(&e.expression)?;
36296        self.write(")");
36297        Ok(())
36298    }
36299
36300    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
36301        // REGR_VALY(this, expression)
36302        self.write_keyword("REGR_VALY");
36303        self.write("(");
36304        self.generate_expression(&e.this)?;
36305        self.write(", ");
36306        self.generate_expression(&e.expression)?;
36307        self.write(")");
36308        Ok(())
36309    }
36310
36311    fn generate_remote_with_connection_model_property(
36312        &mut self,
36313        e: &RemoteWithConnectionModelProperty,
36314    ) -> Result<()> {
36315        // REMOTE WITH CONNECTION this
36316        self.write_keyword("REMOTE WITH CONNECTION");
36317        self.write_space();
36318        self.generate_expression(&e.this)?;
36319        Ok(())
36320    }
36321
36322    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
36323        // RENAME COLUMN [IF EXISTS] this TO new_name
36324        self.write_keyword("RENAME COLUMN");
36325        if e.exists {
36326            self.write_space();
36327            self.write_keyword("IF EXISTS");
36328        }
36329        self.write_space();
36330        self.generate_expression(&e.this)?;
36331        if let Some(to) = &e.to {
36332            self.write_space();
36333            self.write_keyword("TO");
36334            self.write_space();
36335            self.generate_expression(to)?;
36336        }
36337        Ok(())
36338    }
36339
36340    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
36341        // REPLACE PARTITION expression [FROM source]
36342        self.write_keyword("REPLACE PARTITION");
36343        self.write_space();
36344        self.generate_expression(&e.expression)?;
36345        if let Some(source) = &e.source {
36346            self.write_space();
36347            self.write_keyword("FROM");
36348            self.write_space();
36349            self.generate_expression(source)?;
36350        }
36351        Ok(())
36352    }
36353
36354    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
36355        // RETURNING expressions [INTO into]
36356        // TSQL and Fabric use OUTPUT instead of RETURNING
36357        let keyword = match self.config.dialect {
36358            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
36359            _ => "RETURNING",
36360        };
36361        self.write_keyword(keyword);
36362        self.write_space();
36363        for (i, expr) in e.expressions.iter().enumerate() {
36364            if i > 0 {
36365                self.write(", ");
36366            }
36367            self.generate_expression(expr)?;
36368        }
36369        if let Some(into) = &e.into {
36370            self.write_space();
36371            self.write_keyword("INTO");
36372            self.write_space();
36373            self.generate_expression(into)?;
36374        }
36375        Ok(())
36376    }
36377
36378    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
36379        // OUTPUT expressions [INTO into_table]
36380        self.write_space();
36381        self.write_keyword("OUTPUT");
36382        self.write_space();
36383        for (i, expr) in output.columns.iter().enumerate() {
36384            if i > 0 {
36385                self.write(", ");
36386            }
36387            self.generate_expression(expr)?;
36388        }
36389        if let Some(into_table) = &output.into_table {
36390            self.write_space();
36391            self.write_keyword("INTO");
36392            self.write_space();
36393            self.generate_expression(into_table)?;
36394        }
36395        Ok(())
36396    }
36397
36398    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
36399        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
36400        self.write_keyword("RETURNS");
36401        if e.is_table.is_some() {
36402            self.write_space();
36403            self.write_keyword("TABLE");
36404        }
36405        if let Some(table) = &e.table {
36406            self.write_space();
36407            self.generate_expression(table)?;
36408        } else if let Some(this) = &e.this {
36409            self.write_space();
36410            self.generate_expression(this)?;
36411        }
36412        if e.null.is_some() {
36413            self.write_space();
36414            self.write_keyword("NULL ON NULL INPUT");
36415        }
36416        Ok(())
36417    }
36418
36419    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
36420        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
36421        self.write_keyword("ROLLBACK");
36422
36423        // TSQL always uses ROLLBACK TRANSACTION
36424        if e.this.is_none()
36425            && matches!(
36426                self.config.dialect,
36427                Some(DialectType::TSQL) | Some(DialectType::Fabric)
36428            )
36429        {
36430            self.write_space();
36431            self.write_keyword("TRANSACTION");
36432        }
36433
36434        // Check if this has TRANSACTION keyword or transaction name
36435        if let Some(this) = &e.this {
36436            // Check if it's just the "TRANSACTION" marker or an actual transaction name
36437            let is_transaction_marker = matches!(
36438                this.as_ref(),
36439                Expression::Identifier(id) if id.name == "TRANSACTION"
36440            );
36441
36442            self.write_space();
36443            self.write_keyword("TRANSACTION");
36444
36445            // If it's a real transaction name, output it
36446            if !is_transaction_marker {
36447                self.write_space();
36448                self.generate_expression(this)?;
36449            }
36450        }
36451
36452        // Output TO savepoint
36453        if let Some(savepoint) = &e.savepoint {
36454            self.write_space();
36455            self.write_keyword("TO");
36456            self.write_space();
36457            self.generate_expression(savepoint)?;
36458        }
36459        Ok(())
36460    }
36461
36462    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
36463        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
36464        if e.expressions.is_empty() {
36465            self.write_keyword("WITH ROLLUP");
36466        } else {
36467            self.write_keyword("ROLLUP");
36468            self.write("(");
36469            for (i, expr) in e.expressions.iter().enumerate() {
36470                if i > 0 {
36471                    self.write(", ");
36472                }
36473                self.generate_expression(expr)?;
36474            }
36475            self.write(")");
36476        }
36477        Ok(())
36478    }
36479
36480    fn generate_row_format_delimited_property(
36481        &mut self,
36482        e: &RowFormatDelimitedProperty,
36483    ) -> Result<()> {
36484        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
36485        self.write_keyword("ROW FORMAT DELIMITED");
36486        if let Some(fields) = &e.fields {
36487            self.write_space();
36488            self.write_keyword("FIELDS TERMINATED BY");
36489            self.write_space();
36490            self.generate_expression(fields)?;
36491        }
36492        if let Some(escaped) = &e.escaped {
36493            self.write_space();
36494            self.write_keyword("ESCAPED BY");
36495            self.write_space();
36496            self.generate_expression(escaped)?;
36497        }
36498        if let Some(items) = &e.collection_items {
36499            self.write_space();
36500            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
36501            self.write_space();
36502            self.generate_expression(items)?;
36503        }
36504        if let Some(keys) = &e.map_keys {
36505            self.write_space();
36506            self.write_keyword("MAP KEYS TERMINATED BY");
36507            self.write_space();
36508            self.generate_expression(keys)?;
36509        }
36510        if let Some(lines) = &e.lines {
36511            self.write_space();
36512            self.write_keyword("LINES TERMINATED BY");
36513            self.write_space();
36514            self.generate_expression(lines)?;
36515        }
36516        if let Some(null) = &e.null {
36517            self.write_space();
36518            self.write_keyword("NULL DEFINED AS");
36519            self.write_space();
36520            self.generate_expression(null)?;
36521        }
36522        if let Some(serde) = &e.serde {
36523            self.write_space();
36524            self.generate_expression(serde)?;
36525        }
36526        Ok(())
36527    }
36528
36529    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
36530        // ROW FORMAT this
36531        self.write_keyword("ROW FORMAT");
36532        self.write_space();
36533        self.generate_expression(&e.this)?;
36534        Ok(())
36535    }
36536
36537    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
36538        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
36539        self.write_keyword("ROW FORMAT SERDE");
36540        self.write_space();
36541        self.generate_expression(&e.this)?;
36542        if let Some(props) = &e.serde_properties {
36543            self.write_space();
36544            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
36545            self.generate_expression(props)?;
36546        }
36547        Ok(())
36548    }
36549
36550    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
36551        // SHA2(this, length)
36552        self.write_keyword("SHA2");
36553        self.write("(");
36554        self.generate_expression(&e.this)?;
36555        if let Some(length) = e.length {
36556            self.write(", ");
36557            self.write(&length.to_string());
36558        }
36559        self.write(")");
36560        Ok(())
36561    }
36562
36563    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
36564        // SHA2_DIGEST(this, length)
36565        self.write_keyword("SHA2_DIGEST");
36566        self.write("(");
36567        self.generate_expression(&e.this)?;
36568        if let Some(length) = e.length {
36569            self.write(", ");
36570            self.write(&length.to_string());
36571        }
36572        self.write(")");
36573        Ok(())
36574    }
36575
36576    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
36577        let name = if matches!(
36578            self.config.dialect,
36579            Some(crate::dialects::DialectType::Spark)
36580                | Some(crate::dialects::DialectType::Databricks)
36581        ) {
36582            "TRY_ADD"
36583        } else {
36584            "SAFE_ADD"
36585        };
36586        self.write_keyword(name);
36587        self.write("(");
36588        self.generate_expression(&e.this)?;
36589        self.write(", ");
36590        self.generate_expression(&e.expression)?;
36591        self.write(")");
36592        Ok(())
36593    }
36594
36595    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
36596        // SAFE_DIVIDE(this, expression)
36597        self.write_keyword("SAFE_DIVIDE");
36598        self.write("(");
36599        self.generate_expression(&e.this)?;
36600        self.write(", ");
36601        self.generate_expression(&e.expression)?;
36602        self.write(")");
36603        Ok(())
36604    }
36605
36606    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
36607        let name = if matches!(
36608            self.config.dialect,
36609            Some(crate::dialects::DialectType::Spark)
36610                | Some(crate::dialects::DialectType::Databricks)
36611        ) {
36612            "TRY_MULTIPLY"
36613        } else {
36614            "SAFE_MULTIPLY"
36615        };
36616        self.write_keyword(name);
36617        self.write("(");
36618        self.generate_expression(&e.this)?;
36619        self.write(", ");
36620        self.generate_expression(&e.expression)?;
36621        self.write(")");
36622        Ok(())
36623    }
36624
36625    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
36626        let name = if matches!(
36627            self.config.dialect,
36628            Some(crate::dialects::DialectType::Spark)
36629                | Some(crate::dialects::DialectType::Databricks)
36630        ) {
36631            "TRY_SUBTRACT"
36632        } else {
36633            "SAFE_SUBTRACT"
36634        };
36635        self.write_keyword(name);
36636        self.write("(");
36637        self.generate_expression(&e.this)?;
36638        self.write(", ");
36639        self.generate_expression(&e.expression)?;
36640        self.write(")");
36641        Ok(())
36642    }
36643
36644    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
36645    /// METHOD (size UNIT) [REPEATABLE (seed)]
36646    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
36647        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
36648        if matches!(sample.method, SampleMethod::Bucket) {
36649            self.write(" (");
36650            self.write_keyword("BUCKET");
36651            self.write_space();
36652            if let Some(ref num) = sample.bucket_numerator {
36653                self.generate_expression(num)?;
36654            }
36655            self.write_space();
36656            self.write_keyword("OUT OF");
36657            self.write_space();
36658            if let Some(ref denom) = sample.bucket_denominator {
36659                self.generate_expression(denom)?;
36660            }
36661            if let Some(ref field) = sample.bucket_field {
36662                self.write_space();
36663                self.write_keyword("ON");
36664                self.write_space();
36665                self.generate_expression(field)?;
36666            }
36667            self.write(")");
36668            return Ok(());
36669        }
36670
36671        // Output method name if explicitly specified, or for dialects that always require it
36672        let is_snowflake = matches!(
36673            self.config.dialect,
36674            Some(crate::dialects::DialectType::Snowflake)
36675        );
36676        let is_postgres = matches!(
36677            self.config.dialect,
36678            Some(crate::dialects::DialectType::PostgreSQL)
36679                | Some(crate::dialects::DialectType::Redshift)
36680        );
36681        // Databricks and Spark don't output method names
36682        let is_databricks = matches!(
36683            self.config.dialect,
36684            Some(crate::dialects::DialectType::Databricks)
36685        );
36686        let is_spark = matches!(
36687            self.config.dialect,
36688            Some(crate::dialects::DialectType::Spark)
36689        );
36690        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
36691        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
36692        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
36693        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
36694            self.write_space();
36695            if !sample.explicit_method && (is_snowflake || force_method) {
36696                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
36697                self.write_keyword("BERNOULLI");
36698            } else {
36699                match sample.method {
36700                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
36701                    SampleMethod::System => self.write_keyword("SYSTEM"),
36702                    SampleMethod::Block => self.write_keyword("BLOCK"),
36703                    SampleMethod::Row => self.write_keyword("ROW"),
36704                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
36705                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
36706                    SampleMethod::Bucket => {} // handled above
36707                }
36708            }
36709        }
36710
36711        // Output size, with or without parentheses depending on dialect
36712        let emit_size_no_parens = !self.config.tablesample_requires_parens;
36713        if emit_size_no_parens {
36714            self.write_space();
36715            match &sample.size {
36716                Expression::Tuple(tuple) => {
36717                    for (i, expr) in tuple.expressions.iter().enumerate() {
36718                        if i > 0 {
36719                            self.write(", ");
36720                        }
36721                        self.generate_expression(expr)?;
36722                    }
36723                }
36724                expr => self.generate_expression(expr)?,
36725            }
36726        } else {
36727            self.write(" (");
36728            self.generate_expression(&sample.size)?;
36729        }
36730
36731        // Determine unit
36732        let is_rows_method = matches!(
36733            sample.method,
36734            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
36735        );
36736        let is_percent = matches!(
36737            sample.method,
36738            SampleMethod::Percent
36739                | SampleMethod::System
36740                | SampleMethod::Bernoulli
36741                | SampleMethod::Block
36742        );
36743
36744        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
36745        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
36746        // For Databricks and Spark, always output PERCENT for percentage samples.
36747        let is_presto = matches!(
36748            self.config.dialect,
36749            Some(crate::dialects::DialectType::Presto)
36750                | Some(crate::dialects::DialectType::Trino)
36751                | Some(crate::dialects::DialectType::Athena)
36752        );
36753        let should_output_unit = if is_databricks || is_spark {
36754            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
36755            is_percent || is_rows_method || sample.unit_after_size
36756        } else if is_snowflake || is_postgres || is_presto {
36757            sample.unit_after_size
36758        } else {
36759            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
36760        };
36761
36762        if should_output_unit {
36763            self.write_space();
36764            if sample.is_percent {
36765                self.write_keyword("PERCENT");
36766            } else if is_rows_method && !sample.unit_after_size {
36767                self.write_keyword("ROWS");
36768            } else if sample.unit_after_size {
36769                match sample.method {
36770                    SampleMethod::Percent
36771                    | SampleMethod::System
36772                    | SampleMethod::Bernoulli
36773                    | SampleMethod::Block => {
36774                        self.write_keyword("PERCENT");
36775                    }
36776                    SampleMethod::Row | SampleMethod::Reservoir => {
36777                        self.write_keyword("ROWS");
36778                    }
36779                    _ => self.write_keyword("ROWS"),
36780                }
36781            } else {
36782                self.write_keyword("PERCENT");
36783            }
36784        }
36785
36786        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36787            if let Some(ref offset) = sample.offset {
36788                self.write_space();
36789                self.write_keyword("OFFSET");
36790                self.write_space();
36791                self.generate_expression(offset)?;
36792            }
36793        }
36794        if !emit_size_no_parens {
36795            self.write(")");
36796        }
36797
36798        Ok(())
36799    }
36800
36801    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
36802        // SAMPLE this (ClickHouse uses SAMPLE BY)
36803        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36804            self.write_keyword("SAMPLE BY");
36805        } else {
36806            self.write_keyword("SAMPLE");
36807        }
36808        self.write_space();
36809        self.generate_expression(&e.this)?;
36810        Ok(())
36811    }
36812
36813    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
36814        // this (expressions...)
36815        if let Some(this) = &e.this {
36816            self.generate_expression(this)?;
36817        }
36818        if !e.expressions.is_empty() {
36819            // Add space before column list if there's a preceding expression
36820            if e.this.is_some() {
36821                self.write_space();
36822            }
36823            self.write("(");
36824            for (i, expr) in e.expressions.iter().enumerate() {
36825                if i > 0 {
36826                    self.write(", ");
36827                }
36828                self.generate_expression(expr)?;
36829            }
36830            self.write(")");
36831        }
36832        Ok(())
36833    }
36834
36835    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
36836        // COMMENT this
36837        self.write_keyword("COMMENT");
36838        self.write_space();
36839        self.generate_expression(&e.this)?;
36840        Ok(())
36841    }
36842
36843    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
36844        // [this::]expression
36845        if let Some(this) = &e.this {
36846            self.generate_expression(this)?;
36847            self.write("::");
36848        }
36849        self.generate_expression(&e.expression)?;
36850        Ok(())
36851    }
36852
36853    fn generate_search(&mut self, e: &Search) -> Result<()> {
36854        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
36855        self.write_keyword("SEARCH");
36856        self.write("(");
36857        self.generate_expression(&e.this)?;
36858        self.write(", ");
36859        self.generate_expression(&e.expression)?;
36860        if let Some(json_scope) = &e.json_scope {
36861            self.write(", ");
36862            self.generate_expression(json_scope)?;
36863        }
36864        if let Some(analyzer) = &e.analyzer {
36865            self.write(", ");
36866            self.generate_expression(analyzer)?;
36867        }
36868        if let Some(analyzer_options) = &e.analyzer_options {
36869            self.write(", ");
36870            self.generate_expression(analyzer_options)?;
36871        }
36872        if let Some(search_mode) = &e.search_mode {
36873            self.write(", ");
36874            self.generate_expression(search_mode)?;
36875        }
36876        self.write(")");
36877        Ok(())
36878    }
36879
36880    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
36881        // SEARCH_IP(this, expression)
36882        self.write_keyword("SEARCH_IP");
36883        self.write("(");
36884        self.generate_expression(&e.this)?;
36885        self.write(", ");
36886        self.generate_expression(&e.expression)?;
36887        self.write(")");
36888        Ok(())
36889    }
36890
36891    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
36892        // SECURITY this
36893        self.write_keyword("SECURITY");
36894        self.write_space();
36895        self.generate_expression(&e.this)?;
36896        Ok(())
36897    }
36898
36899    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
36900        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
36901        self.write("SEMANTIC_VIEW(");
36902
36903        if self.config.pretty {
36904            // Pretty print: each clause on its own line
36905            self.write_newline();
36906            self.indent_level += 1;
36907            self.write_indent();
36908            self.generate_expression(&e.this)?;
36909
36910            if let Some(metrics) = &e.metrics {
36911                self.write_newline();
36912                self.write_indent();
36913                self.write_keyword("METRICS");
36914                self.write_space();
36915                self.generate_semantic_view_tuple(metrics)?;
36916            }
36917            if let Some(dimensions) = &e.dimensions {
36918                self.write_newline();
36919                self.write_indent();
36920                self.write_keyword("DIMENSIONS");
36921                self.write_space();
36922                self.generate_semantic_view_tuple(dimensions)?;
36923            }
36924            if let Some(facts) = &e.facts {
36925                self.write_newline();
36926                self.write_indent();
36927                self.write_keyword("FACTS");
36928                self.write_space();
36929                self.generate_semantic_view_tuple(facts)?;
36930            }
36931            if let Some(where_) = &e.where_ {
36932                self.write_newline();
36933                self.write_indent();
36934                self.write_keyword("WHERE");
36935                self.write_space();
36936                self.generate_expression(where_)?;
36937            }
36938            self.write_newline();
36939            self.indent_level -= 1;
36940            self.write_indent();
36941        } else {
36942            // Compact: all on one line
36943            self.generate_expression(&e.this)?;
36944            if let Some(metrics) = &e.metrics {
36945                self.write_space();
36946                self.write_keyword("METRICS");
36947                self.write_space();
36948                self.generate_semantic_view_tuple(metrics)?;
36949            }
36950            if let Some(dimensions) = &e.dimensions {
36951                self.write_space();
36952                self.write_keyword("DIMENSIONS");
36953                self.write_space();
36954                self.generate_semantic_view_tuple(dimensions)?;
36955            }
36956            if let Some(facts) = &e.facts {
36957                self.write_space();
36958                self.write_keyword("FACTS");
36959                self.write_space();
36960                self.generate_semantic_view_tuple(facts)?;
36961            }
36962            if let Some(where_) = &e.where_ {
36963                self.write_space();
36964                self.write_keyword("WHERE");
36965                self.write_space();
36966                self.generate_expression(where_)?;
36967            }
36968        }
36969        self.write(")");
36970        Ok(())
36971    }
36972
36973    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
36974    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
36975        if let Expression::Tuple(t) = expr {
36976            for (i, e) in t.expressions.iter().enumerate() {
36977                if i > 0 {
36978                    self.write(", ");
36979                }
36980                self.generate_expression(e)?;
36981            }
36982        } else {
36983            self.generate_expression(expr)?;
36984        }
36985        Ok(())
36986    }
36987
36988    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
36989        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
36990        if let Some(start) = &e.start {
36991            self.write_keyword("START WITH");
36992            self.write_space();
36993            self.generate_expression(start)?;
36994        }
36995        if let Some(increment) = &e.increment {
36996            self.write_space();
36997            self.write_keyword("INCREMENT BY");
36998            self.write_space();
36999            self.generate_expression(increment)?;
37000        }
37001        if let Some(minvalue) = &e.minvalue {
37002            self.write_space();
37003            self.write_keyword("MINVALUE");
37004            self.write_space();
37005            self.generate_expression(minvalue)?;
37006        }
37007        if let Some(maxvalue) = &e.maxvalue {
37008            self.write_space();
37009            self.write_keyword("MAXVALUE");
37010            self.write_space();
37011            self.generate_expression(maxvalue)?;
37012        }
37013        if let Some(cache) = &e.cache {
37014            self.write_space();
37015            self.write_keyword("CACHE");
37016            self.write_space();
37017            self.generate_expression(cache)?;
37018        }
37019        if let Some(owned) = &e.owned {
37020            self.write_space();
37021            self.write_keyword("OWNED BY");
37022            self.write_space();
37023            self.generate_expression(owned)?;
37024        }
37025        for opt in &e.options {
37026            self.write_space();
37027            self.generate_expression(opt)?;
37028        }
37029        Ok(())
37030    }
37031
37032    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
37033        // [WITH] SERDEPROPERTIES (expressions)
37034        if e.with_.is_some() {
37035            self.write_keyword("WITH");
37036            self.write_space();
37037        }
37038        self.write_keyword("SERDEPROPERTIES");
37039        self.write(" (");
37040        for (i, expr) in e.expressions.iter().enumerate() {
37041            if i > 0 {
37042                self.write(", ");
37043            }
37044            // Generate key=value without spaces around =
37045            match expr {
37046                Expression::Eq(eq) => {
37047                    self.generate_expression(&eq.left)?;
37048                    self.write("=");
37049                    self.generate_expression(&eq.right)?;
37050                }
37051                _ => self.generate_expression(expr)?,
37052            }
37053        }
37054        self.write(")");
37055        Ok(())
37056    }
37057
37058    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
37059        // @@[kind.]this
37060        self.write("@@");
37061        if let Some(kind) = &e.kind {
37062            self.write(kind);
37063            self.write(".");
37064        }
37065        self.generate_expression(&e.this)?;
37066        Ok(())
37067    }
37068
37069    fn generate_set(&mut self, e: &Set) -> Result<()> {
37070        // SET/UNSET [TAG] expressions
37071        if e.unset.is_some() {
37072            self.write_keyword("UNSET");
37073        } else {
37074            self.write_keyword("SET");
37075        }
37076        if e.tag.is_some() {
37077            self.write_space();
37078            self.write_keyword("TAG");
37079        }
37080        if !e.expressions.is_empty() {
37081            self.write_space();
37082            for (i, expr) in e.expressions.iter().enumerate() {
37083                if i > 0 {
37084                    self.write(", ");
37085                }
37086                self.generate_expression(expr)?;
37087            }
37088        }
37089        Ok(())
37090    }
37091
37092    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
37093        // SET this or SETCONFIG this
37094        self.write_keyword("SET");
37095        self.write_space();
37096        self.generate_expression(&e.this)?;
37097        Ok(())
37098    }
37099
37100    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
37101        // [kind] name = value
37102        if let Some(kind) = &e.kind {
37103            self.write_keyword(kind);
37104            self.write_space();
37105        }
37106        self.generate_expression(&e.name)?;
37107        self.write(" = ");
37108        self.generate_expression(&e.value)?;
37109        Ok(())
37110    }
37111
37112    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
37113        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
37114        if let Some(with_) = &e.with_ {
37115            self.generate_expression(with_)?;
37116            self.write_space();
37117        }
37118        self.generate_expression(&e.this)?;
37119        self.write_space();
37120        // kind should be UNION, INTERSECT, EXCEPT, etc.
37121        if let Some(kind) = &e.kind {
37122            self.write_keyword(kind);
37123        }
37124        if e.distinct {
37125            self.write_space();
37126            self.write_keyword("DISTINCT");
37127        } else {
37128            self.write_space();
37129            self.write_keyword("ALL");
37130        }
37131        if e.by_name.is_some() {
37132            self.write_space();
37133            self.write_keyword("BY NAME");
37134        }
37135        self.write_space();
37136        self.generate_expression(&e.expression)?;
37137        Ok(())
37138    }
37139
37140    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
37141        // SET or MULTISET
37142        if e.multi.is_some() {
37143            self.write_keyword("MULTISET");
37144        } else {
37145            self.write_keyword("SET");
37146        }
37147        Ok(())
37148    }
37149
37150    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
37151        // SETTINGS expressions
37152        self.write_keyword("SETTINGS");
37153        if self.config.pretty && e.expressions.len() > 1 {
37154            // Pretty print: each setting on its own line, indented
37155            self.indent_level += 1;
37156            for (i, expr) in e.expressions.iter().enumerate() {
37157                if i > 0 {
37158                    self.write(",");
37159                }
37160                self.write_newline();
37161                self.write_indent();
37162                self.generate_expression(expr)?;
37163            }
37164            self.indent_level -= 1;
37165        } else {
37166            self.write_space();
37167            for (i, expr) in e.expressions.iter().enumerate() {
37168                if i > 0 {
37169                    self.write(", ");
37170                }
37171                self.generate_expression(expr)?;
37172            }
37173        }
37174        Ok(())
37175    }
37176
37177    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
37178        // SHARING = this
37179        self.write_keyword("SHARING");
37180        if let Some(this) = &e.this {
37181            self.write(" = ");
37182            self.generate_expression(this)?;
37183        }
37184        Ok(())
37185    }
37186
37187    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
37188        // Python array slicing: begin:end:step
37189        if let Some(begin) = &e.this {
37190            self.generate_expression(begin)?;
37191        }
37192        self.write(":");
37193        if let Some(end) = &e.expression {
37194            self.generate_expression(end)?;
37195        }
37196        if let Some(step) = &e.step {
37197            self.write(":");
37198            self.generate_expression(step)?;
37199        }
37200        Ok(())
37201    }
37202
37203    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
37204        // SORT_ARRAY(this, asc)
37205        self.write_keyword("SORT_ARRAY");
37206        self.write("(");
37207        self.generate_expression(&e.this)?;
37208        if let Some(asc) = &e.asc {
37209            self.write(", ");
37210            self.generate_expression(asc)?;
37211        }
37212        self.write(")");
37213        Ok(())
37214    }
37215
37216    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
37217        // SORT BY expressions
37218        self.write_keyword("SORT BY");
37219        self.write_space();
37220        for (i, expr) in e.expressions.iter().enumerate() {
37221            if i > 0 {
37222                self.write(", ");
37223            }
37224            self.generate_ordered(expr)?;
37225        }
37226        Ok(())
37227    }
37228
37229    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
37230        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
37231        if e.compound.is_some() {
37232            self.write_keyword("COMPOUND");
37233            self.write_space();
37234        }
37235        self.write_keyword("SORTKEY");
37236        self.write("(");
37237        // If this is a Tuple, unwrap its contents to avoid double parentheses
37238        if let Expression::Tuple(t) = e.this.as_ref() {
37239            for (i, expr) in t.expressions.iter().enumerate() {
37240                if i > 0 {
37241                    self.write(", ");
37242                }
37243                self.generate_expression(expr)?;
37244            }
37245        } else {
37246            self.generate_expression(&e.this)?;
37247        }
37248        self.write(")");
37249        Ok(())
37250    }
37251
37252    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
37253        // SPLIT_PART(this, delimiter, part_index)
37254        self.write_keyword("SPLIT_PART");
37255        self.write("(");
37256        self.generate_expression(&e.this)?;
37257        if let Some(delimiter) = &e.delimiter {
37258            self.write(", ");
37259            self.generate_expression(delimiter)?;
37260        }
37261        if let Some(part_index) = &e.part_index {
37262            self.write(", ");
37263            self.generate_expression(part_index)?;
37264        }
37265        self.write(")");
37266        Ok(())
37267    }
37268
37269    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
37270        // READS SQL DATA or MODIFIES SQL DATA, etc.
37271        self.generate_expression(&e.this)?;
37272        Ok(())
37273    }
37274
37275    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
37276        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
37277        self.write_keyword("SQL SECURITY");
37278        self.write_space();
37279        self.generate_expression(&e.this)?;
37280        Ok(())
37281    }
37282
37283    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
37284        // ST_DISTANCE(this, expression, [use_spheroid])
37285        self.write_keyword("ST_DISTANCE");
37286        self.write("(");
37287        self.generate_expression(&e.this)?;
37288        self.write(", ");
37289        self.generate_expression(&e.expression)?;
37290        if let Some(use_spheroid) = &e.use_spheroid {
37291            self.write(", ");
37292            self.generate_expression(use_spheroid)?;
37293        }
37294        self.write(")");
37295        Ok(())
37296    }
37297
37298    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
37299        // ST_POINT(this, expression)
37300        self.write_keyword("ST_POINT");
37301        self.write("(");
37302        self.generate_expression(&e.this)?;
37303        self.write(", ");
37304        self.generate_expression(&e.expression)?;
37305        self.write(")");
37306        Ok(())
37307    }
37308
37309    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
37310        // IMMUTABLE, STABLE, VOLATILE
37311        self.generate_expression(&e.this)?;
37312        Ok(())
37313    }
37314
37315    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
37316        // STANDARD_HASH(this, [expression])
37317        self.write_keyword("STANDARD_HASH");
37318        self.write("(");
37319        self.generate_expression(&e.this)?;
37320        if let Some(expression) = &e.expression {
37321            self.write(", ");
37322            self.generate_expression(expression)?;
37323        }
37324        self.write(")");
37325        Ok(())
37326    }
37327
37328    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
37329        // STORED BY this
37330        self.write_keyword("STORED BY");
37331        self.write_space();
37332        self.generate_expression(&e.this)?;
37333        Ok(())
37334    }
37335
37336    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
37337        // STRPOS(this, substr) or STRPOS(this, substr, position)
37338        // Different dialects have different function names
37339        use crate::dialects::DialectType;
37340        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
37341            // Snowflake: CHARINDEX(substr, str[, position])
37342            self.write_keyword("CHARINDEX");
37343            self.write("(");
37344            if let Some(substr) = &e.substr {
37345                self.generate_expression(substr)?;
37346                self.write(", ");
37347            }
37348            self.generate_expression(&e.this)?;
37349            if let Some(position) = &e.position {
37350                self.write(", ");
37351                self.generate_expression(position)?;
37352            }
37353            self.write(")");
37354        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
37355            self.write_keyword("POSITION");
37356            self.write("(");
37357            self.generate_expression(&e.this)?;
37358            if let Some(substr) = &e.substr {
37359                self.write(", ");
37360                self.generate_expression(substr)?;
37361            }
37362            if let Some(position) = &e.position {
37363                self.write(", ");
37364                self.generate_expression(position)?;
37365            }
37366            if let Some(occurrence) = &e.occurrence {
37367                self.write(", ");
37368                self.generate_expression(occurrence)?;
37369            }
37370            self.write(")");
37371        } else if matches!(
37372            self.config.dialect,
37373            Some(DialectType::SQLite)
37374                | Some(DialectType::Oracle)
37375                | Some(DialectType::BigQuery)
37376                | Some(DialectType::Teradata)
37377        ) {
37378            self.write_keyword("INSTR");
37379            self.write("(");
37380            self.generate_expression(&e.this)?;
37381            if let Some(substr) = &e.substr {
37382                self.write(", ");
37383                self.generate_expression(substr)?;
37384            }
37385            if let Some(position) = &e.position {
37386                self.write(", ");
37387                self.generate_expression(position)?;
37388            } else if e.occurrence.is_some() {
37389                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
37390                // Default start position is 1
37391                self.write(", 1");
37392            }
37393            if let Some(occurrence) = &e.occurrence {
37394                self.write(", ");
37395                self.generate_expression(occurrence)?;
37396            }
37397            self.write(")");
37398        } else if matches!(
37399            self.config.dialect,
37400            Some(DialectType::MySQL)
37401                | Some(DialectType::SingleStore)
37402                | Some(DialectType::Doris)
37403                | Some(DialectType::StarRocks)
37404                | Some(DialectType::Hive)
37405                | Some(DialectType::Spark)
37406                | Some(DialectType::Databricks)
37407        ) {
37408            // LOCATE(substr, str[, position]) - substr first
37409            self.write_keyword("LOCATE");
37410            self.write("(");
37411            if let Some(substr) = &e.substr {
37412                self.generate_expression(substr)?;
37413                self.write(", ");
37414            }
37415            self.generate_expression(&e.this)?;
37416            if let Some(position) = &e.position {
37417                self.write(", ");
37418                self.generate_expression(position)?;
37419            }
37420            self.write(")");
37421        } else if matches!(
37422            self.config.dialect,
37423            Some(DialectType::TSQL) | Some(DialectType::Fabric)
37424        ) {
37425            // CHARINDEX(substr, str[, position])
37426            self.write_keyword("CHARINDEX");
37427            self.write("(");
37428            if let Some(substr) = &e.substr {
37429                self.generate_expression(substr)?;
37430                self.write(", ");
37431            }
37432            self.generate_expression(&e.this)?;
37433            if let Some(position) = &e.position {
37434                self.write(", ");
37435                self.generate_expression(position)?;
37436            }
37437            self.write(")");
37438        } else if matches!(
37439            self.config.dialect,
37440            Some(DialectType::PostgreSQL)
37441                | Some(DialectType::Materialize)
37442                | Some(DialectType::RisingWave)
37443                | Some(DialectType::Redshift)
37444        ) {
37445            // POSITION(substr IN str) syntax
37446            self.write_keyword("POSITION");
37447            self.write("(");
37448            if let Some(substr) = &e.substr {
37449                self.generate_expression(substr)?;
37450                self.write(" IN ");
37451            }
37452            self.generate_expression(&e.this)?;
37453            self.write(")");
37454        } else {
37455            self.write_keyword("STRPOS");
37456            self.write("(");
37457            self.generate_expression(&e.this)?;
37458            if let Some(substr) = &e.substr {
37459                self.write(", ");
37460                self.generate_expression(substr)?;
37461            }
37462            if let Some(position) = &e.position {
37463                self.write(", ");
37464                self.generate_expression(position)?;
37465            }
37466            if let Some(occurrence) = &e.occurrence {
37467                self.write(", ");
37468                self.generate_expression(occurrence)?;
37469            }
37470            self.write(")");
37471        }
37472        Ok(())
37473    }
37474
37475    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
37476        match self.config.dialect {
37477            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
37478                self.generate_tsql_str_to_temporal(&e.this, e.format.as_deref(), "DATE")?;
37479            }
37480            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
37481                // TO_DATE(this, java_format)
37482                self.write_keyword("TO_DATE");
37483                self.write("(");
37484                self.generate_expression(&e.this)?;
37485                if let Some(format) = &e.format {
37486                    self.write(", '");
37487                    self.write(&Self::strftime_to_java_format(format));
37488                    self.write("'");
37489                }
37490                self.write(")");
37491            }
37492            Some(DialectType::DuckDB) => {
37493                // CAST(STRPTIME(this, format) AS DATE)
37494                self.write_keyword("CAST");
37495                self.write("(");
37496                self.write_keyword("STRPTIME");
37497                self.write("(");
37498                self.generate_expression(&e.this)?;
37499                if let Some(format) = &e.format {
37500                    self.write(", '");
37501                    self.write(format);
37502                    self.write("'");
37503                }
37504                self.write(")");
37505                self.write_keyword(" AS ");
37506                self.write_keyword("DATE");
37507                self.write(")");
37508            }
37509            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
37510                // TO_DATE(this, pg_format)
37511                self.write_keyword("TO_DATE");
37512                self.write("(");
37513                self.generate_expression(&e.this)?;
37514                if let Some(format) = &e.format {
37515                    self.write(", '");
37516                    self.write(&Self::strftime_to_postgres_format(format));
37517                    self.write("'");
37518                }
37519                self.write(")");
37520            }
37521            Some(DialectType::BigQuery) => {
37522                // PARSE_DATE(format, this) - note: format comes first for BigQuery
37523                self.write_keyword("PARSE_DATE");
37524                self.write("(");
37525                if let Some(format) = &e.format {
37526                    self.write("'");
37527                    self.write(format);
37528                    self.write("'");
37529                    self.write(", ");
37530                }
37531                self.generate_expression(&e.this)?;
37532                self.write(")");
37533            }
37534            Some(DialectType::Teradata) => {
37535                // CAST(this AS DATE FORMAT 'teradata_fmt')
37536                self.write_keyword("CAST");
37537                self.write("(");
37538                self.generate_expression(&e.this)?;
37539                self.write_keyword(" AS ");
37540                self.write_keyword("DATE");
37541                if let Some(format) = &e.format {
37542                    self.write_keyword(" FORMAT ");
37543                    self.write("'");
37544                    self.write(&Self::strftime_to_teradata_format(format));
37545                    self.write("'");
37546                }
37547                self.write(")");
37548            }
37549            _ => {
37550                // STR_TO_DATE(this, format) - MySQL default
37551                self.write_keyword("STR_TO_DATE");
37552                self.write("(");
37553                self.generate_expression(&e.this)?;
37554                if let Some(format) = &e.format {
37555                    self.write(", '");
37556                    self.write(format);
37557                    self.write("'");
37558                }
37559                self.write(")");
37560            }
37561        }
37562        Ok(())
37563    }
37564
37565    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
37566    fn strftime_to_teradata_format(fmt: &str) -> String {
37567        let mut result = String::with_capacity(fmt.len() * 2);
37568        let bytes = fmt.as_bytes();
37569        let len = bytes.len();
37570        let mut i = 0;
37571        while i < len {
37572            if bytes[i] == b'%' && i + 1 < len {
37573                let replacement = match bytes[i + 1] {
37574                    b'Y' => "YYYY",
37575                    b'y' => "YY",
37576                    b'm' => "MM",
37577                    b'B' => "MMMM",
37578                    b'b' => "MMM",
37579                    b'd' => "DD",
37580                    b'j' => "DDD",
37581                    b'H' => "HH",
37582                    b'M' => "MI",
37583                    b'S' => "SS",
37584                    b'f' => "SSSSSS",
37585                    b'A' => "EEEE",
37586                    b'a' => "EEE",
37587                    _ => {
37588                        result.push('%');
37589                        i += 1;
37590                        continue;
37591                    }
37592                };
37593                result.push_str(replacement);
37594                i += 2;
37595            } else {
37596                result.push(bytes[i] as char);
37597                i += 1;
37598            }
37599        }
37600        result
37601    }
37602
37603    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
37604    /// Public static version for use by other modules
37605    pub fn strftime_to_java_format_static(fmt: &str) -> String {
37606        Self::strftime_to_java_format_with_padding(fmt, true)
37607    }
37608
37609    /// Convert strftime directives to the non-padded Java patterns used by
37610    /// Spark's parsing functions in current sqlglot versions.
37611    pub fn strftime_to_java_format_non_padded_static(fmt: &str) -> String {
37612        Self::strftime_to_java_format_with_padding(fmt, false)
37613    }
37614
37615    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
37616    fn strftime_to_java_format(fmt: &str) -> String {
37617        Self::strftime_to_java_format_with_padding(fmt, true)
37618    }
37619
37620    fn strftime_to_java_format_non_padded(fmt: &str) -> String {
37621        Self::strftime_to_java_format_with_padding(fmt, false)
37622    }
37623
37624    fn strftime_to_java_format_with_padding(fmt: &str, padded: bool) -> String {
37625        let mut result = String::with_capacity(fmt.len() * 2);
37626        let bytes = fmt.as_bytes();
37627        let len = bytes.len();
37628        let mut i = 0;
37629        while i < len {
37630            if bytes[i] == b'\\' && i + 1 < len {
37631                // PostgreSQL TO_CHAR literals are marked with a backslash by the
37632                // parser so .NET does not interpret reserved format characters.
37633                result.push('\\');
37634                if bytes[i + 1] == b'\'' {
37635                    result.push_str("''");
37636                } else {
37637                    result.push(bytes[i + 1] as char);
37638                }
37639                i += 2;
37640            } else if bytes[i] == b'%' && i + 1 < len {
37641                // Check for non-padded variants (%-X)
37642                if bytes[i + 1] == b'-' && i + 2 < len {
37643                    let replacement = match bytes[i + 2] {
37644                        b'd' => "d",
37645                        b'm' => "M",
37646                        b'H' => "H",
37647                        b'I' => "h",
37648                        b'M' => "m",
37649                        b'S' => "s",
37650                        _ => {
37651                            result.push('%');
37652                            i += 1;
37653                            continue;
37654                        }
37655                    };
37656                    result.push_str(replacement);
37657                    i += 3;
37658                } else {
37659                    let replacement = match bytes[i + 1] {
37660                        b'Y' => "yyyy",
37661                        b'y' => "yy",
37662                        b'm' => {
37663                            if padded {
37664                                "MM"
37665                            } else {
37666                                "M"
37667                            }
37668                        }
37669                        b'B' => "MMMM",
37670                        b'b' => "MMM",
37671                        b'd' => {
37672                            if padded {
37673                                "dd"
37674                            } else {
37675                                "d"
37676                            }
37677                        }
37678                        b'j' => "DDD",
37679                        b'H' => {
37680                            if padded {
37681                                "HH"
37682                            } else {
37683                                "H"
37684                            }
37685                        }
37686                        b'M' => {
37687                            if padded {
37688                                "mm"
37689                            } else {
37690                                "m"
37691                            }
37692                        }
37693                        b'S' => {
37694                            if padded {
37695                                "ss"
37696                            } else {
37697                                "s"
37698                            }
37699                        }
37700                        b'f' => "SSSSSS",
37701                        b'A' => "EEEE",
37702                        b'a' => "EEE",
37703                        _ => {
37704                            result.push('%');
37705                            i += 1;
37706                            continue;
37707                        }
37708                    };
37709                    result.push_str(replacement);
37710                    i += 2;
37711                }
37712            } else {
37713                result.push(bytes[i] as char);
37714                i += 1;
37715            }
37716        }
37717        result
37718    }
37719
37720    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
37721    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
37722    fn strftime_to_tsql_format(fmt: &str) -> String {
37723        let mut result = String::with_capacity(fmt.len() * 2);
37724        let bytes = fmt.as_bytes();
37725        let len = bytes.len();
37726        let mut i = 0;
37727        while i < len {
37728            if bytes[i] == b'%' && i + 1 < len {
37729                // Check for non-padded variants (%-X)
37730                if bytes[i + 1] == b'-' && i + 2 < len {
37731                    let replacement = match bytes[i + 2] {
37732                        b'd' => "d",
37733                        b'm' => "M",
37734                        b'H' => "H",
37735                        b'M' => "m",
37736                        b'S' => "s",
37737                        _ => {
37738                            result.push('%');
37739                            i += 1;
37740                            continue;
37741                        }
37742                    };
37743                    result.push_str(replacement);
37744                    i += 3;
37745                } else {
37746                    let replacement = match bytes[i + 1] {
37747                        b'Y' => "yyyy",
37748                        b'y' => "yy",
37749                        b'm' => "MM",
37750                        b'B' => "MMMM",
37751                        b'b' => "MMM",
37752                        b'd' => "dd",
37753                        b'j' => "DDD",
37754                        b'H' => "HH",
37755                        b'I' => "hh",
37756                        b'M' => "mm",
37757                        b'S' => "ss",
37758                        b'f' => "ffffff",
37759                        b'A' => "dddd",
37760                        b'a' => "ddd",
37761                        _ => {
37762                            result.push('%');
37763                            i += 1;
37764                            continue;
37765                        }
37766                    };
37767                    result.push_str(replacement);
37768                    i += 2;
37769                }
37770            } else {
37771                result.push(bytes[i] as char);
37772                i += 1;
37773            }
37774        }
37775        result
37776    }
37777
37778    fn tsql_convert_style_for_strftime(fmt: &str) -> Option<u16> {
37779        match fmt.trim() {
37780            "%b %d %Y %-I:%M%p" => Some(100),
37781            "%m/%d/%y" => Some(1),
37782            "%y.%m.%d" => Some(2),
37783            "%d/%m/%y" => Some(3),
37784            "%d.%m.%y" => Some(4),
37785            "%d-%m-%y" => Some(5),
37786            "%d %b %y" => Some(6),
37787            "%b %d, %y" => Some(7),
37788            "%H:%M:%S" => Some(108),
37789            "%b %d %Y %-I:%M:%S:%f%p" => Some(109),
37790            "%m-%d-%y" => Some(10),
37791            "%y/%m/%d" => Some(11),
37792            "%y%m%d" => Some(12),
37793            "%d %b %Y %H:%M:%S:%f" => Some(113),
37794            "%H:%M:%S:%f" => Some(114),
37795            "%m/%d/%Y" => Some(101),
37796            "%Y.%m.%d" => Some(102),
37797            "%d/%m/%Y" => Some(103),
37798            "%d.%m.%Y" => Some(104),
37799            "%d-%m-%Y" => Some(105),
37800            "%d %b %Y" => Some(106),
37801            "%b %d, %Y" => Some(107),
37802            "%m-%d-%Y" => Some(110),
37803            "%Y/%m/%d" => Some(111),
37804            "%Y%m%d" => Some(112),
37805            "%Y-%m-%d %H:%M:%S" => Some(120),
37806            "%Y-%m-%d %H:%M:%S.%f" => Some(121),
37807            "%Y-%m-%dT%H:%M:%S" | "%Y-%m-%dT%H:%M:%S.%f" => Some(126),
37808            "%Y-%m-%d" => Some(23),
37809            _ => None,
37810        }
37811    }
37812
37813    fn postgres_year_is_outside_tsql_range(value: &str, format: &str) -> bool {
37814        fn component_is_outside(component: &str, forced_negative: bool) -> bool {
37815            let component = component.trim().trim_end_matches(',');
37816            let (negative, digits) = match component.as_bytes().first() {
37817                Some(b'-') => (true, &component[1..]),
37818                Some(b'+') => (false, &component[1..]),
37819                _ => (false, component),
37820            };
37821
37822            if forced_negative || negative {
37823                return !digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit());
37824            }
37825
37826            if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
37827                return false;
37828            }
37829
37830            let significant_digits = digits.trim_start_matches('0');
37831            significant_digits.is_empty()
37832                || significant_digits.len() > 4
37833                || significant_digits
37834                    .parse::<u16>()
37835                    .is_ok_and(|year| year > 9999)
37836        }
37837
37838        let value = value.trim();
37839        let format = format.trim();
37840
37841        if format.starts_with("%Y") {
37842            let bytes = value.as_bytes();
37843            let digit_start = usize::from(matches!(bytes.first(), Some(b'-' | b'+')));
37844            let available_digits = bytes[digit_start..]
37845                .iter()
37846                .take_while(|byte| byte.is_ascii_digit())
37847                .count();
37848            if available_digits == 0 {
37849                return false;
37850            }
37851
37852            let digit_count = if format.starts_with("%Y%") {
37853                available_digits.min(4)
37854            } else {
37855                available_digits
37856            };
37857            return component_is_outside(&value[..digit_start + digit_count], false);
37858        }
37859
37860        if let Some(year_index) = format
37861            .split_whitespace()
37862            .position(|component| component == "%Y")
37863        {
37864            if let Some(component) = value.split_whitespace().nth(year_index) {
37865                return component_is_outside(component, false);
37866            }
37867        }
37868
37869        if format.ends_with("%Y") {
37870            for delimiter in ['/', '.', '-'] {
37871                if format.ends_with(&format!("{delimiter}%Y")) {
37872                    if let Some((prefix, component)) = value.rsplit_once(delimiter) {
37873                        let forced_negative = delimiter == '-' && prefix.ends_with('-');
37874                        return component_is_outside(component, forced_negative);
37875                    }
37876                }
37877            }
37878        }
37879
37880        false
37881    }
37882
37883    fn generate_tsql_str_to_temporal(
37884        &mut self,
37885        this: &Expression,
37886        format: Option<&str>,
37887        target_type: &str,
37888    ) -> Result<()> {
37889        if let Some(format) = format {
37890            if let Some(style) = Self::tsql_convert_style_for_strftime(format) {
37891                if target_type == "DATE"
37892                    && matches!(self.config.source_dialect, Some(DialectType::PostgreSQL))
37893                {
37894                    if let Expression::Literal(literal) = this {
37895                        if let Literal::String(value) = literal.as_ref() {
37896                            if Self::postgres_year_is_outside_tsql_range(value, format) {
37897                                self.unsupported(
37898                                    "PostgreSQL TO_DATE literal is outside the T-SQL/Fabric DATE range 0001-01-01 through 9999-12-31",
37899                                )?;
37900                            }
37901                        }
37902                    }
37903                }
37904
37905                self.write_keyword("CONVERT");
37906                self.write("(");
37907                self.write_keyword(target_type);
37908                self.write(", ");
37909                self.generate_expression(this)?;
37910                self.write(", ");
37911                self.write(&style.to_string());
37912                self.write(")");
37913                return Ok(());
37914            }
37915
37916            self.unsupported(format!(
37917                "T-SQL/Fabric {target_type} parsing format '{format}' has no CONVERT style mapping"
37918            ))?;
37919        }
37920
37921        self.write_keyword("CAST");
37922        self.write("(");
37923        self.generate_expression(this)?;
37924        self.write(" AS ");
37925        self.write_keyword(target_type);
37926        self.write(")");
37927        Ok(())
37928    }
37929
37930    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
37931    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
37932    fn decompose_json_path(path: &str) -> Vec<String> {
37933        let mut parts = Vec::new();
37934        // Strip leading $ and optional .
37935        let path = if path.starts_with("$.") {
37936            &path[2..]
37937        } else if path.starts_with('$') {
37938            &path[1..]
37939        } else {
37940            path
37941        };
37942        if path.is_empty() {
37943            return parts;
37944        }
37945        let mut current = String::new();
37946        let chars: Vec<char> = path.chars().collect();
37947        let mut i = 0;
37948        while i < chars.len() {
37949            match chars[i] {
37950                '.' => {
37951                    if !current.is_empty() {
37952                        parts.push(current.clone());
37953                        current.clear();
37954                    }
37955                    i += 1;
37956                }
37957                '[' => {
37958                    if !current.is_empty() {
37959                        parts.push(current.clone());
37960                        current.clear();
37961                    }
37962                    i += 1;
37963                    // Read the content inside brackets
37964                    let mut bracket_content = String::new();
37965                    while i < chars.len() && chars[i] != ']' {
37966                        // Skip quotes inside brackets
37967                        if chars[i] == '"' || chars[i] == '\'' {
37968                            let quote = chars[i];
37969                            i += 1;
37970                            while i < chars.len() && chars[i] != quote {
37971                                bracket_content.push(chars[i]);
37972                                i += 1;
37973                            }
37974                            if i < chars.len() {
37975                                i += 1;
37976                            } // skip closing quote
37977                        } else {
37978                            bracket_content.push(chars[i]);
37979                            i += 1;
37980                        }
37981                    }
37982                    if i < chars.len() {
37983                        i += 1;
37984                    } // skip ]
37985                      // Skip wildcard [*] - don't add as a part
37986                    if bracket_content != "*" {
37987                        parts.push(bracket_content);
37988                    }
37989                }
37990                _ => {
37991                    current.push(chars[i]);
37992                    i += 1;
37993                }
37994            }
37995        }
37996        if !current.is_empty() {
37997            parts.push(current);
37998        }
37999        parts
38000    }
38001
38002    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
38003    fn strftime_to_postgres_format(fmt: &str) -> String {
38004        let mut result = String::with_capacity(fmt.len() * 2);
38005        let bytes = fmt.as_bytes();
38006        let len = bytes.len();
38007        let mut i = 0;
38008        while i < len {
38009            if bytes[i] == b'%' && i + 1 < len {
38010                // Check for non-padded variants (%-X)
38011                if bytes[i + 1] == b'-' && i + 2 < len {
38012                    let replacement = match bytes[i + 2] {
38013                        b'd' => "FMDD",
38014                        b'm' => "FMMM",
38015                        b'H' => "FMHH24",
38016                        b'M' => "FMMI",
38017                        b'S' => "FMSS",
38018                        _ => {
38019                            result.push('%');
38020                            i += 1;
38021                            continue;
38022                        }
38023                    };
38024                    result.push_str(replacement);
38025                    i += 3;
38026                } else {
38027                    let replacement = match bytes[i + 1] {
38028                        b'Y' => "YYYY",
38029                        b'y' => "YY",
38030                        b'm' => "MM",
38031                        b'B' => "Month",
38032                        b'b' => "Mon",
38033                        b'd' => "DD",
38034                        b'j' => "DDD",
38035                        b'H' => "HH24",
38036                        b'M' => "MI",
38037                        b'S' => "SS",
38038                        b'f' => "US",
38039                        b'A' => "Day",
38040                        b'a' => "Dy",
38041                        _ => {
38042                            result.push('%');
38043                            i += 1;
38044                            continue;
38045                        }
38046                    };
38047                    result.push_str(replacement);
38048                    i += 2;
38049                }
38050            } else {
38051                result.push(bytes[i] as char);
38052                i += 1;
38053            }
38054        }
38055        result
38056    }
38057
38058    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
38059    fn strftime_to_snowflake_format(fmt: &str) -> String {
38060        let mut result = String::with_capacity(fmt.len() * 2);
38061        let bytes = fmt.as_bytes();
38062        let len = bytes.len();
38063        let mut i = 0;
38064        while i < len {
38065            if bytes[i] == b'%' && i + 1 < len {
38066                // Check for non-padded variants (%-X)
38067                if bytes[i + 1] == b'-' && i + 2 < len {
38068                    let replacement = match bytes[i + 2] {
38069                        b'd' => "dd",
38070                        b'm' => "mm",
38071                        _ => {
38072                            result.push('%');
38073                            i += 1;
38074                            continue;
38075                        }
38076                    };
38077                    result.push_str(replacement);
38078                    i += 3;
38079                } else {
38080                    let replacement = match bytes[i + 1] {
38081                        b'Y' => "yyyy",
38082                        b'y' => "yy",
38083                        b'm' => "mm",
38084                        b'd' => "DD",
38085                        b'H' => "hh24",
38086                        b'M' => "mi",
38087                        b'S' => "ss",
38088                        b'f' => "ff",
38089                        _ => {
38090                            result.push('%');
38091                            i += 1;
38092                            continue;
38093                        }
38094                    };
38095                    result.push_str(replacement);
38096                    i += 2;
38097                }
38098            } else {
38099                result.push(bytes[i] as char);
38100                i += 1;
38101            }
38102        }
38103        result
38104    }
38105
38106    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
38107        // STR_TO_MAP(this, pair_delim, key_value_delim)
38108        self.write_keyword("STR_TO_MAP");
38109        self.write("(");
38110        self.generate_expression(&e.this)?;
38111        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
38112        let needs_defaults = matches!(
38113            self.config.dialect,
38114            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
38115        );
38116        if let Some(pair_delim) = &e.pair_delim {
38117            self.write(", ");
38118            self.generate_expression(pair_delim)?;
38119        } else if needs_defaults {
38120            self.write(", ','");
38121        }
38122        if let Some(key_value_delim) = &e.key_value_delim {
38123            self.write(", ");
38124            self.generate_expression(key_value_delim)?;
38125        } else if needs_defaults {
38126            self.write(", ':'");
38127        }
38128        self.write(")");
38129        Ok(())
38130    }
38131
38132    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
38133        // Detect format style: strftime (starts with %) vs Snowflake/Java
38134        let is_strftime = e.format.contains('%');
38135        // Helper: get strftime format from whatever style is stored
38136        let to_strftime = |f: &str| -> String {
38137            if is_strftime {
38138                f.to_string()
38139            } else {
38140                Self::snowflake_format_to_strftime(f)
38141            }
38142        };
38143        // Helper: get Java format
38144        let to_java = |f: &str| -> String {
38145            if is_strftime {
38146                Self::strftime_to_java_format(f)
38147            } else {
38148                Self::snowflake_format_to_spark(f)
38149            }
38150        };
38151        // Helper: get PG format
38152        let to_pg = |f: &str| -> String {
38153            if is_strftime {
38154                Self::strftime_to_postgres_format(f)
38155            } else {
38156                Self::convert_strptime_to_postgres_format(f)
38157            }
38158        };
38159
38160        match self.config.dialect {
38161            Some(DialectType::Exasol) => {
38162                self.write_keyword("TO_DATE");
38163                self.write("(");
38164                self.generate_expression(&e.this)?;
38165                self.write(", '");
38166                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
38167                self.write("'");
38168                self.write(")");
38169            }
38170            Some(DialectType::BigQuery) => {
38171                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
38172                let fmt = to_strftime(&e.format);
38173                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
38174                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
38175                self.write_keyword("PARSE_TIMESTAMP");
38176                self.write("('");
38177                self.write(&fmt);
38178                self.write("', ");
38179                self.generate_expression(&e.this)?;
38180                self.write(")");
38181            }
38182            Some(DialectType::Hive) => {
38183                // Hive: CAST(x AS TIMESTAMP) for simple date formats
38184                // Check both the raw format and the converted format (in case it's already Java)
38185                let java_fmt = if is_strftime {
38186                    Self::strftime_to_java_format_non_padded(&e.format)
38187                } else {
38188                    Self::snowflake_format_to_spark(&e.format)
38189                };
38190                if java_fmt == "yyyy-MM-dd HH:mm:ss"
38191                    || java_fmt == "yyyy-MM-dd"
38192                    || java_fmt == "yyyy-M-d H:m:s"
38193                    || java_fmt == "yyyy-M-d"
38194                    || e.format == "yyyy-MM-dd HH:mm:ss"
38195                    || e.format == "yyyy-MM-dd"
38196                {
38197                    self.write_keyword("CAST");
38198                    self.write("(");
38199                    self.generate_expression(&e.this)?;
38200                    self.write(" ");
38201                    self.write_keyword("AS TIMESTAMP");
38202                    self.write(")");
38203                } else {
38204                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
38205                    self.write_keyword("CAST");
38206                    self.write("(");
38207                    self.write_keyword("FROM_UNIXTIME");
38208                    self.write("(");
38209                    self.write_keyword("UNIX_TIMESTAMP");
38210                    self.write("(");
38211                    self.generate_expression(&e.this)?;
38212                    self.write(", '");
38213                    self.write(&java_fmt);
38214                    self.write("')");
38215                    self.write(") ");
38216                    self.write_keyword("AS TIMESTAMP");
38217                    self.write(")");
38218                }
38219            }
38220            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38221                // Spark: TO_TIMESTAMP(value, java_format)
38222                let java_fmt = if is_strftime {
38223                    Self::strftime_to_java_format_non_padded(&e.format)
38224                } else {
38225                    Self::snowflake_format_to_spark(&e.format)
38226                };
38227                self.write_keyword("TO_TIMESTAMP");
38228                self.write("(");
38229                self.generate_expression(&e.this)?;
38230                self.write(", '");
38231                self.write(&java_fmt);
38232                self.write("')");
38233            }
38234            Some(DialectType::MySQL) => {
38235                // MySQL: STR_TO_DATE(value, format)
38236                let mut fmt = to_strftime(&e.format);
38237                // MySQL uses %e for non-padded day, %T for %H:%M:%S
38238                fmt = fmt.replace("%-d", "%e");
38239                fmt = fmt.replace("%-m", "%c");
38240                fmt = fmt.replace("%H:%M:%S", "%T");
38241                self.write_keyword("STR_TO_DATE");
38242                self.write("(");
38243                self.generate_expression(&e.this)?;
38244                self.write(", '");
38245                self.write(&fmt);
38246                self.write("')");
38247            }
38248            Some(DialectType::Drill) => {
38249                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
38250                let java_fmt = to_java(&e.format);
38251                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
38252                let java_fmt = java_fmt.replace('T', "''T''");
38253                self.write_keyword("TO_TIMESTAMP");
38254                self.write("(");
38255                self.generate_expression(&e.this)?;
38256                self.write(", '");
38257                self.write(&java_fmt);
38258                self.write("')");
38259            }
38260            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
38261                // Presto: DATE_PARSE(value, strftime_format)
38262                let mut fmt = to_strftime(&e.format);
38263                // Presto uses %e for non-padded day, %T for %H:%M:%S
38264                fmt = fmt.replace("%-d", "%e");
38265                fmt = fmt.replace("%-m", "%c");
38266                fmt = fmt.replace("%H:%M:%S", "%T");
38267                self.write_keyword("DATE_PARSE");
38268                self.write("(");
38269                self.generate_expression(&e.this)?;
38270                self.write(", '");
38271                self.write(&fmt);
38272                self.write("')");
38273            }
38274            Some(DialectType::DuckDB) => {
38275                // DuckDB: STRPTIME(value, strftime_format)
38276                let fmt = to_strftime(&e.format);
38277                self.write_keyword("STRPTIME");
38278                self.write("(");
38279                self.generate_expression(&e.this)?;
38280                self.write(", '");
38281                self.write(&fmt);
38282                self.write("')");
38283            }
38284            Some(DialectType::PostgreSQL)
38285            | Some(DialectType::Redshift)
38286            | Some(DialectType::Materialize) => {
38287                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
38288                let pg_fmt = to_pg(&e.format);
38289                self.write_keyword("TO_TIMESTAMP");
38290                self.write("(");
38291                self.generate_expression(&e.this)?;
38292                self.write(", '");
38293                self.write(&pg_fmt);
38294                self.write("')");
38295            }
38296            Some(DialectType::Oracle) => {
38297                // Oracle: TO_TIMESTAMP(value, pg_format)
38298                let pg_fmt = to_pg(&e.format);
38299                self.write_keyword("TO_TIMESTAMP");
38300                self.write("(");
38301                self.generate_expression(&e.this)?;
38302                self.write(", '");
38303                self.write(&pg_fmt);
38304                self.write("')");
38305            }
38306            Some(DialectType::Snowflake) => {
38307                // Snowflake: TO_TIMESTAMP(value, format) - native format
38308                self.write_keyword("TO_TIMESTAMP");
38309                self.write("(");
38310                self.generate_expression(&e.this)?;
38311                self.write(", '");
38312                self.write(&e.format);
38313                self.write("')");
38314            }
38315            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
38316                self.generate_tsql_str_to_temporal(&e.this, Some(&e.format), "DATETIME2")?;
38317            }
38318            _ => {
38319                // Default: STR_TO_TIME(this, format)
38320                self.write_keyword("STR_TO_TIME");
38321                self.write("(");
38322                self.generate_expression(&e.this)?;
38323                self.write(", '");
38324                self.write(&e.format);
38325                self.write("'");
38326                self.write(")");
38327            }
38328        }
38329        Ok(())
38330    }
38331
38332    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
38333    fn snowflake_format_to_strftime(format: &str) -> String {
38334        let mut result = String::new();
38335        let chars: Vec<char> = format.chars().collect();
38336        let mut i = 0;
38337        while i < chars.len() {
38338            let remaining = &format[i..];
38339            if remaining.starts_with("yyyy") {
38340                result.push_str("%Y");
38341                i += 4;
38342            } else if remaining.starts_with("yy") {
38343                result.push_str("%y");
38344                i += 2;
38345            } else if remaining.starts_with("mmmm") {
38346                result.push_str("%B"); // full month name
38347                i += 4;
38348            } else if remaining.starts_with("mon") {
38349                result.push_str("%b"); // abbreviated month
38350                i += 3;
38351            } else if remaining.starts_with("mm") {
38352                result.push_str("%m");
38353                i += 2;
38354            } else if remaining.starts_with("DD") {
38355                result.push_str("%d");
38356                i += 2;
38357            } else if remaining.starts_with("dy") {
38358                result.push_str("%a"); // abbreviated day name
38359                i += 2;
38360            } else if remaining.starts_with("hh24") {
38361                result.push_str("%H");
38362                i += 4;
38363            } else if remaining.starts_with("hh12") {
38364                result.push_str("%I");
38365                i += 4;
38366            } else if remaining.starts_with("hh") {
38367                result.push_str("%H");
38368                i += 2;
38369            } else if remaining.starts_with("mi") {
38370                result.push_str("%M");
38371                i += 2;
38372            } else if remaining.starts_with("ss") {
38373                result.push_str("%S");
38374                i += 2;
38375            } else if remaining.starts_with("ff") {
38376                // Fractional seconds
38377                result.push_str("%f");
38378                i += 2;
38379                // Skip digits after ff (ff3, ff6, ff9)
38380                while i < chars.len() && chars[i].is_ascii_digit() {
38381                    i += 1;
38382                }
38383            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
38384                result.push_str("%p");
38385                i += 2;
38386            } else if remaining.starts_with("tz") {
38387                result.push_str("%Z");
38388                i += 2;
38389            } else {
38390                result.push(chars[i]);
38391                i += 1;
38392            }
38393        }
38394        result
38395    }
38396
38397    /// Convert Snowflake normalized format to Spark format (Java-style)
38398    fn snowflake_format_to_spark(format: &str) -> String {
38399        let mut result = String::new();
38400        let chars: Vec<char> = format.chars().collect();
38401        let mut i = 0;
38402        while i < chars.len() {
38403            let remaining = &format[i..];
38404            if remaining.starts_with("yyyy") {
38405                result.push_str("yyyy");
38406                i += 4;
38407            } else if remaining.starts_with("yy") {
38408                result.push_str("yy");
38409                i += 2;
38410            } else if remaining.starts_with("mmmm") {
38411                result.push_str("MMMM"); // full month name
38412                i += 4;
38413            } else if remaining.starts_with("mon") {
38414                result.push_str("MMM"); // abbreviated month
38415                i += 3;
38416            } else if remaining.starts_with("mm") {
38417                result.push('M');
38418                i += 2;
38419            } else if remaining.starts_with("DD") {
38420                result.push('d');
38421                i += 2;
38422            } else if remaining.starts_with("dy") {
38423                result.push_str("EEE"); // abbreviated day name
38424                i += 2;
38425            } else if remaining.starts_with("hh24") {
38426                result.push('H');
38427                i += 4;
38428            } else if remaining.starts_with("hh12") {
38429                result.push('h');
38430                i += 4;
38431            } else if remaining.starts_with("hh") {
38432                result.push_str("HH");
38433                i += 2;
38434            } else if remaining.starts_with("mi") {
38435                result.push('m');
38436                i += 2;
38437            } else if remaining.starts_with("ss") {
38438                result.push('s');
38439                i += 2;
38440            } else if remaining.starts_with("ff") {
38441                result.push_str("SSS"); // milliseconds
38442                i += 2;
38443                // Skip digits after ff
38444                while i < chars.len() && chars[i].is_ascii_digit() {
38445                    i += 1;
38446                }
38447            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
38448                result.push_str("a");
38449                i += 2;
38450            } else if remaining.starts_with("tz") {
38451                result.push_str("z");
38452                i += 2;
38453            } else {
38454                result.push(chars[i]);
38455                i += 1;
38456            }
38457        }
38458        result
38459    }
38460
38461    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
38462        match self.config.dialect {
38463            Some(DialectType::DuckDB) => {
38464                // DuckDB: EPOCH(STRPTIME(value, format))
38465                self.write_keyword("EPOCH");
38466                self.write("(");
38467                self.write_keyword("STRPTIME");
38468                self.write("(");
38469                if let Some(this) = &e.this {
38470                    self.generate_expression(this)?;
38471                }
38472                if let Some(format) = &e.format {
38473                    self.write(", '");
38474                    self.write(format);
38475                    self.write("'");
38476                }
38477                self.write("))");
38478            }
38479            Some(DialectType::Hive) => {
38480                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
38481                self.write_keyword("UNIX_TIMESTAMP");
38482                self.write("(");
38483                if let Some(this) = &e.this {
38484                    self.generate_expression(this)?;
38485                }
38486                if let Some(format) = &e.format {
38487                    let java_fmt = Self::strftime_to_java_format_non_padded(format);
38488                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
38489                        self.write(", '");
38490                        self.write(&java_fmt);
38491                        self.write("'");
38492                    }
38493                }
38494                self.write(")");
38495            }
38496            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38497                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
38498                self.write_keyword("UNIX_TIMESTAMP");
38499                self.write("(");
38500                if let Some(this) = &e.this {
38501                    self.generate_expression(this)?;
38502                }
38503                if let Some(format) = &e.format {
38504                    self.write(", '");
38505                    self.write(format);
38506                    self.write("'");
38507                }
38508                self.write(")");
38509            }
38510            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38511                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
38512                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
38513                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
38514                let java_fmt = Self::strftime_to_java_format(c_fmt);
38515                self.write_keyword("TO_UNIXTIME");
38516                self.write("(");
38517                self.write_keyword("COALESCE");
38518                self.write("(");
38519                self.write_keyword("TRY");
38520                self.write("(");
38521                self.write_keyword("DATE_PARSE");
38522                self.write("(");
38523                self.write_keyword("CAST");
38524                self.write("(");
38525                if let Some(this) = &e.this {
38526                    self.generate_expression(this)?;
38527                }
38528                self.write(" ");
38529                self.write_keyword("AS VARCHAR");
38530                self.write("), '");
38531                self.write(c_fmt);
38532                self.write("')), ");
38533                self.write_keyword("PARSE_DATETIME");
38534                self.write("(");
38535                self.write_keyword("DATE_FORMAT");
38536                self.write("(");
38537                self.write_keyword("CAST");
38538                self.write("(");
38539                if let Some(this) = &e.this {
38540                    self.generate_expression(this)?;
38541                }
38542                self.write(" ");
38543                self.write_keyword("AS TIMESTAMP");
38544                self.write("), '");
38545                self.write(c_fmt);
38546                self.write("'), '");
38547                self.write(&java_fmt);
38548                self.write("')))");
38549            }
38550            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38551                // Spark: UNIX_TIMESTAMP(value, java_format)
38552                self.write_keyword("UNIX_TIMESTAMP");
38553                self.write("(");
38554                if let Some(this) = &e.this {
38555                    self.generate_expression(this)?;
38556                }
38557                if let Some(format) = &e.format {
38558                    let java_fmt = Self::strftime_to_java_format_non_padded(format);
38559                    self.write(", '");
38560                    self.write(&java_fmt);
38561                    self.write("'");
38562                }
38563                self.write(")");
38564            }
38565            _ => {
38566                // Default: STR_TO_UNIX(this, format)
38567                self.write_keyword("STR_TO_UNIX");
38568                self.write("(");
38569                if let Some(this) = &e.this {
38570                    self.generate_expression(this)?;
38571                }
38572                if let Some(format) = &e.format {
38573                    self.write(", '");
38574                    self.write(format);
38575                    self.write("'");
38576                }
38577                self.write(")");
38578            }
38579        }
38580        Ok(())
38581    }
38582
38583    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
38584        // STRING_TO_ARRAY(this, delimiter, null_string)
38585        self.write_keyword("STRING_TO_ARRAY");
38586        self.write("(");
38587        self.generate_expression(&e.this)?;
38588        if let Some(expression) = &e.expression {
38589            self.write(", ");
38590            self.generate_expression(expression)?;
38591        }
38592        if let Some(null_val) = &e.null {
38593            self.write(", ");
38594            self.generate_expression(null_val)?;
38595        }
38596        self.write(")");
38597        Ok(())
38598    }
38599
38600    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
38601        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
38602            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
38603            self.write_keyword("OBJECT_CONSTRUCT");
38604            self.write("(");
38605            for (i, (name, expr)) in e.fields.iter().enumerate() {
38606                if i > 0 {
38607                    self.write(", ");
38608                }
38609                if let Some(name) = name {
38610                    self.write("'");
38611                    self.write(name);
38612                    self.write("'");
38613                    self.write(", ");
38614                } else {
38615                    self.write("'_");
38616                    self.write(&i.to_string());
38617                    self.write("'");
38618                    self.write(", ");
38619                }
38620                self.generate_expression(expr)?;
38621            }
38622            self.write(")");
38623        } else if self.config.struct_curly_brace_notation {
38624            // DuckDB-style: {'key': value, ...}
38625            self.write("{");
38626            for (i, (name, expr)) in e.fields.iter().enumerate() {
38627                if i > 0 {
38628                    self.write(", ");
38629                }
38630                if let Some(name) = name {
38631                    // Quote the key as a string literal
38632                    self.write("'");
38633                    self.write(name);
38634                    self.write("'");
38635                    self.write(": ");
38636                } else {
38637                    // Unnamed field: use positional key
38638                    self.write("'_");
38639                    self.write(&i.to_string());
38640                    self.write("'");
38641                    self.write(": ");
38642                }
38643                self.generate_expression(expr)?;
38644            }
38645            self.write("}");
38646        } else {
38647            // Standard SQL struct notation
38648            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
38649            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
38650            let value_as_name = matches!(
38651                self.config.dialect,
38652                Some(DialectType::BigQuery)
38653                    | Some(DialectType::Spark)
38654                    | Some(DialectType::Databricks)
38655                    | Some(DialectType::Hive)
38656            );
38657            self.write_keyword("STRUCT");
38658            self.write("(");
38659            for (i, (name, expr)) in e.fields.iter().enumerate() {
38660                if i > 0 {
38661                    self.write(", ");
38662                }
38663                if let Some(name) = name {
38664                    if value_as_name {
38665                        // STRUCT(value AS name)
38666                        self.generate_expression(expr)?;
38667                        self.write_space();
38668                        self.write_keyword("AS");
38669                        self.write_space();
38670                        // Quote name if it contains spaces or special chars
38671                        let needs_quoting = name.contains(' ') || name.contains('-');
38672                        if needs_quoting {
38673                            if matches!(
38674                                self.config.dialect,
38675                                Some(DialectType::Spark)
38676                                    | Some(DialectType::Databricks)
38677                                    | Some(DialectType::Hive)
38678                            ) {
38679                                self.write("`");
38680                                self.write(name);
38681                                self.write("`");
38682                            } else {
38683                                self.write(name);
38684                            }
38685                        } else {
38686                            self.write(name);
38687                        }
38688                    } else {
38689                        // STRUCT(name AS value)
38690                        self.write(name);
38691                        self.write_space();
38692                        self.write_keyword("AS");
38693                        self.write_space();
38694                        self.generate_expression(expr)?;
38695                    }
38696                } else {
38697                    self.generate_expression(expr)?;
38698                }
38699            }
38700            self.write(")");
38701        }
38702        Ok(())
38703    }
38704
38705    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
38706        // STUFF(this, start, length, expression)
38707        self.write_keyword("STUFF");
38708        self.write("(");
38709        self.generate_expression(&e.this)?;
38710        if let Some(start) = &e.start {
38711            self.write(", ");
38712            self.generate_expression(start)?;
38713        }
38714        if let Some(length) = e.length {
38715            self.write(", ");
38716            self.write(&length.to_string());
38717        }
38718        self.write(", ");
38719        self.generate_expression(&e.expression)?;
38720        self.write(")");
38721        Ok(())
38722    }
38723
38724    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
38725        // SUBSTRING_INDEX(this, delimiter, count)
38726        self.write_keyword("SUBSTRING_INDEX");
38727        self.write("(");
38728        self.generate_expression(&e.this)?;
38729        if let Some(delimiter) = &e.delimiter {
38730            self.write(", ");
38731            self.generate_expression(delimiter)?;
38732        }
38733        if let Some(count) = &e.count {
38734            self.write(", ");
38735            self.generate_expression(count)?;
38736        }
38737        self.write(")");
38738        Ok(())
38739    }
38740
38741    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
38742        // SUMMARIZE [TABLE] this
38743        self.write_keyword("SUMMARIZE");
38744        if e.table.is_some() {
38745            self.write_space();
38746            self.write_keyword("TABLE");
38747        }
38748        self.write_space();
38749        self.generate_expression(&e.this)?;
38750        Ok(())
38751    }
38752
38753    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
38754        // SYSTIMESTAMP
38755        self.write_keyword("SYSTIMESTAMP");
38756        Ok(())
38757    }
38758
38759    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
38760        // alias (columns...)
38761        if let Some(this) = &e.this {
38762            self.generate_expression(this)?;
38763        }
38764        if !e.columns.is_empty() {
38765            self.write("(");
38766            for (i, col) in e.columns.iter().enumerate() {
38767                if i > 0 {
38768                    self.write(", ");
38769                }
38770                self.generate_expression(col)?;
38771            }
38772            self.write(")");
38773        }
38774        Ok(())
38775    }
38776
38777    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
38778        // TABLE(this) [AS alias]
38779        self.write_keyword("TABLE");
38780        self.write("(");
38781        self.generate_expression(&e.this)?;
38782        self.write(")");
38783        if let Some(alias) = &e.alias {
38784            self.write_space();
38785            self.write_keyword("AS");
38786            self.write_space();
38787            self.write(alias);
38788        }
38789        Ok(())
38790    }
38791
38792    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
38793        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
38794        self.write_keyword("ROWS FROM");
38795        self.write(" (");
38796        for (i, expr) in e.expressions.iter().enumerate() {
38797            if i > 0 {
38798                self.write(", ");
38799            }
38800            // Each expression is either:
38801            // - A plain function (no alias)
38802            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
38803            match expr {
38804                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
38805                    // First element is the function, second is the TableAlias
38806                    self.generate_expression(&tuple.expressions[0])?;
38807                    self.write_space();
38808                    self.write_keyword("AS");
38809                    self.write_space();
38810                    self.generate_expression(&tuple.expressions[1])?;
38811                }
38812                _ => {
38813                    self.generate_expression(expr)?;
38814                }
38815            }
38816        }
38817        self.write(")");
38818        if e.ordinality {
38819            self.write_space();
38820            self.write_keyword("WITH ORDINALITY");
38821        }
38822        if let Some(alias) = &e.alias {
38823            self.write_space();
38824            self.write_keyword("AS");
38825            self.write_space();
38826            self.generate_expression(alias)?;
38827        }
38828        Ok(())
38829    }
38830
38831    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
38832        use crate::dialects::DialectType;
38833
38834        // New wrapper pattern: expression + Sample struct
38835        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
38836            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
38837            if self.config.alias_post_tablesample {
38838                // Handle Subquery with alias and Alias wrapper
38839                if let Expression::Subquery(ref s) = **this {
38840                    if let Some(ref alias) = s.alias {
38841                        // Create a clone without alias for output
38842                        let mut subquery_no_alias = (**s).clone();
38843                        subquery_no_alias.alias = None;
38844                        subquery_no_alias.column_aliases = Vec::new();
38845                        self.generate_expression(&Expression::Subquery(Box::new(
38846                            subquery_no_alias,
38847                        )))?;
38848                        self.write_space();
38849                        self.write_keyword(self.config.tablesample_keywords);
38850                        self.generate_sample_body(sample)?;
38851                        if let Some(ref seed) = sample.seed {
38852                            self.write_space();
38853                            let use_seed = sample.use_seed_keyword
38854                                && !matches!(
38855                                    self.config.dialect,
38856                                    Some(crate::dialects::DialectType::Databricks)
38857                                        | Some(crate::dialects::DialectType::Spark)
38858                                );
38859                            if use_seed {
38860                                self.write_keyword("SEED");
38861                            } else {
38862                                self.write_keyword("REPEATABLE");
38863                            }
38864                            self.write(" (");
38865                            self.generate_expression(seed)?;
38866                            self.write(")");
38867                        }
38868                        self.write_space();
38869                        self.write_keyword("AS");
38870                        self.write_space();
38871                        self.generate_identifier(alias)?;
38872                        return Ok(());
38873                    }
38874                } else if let Expression::Alias(ref a) = **this {
38875                    // Output the base expression without alias
38876                    self.generate_expression(&a.this)?;
38877                    self.write_space();
38878                    self.write_keyword(self.config.tablesample_keywords);
38879                    self.generate_sample_body(sample)?;
38880                    if let Some(ref seed) = sample.seed {
38881                        self.write_space();
38882                        let use_seed = sample.use_seed_keyword
38883                            && !matches!(
38884                                self.config.dialect,
38885                                Some(crate::dialects::DialectType::Databricks)
38886                                    | Some(crate::dialects::DialectType::Spark)
38887                            );
38888                        if use_seed {
38889                            self.write_keyword("SEED");
38890                        } else {
38891                            self.write_keyword("REPEATABLE");
38892                        }
38893                        self.write(" (");
38894                        self.generate_expression(seed)?;
38895                        self.write(")");
38896                    }
38897                    // Output alias after TABLESAMPLE
38898                    self.write_space();
38899                    self.write_keyword("AS");
38900                    self.write_space();
38901                    self.generate_identifier(&a.alias)?;
38902                    return Ok(());
38903                }
38904            }
38905            // Default: generate wrapped expression first, then TABLESAMPLE
38906            self.generate_expression(this)?;
38907            self.write_space();
38908            self.write_keyword(self.config.tablesample_keywords);
38909            self.generate_sample_body(sample)?;
38910            // Seed for table-level sample
38911            if let Some(ref seed) = sample.seed {
38912                self.write_space();
38913                // Databricks uses REPEATABLE, not SEED
38914                let use_seed = sample.use_seed_keyword
38915                    && !matches!(
38916                        self.config.dialect,
38917                        Some(crate::dialects::DialectType::Databricks)
38918                            | Some(crate::dialects::DialectType::Spark)
38919                    );
38920                if use_seed {
38921                    self.write_keyword("SEED");
38922                } else {
38923                    self.write_keyword("REPEATABLE");
38924                }
38925                self.write(" (");
38926                self.generate_expression(seed)?;
38927                self.write(")");
38928            }
38929            return Ok(());
38930        }
38931
38932        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
38933        self.write_keyword(self.config.tablesample_keywords);
38934        if let Some(method) = &e.method {
38935            self.write_space();
38936            self.write_keyword(method);
38937        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
38938            // Snowflake defaults to BERNOULLI when no method is specified
38939            self.write_space();
38940            self.write_keyword("BERNOULLI");
38941        }
38942        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
38943            self.write_space();
38944            self.write_keyword("BUCKET");
38945            self.write_space();
38946            self.generate_expression(numerator)?;
38947            self.write_space();
38948            self.write_keyword("OUT OF");
38949            self.write_space();
38950            self.generate_expression(denominator)?;
38951            if let Some(field) = &e.bucket_field {
38952                self.write_space();
38953                self.write_keyword("ON");
38954                self.write_space();
38955                self.generate_expression(field)?;
38956            }
38957        } else if !e.expressions.is_empty() {
38958            self.write(" (");
38959            for (i, expr) in e.expressions.iter().enumerate() {
38960                if i > 0 {
38961                    self.write(", ");
38962                }
38963                self.generate_expression(expr)?;
38964            }
38965            self.write(")");
38966        } else if let Some(percent) = &e.percent {
38967            self.write(" (");
38968            self.generate_expression(percent)?;
38969            self.write_space();
38970            self.write_keyword("PERCENT");
38971            self.write(")");
38972        }
38973        Ok(())
38974    }
38975
38976    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
38977        // [prefix]this[postfix]
38978        if let Some(prefix) = &e.prefix {
38979            self.generate_expression(prefix)?;
38980        }
38981        if let Some(this) = &e.this {
38982            self.generate_expression(this)?;
38983        }
38984        if let Some(postfix) = &e.postfix {
38985            self.generate_expression(postfix)?;
38986        }
38987        Ok(())
38988    }
38989
38990    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
38991        // TAG (expressions)
38992        self.write_keyword("TAG");
38993        self.write(" (");
38994        for (i, expr) in e.expressions.iter().enumerate() {
38995            if i > 0 {
38996                self.write(", ");
38997            }
38998            self.generate_expression(expr)?;
38999        }
39000        self.write(")");
39001        Ok(())
39002    }
39003
39004    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
39005        // TEMPORARY or TEMP or [this] TEMPORARY
39006        if let Some(this) = &e.this {
39007            self.generate_expression(this)?;
39008            self.write_space();
39009        }
39010        self.write_keyword("TEMPORARY");
39011        Ok(())
39012    }
39013
39014    /// Generate a Time function expression
39015    /// For most dialects: TIME('value')
39016    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
39017        // Standard: TIME(value)
39018        self.write_keyword("TIME");
39019        self.write("(");
39020        self.generate_expression(&e.this)?;
39021        self.write(")");
39022        Ok(())
39023    }
39024
39025    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
39026        // TIME_ADD(this, expression, unit)
39027        self.write_keyword("TIME_ADD");
39028        self.write("(");
39029        self.generate_expression(&e.this)?;
39030        self.write(", ");
39031        self.generate_expression(&e.expression)?;
39032        if let Some(unit) = &e.unit {
39033            self.write(", ");
39034            self.write_keyword(unit);
39035        }
39036        self.write(")");
39037        Ok(())
39038    }
39039
39040    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
39041        // TIME_DIFF(this, expression, unit)
39042        self.write_keyword("TIME_DIFF");
39043        self.write("(");
39044        self.generate_expression(&e.this)?;
39045        self.write(", ");
39046        self.generate_expression(&e.expression)?;
39047        if let Some(unit) = &e.unit {
39048            self.write(", ");
39049            self.write_keyword(unit);
39050        }
39051        self.write(")");
39052        Ok(())
39053    }
39054
39055    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
39056        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
39057        self.write_keyword("TIME_FROM_PARTS");
39058        self.write("(");
39059        let mut first = true;
39060        if let Some(hour) = &e.hour {
39061            self.generate_expression(hour)?;
39062            first = false;
39063        }
39064        if let Some(minute) = &e.min {
39065            if !first {
39066                self.write(", ");
39067            }
39068            self.generate_expression(minute)?;
39069            first = false;
39070        }
39071        if let Some(second) = &e.sec {
39072            if !first {
39073                self.write(", ");
39074            }
39075            self.generate_expression(second)?;
39076            first = false;
39077        }
39078        if let Some(ns) = &e.nano {
39079            if !first {
39080                self.write(", ");
39081            }
39082            self.generate_expression(ns)?;
39083        }
39084        self.write(")");
39085        Ok(())
39086    }
39087
39088    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
39089        // TIME_SLICE(this, expression, unit)
39090        self.write_keyword("TIME_SLICE");
39091        self.write("(");
39092        self.generate_expression(&e.this)?;
39093        self.write(", ");
39094        self.generate_expression(&e.expression)?;
39095        self.write(", ");
39096        self.write_keyword(&e.unit);
39097        self.write(")");
39098        Ok(())
39099    }
39100
39101    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
39102        // TIME_STR_TO_TIME(this)
39103        self.write_keyword("TIME_STR_TO_TIME");
39104        self.write("(");
39105        self.generate_expression(&e.this)?;
39106        self.write(")");
39107        Ok(())
39108    }
39109
39110    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
39111        // TIME_SUB(this, expression, unit)
39112        self.write_keyword("TIME_SUB");
39113        self.write("(");
39114        self.generate_expression(&e.this)?;
39115        self.write(", ");
39116        self.generate_expression(&e.expression)?;
39117        if let Some(unit) = &e.unit {
39118            self.write(", ");
39119            self.write_keyword(unit);
39120        }
39121        self.write(")");
39122        Ok(())
39123    }
39124
39125    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
39126        match self.config.dialect {
39127            Some(DialectType::Exasol) => {
39128                // Exasol uses TO_CHAR with Exasol-specific format
39129                self.write_keyword("TO_CHAR");
39130                self.write("(");
39131                self.generate_expression(&e.this)?;
39132                self.write(", '");
39133                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
39134                self.write("'");
39135                self.write(")");
39136            }
39137            Some(DialectType::PostgreSQL)
39138            | Some(DialectType::Redshift)
39139            | Some(DialectType::Materialize) => {
39140                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
39141                self.write_keyword("TO_CHAR");
39142                self.write("(");
39143                self.generate_expression(&e.this)?;
39144                self.write(", '");
39145                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
39146                self.write("'");
39147                self.write(")");
39148            }
39149            Some(DialectType::Oracle) => {
39150                // Oracle uses TO_CHAR with PG-like format
39151                self.write_keyword("TO_CHAR");
39152                self.write("(");
39153                self.generate_expression(&e.this)?;
39154                self.write(", '");
39155                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
39156                self.write("'");
39157                self.write(")");
39158            }
39159            Some(DialectType::Drill) => {
39160                // Drill: TO_CHAR with Java format
39161                self.write_keyword("TO_CHAR");
39162                self.write("(");
39163                self.generate_expression(&e.this)?;
39164                self.write(", '");
39165                self.write(&Self::strftime_to_java_format(&e.format));
39166                self.write("'");
39167                self.write(")");
39168            }
39169            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
39170                // TSQL: FORMAT(value, format[, culture]) with .NET-style format
39171                self.write_keyword("FORMAT");
39172                self.write("(");
39173                self.generate_expression(&e.this)?;
39174                self.write(", '");
39175                self.write(&Self::strftime_to_tsql_format(&e.format));
39176                self.write("'");
39177                if let Some(culture) = &e.culture {
39178                    self.write(", ");
39179                    self.generate_expression(culture)?;
39180                }
39181                self.write(")");
39182            }
39183            Some(DialectType::DuckDB) => {
39184                // DuckDB: STRFTIME(value, format) - keeps C format
39185                self.write_keyword("STRFTIME");
39186                self.write("(");
39187                self.generate_expression(&e.this)?;
39188                self.write(", '");
39189                self.write(&e.format);
39190                self.write("'");
39191                self.write(")");
39192            }
39193            Some(DialectType::BigQuery) => {
39194                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
39195                self.write_keyword("FORMAT_DATE");
39196                self.write("('");
39197                self.write(&e.format);
39198                self.write("', ");
39199                self.generate_expression(&e.this)?;
39200                self.write(")");
39201            }
39202            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
39203                // Hive/Spark: DATE_FORMAT(value, java_format)
39204                self.write_keyword("DATE_FORMAT");
39205                self.write("(");
39206                self.generate_expression(&e.this)?;
39207                self.write(", '");
39208                self.write(&Self::strftime_to_java_format(&e.format));
39209                self.write("'");
39210                self.write(")");
39211            }
39212            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
39213                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
39214                self.write_keyword("DATE_FORMAT");
39215                self.write("(");
39216                self.generate_expression(&e.this)?;
39217                self.write(", '");
39218                self.write(&e.format);
39219                self.write("'");
39220                self.write(")");
39221            }
39222            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
39223                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
39224                self.write_keyword("DATE_FORMAT");
39225                self.write("(");
39226                self.generate_expression(&e.this)?;
39227                self.write(", '");
39228                self.write(&e.format);
39229                self.write("'");
39230                self.write(")");
39231            }
39232            _ => {
39233                // Default: TIME_TO_STR(this, format)
39234                self.write_keyword("TIME_TO_STR");
39235                self.write("(");
39236                self.generate_expression(&e.this)?;
39237                self.write(", '");
39238                self.write(&e.format);
39239                self.write("'");
39240                self.write(")");
39241            }
39242        }
39243        Ok(())
39244    }
39245
39246    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
39247        match self.config.dialect {
39248            Some(DialectType::DuckDB) => {
39249                // DuckDB: EPOCH(x)
39250                self.write_keyword("EPOCH");
39251                self.write("(");
39252                self.generate_expression(&e.this)?;
39253                self.write(")");
39254            }
39255            Some(DialectType::Hive)
39256            | Some(DialectType::Spark)
39257            | Some(DialectType::Databricks)
39258            | Some(DialectType::Doris)
39259            | Some(DialectType::StarRocks)
39260            | Some(DialectType::Drill) => {
39261                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
39262                self.write_keyword("UNIX_TIMESTAMP");
39263                self.write("(");
39264                self.generate_expression(&e.this)?;
39265                self.write(")");
39266            }
39267            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39268                // Presto: TO_UNIXTIME(x)
39269                self.write_keyword("TO_UNIXTIME");
39270                self.write("(");
39271                self.generate_expression(&e.this)?;
39272                self.write(")");
39273            }
39274            _ => {
39275                // Default: TIME_TO_UNIX(x)
39276                self.write_keyword("TIME_TO_UNIX");
39277                self.write("(");
39278                self.generate_expression(&e.this)?;
39279                self.write(")");
39280            }
39281        }
39282        Ok(())
39283    }
39284
39285    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
39286        match self.config.dialect {
39287            Some(DialectType::Hive) => {
39288                // Hive: TO_DATE(x)
39289                self.write_keyword("TO_DATE");
39290                self.write("(");
39291                self.generate_expression(&e.this)?;
39292                self.write(")");
39293            }
39294            _ => {
39295                // Default: TIME_STR_TO_DATE(x)
39296                self.write_keyword("TIME_STR_TO_DATE");
39297                self.write("(");
39298                self.generate_expression(&e.this)?;
39299                self.write(")");
39300            }
39301        }
39302        Ok(())
39303    }
39304
39305    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
39306        // TIME_TRUNC(this, unit)
39307        self.write_keyword("TIME_TRUNC");
39308        self.write("(");
39309        self.generate_expression(&e.this)?;
39310        self.write(", ");
39311        self.write_keyword(&e.unit);
39312        self.write(")");
39313        Ok(())
39314    }
39315
39316    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
39317        // Just output the unit name
39318        if let Some(unit) = &e.unit {
39319            self.write_keyword(unit);
39320        }
39321        Ok(())
39322    }
39323
39324    /// Generate a Timestamp function expression
39325    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
39326    /// For other dialects: TIMESTAMP('value')
39327    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
39328        use crate::dialects::DialectType;
39329        use crate::expressions::Literal;
39330
39331        match self.config.dialect {
39332            // Exasol uses TO_TIMESTAMP for Timestamp expressions
39333            Some(DialectType::Exasol) => {
39334                self.write_keyword("TO_TIMESTAMP");
39335                self.write("(");
39336                // Extract the string value from the expression if it's a string literal
39337                if let Some(this) = &e.this {
39338                    match this.as_ref() {
39339                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
39340                            let Literal::String(s) = lit.as_ref() else {
39341                                unreachable!()
39342                            };
39343                            self.write("'");
39344                            self.write(s);
39345                            self.write("'");
39346                        }
39347                        _ => {
39348                            self.generate_expression(this)?;
39349                        }
39350                    }
39351                }
39352                self.write(")");
39353            }
39354            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
39355            _ => {
39356                self.write_keyword("TIMESTAMP");
39357                self.write("(");
39358                if let Some(this) = &e.this {
39359                    self.generate_expression(this)?;
39360                }
39361                if let Some(zone) = &e.zone {
39362                    self.write(", ");
39363                    self.generate_expression(zone)?;
39364                }
39365                self.write(")");
39366            }
39367        }
39368        Ok(())
39369    }
39370
39371    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
39372        // TIMESTAMP_ADD(this, expression, unit)
39373        self.write_keyword("TIMESTAMP_ADD");
39374        self.write("(");
39375        self.generate_expression(&e.this)?;
39376        self.write(", ");
39377        self.generate_expression(&e.expression)?;
39378        if let Some(unit) = &e.unit {
39379            self.write(", ");
39380            self.write_keyword(unit);
39381        }
39382        self.write(")");
39383        Ok(())
39384    }
39385
39386    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
39387        // TIMESTAMP_DIFF(this, expression, unit)
39388        self.write_keyword("TIMESTAMP_DIFF");
39389        self.write("(");
39390        self.generate_expression(&e.this)?;
39391        self.write(", ");
39392        self.generate_expression(&e.expression)?;
39393        if let Some(unit) = &e.unit {
39394            self.write(", ");
39395            self.write_keyword(unit);
39396        }
39397        self.write(")");
39398        Ok(())
39399    }
39400
39401    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
39402        // TIMESTAMP_FROM_PARTS(this, expression)
39403        self.write_keyword("TIMESTAMP_FROM_PARTS");
39404        self.write("(");
39405        if let Some(this) = &e.this {
39406            self.generate_expression(this)?;
39407        }
39408        if let Some(expression) = &e.expression {
39409            self.write(", ");
39410            self.generate_expression(expression)?;
39411        }
39412        if let Some(zone) = &e.zone {
39413            self.write(", ");
39414            self.generate_expression(zone)?;
39415        }
39416        if let Some(milli) = &e.milli {
39417            self.write(", ");
39418            self.generate_expression(milli)?;
39419        }
39420        self.write(")");
39421        Ok(())
39422    }
39423
39424    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
39425        // TIMESTAMP_SUB(this, INTERVAL expression unit)
39426        self.write_keyword("TIMESTAMP_SUB");
39427        self.write("(");
39428        self.generate_expression(&e.this)?;
39429        self.write(", ");
39430        self.write_keyword("INTERVAL");
39431        self.write_space();
39432        self.generate_expression(&e.expression)?;
39433        if let Some(unit) = &e.unit {
39434            self.write_space();
39435            self.write_keyword(unit);
39436        }
39437        self.write(")");
39438        Ok(())
39439    }
39440
39441    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
39442        // TIMESTAMP_TZ_FROM_PARTS(...)
39443        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
39444        self.write("(");
39445        if let Some(zone) = &e.zone {
39446            self.generate_expression(zone)?;
39447        }
39448        self.write(")");
39449        Ok(())
39450    }
39451
39452    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
39453        // TO_BINARY(this, [format])
39454        self.write_keyword("TO_BINARY");
39455        self.write("(");
39456        self.generate_expression(&e.this)?;
39457        if let Some(format) = &e.format {
39458            self.write(", '");
39459            self.write(format);
39460            self.write("'");
39461        }
39462        self.write(")");
39463        Ok(())
39464    }
39465
39466    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
39467        // TO_BOOLEAN(this)
39468        self.write_keyword("TO_BOOLEAN");
39469        self.write("(");
39470        self.generate_expression(&e.this)?;
39471        self.write(")");
39472        Ok(())
39473    }
39474
39475    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
39476        // TO_CHAR(this, [format], [nlsparam])
39477        self.write_keyword("TO_CHAR");
39478        self.write("(");
39479        self.generate_expression(&e.this)?;
39480        if let Some(format) = &e.format {
39481            self.write(", '");
39482            self.write(format);
39483            self.write("'");
39484        }
39485        if let Some(nlsparam) = &e.nlsparam {
39486            self.write(", ");
39487            self.generate_expression(nlsparam)?;
39488        }
39489        self.write(")");
39490        Ok(())
39491    }
39492
39493    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
39494        // TO_DECFLOAT(this, [format])
39495        self.write_keyword("TO_DECFLOAT");
39496        self.write("(");
39497        self.generate_expression(&e.this)?;
39498        if let Some(format) = &e.format {
39499            self.write(", '");
39500            self.write(format);
39501            self.write("'");
39502        }
39503        self.write(")");
39504        Ok(())
39505    }
39506
39507    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
39508        // TO_DOUBLE(this, [format])
39509        self.write_keyword("TO_DOUBLE");
39510        self.write("(");
39511        self.generate_expression(&e.this)?;
39512        if let Some(format) = &e.format {
39513            self.write(", '");
39514            self.write(format);
39515            self.write("'");
39516        }
39517        self.write(")");
39518        Ok(())
39519    }
39520
39521    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
39522        // TO_FILE(this, path)
39523        self.write_keyword("TO_FILE");
39524        self.write("(");
39525        self.generate_expression(&e.this)?;
39526        if let Some(path) = &e.path {
39527            self.write(", ");
39528            self.generate_expression(path)?;
39529        }
39530        self.write(")");
39531        Ok(())
39532    }
39533
39534    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
39535        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
39536        // If safe flag is set, output TRY_TO_NUMBER
39537        let is_safe = e.safe.is_some();
39538        if is_safe {
39539            self.write_keyword("TRY_TO_NUMBER");
39540        } else {
39541            self.write_keyword("TO_NUMBER");
39542        }
39543        self.write("(");
39544        self.generate_expression(&e.this)?;
39545        let precision_is_snowflake_default = e.precision.is_none()
39546            || matches!(
39547                e.precision.as_deref(),
39548                Some(Expression::Literal(lit))
39549                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
39550            );
39551        let is_snowflake_default_precision =
39552            matches!(self.config.dialect, Some(DialectType::Snowflake))
39553                && e.nlsparam.is_none()
39554                && e.scale.is_none()
39555                && matches!(
39556                    e.format.as_deref(),
39557                    Some(Expression::Literal(lit))
39558                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
39559                )
39560                && precision_is_snowflake_default;
39561
39562        if !is_snowflake_default_precision {
39563            if let Some(format) = &e.format {
39564                self.write(", ");
39565                self.generate_expression(format)?;
39566            }
39567            if let Some(nlsparam) = &e.nlsparam {
39568                self.write(", ");
39569                self.generate_expression(nlsparam)?;
39570            }
39571            if let Some(precision) = &e.precision {
39572                self.write(", ");
39573                self.generate_expression(precision)?;
39574            }
39575            if let Some(scale) = &e.scale {
39576                self.write(", ");
39577                self.generate_expression(scale)?;
39578            }
39579        }
39580        self.write(")");
39581        Ok(())
39582    }
39583
39584    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
39585        // TO_TABLE this
39586        self.write_keyword("TO_TABLE");
39587        self.write_space();
39588        self.generate_expression(&e.this)?;
39589        Ok(())
39590    }
39591
39592    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
39593        // Check mark to determine the format
39594        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
39595            Expression::Identifier(id) => id.name.clone(),
39596            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
39597                let Literal::String(s) = lit.as_ref() else {
39598                    unreachable!()
39599                };
39600                s.clone()
39601            }
39602            _ => String::new(),
39603        });
39604
39605        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
39606        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
39607        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
39608            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
39609        });
39610
39611        // For Presto/Trino: always use START TRANSACTION
39612        let use_start_transaction = matches!(
39613            self.config.dialect,
39614            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
39615        );
39616        // For most dialects: strip TRANSACTION keyword
39617        let strip_transaction = matches!(
39618            self.config.dialect,
39619            Some(DialectType::Snowflake)
39620                | Some(DialectType::PostgreSQL)
39621                | Some(DialectType::Redshift)
39622                | Some(DialectType::MySQL)
39623                | Some(DialectType::Hive)
39624                | Some(DialectType::Spark)
39625                | Some(DialectType::Databricks)
39626                | Some(DialectType::DuckDB)
39627                | Some(DialectType::Oracle)
39628                | Some(DialectType::Doris)
39629                | Some(DialectType::StarRocks)
39630                | Some(DialectType::Materialize)
39631                | Some(DialectType::ClickHouse)
39632        );
39633
39634        if is_start || use_start_transaction {
39635            // START TRANSACTION [modes]
39636            self.write_keyword("START TRANSACTION");
39637            if let Some(modes) = &e.modes {
39638                self.write_space();
39639                self.generate_expression(modes)?;
39640            }
39641        } else {
39642            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
39643            self.write_keyword("BEGIN");
39644
39645            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
39646            let is_kind = e.this.as_ref().map_or(false, |t| {
39647                if let Expression::Identifier(id) = t.as_ref() {
39648                    id.name.eq_ignore_ascii_case("DEFERRED")
39649                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
39650                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
39651                } else {
39652                    false
39653                }
39654            });
39655
39656            // Output kind before TRANSACTION keyword
39657            if is_kind {
39658                if let Some(this) = &e.this {
39659                    self.write_space();
39660                    if let Expression::Identifier(id) = this.as_ref() {
39661                        self.write_keyword(&id.name);
39662                    }
39663                }
39664            }
39665
39666            // Output TRANSACTION keyword if it was present and target supports it
39667            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
39668                self.write_space();
39669                self.write_keyword("TRANSACTION");
39670            }
39671
39672            // Output transaction name (not kind)
39673            if !is_kind {
39674                if let Some(this) = &e.this {
39675                    self.write_space();
39676                    self.generate_expression(this)?;
39677                }
39678            }
39679
39680            // Output WITH MARK 'description' for TSQL
39681            if has_with_mark {
39682                self.write_space();
39683                self.write_keyword("WITH MARK");
39684                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
39685                    if let Literal::String(desc) = lit.as_ref() {
39686                        if !desc.is_empty() {
39687                            self.write_space();
39688                            self.write(&format!("'{}'", desc));
39689                        }
39690                    }
39691                }
39692            }
39693
39694            // Output modes (isolation levels, etc.)
39695            if let Some(modes) = &e.modes {
39696                self.write_space();
39697                self.generate_expression(modes)?;
39698            }
39699        }
39700        Ok(())
39701    }
39702
39703    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
39704        // TRANSFORM(this, expression)
39705        self.write_keyword("TRANSFORM");
39706        self.write("(");
39707        self.generate_expression(&e.this)?;
39708        self.write(", ");
39709        self.generate_expression(&e.expression)?;
39710        self.write(")");
39711        Ok(())
39712    }
39713
39714    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
39715        // TRANSFORM(expressions)
39716        self.write_keyword("TRANSFORM");
39717        self.write("(");
39718        if self.config.pretty && !e.expressions.is_empty() {
39719            self.indent_level += 1;
39720            for (i, expr) in e.expressions.iter().enumerate() {
39721                if i > 0 {
39722                    self.write(",");
39723                }
39724                self.write_newline();
39725                self.write_indent();
39726                self.generate_expression(expr)?;
39727            }
39728            self.indent_level -= 1;
39729            self.write_newline();
39730            self.write(")");
39731        } else {
39732            for (i, expr) in e.expressions.iter().enumerate() {
39733                if i > 0 {
39734                    self.write(", ");
39735                }
39736                self.generate_expression(expr)?;
39737            }
39738            self.write(")");
39739        }
39740        Ok(())
39741    }
39742
39743    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
39744        use crate::dialects::DialectType;
39745        // TRANSIENT is Snowflake-specific; skip for other dialects
39746        if let Some(this) = &e.this {
39747            self.generate_expression(this)?;
39748            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
39749                self.write_space();
39750            }
39751        }
39752        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
39753            self.write_keyword("TRANSIENT");
39754        }
39755        Ok(())
39756    }
39757
39758    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
39759        // TRANSLATE(this, from_, to)
39760        self.write_keyword("TRANSLATE");
39761        self.write("(");
39762        self.generate_expression(&e.this)?;
39763        if let Some(from) = &e.from_ {
39764            self.write(", ");
39765            self.generate_expression(from)?;
39766        }
39767        if let Some(to) = &e.to {
39768            self.write(", ");
39769            self.generate_expression(to)?;
39770        }
39771        self.write(")");
39772        Ok(())
39773    }
39774
39775    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
39776        // TRANSLATE(this USING expression)
39777        self.write_keyword("TRANSLATE");
39778        self.write("(");
39779        self.generate_expression(&e.this)?;
39780        self.write_space();
39781        self.write_keyword("USING");
39782        self.write_space();
39783        self.generate_expression(&e.expression)?;
39784        if e.with_error.is_some() {
39785            self.write_space();
39786            self.write_keyword("WITH ERROR");
39787        }
39788        self.write(")");
39789        Ok(())
39790    }
39791
39792    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
39793        // TRUNCATE TABLE table1, table2, ...
39794        self.write_keyword("TRUNCATE TABLE");
39795        self.write_space();
39796        for (i, expr) in e.expressions.iter().enumerate() {
39797            if i > 0 {
39798                self.write(", ");
39799            }
39800            self.generate_expression(expr)?;
39801        }
39802        Ok(())
39803    }
39804
39805    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
39806        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
39807        self.write_keyword("TRY_BASE64_DECODE_BINARY");
39808        self.write("(");
39809        self.generate_expression(&e.this)?;
39810        if let Some(alphabet) = &e.alphabet {
39811            self.write(", ");
39812            self.generate_expression(alphabet)?;
39813        }
39814        self.write(")");
39815        Ok(())
39816    }
39817
39818    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
39819        // TRY_BASE64_DECODE_STRING(this, [alphabet])
39820        self.write_keyword("TRY_BASE64_DECODE_STRING");
39821        self.write("(");
39822        self.generate_expression(&e.this)?;
39823        if let Some(alphabet) = &e.alphabet {
39824            self.write(", ");
39825            self.generate_expression(alphabet)?;
39826        }
39827        self.write(")");
39828        Ok(())
39829    }
39830
39831    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
39832        // TRY_TO_DECFLOAT(this, [format])
39833        self.write_keyword("TRY_TO_DECFLOAT");
39834        self.write("(");
39835        self.generate_expression(&e.this)?;
39836        if let Some(format) = &e.format {
39837            self.write(", '");
39838            self.write(format);
39839            self.write("'");
39840        }
39841        self.write(")");
39842        Ok(())
39843    }
39844
39845    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
39846        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
39847        self.write_keyword("TS_OR_DS_ADD");
39848        self.write("(");
39849        self.generate_expression(&e.this)?;
39850        self.write(", ");
39851        self.generate_expression(&e.expression)?;
39852        if let Some(unit) = &e.unit {
39853            self.write(", ");
39854            self.write_keyword(unit);
39855        }
39856        if let Some(return_type) = &e.return_type {
39857            self.write(", ");
39858            self.generate_expression(return_type)?;
39859        }
39860        self.write(")");
39861        Ok(())
39862    }
39863
39864    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
39865        // TS_OR_DS_DIFF(this, expression, [unit])
39866        self.write_keyword("TS_OR_DS_DIFF");
39867        self.write("(");
39868        self.generate_expression(&e.this)?;
39869        self.write(", ");
39870        self.generate_expression(&e.expression)?;
39871        if let Some(unit) = &e.unit {
39872            self.write(", ");
39873            self.write_keyword(unit);
39874        }
39875        self.write(")");
39876        Ok(())
39877    }
39878
39879    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
39880        let default_time_format = "%Y-%m-%d %H:%M:%S";
39881        let default_date_format = "%Y-%m-%d";
39882        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
39883            f != default_time_format && f != default_date_format
39884        });
39885
39886        if has_non_default_format {
39887            // With non-default format: dialect-specific handling
39888            let fmt = e.format.as_ref().unwrap();
39889            match self.config.dialect {
39890                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
39891                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
39892                    // STR_TO_DATE is the MySQL-native form of StrToTime
39893                    let str_to_time = crate::expressions::StrToTime {
39894                        this: Box::new((*e.this).clone()),
39895                        format: fmt.clone(),
39896                        zone: None,
39897                        safe: None,
39898                        target_type: None,
39899                    };
39900                    self.generate_str_to_time(&str_to_time)?;
39901                }
39902                Some(DialectType::Hive)
39903                | Some(DialectType::Spark)
39904                | Some(DialectType::Databricks) => {
39905                    // Hive/Spark: TO_DATE(x, java_fmt)
39906                    self.write_keyword("TO_DATE");
39907                    self.write("(");
39908                    self.generate_expression(&e.this)?;
39909                    self.write(", '");
39910                    self.write(&Self::strftime_to_java_format(fmt));
39911                    self.write("')");
39912                }
39913                Some(DialectType::Snowflake) => {
39914                    // Snowflake: TO_DATE(x, snowflake_fmt)
39915                    self.write_keyword("TO_DATE");
39916                    self.write("(");
39917                    self.generate_expression(&e.this)?;
39918                    self.write(", '");
39919                    self.write(&Self::strftime_to_snowflake_format(fmt));
39920                    self.write("')");
39921                }
39922                Some(DialectType::Doris) => {
39923                    // Doris: TO_DATE(x) - ignores format
39924                    self.write_keyword("TO_DATE");
39925                    self.write("(");
39926                    self.generate_expression(&e.this)?;
39927                    self.write(")");
39928                }
39929                _ => {
39930                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
39931                    self.write_keyword("CAST");
39932                    self.write("(");
39933                    let str_to_time = crate::expressions::StrToTime {
39934                        this: Box::new((*e.this).clone()),
39935                        format: fmt.clone(),
39936                        zone: None,
39937                        safe: None,
39938                        target_type: None,
39939                    };
39940                    self.generate_str_to_time(&str_to_time)?;
39941                    self.write_keyword(" AS ");
39942                    self.write_keyword("DATE");
39943                    self.write(")");
39944                }
39945            }
39946        } else {
39947            // Without format (or default format): simple date conversion
39948            match self.config.dialect {
39949                Some(DialectType::MySQL)
39950                | Some(DialectType::SQLite)
39951                | Some(DialectType::StarRocks) => {
39952                    // MySQL/SQLite/StarRocks: DATE(x)
39953                    self.write_keyword("DATE");
39954                    self.write("(");
39955                    self.generate_expression(&e.this)?;
39956                    self.write(")");
39957                }
39958                Some(DialectType::Hive)
39959                | Some(DialectType::Spark)
39960                | Some(DialectType::Databricks)
39961                | Some(DialectType::Snowflake)
39962                | Some(DialectType::Doris) => {
39963                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
39964                    self.write_keyword("TO_DATE");
39965                    self.write("(");
39966                    self.generate_expression(&e.this)?;
39967                    self.write(")");
39968                }
39969                Some(DialectType::Presto)
39970                | Some(DialectType::Trino)
39971                | Some(DialectType::Athena) => {
39972                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
39973                    self.write_keyword("CAST");
39974                    self.write("(");
39975                    self.write_keyword("CAST");
39976                    self.write("(");
39977                    self.generate_expression(&e.this)?;
39978                    self.write_keyword(" AS ");
39979                    self.write_keyword("TIMESTAMP");
39980                    self.write(")");
39981                    self.write_keyword(" AS ");
39982                    self.write_keyword("DATE");
39983                    self.write(")");
39984                }
39985                Some(DialectType::ClickHouse) => {
39986                    // ClickHouse: CAST(x AS Nullable(DATE))
39987                    self.write_keyword("CAST");
39988                    self.write("(");
39989                    self.generate_expression(&e.this)?;
39990                    self.write_keyword(" AS ");
39991                    self.write("Nullable(DATE)");
39992                    self.write(")");
39993                }
39994                _ => {
39995                    // Default: CAST(x AS DATE)
39996                    self.write_keyword("CAST");
39997                    self.write("(");
39998                    self.generate_expression(&e.this)?;
39999                    self.write_keyword(" AS ");
40000                    self.write_keyword("DATE");
40001                    self.write(")");
40002                }
40003            }
40004        }
40005        Ok(())
40006    }
40007
40008    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
40009        // TS_OR_DS_TO_TIME(this, [format])
40010        self.write_keyword("TS_OR_DS_TO_TIME");
40011        self.write("(");
40012        self.generate_expression(&e.this)?;
40013        if let Some(format) = &e.format {
40014            self.write(", '");
40015            self.write(format);
40016            self.write("'");
40017        }
40018        self.write(")");
40019        Ok(())
40020    }
40021
40022    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
40023        // UNHEX(this, [expression])
40024        self.write_keyword("UNHEX");
40025        self.write("(");
40026        self.generate_expression(&e.this)?;
40027        if let Some(expression) = &e.expression {
40028            self.write(", ");
40029            self.generate_expression(expression)?;
40030        }
40031        self.write(")");
40032        Ok(())
40033    }
40034
40035    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
40036        // U&this [UESCAPE escape]
40037        self.write("U&");
40038        self.generate_expression(&e.this)?;
40039        if let Some(escape) = &e.escape {
40040            self.write_space();
40041            self.write_keyword("UESCAPE");
40042            self.write_space();
40043            self.generate_expression(escape)?;
40044        }
40045        Ok(())
40046    }
40047
40048    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
40049        // UNIFORM(this, expression, [gen], [seed])
40050        self.write_keyword("UNIFORM");
40051        self.write("(");
40052        self.generate_expression(&e.this)?;
40053        self.write(", ");
40054        self.generate_expression(&e.expression)?;
40055        if let Some(gen) = &e.gen {
40056            self.write(", ");
40057            self.generate_expression(gen)?;
40058        }
40059        if let Some(seed) = &e.seed {
40060            self.write(", ");
40061            self.generate_expression(seed)?;
40062        }
40063        self.write(")");
40064        Ok(())
40065    }
40066
40067    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
40068        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
40069        self.write_keyword("UNIQUE");
40070        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
40071        if e.nulls.is_some() {
40072            self.write(" NULLS NOT DISTINCT");
40073        }
40074        if let Some(this) = &e.this {
40075            self.write_space();
40076            self.generate_expression(this)?;
40077        }
40078        if let Some(index_type) = &e.index_type {
40079            self.write(" USING ");
40080            self.generate_expression(index_type)?;
40081        }
40082        if let Some(on_conflict) = &e.on_conflict {
40083            self.write_space();
40084            self.generate_expression(on_conflict)?;
40085        }
40086        for opt in &e.options {
40087            self.write_space();
40088            self.generate_expression(opt)?;
40089        }
40090        Ok(())
40091    }
40092
40093    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
40094        // UNIQUE KEY (expressions)
40095        self.write_keyword("UNIQUE KEY");
40096        self.write(" (");
40097        for (i, expr) in e.expressions.iter().enumerate() {
40098            if i > 0 {
40099                self.write(", ");
40100            }
40101            self.generate_expression(expr)?;
40102        }
40103        self.write(")");
40104        Ok(())
40105    }
40106
40107    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
40108        // ROLLUP (r1(col1, col2), r2(col1))
40109        self.write_keyword("ROLLUP");
40110        self.write(" (");
40111        for (i, index) in e.expressions.iter().enumerate() {
40112            if i > 0 {
40113                self.write(", ");
40114            }
40115            self.generate_identifier(&index.name)?;
40116            self.write("(");
40117            for (j, col) in index.expressions.iter().enumerate() {
40118                if j > 0 {
40119                    self.write(", ");
40120                }
40121                self.generate_identifier(col)?;
40122            }
40123            self.write(")");
40124        }
40125        self.write(")");
40126        Ok(())
40127    }
40128
40129    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
40130        match self.config.dialect {
40131            Some(DialectType::DuckDB) => {
40132                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
40133                self.write_keyword("STRFTIME");
40134                self.write("(");
40135                self.write_keyword("TO_TIMESTAMP");
40136                self.write("(");
40137                self.generate_expression(&e.this)?;
40138                self.write("), '");
40139                if let Some(format) = &e.format {
40140                    self.write(format);
40141                }
40142                self.write("')");
40143            }
40144            Some(DialectType::Hive) => {
40145                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
40146                self.write_keyword("FROM_UNIXTIME");
40147                self.write("(");
40148                self.generate_expression(&e.this)?;
40149                if let Some(format) = &e.format {
40150                    if format != "yyyy-MM-dd HH:mm:ss" {
40151                        self.write(", '");
40152                        self.write(format);
40153                        self.write("'");
40154                    }
40155                }
40156                self.write(")");
40157            }
40158            Some(DialectType::Presto) | Some(DialectType::Trino) => {
40159                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
40160                self.write_keyword("DATE_FORMAT");
40161                self.write("(");
40162                self.write_keyword("FROM_UNIXTIME");
40163                self.write("(");
40164                self.generate_expression(&e.this)?;
40165                self.write("), '");
40166                if let Some(format) = &e.format {
40167                    self.write(format);
40168                }
40169                self.write("')");
40170            }
40171            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
40172                // Spark: FROM_UNIXTIME(value, format)
40173                self.write_keyword("FROM_UNIXTIME");
40174                self.write("(");
40175                self.generate_expression(&e.this)?;
40176                if let Some(format) = &e.format {
40177                    self.write(", '");
40178                    self.write(format);
40179                    self.write("'");
40180                }
40181                self.write(")");
40182            }
40183            _ => {
40184                // Default: UNIX_TO_STR(this, [format])
40185                self.write_keyword("UNIX_TO_STR");
40186                self.write("(");
40187                self.generate_expression(&e.this)?;
40188                if let Some(format) = &e.format {
40189                    self.write(", '");
40190                    self.write(format);
40191                    self.write("'");
40192                }
40193                self.write(")");
40194            }
40195        }
40196        Ok(())
40197    }
40198
40199    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
40200        use crate::dialects::DialectType;
40201        let scale = e.scale.unwrap_or(0); // 0 = seconds
40202
40203        match self.config.dialect {
40204            Some(DialectType::Snowflake) => {
40205                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
40206                self.write_keyword("TO_TIMESTAMP");
40207                self.write("(");
40208                self.generate_expression(&e.this)?;
40209                if let Some(s) = e.scale {
40210                    if s > 0 {
40211                        self.write(", ");
40212                        self.write(&s.to_string());
40213                    }
40214                }
40215                self.write(")");
40216            }
40217            Some(DialectType::BigQuery) => {
40218                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
40219                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
40220                match scale {
40221                    0 => {
40222                        self.write_keyword("TIMESTAMP_SECONDS");
40223                        self.write("(");
40224                        self.generate_expression(&e.this)?;
40225                        self.write(")");
40226                    }
40227                    3 => {
40228                        self.write_keyword("TIMESTAMP_MILLIS");
40229                        self.write("(");
40230                        self.generate_expression(&e.this)?;
40231                        self.write(")");
40232                    }
40233                    6 => {
40234                        self.write_keyword("TIMESTAMP_MICROS");
40235                        self.write("(");
40236                        self.generate_expression(&e.this)?;
40237                        self.write(")");
40238                    }
40239                    _ => {
40240                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
40241                        self.write_keyword("TIMESTAMP_SECONDS");
40242                        self.write("(CAST(");
40243                        self.generate_expression(&e.this)?;
40244                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
40245                    }
40246                }
40247            }
40248            Some(DialectType::Spark) => {
40249                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
40250                // TIMESTAMP_MILLIS(value) for scale=3
40251                // TIMESTAMP_MICROS(value) for scale=6
40252                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
40253                match scale {
40254                    0 => {
40255                        self.write_keyword("CAST");
40256                        self.write("(");
40257                        self.write_keyword("FROM_UNIXTIME");
40258                        self.write("(");
40259                        self.generate_expression(&e.this)?;
40260                        self.write(") ");
40261                        self.write_keyword("AS TIMESTAMP");
40262                        self.write(")");
40263                    }
40264                    3 => {
40265                        self.write_keyword("TIMESTAMP_MILLIS");
40266                        self.write("(");
40267                        self.generate_expression(&e.this)?;
40268                        self.write(")");
40269                    }
40270                    6 => {
40271                        self.write_keyword("TIMESTAMP_MICROS");
40272                        self.write("(");
40273                        self.generate_expression(&e.this)?;
40274                        self.write(")");
40275                    }
40276                    _ => {
40277                        self.write_keyword("TIMESTAMP_SECONDS");
40278                        self.write("(");
40279                        self.generate_expression(&e.this)?;
40280                        self.write(&format!(" / POWER(10, {}))", scale));
40281                    }
40282                }
40283            }
40284            Some(DialectType::Databricks) => {
40285                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
40286                // TIMESTAMP_MILLIS(value) for scale=3
40287                // TIMESTAMP_MICROS(value) for scale=6
40288                match scale {
40289                    0 => {
40290                        self.write_keyword("CAST");
40291                        self.write("(");
40292                        self.write_keyword("FROM_UNIXTIME");
40293                        self.write("(");
40294                        self.generate_expression(&e.this)?;
40295                        self.write(") ");
40296                        self.write_keyword("AS TIMESTAMP");
40297                        self.write(")");
40298                    }
40299                    3 => {
40300                        self.write_keyword("TIMESTAMP_MILLIS");
40301                        self.write("(");
40302                        self.generate_expression(&e.this)?;
40303                        self.write(")");
40304                    }
40305                    6 => {
40306                        self.write_keyword("TIMESTAMP_MICROS");
40307                        self.write("(");
40308                        self.generate_expression(&e.this)?;
40309                        self.write(")");
40310                    }
40311                    _ => {
40312                        self.write_keyword("TIMESTAMP_SECONDS");
40313                        self.write("(");
40314                        self.generate_expression(&e.this)?;
40315                        self.write(&format!(" / POWER(10, {}))", scale));
40316                    }
40317                }
40318            }
40319            Some(DialectType::Hive) => {
40320                // Hive: FROM_UNIXTIME(value)
40321                if scale == 0 {
40322                    self.write_keyword("FROM_UNIXTIME");
40323                    self.write("(");
40324                    self.generate_expression(&e.this)?;
40325                    self.write(")");
40326                } else {
40327                    self.write_keyword("FROM_UNIXTIME");
40328                    self.write("(");
40329                    self.generate_expression(&e.this)?;
40330                    self.write(&format!(" / POWER(10, {})", scale));
40331                    self.write(")");
40332                }
40333            }
40334            Some(DialectType::Presto) | Some(DialectType::Trino) => {
40335                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
40336                // FROM_UNIXTIME(value) for scale=0
40337                if scale == 0 {
40338                    self.write_keyword("FROM_UNIXTIME");
40339                    self.write("(");
40340                    self.generate_expression(&e.this)?;
40341                    self.write(")");
40342                } else {
40343                    self.write_keyword("FROM_UNIXTIME");
40344                    self.write("(CAST(");
40345                    self.generate_expression(&e.this)?;
40346                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
40347                }
40348            }
40349            Some(DialectType::DuckDB) => {
40350                // DuckDB: TO_TIMESTAMP(value) for scale=0
40351                // EPOCH_MS(value) for scale=3
40352                // MAKE_TIMESTAMP(value) for scale=6
40353                match scale {
40354                    0 => {
40355                        self.write_keyword("TO_TIMESTAMP");
40356                        self.write("(");
40357                        self.generate_expression(&e.this)?;
40358                        self.write(")");
40359                    }
40360                    3 => {
40361                        self.write_keyword("EPOCH_MS");
40362                        self.write("(");
40363                        self.generate_expression(&e.this)?;
40364                        self.write(")");
40365                    }
40366                    6 => {
40367                        self.write_keyword("MAKE_TIMESTAMP");
40368                        self.write("(");
40369                        self.generate_expression(&e.this)?;
40370                        self.write(")");
40371                    }
40372                    _ => {
40373                        self.write_keyword("TO_TIMESTAMP");
40374                        self.write("(");
40375                        self.generate_expression(&e.this)?;
40376                        self.write(&format!(" / POWER(10, {}))", scale));
40377                        self.write_keyword(" AT TIME ZONE");
40378                        self.write(" 'UTC'");
40379                    }
40380                }
40381            }
40382            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
40383                // Doris/StarRocks: FROM_UNIXTIME(value)
40384                self.write_keyword("FROM_UNIXTIME");
40385                self.write("(");
40386                self.generate_expression(&e.this)?;
40387                self.write(")");
40388            }
40389            Some(DialectType::Oracle) => {
40390                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
40391                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
40392                self.generate_expression(&e.this)?;
40393                self.write(" / 86400)");
40394            }
40395            Some(DialectType::Redshift) => {
40396                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
40397                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
40398                self.write("(TIMESTAMP 'epoch' + ");
40399                if scale == 0 {
40400                    self.generate_expression(&e.this)?;
40401                } else {
40402                    self.write("(");
40403                    self.generate_expression(&e.this)?;
40404                    self.write(&format!(" / POWER(10, {}))", scale));
40405                }
40406                self.write(" * INTERVAL '1 SECOND')");
40407            }
40408            Some(DialectType::Exasol) => {
40409                // Exasol: FROM_POSIX_TIME(value)
40410                self.write_keyword("FROM_POSIX_TIME");
40411                self.write("(");
40412                self.generate_expression(&e.this)?;
40413                self.write(")");
40414            }
40415            _ => {
40416                // Default: TO_TIMESTAMP(value[, scale])
40417                self.write_keyword("TO_TIMESTAMP");
40418                self.write("(");
40419                self.generate_expression(&e.this)?;
40420                if let Some(s) = e.scale {
40421                    self.write(", ");
40422                    self.write(&s.to_string());
40423                }
40424                self.write(")");
40425            }
40426        }
40427        Ok(())
40428    }
40429
40430    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
40431        // NAME col VALUE col1, col2, ...
40432        if !matches!(&*e.this, Expression::Null(_)) {
40433            self.write_keyword("NAME");
40434            self.write_space();
40435            self.generate_expression(&e.this)?;
40436        }
40437        if !e.expressions.is_empty() {
40438            self.write_space();
40439            self.write_keyword("VALUE");
40440            self.write_space();
40441            for (i, expr) in e.expressions.iter().enumerate() {
40442                if i > 0 {
40443                    self.write(", ");
40444                }
40445                self.generate_expression(expr)?;
40446            }
40447        }
40448        Ok(())
40449    }
40450
40451    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
40452        // this(expressions) or (this)(expressions)
40453        if e.wrapped.is_some() {
40454            self.write("(");
40455        }
40456        self.generate_expression(&e.this)?;
40457        if e.wrapped.is_some() {
40458            self.write(")");
40459        }
40460        self.write("(");
40461        for (i, expr) in e.expressions.iter().enumerate() {
40462            if i > 0 {
40463                self.write(", ");
40464            }
40465            self.generate_expression(expr)?;
40466        }
40467        self.write(")");
40468        Ok(())
40469    }
40470
40471    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
40472        // USING TEMPLATE this
40473        self.write_keyword("USING TEMPLATE");
40474        self.write_space();
40475        self.generate_expression(&e.this)?;
40476        Ok(())
40477    }
40478
40479    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
40480        // UTC_TIME
40481        self.write_keyword("UTC_TIME");
40482        Ok(())
40483    }
40484
40485    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
40486        if matches!(
40487            self.config.dialect,
40488            Some(crate::dialects::DialectType::ClickHouse)
40489        ) {
40490            self.write_keyword("CURRENT_TIMESTAMP");
40491            self.write("('UTC')");
40492        } else {
40493            self.write_keyword("UTC_TIMESTAMP");
40494        }
40495        Ok(())
40496    }
40497
40498    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
40499        use crate::dialects::DialectType;
40500        // Choose UUID function name based on target dialect
40501        let func_name = match self.config.dialect {
40502            Some(DialectType::Snowflake) => "UUID_STRING",
40503            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
40504            Some(DialectType::BigQuery) => "GENERATE_UUID",
40505            _ => {
40506                if let Some(name) = &e.name {
40507                    name.as_str()
40508                } else {
40509                    "UUID"
40510                }
40511            }
40512        };
40513        self.write_keyword(func_name);
40514        self.write("(");
40515        if let Some(this) = &e.this {
40516            self.generate_expression(this)?;
40517        }
40518        self.write(")");
40519        Ok(())
40520    }
40521
40522    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
40523        // MAP(key1, value1, key2, value2, ...)
40524        self.write_keyword("MAP");
40525        self.write("(");
40526        let mut first = true;
40527        for (k, v) in e.keys.iter().zip(e.values.iter()) {
40528            if !first {
40529                self.write(", ");
40530            }
40531            self.generate_expression(k)?;
40532            self.write(", ");
40533            self.generate_expression(v)?;
40534            first = false;
40535        }
40536        self.write(")");
40537        Ok(())
40538    }
40539
40540    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
40541        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
40542        self.write_keyword("VECTOR_SEARCH");
40543        self.write("(");
40544        self.generate_expression(&e.this)?;
40545        if let Some(col) = &e.column_to_search {
40546            self.write(", ");
40547            self.generate_expression(col)?;
40548        }
40549        if let Some(query_table) = &e.query_table {
40550            self.write(", ");
40551            self.generate_expression(query_table)?;
40552        }
40553        if let Some(query_col) = &e.query_column_to_search {
40554            self.write(", ");
40555            self.generate_expression(query_col)?;
40556        }
40557        if let Some(top_k) = &e.top_k {
40558            self.write(", ");
40559            self.generate_expression(top_k)?;
40560        }
40561        if let Some(dist_type) = &e.distance_type {
40562            self.write(", ");
40563            self.generate_expression(dist_type)?;
40564        }
40565        self.write(")");
40566        Ok(())
40567    }
40568
40569    fn generate_version(&mut self, e: &Version) -> Result<()> {
40570        // Python: f"FOR {expression.name} {kind} {expr}"
40571        // e.this = Identifier("TIMESTAMP" or "VERSION")
40572        // e.kind = "AS OF" (or "BETWEEN", etc.)
40573        // e.expression = the value expression
40574        // Hive does NOT use the FOR prefix for time travel
40575        use crate::dialects::DialectType;
40576        if matches!(self.config.dialect, Some(DialectType::Dremio)) {
40577            self.write_keyword("AT");
40578            self.write_space();
40579            let name = match e.this.as_ref() {
40580                Expression::Identifier(ident) if ident.name.eq_ignore_ascii_case("VERSION") => {
40581                    "SNAPSHOT"
40582                }
40583                _ => "TIMESTAMP",
40584            };
40585            self.write_keyword(name);
40586            if let Some(expression) = &e.expression {
40587                self.write_space();
40588                self.generate_expression(expression)?;
40589            }
40590            return Ok(());
40591        }
40592        let skip_for = matches!(
40593            self.config.dialect,
40594            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
40595        );
40596        if !skip_for {
40597            self.write_keyword("FOR");
40598            self.write_space();
40599        }
40600        // Extract the name from this (which is an Identifier expression)
40601        match e.this.as_ref() {
40602            Expression::Identifier(ident) => {
40603                self.write_keyword(&ident.name);
40604            }
40605            _ => {
40606                self.generate_expression(&e.this)?;
40607            }
40608        }
40609        self.write_space();
40610        self.write_keyword(&e.kind);
40611        if let Some(expression) = &e.expression {
40612            self.write_space();
40613            self.generate_expression(expression)?;
40614        }
40615        Ok(())
40616    }
40617
40618    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
40619        // Python: return self.sql(expression, "this")
40620        self.generate_expression(&e.this)?;
40621        Ok(())
40622    }
40623
40624    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
40625        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
40626        if e.this.is_some() {
40627            self.write_keyword("NOT VOLATILE");
40628        } else {
40629            self.write_keyword("VOLATILE");
40630        }
40631        Ok(())
40632    }
40633
40634    fn generate_watermark_column_constraint(
40635        &mut self,
40636        e: &WatermarkColumnConstraint,
40637    ) -> Result<()> {
40638        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
40639        self.write_keyword("WATERMARK FOR");
40640        self.write_space();
40641        self.generate_expression(&e.this)?;
40642        self.write_space();
40643        self.write_keyword("AS");
40644        self.write_space();
40645        self.generate_expression(&e.expression)?;
40646        Ok(())
40647    }
40648
40649    fn generate_week(&mut self, e: &Week) -> Result<()> {
40650        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
40651        self.write_keyword("WEEK");
40652        self.write("(");
40653        self.generate_expression(&e.this)?;
40654        if let Some(mode) = &e.mode {
40655            self.write(", ");
40656            self.generate_expression(mode)?;
40657        }
40658        self.write(")");
40659        Ok(())
40660    }
40661
40662    fn generate_when(&mut self, e: &When) -> Result<()> {
40663        // Python: WHEN {matched}{source}{condition} THEN {then}
40664        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
40665        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
40666        self.write_keyword("WHEN");
40667        self.write_space();
40668
40669        // Check if matched
40670        if let Some(matched) = &e.matched {
40671            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
40672            match matched.as_ref() {
40673                Expression::Boolean(b) if b.value => {
40674                    self.write_keyword("MATCHED");
40675                }
40676                _ => {
40677                    self.write_keyword("NOT MATCHED");
40678                }
40679            }
40680        } else {
40681            self.write_keyword("NOT MATCHED");
40682        }
40683
40684        // BY SOURCE / BY TARGET
40685        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
40686        // BY TARGET is the default and typically omitted in output
40687        // Only emit if the dialect supports BY SOURCE syntax
40688        if self.config.matched_by_source {
40689            if let Some(source) = &e.source {
40690                if let Expression::Boolean(b) = source.as_ref() {
40691                    if b.value {
40692                        // BY SOURCE
40693                        self.write_space();
40694                        self.write_keyword("BY SOURCE");
40695                    }
40696                    // BY TARGET (b.value == false) is omitted as it's the default
40697                } else {
40698                    // For non-boolean source, output as BY SOURCE (legacy behavior)
40699                    self.write_space();
40700                    self.write_keyword("BY SOURCE");
40701                }
40702            }
40703        }
40704
40705        // Condition
40706        if let Some(condition) = &e.condition {
40707            self.write_space();
40708            self.write_keyword("AND");
40709            self.write_space();
40710            self.generate_expression(condition)?;
40711        }
40712
40713        self.write_space();
40714        self.write_keyword("THEN");
40715        self.write_space();
40716
40717        // Generate the then expression (could be INSERT, UPDATE, DELETE)
40718        // MERGE actions are stored as Tuples with the action keyword as first element
40719        self.generate_merge_action(&e.then)?;
40720
40721        Ok(())
40722    }
40723
40724    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
40725        match action {
40726            Expression::Tuple(tuple) => {
40727                let elements = &tuple.expressions;
40728                if elements.is_empty() {
40729                    return self.generate_expression(action);
40730                }
40731                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
40732                match &elements[0] {
40733                    Expression::Var(v) if v.this == "INSERT" => {
40734                        self.write_keyword("INSERT");
40735                        // Spark: INSERT * (insert all columns)
40736                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
40737                            self.write(" *");
40738                            if let Some(Expression::Where(w)) = elements.get(2) {
40739                                self.write_space();
40740                                self.generate_where(w)?;
40741                            }
40742                        } else {
40743                            let mut values_idx = 1;
40744                            // Check if second element is column list (Tuple)
40745                            if elements.len() > 1 {
40746                                if let Expression::Tuple(cols) = &elements[1] {
40747                                    // Could be columns or values - if there's a third element, second is columns
40748                                    if elements.len() > 2 {
40749                                        // Second is columns, third is values
40750                                        self.write(" (");
40751                                        for (i, col) in cols.expressions.iter().enumerate() {
40752                                            if i > 0 {
40753                                                self.write(", ");
40754                                            }
40755                                            // Strip MERGE target qualifiers from INSERT column list
40756                                            if !self.merge_strip_qualifiers.is_empty() {
40757                                                let stripped = self.strip_merge_qualifier(col);
40758                                                self.generate_expression(&stripped)?;
40759                                            } else {
40760                                                self.generate_expression(col)?;
40761                                            }
40762                                        }
40763                                        self.write(")");
40764                                        values_idx = 2;
40765                                    } else {
40766                                        // Only two elements: INSERT + values (no explicit columns)
40767                                        values_idx = 1;
40768                                    }
40769                                }
40770                            }
40771                            let mut next_idx = values_idx;
40772                            // Generate VALUES clause
40773                            if values_idx < elements.len()
40774                                && !matches!(&elements[values_idx], Expression::Where(_))
40775                            {
40776                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
40777                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
40778                                if !is_row {
40779                                    self.write_space();
40780                                    self.write_keyword("VALUES");
40781                                }
40782                                self.write(" ");
40783                                if let Expression::Tuple(vals) = &elements[values_idx] {
40784                                    self.write("(");
40785                                    for (i, val) in vals.expressions.iter().enumerate() {
40786                                        if i > 0 {
40787                                            self.write(", ");
40788                                        }
40789                                        self.generate_expression(val)?;
40790                                    }
40791                                    self.write(")");
40792                                } else {
40793                                    self.generate_expression(&elements[values_idx])?;
40794                                }
40795                                next_idx += 1;
40796                            }
40797                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
40798                                self.write_space();
40799                                self.generate_where(w)?;
40800                            }
40801                        } // close else for INSERT * check
40802                    }
40803                    Expression::Var(v) if v.this == "UPDATE" => {
40804                        self.write_keyword("UPDATE");
40805                        // Spark: UPDATE * (update all columns)
40806                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
40807                            self.write(" *");
40808                            if let Some(Expression::Where(w)) = elements.get(2) {
40809                                self.write_space();
40810                                self.generate_where(w)?;
40811                            }
40812                        } else if elements.len() > 1 {
40813                            self.write_space();
40814                            self.write_keyword("SET");
40815                            // In pretty mode, put assignments on next line with extra indent
40816                            if self.config.pretty {
40817                                self.write_newline();
40818                                self.indent_level += 1;
40819                                self.write_indent();
40820                            } else {
40821                                self.write_space();
40822                            }
40823                            if let Expression::Tuple(assignments) = &elements[1] {
40824                                for (i, assignment) in assignments.expressions.iter().enumerate() {
40825                                    if i > 0 {
40826                                        if self.config.pretty {
40827                                            self.write(",");
40828                                            self.write_newline();
40829                                            self.write_indent();
40830                                        } else {
40831                                            self.write(", ");
40832                                        }
40833                                    }
40834                                    // Strip MERGE target qualifiers from left side of UPDATE SET
40835                                    if !self.merge_strip_qualifiers.is_empty() {
40836                                        self.generate_merge_set_assignment(assignment)?;
40837                                    } else {
40838                                        self.generate_expression(assignment)?;
40839                                    }
40840                                }
40841                            } else {
40842                                self.generate_expression(&elements[1])?;
40843                            }
40844                            if self.config.pretty {
40845                                self.indent_level -= 1;
40846                            }
40847                            if let Some(Expression::Where(w)) = elements.get(2) {
40848                                self.write_space();
40849                                self.generate_where(w)?;
40850                            }
40851                        }
40852                    }
40853                    Expression::Var(v) if v.this == "DELETE" => {
40854                        self.write_keyword("DELETE");
40855                        if let Some(Expression::Where(w)) = elements.get(1) {
40856                            self.write_space();
40857                            self.generate_where(w)?;
40858                        }
40859                    }
40860                    _ => {
40861                        // Fallback: generic tuple generation
40862                        self.generate_expression(action)?;
40863                    }
40864                }
40865            }
40866            Expression::Var(v)
40867                if v.this == "INSERT"
40868                    || v.this == "UPDATE"
40869                    || v.this == "DELETE"
40870                    || v.this == "DO NOTHING" =>
40871            {
40872                self.write_keyword(&v.this);
40873            }
40874            _ => {
40875                self.generate_expression(action)?;
40876            }
40877        }
40878        Ok(())
40879    }
40880
40881    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
40882    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
40883        match assignment {
40884            Expression::Eq(eq) => {
40885                // Strip qualifier from the left side if it matches a MERGE target name
40886                let stripped_left = self.strip_merge_qualifier(&eq.left);
40887                self.generate_expression(&stripped_left)?;
40888                self.write(" = ");
40889                self.generate_expression(&eq.right)?;
40890                Ok(())
40891            }
40892            other => self.generate_expression(other),
40893        }
40894    }
40895
40896    /// Strip table qualifier from a column reference if it matches a MERGE target name
40897    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
40898        match expr {
40899            Expression::Column(col) => {
40900                if let Some(ref table_ident) = col.table {
40901                    if self
40902                        .merge_strip_qualifiers
40903                        .iter()
40904                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
40905                    {
40906                        // Strip the table qualifier
40907                        let mut col = col.clone();
40908                        col.table = None;
40909                        return Expression::Column(col);
40910                    }
40911                }
40912                expr.clone()
40913            }
40914            Expression::Dot(dot) => {
40915                // table.column -> column (strip qualifier)
40916                if let Expression::Identifier(id) = &dot.this {
40917                    if self
40918                        .merge_strip_qualifiers
40919                        .iter()
40920                        .any(|n| n.eq_ignore_ascii_case(&id.name))
40921                    {
40922                        return Expression::Identifier(dot.field.clone());
40923                    }
40924                }
40925                expr.clone()
40926            }
40927            _ => expr.clone(),
40928        }
40929    }
40930
40931    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
40932        // Python: return self.expressions(expression, sep=" ", indent=False)
40933        for (i, expr) in e.expressions.iter().enumerate() {
40934            if i > 0 {
40935                // In pretty mode, each WHEN clause on its own line
40936                if self.config.pretty {
40937                    self.write_newline();
40938                    self.write_indent();
40939                } else {
40940                    self.write_space();
40941                }
40942            }
40943            self.generate_expression(expr)?;
40944        }
40945        Ok(())
40946    }
40947
40948    fn generate_where(&mut self, e: &Where) -> Result<()> {
40949        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
40950        self.write_keyword("WHERE");
40951        self.write_space();
40952        self.generate_expression(&e.this)?;
40953        Ok(())
40954    }
40955
40956    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
40957        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
40958        self.write_keyword("WIDTH_BUCKET");
40959        self.write("(");
40960        self.generate_expression(&e.this)?;
40961        if let Some(min_value) = &e.min_value {
40962            self.write(", ");
40963            self.generate_expression(min_value)?;
40964        }
40965        if let Some(max_value) = &e.max_value {
40966            self.write(", ");
40967            self.generate_expression(max_value)?;
40968        }
40969        if let Some(num_buckets) = &e.num_buckets {
40970            self.write(", ");
40971            self.generate_expression(num_buckets)?;
40972        }
40973        self.write(")");
40974        Ok(())
40975    }
40976
40977    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
40978        // Window specification: PARTITION BY ... ORDER BY ... frame
40979        self.generate_window_spec(e)
40980    }
40981
40982    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
40983        // Window specification: PARTITION BY ... ORDER BY ... frame
40984        let mut has_content = false;
40985
40986        // PARTITION BY
40987        if !e.partition_by.is_empty() {
40988            self.write_keyword("PARTITION BY");
40989            self.write_space();
40990            for (i, expr) in e.partition_by.iter().enumerate() {
40991                if i > 0 {
40992                    self.write(", ");
40993                }
40994                self.generate_expression(expr)?;
40995            }
40996            has_content = true;
40997        }
40998
40999        // ORDER BY
41000        if !e.order_by.is_empty() {
41001            if has_content {
41002                self.write_space();
41003            }
41004            self.write_keyword("ORDER BY");
41005            self.write_space();
41006            for (i, ordered) in e.order_by.iter().enumerate() {
41007                if i > 0 {
41008                    self.write(", ");
41009                }
41010                self.generate_expression(&ordered.this)?;
41011                if ordered.desc {
41012                    self.write_space();
41013                    self.write_keyword("DESC");
41014                } else if ordered.explicit_asc {
41015                    self.write_space();
41016                    self.write_keyword("ASC");
41017                }
41018                if let Some(nulls_first) = ordered.nulls_first {
41019                    self.write_space();
41020                    self.write_keyword("NULLS");
41021                    self.write_space();
41022                    if nulls_first {
41023                        self.write_keyword("FIRST");
41024                    } else {
41025                        self.write_keyword("LAST");
41026                    }
41027                }
41028            }
41029            has_content = true;
41030        }
41031
41032        // Frame specification
41033        if let Some(frame) = &e.frame {
41034            if has_content {
41035                self.write_space();
41036            }
41037            self.generate_window_frame(frame)?;
41038        }
41039
41040        Ok(())
41041    }
41042
41043    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
41044        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
41045        self.write_keyword("WITH");
41046        self.write_space();
41047        if e.no.is_some() {
41048            self.write_keyword("NO");
41049            self.write_space();
41050        }
41051        self.write_keyword("DATA");
41052
41053        // statistics
41054        if let Some(statistics) = &e.statistics {
41055            self.write_space();
41056            self.write_keyword("AND");
41057            self.write_space();
41058            // Check if statistics is true or false
41059            match statistics.as_ref() {
41060                Expression::Boolean(b) if !b.value => {
41061                    self.write_keyword("NO");
41062                    self.write_space();
41063                }
41064                _ => {}
41065            }
41066            self.write_keyword("STATISTICS");
41067        }
41068        Ok(())
41069    }
41070
41071    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
41072        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
41073        self.write_keyword("WITH FILL");
41074
41075        if let Some(from_) = &e.from_ {
41076            self.write_space();
41077            self.write_keyword("FROM");
41078            self.write_space();
41079            self.generate_expression(from_)?;
41080        }
41081
41082        if let Some(to) = &e.to {
41083            self.write_space();
41084            self.write_keyword("TO");
41085            self.write_space();
41086            self.generate_expression(to)?;
41087        }
41088
41089        if let Some(step) = &e.step {
41090            self.write_space();
41091            self.write_keyword("STEP");
41092            self.write_space();
41093            self.generate_expression(step)?;
41094        }
41095
41096        if let Some(staleness) = &e.staleness {
41097            self.write_space();
41098            self.write_keyword("STALENESS");
41099            self.write_space();
41100            self.generate_expression(staleness)?;
41101        }
41102
41103        if let Some(interpolate) = &e.interpolate {
41104            self.write_space();
41105            self.write_keyword("INTERPOLATE");
41106            self.write(" (");
41107            // INTERPOLATE items use reversed alias format: name AS expression
41108            self.generate_interpolate_item(interpolate)?;
41109            self.write(")");
41110        }
41111
41112        Ok(())
41113    }
41114
41115    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
41116    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
41117        match expr {
41118            Expression::Alias(alias) => {
41119                // Output as: alias_name AS expression
41120                self.generate_identifier(&alias.alias)?;
41121                self.write_space();
41122                self.write_keyword("AS");
41123                self.write_space();
41124                self.generate_expression(&alias.this)?;
41125            }
41126            Expression::Tuple(tuple) => {
41127                for (i, item) in tuple.expressions.iter().enumerate() {
41128                    if i > 0 {
41129                        self.write(", ");
41130                    }
41131                    self.generate_interpolate_item(item)?;
41132                }
41133            }
41134            other => {
41135                self.generate_expression(other)?;
41136            }
41137        }
41138        Ok(())
41139    }
41140
41141    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
41142        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
41143        self.write_keyword("WITH JOURNAL TABLE");
41144        self.write("=");
41145        self.generate_expression(&e.this)?;
41146        Ok(())
41147    }
41148
41149    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
41150        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
41151        self.generate_expression(&e.this)?;
41152        self.write_space();
41153        self.write_keyword("WITH");
41154        self.write_space();
41155        self.write_keyword(&e.op);
41156        Ok(())
41157    }
41158
41159    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
41160        // Python: return f"WITH {self.expressions(expression, flat=True)}"
41161        self.write_keyword("WITH");
41162        self.write_space();
41163        for (i, expr) in e.expressions.iter().enumerate() {
41164            if i > 0 {
41165                self.write(", ");
41166            }
41167            self.generate_expression(expr)?;
41168        }
41169        Ok(())
41170    }
41171
41172    fn generate_with_schema_binding_property(
41173        &mut self,
41174        e: &WithSchemaBindingProperty,
41175    ) -> Result<()> {
41176        // Python: return f"WITH {self.sql(expression, 'this')}"
41177        self.write_keyword("WITH");
41178        self.write_space();
41179        self.generate_expression(&e.this)?;
41180        Ok(())
41181    }
41182
41183    fn generate_with_system_versioning_property(
41184        &mut self,
41185        e: &WithSystemVersioningProperty,
41186    ) -> Result<()> {
41187        // Python: complex logic for SYSTEM_VERSIONING with options
41188        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
41189        // or SYSTEM_VERSIONING=ON/OFF
41190        // with WITH(...) wrapper if with_ is set
41191
41192        let mut parts = Vec::new();
41193
41194        if let Some(this) = &e.this {
41195            // HISTORY_TABLE=...
41196            let mut s = String::from("HISTORY_TABLE=");
41197            let mut gen = Generator::new();
41198            gen.generate_expression(this)?;
41199            s.push_str(&gen.output);
41200            parts.push(s);
41201        }
41202
41203        if let Some(data_consistency) = &e.data_consistency {
41204            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
41205            let mut gen = Generator::new();
41206            gen.generate_expression(data_consistency)?;
41207            s.push_str(&gen.output);
41208            parts.push(s);
41209        }
41210
41211        if let Some(retention_period) = &e.retention_period {
41212            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
41213            let mut gen = Generator::new();
41214            gen.generate_expression(retention_period)?;
41215            s.push_str(&gen.output);
41216            parts.push(s);
41217        }
41218
41219        self.write_keyword("SYSTEM_VERSIONING");
41220        self.write("=");
41221
41222        if !parts.is_empty() {
41223            self.write_keyword("ON");
41224            self.write("(");
41225            self.write(&parts.join(", "));
41226            self.write(")");
41227        } else if e.on.is_some() {
41228            self.write_keyword("ON");
41229        } else {
41230            self.write_keyword("OFF");
41231        }
41232
41233        // Wrap in WITH(...) if with_ is set
41234        if e.with_.is_some() {
41235            let inner = self.output.clone();
41236            self.output.clear();
41237            self.write("WITH(");
41238            self.write(&inner);
41239            self.write(")");
41240        }
41241
41242        Ok(())
41243    }
41244
41245    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
41246        // Python: f"WITH ({self.expressions(expression, flat=True)})"
41247        self.write_keyword("WITH");
41248        self.write(" (");
41249        for (i, expr) in e.expressions.iter().enumerate() {
41250            if i > 0 {
41251                self.write(", ");
41252            }
41253            self.generate_expression(expr)?;
41254        }
41255        self.write(")");
41256        Ok(())
41257    }
41258
41259    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
41260        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
41261        // return self.func("XMLELEMENT", name, *expression.expressions)
41262        self.write_keyword("XMLELEMENT");
41263        self.write("(");
41264
41265        if e.evalname.is_some() {
41266            self.write_keyword("EVALNAME");
41267        } else {
41268            self.write_keyword("NAME");
41269        }
41270        self.write_space();
41271        self.generate_expression(&e.this)?;
41272
41273        for expr in &e.expressions {
41274            self.write(", ");
41275            self.generate_expression(expr)?;
41276        }
41277        self.write(")");
41278        Ok(())
41279    }
41280
41281    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
41282        // XMLGET(this, expression [, instance])
41283        self.write_keyword("XMLGET");
41284        self.write("(");
41285        self.generate_expression(&e.this)?;
41286        self.write(", ");
41287        self.generate_expression(&e.expression)?;
41288        if let Some(instance) = &e.instance {
41289            self.write(", ");
41290            self.generate_expression(instance)?;
41291        }
41292        self.write(")");
41293        Ok(())
41294    }
41295
41296    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
41297        // Python: this + optional (expr)
41298        self.generate_expression(&e.this)?;
41299        if let Some(expression) = &e.expression {
41300            self.write("(");
41301            self.generate_expression(expression)?;
41302            self.write(")");
41303        }
41304        Ok(())
41305    }
41306
41307    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
41308        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
41309        self.write_keyword("XMLTABLE");
41310        self.write("(");
41311
41312        if self.config.pretty {
41313            self.indent_level += 1;
41314            self.write_newline();
41315            self.write_indent();
41316            self.generate_expression(&e.this)?;
41317
41318            if let Some(passing) = &e.passing {
41319                self.write_newline();
41320                self.write_indent();
41321                self.write_keyword("PASSING");
41322                if let Expression::Tuple(tuple) = passing.as_ref() {
41323                    for expr in &tuple.expressions {
41324                        self.write_newline();
41325                        self.indent_level += 1;
41326                        self.write_indent();
41327                        self.generate_expression(expr)?;
41328                        self.indent_level -= 1;
41329                    }
41330                } else {
41331                    self.write_newline();
41332                    self.indent_level += 1;
41333                    self.write_indent();
41334                    self.generate_expression(passing)?;
41335                    self.indent_level -= 1;
41336                }
41337            }
41338
41339            if e.by_ref.is_some() {
41340                self.write_newline();
41341                self.write_indent();
41342                self.write_keyword("RETURNING SEQUENCE BY REF");
41343            }
41344
41345            if !e.columns.is_empty() {
41346                self.write_newline();
41347                self.write_indent();
41348                self.write_keyword("COLUMNS");
41349                for (i, col) in e.columns.iter().enumerate() {
41350                    self.write_newline();
41351                    self.indent_level += 1;
41352                    self.write_indent();
41353                    self.generate_expression(col)?;
41354                    self.indent_level -= 1;
41355                    if i < e.columns.len() - 1 {
41356                        self.write(",");
41357                    }
41358                }
41359            }
41360
41361            self.indent_level -= 1;
41362            self.write_newline();
41363            self.write_indent();
41364            self.write(")");
41365            return Ok(());
41366        }
41367
41368        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
41369        if let Some(namespaces) = &e.namespaces {
41370            self.write_keyword("XMLNAMESPACES");
41371            self.write("(");
41372            // Unwrap Tuple if present to avoid extra parentheses
41373            if let Expression::Tuple(tuple) = namespaces.as_ref() {
41374                for (i, expr) in tuple.expressions.iter().enumerate() {
41375                    if i > 0 {
41376                        self.write(", ");
41377                    }
41378                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
41379                    // See xmlnamespace_sql in generator.py
41380                    if !matches!(expr, Expression::Alias(_)) {
41381                        self.write_keyword("DEFAULT");
41382                        self.write_space();
41383                    }
41384                    self.generate_expression(expr)?;
41385                }
41386            } else {
41387                // Single namespace - check if DEFAULT
41388                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
41389                    self.write_keyword("DEFAULT");
41390                    self.write_space();
41391                }
41392                self.generate_expression(namespaces)?;
41393            }
41394            self.write("), ");
41395        }
41396
41397        // XPath expression
41398        self.generate_expression(&e.this)?;
41399
41400        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
41401        if let Some(passing) = &e.passing {
41402            self.write_space();
41403            self.write_keyword("PASSING");
41404            self.write_space();
41405            // Unwrap Tuple if present to avoid extra parentheses
41406            if let Expression::Tuple(tuple) = passing.as_ref() {
41407                for (i, expr) in tuple.expressions.iter().enumerate() {
41408                    if i > 0 {
41409                        self.write(", ");
41410                    }
41411                    self.generate_expression(expr)?;
41412                }
41413            } else {
41414                self.generate_expression(passing)?;
41415            }
41416        }
41417
41418        // RETURNING SEQUENCE BY REF
41419        if e.by_ref.is_some() {
41420            self.write_space();
41421            self.write_keyword("RETURNING SEQUENCE BY REF");
41422        }
41423
41424        // COLUMNS clause
41425        if !e.columns.is_empty() {
41426            self.write_space();
41427            self.write_keyword("COLUMNS");
41428            self.write_space();
41429            for (i, col) in e.columns.iter().enumerate() {
41430                if i > 0 {
41431                    self.write(", ");
41432                }
41433                self.generate_expression(col)?;
41434            }
41435        }
41436
41437        self.write(")");
41438        Ok(())
41439    }
41440
41441    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
41442        // Python: return self.connector_sql(expression, "XOR", stack)
41443        // Handles: this XOR expression or expressions joined by XOR
41444        if let Some(this) = &e.this {
41445            self.generate_expression(this)?;
41446            if let Some(expression) = &e.expression {
41447                self.write_space();
41448                self.write_keyword("XOR");
41449                self.write_space();
41450                self.generate_expression(expression)?;
41451            }
41452        }
41453
41454        // Handle multiple expressions
41455        for (i, expr) in e.expressions.iter().enumerate() {
41456            if i > 0 || e.this.is_some() {
41457                self.write_space();
41458                self.write_keyword("XOR");
41459                self.write_space();
41460            }
41461            self.generate_expression(expr)?;
41462        }
41463        Ok(())
41464    }
41465
41466    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
41467        // ZIPF(this, elementcount [, gen])
41468        self.write_keyword("ZIPF");
41469        self.write("(");
41470        self.generate_expression(&e.this)?;
41471        if let Some(elementcount) = &e.elementcount {
41472            self.write(", ");
41473            self.generate_expression(elementcount)?;
41474        }
41475        if let Some(gen) = &e.gen {
41476            self.write(", ");
41477            self.generate_expression(gen)?;
41478        }
41479        self.write(")");
41480        Ok(())
41481    }
41482}
41483
41484impl Default for Generator {
41485    fn default() -> Self {
41486        Self::new()
41487    }
41488}
41489
41490#[cfg(test)]
41491mod tests {
41492    use super::*;
41493    use crate::parser::Parser;
41494
41495    fn roundtrip(sql: &str) -> String {
41496        let ast = Parser::parse_sql(sql).unwrap();
41497        Generator::sql(&ast[0]).unwrap()
41498    }
41499
41500    #[test]
41501    fn test_simple_select() {
41502        let result = roundtrip("SELECT 1");
41503        assert_eq!(result, "SELECT 1");
41504    }
41505
41506    #[test]
41507    fn test_select_from() {
41508        let result = roundtrip("SELECT a, b FROM t");
41509        assert_eq!(result, "SELECT a, b FROM t");
41510    }
41511
41512    #[test]
41513    fn test_select_where() {
41514        let result = roundtrip("SELECT * FROM t WHERE x = 1");
41515        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
41516    }
41517
41518    #[test]
41519    fn test_select_join() {
41520        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
41521        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
41522    }
41523
41524    #[test]
41525    fn test_insert() {
41526        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
41527        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
41528    }
41529
41530    #[test]
41531    fn test_pretty_print() {
41532        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
41533        let result = Generator::pretty_sql(&ast[0]).unwrap();
41534        assert!(result.contains('\n'));
41535    }
41536
41537    #[test]
41538    fn test_window_function() {
41539        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
41540        assert_eq!(
41541            result,
41542            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
41543        );
41544    }
41545
41546    #[test]
41547    fn test_window_function_with_frame() {
41548        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
41549        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
41550    }
41551
41552    #[test]
41553    fn test_aggregate_with_filter() {
41554        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
41555        assert_eq!(
41556            result,
41557            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
41558        );
41559    }
41560
41561    #[test]
41562    fn test_subscript() {
41563        let result = roundtrip("SELECT arr[0]");
41564        assert_eq!(result, "SELECT arr[0]");
41565    }
41566
41567    // DDL tests
41568    #[test]
41569    fn test_create_table() {
41570        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
41571        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
41572    }
41573
41574    #[test]
41575    fn test_create_table_with_constraints() {
41576        let result = roundtrip(
41577            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
41578        );
41579        assert_eq!(
41580            result,
41581            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
41582        );
41583    }
41584
41585    #[test]
41586    fn test_create_table_if_not_exists() {
41587        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
41588        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
41589    }
41590
41591    #[test]
41592    fn test_drop_table() {
41593        let result = roundtrip("DROP TABLE users");
41594        assert_eq!(result, "DROP TABLE users");
41595    }
41596
41597    #[test]
41598    fn test_drop_table_if_exists_cascade() {
41599        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
41600        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
41601    }
41602
41603    #[test]
41604    fn test_alter_table_add_column() {
41605        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
41606        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
41607    }
41608
41609    #[test]
41610    fn test_alter_table_drop_column() {
41611        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
41612        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
41613    }
41614
41615    #[test]
41616    fn test_create_index() {
41617        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
41618        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
41619    }
41620
41621    #[test]
41622    fn test_create_unique_index() {
41623        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
41624        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
41625    }
41626
41627    #[test]
41628    fn test_drop_index() {
41629        let result = roundtrip("DROP INDEX idx_name");
41630        assert_eq!(result, "DROP INDEX idx_name");
41631
41632        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
41633        assert_eq!(
41634            result,
41635            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
41636        );
41637
41638        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
41639        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
41640    }
41641
41642    #[test]
41643    fn test_create_view() {
41644        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
41645        assert_eq!(
41646            result,
41647            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
41648        );
41649    }
41650
41651    #[test]
41652    fn test_drop_view() {
41653        let result = roundtrip("DROP VIEW active_users");
41654        assert_eq!(result, "DROP VIEW active_users");
41655    }
41656
41657    #[test]
41658    fn test_truncate() {
41659        let result = roundtrip("TRUNCATE TABLE users");
41660        assert_eq!(result, "TRUNCATE TABLE users");
41661    }
41662
41663    #[test]
41664    fn test_string_literal_escaping_default() {
41665        // Default: double single quotes
41666        let result = roundtrip("SELECT 'hello'");
41667        assert_eq!(result, "SELECT 'hello'");
41668
41669        // Single quotes are doubled
41670        let result = roundtrip("SELECT 'it''s a test'");
41671        assert_eq!(result, "SELECT 'it''s a test'");
41672    }
41673
41674    #[test]
41675    fn test_not_in_style_prefix_default_generic() {
41676        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
41677        assert_eq!(
41678            result,
41679            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
41680        );
41681    }
41682
41683    #[test]
41684    fn test_not_in_style_infix_generic_override() {
41685        let ast =
41686            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
41687                .unwrap();
41688        let config = GeneratorConfig {
41689            not_in_style: NotInStyle::Infix,
41690            ..Default::default()
41691        };
41692        let mut gen = Generator::with_config(config);
41693        let result = gen.generate(&ast[0]).unwrap();
41694        assert_eq!(
41695            result,
41696            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
41697        );
41698    }
41699
41700    #[test]
41701    fn test_string_literal_escaping_mysql() {
41702        use crate::dialects::DialectType;
41703
41704        let config = GeneratorConfig {
41705            dialect: Some(DialectType::MySQL),
41706            ..Default::default()
41707        };
41708
41709        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
41710        let mut gen = Generator::with_config(config.clone());
41711        let result = gen.generate(&ast[0]).unwrap();
41712        assert_eq!(result, "SELECT 'hello'");
41713
41714        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
41715        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
41716        let mut gen = Generator::with_config(config.clone());
41717        let result = gen.generate(&ast[0]).unwrap();
41718        assert_eq!(result, "SELECT 'it''s'");
41719    }
41720
41721    #[test]
41722    fn test_string_literal_escaping_postgres() {
41723        use crate::dialects::DialectType;
41724
41725        let config = GeneratorConfig {
41726            dialect: Some(DialectType::PostgreSQL),
41727            ..Default::default()
41728        };
41729
41730        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
41731        let mut gen = Generator::with_config(config.clone());
41732        let result = gen.generate(&ast[0]).unwrap();
41733        assert_eq!(result, "SELECT 'hello'");
41734
41735        // PostgreSQL uses doubled quotes for regular strings
41736        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
41737        let mut gen = Generator::with_config(config.clone());
41738        let result = gen.generate(&ast[0]).unwrap();
41739        assert_eq!(result, "SELECT 'it''s'");
41740    }
41741
41742    #[test]
41743    fn test_string_literal_escaping_bigquery() {
41744        use crate::dialects::DialectType;
41745
41746        let config = GeneratorConfig {
41747            dialect: Some(DialectType::BigQuery),
41748            ..Default::default()
41749        };
41750
41751        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
41752        let mut gen = Generator::with_config(config.clone());
41753        let result = gen.generate(&ast[0]).unwrap();
41754        assert_eq!(result, "SELECT 'hello'");
41755
41756        // BigQuery escapes single quotes with backslash
41757        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
41758        let mut gen = Generator::with_config(config.clone());
41759        let result = gen.generate(&ast[0]).unwrap();
41760        assert_eq!(result, "SELECT 'it\\'s'");
41761    }
41762
41763    #[test]
41764    fn test_generate_deep_and_chain_without_stack_growth() {
41765        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
41766            Expression::column("c0"),
41767            Expression::number(0),
41768        )));
41769
41770        for i in 1..2500 {
41771            let predicate = Expression::Eq(Box::new(BinaryOp::new(
41772                Expression::column(format!("c{i}")),
41773                Expression::number(i as i64),
41774            )));
41775            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
41776        }
41777
41778        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
41779        assert!(sql.contains("c2499 = 2499"), "{}", sql);
41780    }
41781
41782    #[test]
41783    fn test_generate_deep_or_chain_without_stack_growth() {
41784        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
41785            Expression::column("c0"),
41786            Expression::number(0),
41787        )));
41788
41789        for i in 1..2500 {
41790            let predicate = Expression::Eq(Box::new(BinaryOp::new(
41791                Expression::column(format!("c{i}")),
41792                Expression::number(i as i64),
41793            )));
41794            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
41795        }
41796
41797        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
41798        assert!(sql.contains("c2499 = 2499"), "{}", sql);
41799    }
41800}