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::Hive)
3057                    | Some(DialectType::Spark)
3058                    | Some(DialectType::MySQL)
3059                    | Some(DialectType::SingleStore)
3060                    | Some(DialectType::TiDB)
3061                    | Some(DialectType::TSQL) => {
3062                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3063                        self.generate_expression(&f.this)?;
3064                        return Ok(());
3065                    }
3066                    Some(DialectType::DuckDB) => "JSON",
3067                    _ => "PARSE_JSON",
3068                };
3069                self.generate_simple_func(name, &f.this)
3070            }
3071            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3072            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3073            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3074            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3075            Expression::JsonMergePatch(f) => {
3076                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3077            }
3078            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3079            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3080
3081            // Type casting/conversion
3082            Expression::Convert(f) => self.generate_convert(f),
3083            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3084
3085            // Additional expressions
3086            Expression::Lambda(f) => self.generate_lambda(f),
3087            Expression::Parameter(f) => self.generate_parameter(f),
3088            Expression::Placeholder(f) => self.generate_placeholder(f),
3089            Expression::NamedArgument(f) => self.generate_named_argument(f),
3090            Expression::TableArgument(f) => self.generate_table_argument(f),
3091            Expression::SqlComment(f) => self.generate_sql_comment(f),
3092
3093            // Additional predicates
3094            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3095            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3096            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3097            Expression::SimilarTo(f) => self.generate_similar_to(f),
3098            Expression::Any(f) => self.generate_quantified("ANY", f),
3099            Expression::All(f) => self.generate_quantified("ALL", f),
3100            Expression::Overlaps(f) => self.generate_overlaps(f),
3101
3102            // Bitwise operations
3103            Expression::BitwiseLeftShift(op) => {
3104                if matches!(
3105                    self.config.dialect,
3106                    Some(DialectType::Presto) | Some(DialectType::Trino)
3107                ) {
3108                    self.write_keyword("BITWISE_LEFT_SHIFT");
3109                    self.write("(");
3110                    self.generate_expression(&op.left)?;
3111                    self.write(", ");
3112                    self.generate_expression(&op.right)?;
3113                    self.write(")");
3114                    Ok(())
3115                } else if matches!(
3116                    self.config.dialect,
3117                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3118                ) {
3119                    self.write_keyword("SHIFTLEFT");
3120                    self.write("(");
3121                    self.generate_expression(&op.left)?;
3122                    self.write(", ");
3123                    self.generate_expression(&op.right)?;
3124                    self.write(")");
3125                    Ok(())
3126                } else {
3127                    self.generate_binary_op(op, "<<")
3128                }
3129            }
3130            Expression::BitwiseRightShift(op) => {
3131                if matches!(
3132                    self.config.dialect,
3133                    Some(DialectType::Presto) | Some(DialectType::Trino)
3134                ) {
3135                    self.write_keyword("BITWISE_RIGHT_SHIFT");
3136                    self.write("(");
3137                    self.generate_expression(&op.left)?;
3138                    self.write(", ");
3139                    self.generate_expression(&op.right)?;
3140                    self.write(")");
3141                    Ok(())
3142                } else if matches!(
3143                    self.config.dialect,
3144                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3145                ) {
3146                    self.write_keyword("SHIFTRIGHT");
3147                    self.write("(");
3148                    self.generate_expression(&op.left)?;
3149                    self.write(", ");
3150                    self.generate_expression(&op.right)?;
3151                    self.write(")");
3152                    Ok(())
3153                } else {
3154                    self.generate_binary_op(op, ">>")
3155                }
3156            }
3157            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3158            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3159            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3160
3161            // Array/struct/map access
3162            Expression::Subscript(s) => self.generate_subscript(s),
3163            Expression::Dot(d) => self.generate_dot_access(d),
3164            Expression::MethodCall(m) => self.generate_method_call(m),
3165            Expression::ArraySlice(s) => self.generate_array_slice(s),
3166
3167            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3168            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3169            Expression::Add(op) => self.generate_binary_op(op, "+"),
3170            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3171            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3172            Expression::Div(op) => self.generate_binary_op(op, "/"),
3173            Expression::IntDiv(f) => {
3174                use crate::dialects::DialectType;
3175                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3176                    // DuckDB uses // operator for integer division
3177                    self.generate_expression(&f.this)?;
3178                    self.write(" // ");
3179                    self.generate_expression(&f.expression)?;
3180                    Ok(())
3181                } else if matches!(
3182                    self.config.dialect,
3183                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3184                ) {
3185                    // Hive/Spark use DIV as an infix operator
3186                    self.generate_expression(&f.this)?;
3187                    self.write(" ");
3188                    self.write_keyword("DIV");
3189                    self.write(" ");
3190                    self.generate_expression(&f.expression)?;
3191                    Ok(())
3192                } else {
3193                    // Other dialects use DIV function
3194                    self.write_keyword("DIV");
3195                    self.write("(");
3196                    self.generate_expression(&f.this)?;
3197                    self.write(", ");
3198                    self.generate_expression(&f.expression)?;
3199                    self.write(")");
3200                    Ok(())
3201                }
3202            }
3203            Expression::Mod(op) => {
3204                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3205                    self.generate_binary_op(op, "MOD")
3206                } else {
3207                    self.generate_binary_op(op, "%")
3208                }
3209            }
3210            Expression::Eq(op) => self.generate_binary_op(op, "="),
3211            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3212            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3213            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3214            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3215            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3216            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3217            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3218            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3219            Expression::Concat(op) => {
3220                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3221                if self.config.dialect == Some(DialectType::Solr) {
3222                    self.generate_binary_op(op, "OR")
3223                } else if self.config.dialect == Some(DialectType::MySQL) {
3224                    self.generate_mysql_concat_from_concat(op)
3225                } else {
3226                    self.generate_binary_op(op, "||")
3227                }
3228            }
3229            Expression::BitwiseAnd(op) => {
3230                // Presto/Trino use BITWISE_AND function
3231                if matches!(
3232                    self.config.dialect,
3233                    Some(DialectType::Presto) | Some(DialectType::Trino)
3234                ) {
3235                    self.write_keyword("BITWISE_AND");
3236                    self.write("(");
3237                    self.generate_expression(&op.left)?;
3238                    self.write(", ");
3239                    self.generate_expression(&op.right)?;
3240                    self.write(")");
3241                    Ok(())
3242                } else {
3243                    self.generate_binary_op(op, "&")
3244                }
3245            }
3246            Expression::BitwiseOr(op) => {
3247                // Presto/Trino use BITWISE_OR function
3248                if matches!(
3249                    self.config.dialect,
3250                    Some(DialectType::Presto) | Some(DialectType::Trino)
3251                ) {
3252                    self.write_keyword("BITWISE_OR");
3253                    self.write("(");
3254                    self.generate_expression(&op.left)?;
3255                    self.write(", ");
3256                    self.generate_expression(&op.right)?;
3257                    self.write(")");
3258                    Ok(())
3259                } else {
3260                    self.generate_binary_op(op, "|")
3261                }
3262            }
3263            Expression::BitwiseXor(op) => {
3264                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3265                if matches!(
3266                    self.config.dialect,
3267                    Some(DialectType::Presto) | Some(DialectType::Trino)
3268                ) {
3269                    self.write_keyword("BITWISE_XOR");
3270                    self.write("(");
3271                    self.generate_expression(&op.left)?;
3272                    self.write(", ");
3273                    self.generate_expression(&op.right)?;
3274                    self.write(")");
3275                    Ok(())
3276                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3277                    self.generate_binary_op(op, "#")
3278                } else {
3279                    self.generate_binary_op(op, "^")
3280                }
3281            }
3282            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3283            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3284            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3285            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3286            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3287            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3288            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3289            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3290            Expression::JSONBContains(f) => {
3291                // PostgreSQL JSONB contains key/path operators: a ? b, a @? b
3292                self.generate_expression(&f.this)?;
3293                self.write_space();
3294                self.write(f.original_name.as_deref().unwrap_or("?"));
3295                self.write_space();
3296                self.generate_expression(&f.expression)
3297            }
3298            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3299            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3300            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3301            Expression::Not(op) => match &op.this {
3302                Expression::Like(like) => self.generate_like_op_negated(like, "LIKE"),
3303                Expression::ILike(like) => self.generate_like_op_negated(like, "ILIKE"),
3304                _ => self.generate_unary_op(op, "NOT"),
3305            },
3306            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3307            Expression::BitwiseNot(op) => {
3308                // Presto/Trino use BITWISE_NOT function
3309                if matches!(
3310                    self.config.dialect,
3311                    Some(DialectType::Presto) | Some(DialectType::Trino)
3312                ) {
3313                    self.write_keyword("BITWISE_NOT");
3314                    self.write("(");
3315                    self.generate_expression(&op.this)?;
3316                    self.write(")");
3317                    Ok(())
3318                } else {
3319                    self.generate_unary_op(op, "~")
3320                }
3321            }
3322            Expression::In(in_expr) => self.generate_in(in_expr),
3323            Expression::Between(between) => self.generate_between(between),
3324            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3325            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3326            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3327            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3328            Expression::Is(is_expr) => self.generate_is(is_expr),
3329            Expression::Exists(exists) => self.generate_exists(exists),
3330            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3331            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3332            Expression::Paren(paren) => {
3333                // JoinedTable already outputs its own parentheses, so don't double-wrap
3334                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3335
3336                if !skip_parens {
3337                    self.write("(");
3338                    if self.config.pretty {
3339                        self.write_newline();
3340                        self.indent_level += 1;
3341                        self.write_indent();
3342                    }
3343                }
3344                self.generate_expression(&paren.this)?;
3345                if !skip_parens {
3346                    if self.config.pretty {
3347                        self.write_newline();
3348                        self.indent_level -= 1;
3349                        self.write_indent();
3350                    }
3351                    self.write(")");
3352                }
3353                // Output trailing comments after closing paren
3354                for comment in &paren.trailing_comments {
3355                    self.write(" ");
3356                    self.write_formatted_comment(comment);
3357                }
3358                Ok(())
3359            }
3360            Expression::Array(arr) => self.generate_array(arr),
3361            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3362            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3363            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3364            Expression::DataType(dt) => self.generate_data_type(dt),
3365            Expression::Raw(raw) => {
3366                self.write(&raw.sql);
3367                Ok(())
3368            }
3369            Expression::CreateTask(task) => self.generate_create_task(task),
3370            Expression::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3371            Expression::Command(cmd) => {
3372                self.write(&cmd.this);
3373                if matches!(self.config.dialect, Some(DialectType::ClickHouse))
3374                    && cmd
3375                        .this
3376                        .trim_start()
3377                        .get(..7)
3378                        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("EXPLAIN"))
3379                {
3380                    for _ in 0..Self::missing_closing_parens_outside_quotes(&cmd.this) {
3381                        self.write(")");
3382                    }
3383                }
3384                Ok(())
3385            }
3386            Expression::Kill(kill) => {
3387                self.write_keyword("KILL");
3388                if let Some(kind) = &kill.kind {
3389                    self.write_space();
3390                    self.write_keyword(kind);
3391                }
3392                self.write_space();
3393                self.generate_expression(&kill.this)?;
3394                Ok(())
3395            }
3396            Expression::Prepare(prepare) => self.generate_prepare(prepare),
3397            Expression::Execute(exec) => {
3398                self.write_keyword("EXECUTE");
3399                self.write_space();
3400                self.generate_expression(&exec.this)?;
3401                if exec.prepared {
3402                    if !exec.arguments.is_empty() {
3403                        self.write("(");
3404                        for (i, argument) in exec.arguments.iter().enumerate() {
3405                            if i > 0 {
3406                                self.write(", ");
3407                            }
3408                            self.generate_expression(argument)?;
3409                        }
3410                        self.write(")");
3411                    }
3412                    return Ok(());
3413                }
3414                for (i, param) in exec.parameters.iter().enumerate() {
3415                    if i == 0 {
3416                        self.write_space();
3417                    } else {
3418                        self.write(", ");
3419                    }
3420                    self.write(&param.name);
3421                    // Only write = value for named parameters (not positional)
3422                    if !param.positional {
3423                        self.write(" = ");
3424                        self.generate_expression(&param.value)?;
3425                    }
3426                    if param.output {
3427                        self.write_space();
3428                        self.write_keyword("OUTPUT");
3429                    }
3430                }
3431                if let Some(ref suffix) = exec.suffix {
3432                    self.write_space();
3433                    self.write(suffix);
3434                }
3435                Ok(())
3436            }
3437            Expression::Annotated(annotated) => {
3438                self.generate_expression(&annotated.this)?;
3439                for comment in &annotated.trailing_comments {
3440                    self.write(" ");
3441                    self.write_formatted_comment(comment);
3442                }
3443                Ok(())
3444            }
3445
3446            // DDL statements
3447            Expression::CreateTable(ct) => self.generate_create_table(ct),
3448            Expression::DropTable(dt) => self.generate_drop_table(dt),
3449            Expression::Undrop(u) => self.generate_undrop(u),
3450            Expression::AlterTable(at) => self.generate_alter_table(at),
3451            Expression::SplitTable(st) => self.generate_split_table(st),
3452            Expression::FlashbackTable(ft) => self.generate_flashback_table(ft),
3453            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3454            Expression::DropIndex(di) => self.generate_drop_index(di),
3455            Expression::CreateView(cv) => self.generate_create_view(cv),
3456            Expression::DropView(dv) => self.generate_drop_view(dv),
3457            Expression::AlterView(av) => self.generate_alter_view(av),
3458            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3459            Expression::Truncate(tr) => self.generate_truncate(tr),
3460            Expression::Use(u) => self.generate_use(u),
3461            // Phase 4: Additional DDL statements
3462            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3463            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3464            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3465            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3466            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3467            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3468            Expression::DropFunction(df) => self.generate_drop_function(df),
3469            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3470            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3471            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3472            Expression::CreateSynonym(cs) => {
3473                self.write_keyword("CREATE SYNONYM");
3474                self.write_space();
3475                self.generate_table(&cs.name)?;
3476                self.write_space();
3477                self.write_keyword("FOR");
3478                self.write_space();
3479                self.generate_table(&cs.target)?;
3480                Ok(())
3481            }
3482            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3483            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3484            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3485            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3486            Expression::CreateType(ct) => self.generate_create_type(ct),
3487            Expression::DropType(dt) => self.generate_drop_type(dt),
3488            Expression::Describe(d) => self.generate_describe(d),
3489            Expression::Show(s) => self.generate_show(s),
3490
3491            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3492            Expression::Cache(c) => self.generate_cache(c),
3493            Expression::Uncache(u) => self.generate_uncache(u),
3494            Expression::LoadData(l) => self.generate_load_data(l),
3495            Expression::Pragma(p) => self.generate_pragma(p),
3496            Expression::Grant(g) => self.generate_grant(g),
3497            Expression::Revoke(r) => self.generate_revoke(r),
3498            Expression::Comment(c) => self.generate_comment(c),
3499            Expression::SetStatement(s) => self.generate_set_statement(s),
3500
3501            // PIVOT/UNPIVOT
3502            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3503            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3504
3505            // VALUES table constructor
3506            Expression::Values(values) => self.generate_values(values),
3507
3508            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3509            Expression::AIAgg(e) => self.generate_ai_agg(e),
3510            Expression::AIClassify(e) => self.generate_ai_classify(e),
3511            Expression::AddPartition(e) => self.generate_add_partition(e),
3512            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3513            Expression::Aliases(e) => self.generate_aliases(e),
3514            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3515            Expression::AlterColumn(e) => self.generate_alter_column(e),
3516            Expression::AlterSession(e) => self.generate_alter_session(e),
3517            Expression::AlterSet(e) => self.generate_alter_set(e),
3518            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3519            Expression::Analyze(e) => self.generate_analyze(e),
3520            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3521            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3522            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3523            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3524            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3525            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3526            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3527            Expression::Anonymous(e) => self.generate_anonymous(e),
3528            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3529            Expression::Apply(e) => self.generate_apply(e),
3530            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3531            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3532            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3533            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3534            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3535            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3536            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3537            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3538            Expression::ArgMax(e) => self.generate_arg_max(e),
3539            Expression::ArgMin(e) => self.generate_arg_min(e),
3540            Expression::ArrayAll(e) => self.generate_array_all(e),
3541            Expression::ArrayAny(e) => self.generate_array_any(e),
3542            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3543            Expression::ArraySum(e) => self.generate_array_sum(e),
3544            Expression::AtIndex(e) => self.generate_at_index(e),
3545            Expression::Attach(e) => self.generate_attach(e),
3546            Expression::AttachOption(e) => self.generate_attach_option(e),
3547            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3548            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3549            Expression::BackupProperty(e) => self.generate_backup_property(e),
3550            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3551            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3552            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3553            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3554            Expression::Booland(e) => self.generate_booland(e),
3555            Expression::Boolor(e) => self.generate_boolor(e),
3556            Expression::BuildProperty(e) => self.generate_build_property(e),
3557            Expression::ByteString(e) => self.generate_byte_string(e),
3558            Expression::CaseSpecificColumnConstraint(e) => {
3559                self.generate_case_specific_column_constraint(e)
3560            }
3561            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3562            Expression::Changes(e) => self.generate_changes(e),
3563            Expression::CharacterSetColumnConstraint(e) => {
3564                self.generate_character_set_column_constraint(e)
3565            }
3566            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3567            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3568            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3569            Expression::CheckJson(e) => self.generate_check_json(e),
3570            Expression::CheckXml(e) => self.generate_check_xml(e),
3571            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3572            Expression::Clone(e) => self.generate_clone(e),
3573            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3574            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3575            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3576            Expression::CollateProperty(e) => self.generate_collate_property(e),
3577            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3578            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3579            Expression::ColumnPosition(e) => self.generate_column_position(e),
3580            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3581            Expression::Columns(e) => self.generate_columns(e),
3582            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3583            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3584            Expression::Commit(e) => self.generate_commit(e),
3585            Expression::Comprehension(e) => self.generate_comprehension(e),
3586            Expression::Compress(e) => self.generate_compress(e),
3587            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3588            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3589            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3590            Expression::Constraint(e) => self.generate_constraint(e),
3591            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3592            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3593            Expression::Copy(e) => self.generate_copy(e),
3594            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3595            Expression::Corr(e) => self.generate_corr(e),
3596            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3597            Expression::CovarPop(e) => self.generate_covar_pop(e),
3598            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3599            Expression::Credentials(e) => self.generate_credentials(e),
3600            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3601            Expression::Cte(e) => self.generate_cte(e),
3602            Expression::Cube(e) => self.generate_cube(e),
3603            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3604            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3605            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3606            Expression::CurrentUser(e) => self.generate_current_user(e),
3607            Expression::DPipe(e) => self.generate_d_pipe(e),
3608            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3609            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3610            Expression::Date(e) => self.generate_date_func(e),
3611            Expression::DateBin(e) => self.generate_date_bin(e),
3612            Expression::DateFormatColumnConstraint(e) => {
3613                self.generate_date_format_column_constraint(e)
3614            }
3615            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3616            Expression::Datetime(e) => self.generate_datetime(e),
3617            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3618            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3619            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3620            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3621            Expression::Dayname(e) => self.generate_dayname(e),
3622            Expression::Declare(e) => self.generate_declare(e),
3623            Expression::DeclareItem(e) => self.generate_declare_item(e),
3624            Expression::DecodeCase(e) => self.generate_decode_case(e),
3625            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3626            Expression::DecompressString(e) => self.generate_decompress_string(e),
3627            Expression::Decrypt(e) => self.generate_decrypt(e),
3628            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3629            Expression::DefaultColumnConstraint(e) => {
3630                self.write_keyword("DEFAULT");
3631                self.write_space();
3632                self.generate_expression(&e.this)?;
3633                if let Some(ref col) = e.for_column {
3634                    self.write_space();
3635                    self.write_keyword("FOR");
3636                    self.write_space();
3637                    self.generate_identifier(col)?;
3638                }
3639                Ok(())
3640            }
3641            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3642            Expression::Detach(e) => self.generate_detach(e),
3643            Expression::DictProperty(e) => self.generate_dict_property(e),
3644            Expression::DictRange(e) => self.generate_dict_range(e),
3645            Expression::Directory(e) => self.generate_directory(e),
3646            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3647            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3648            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3649            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3650            Expression::DotProduct(e) => self.generate_dot_product(e),
3651            Expression::DropPartition(e) => self.generate_drop_partition(e),
3652            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3653            Expression::Elt(e) => self.generate_elt(e),
3654            Expression::Encode(e) => self.generate_encode(e),
3655            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3656            Expression::Encrypt(e) => self.generate_encrypt(e),
3657            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3658            Expression::EngineProperty(e) => self.generate_engine_property(e),
3659            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3660            Expression::EphemeralColumnConstraint(e) => {
3661                self.generate_ephemeral_column_constraint(e)
3662            }
3663            Expression::EqualNull(e) => self.generate_equal_null(e),
3664            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3665            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3666            Expression::Export(e) => self.generate_export(e),
3667            Expression::ExternalProperty(e) => self.generate_external_property(e),
3668            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3669            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3670            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3671            Expression::Fetch(e) => self.generate_fetch(e),
3672            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3673            Expression::Filter(e) => self.generate_filter(e),
3674            Expression::Float64(e) => self.generate_float64(e),
3675            Expression::ForIn(e) => self.generate_for_in(e),
3676            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3677            Expression::Format(e) => self.generate_format(e),
3678            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3679            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3680            Expression::From(e) => self.generate_from(e),
3681            Expression::FromBase(e) => self.generate_from_base(e),
3682            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3683            Expression::GapFill(e) => self.generate_gap_fill(e),
3684            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3685            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3686            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3687            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3688            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3689                self.generate_generated_as_identity_column_constraint(e)
3690            }
3691            Expression::GeneratedAsRowColumnConstraint(e) => {
3692                self.generate_generated_as_row_column_constraint(e)
3693            }
3694            Expression::Get(e) => self.generate_get(e),
3695            Expression::GetExtract(e) => self.generate_get_extract(e),
3696            Expression::Getbit(e) => self.generate_getbit(e),
3697            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3698            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3699            Expression::Group(e) => self.generate_group(e),
3700            Expression::GroupBy(e) => self.generate_group_by(e),
3701            Expression::Grouping(e) => self.generate_grouping(e),
3702            Expression::GroupingId(e) => self.generate_grouping_id(e),
3703            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3704            Expression::HashAgg(e) => self.generate_hash_agg(e),
3705            Expression::Having(e) => self.generate_having(e),
3706            Expression::HavingMax(e) => self.generate_having_max(e),
3707            Expression::Heredoc(e) => self.generate_heredoc(e),
3708            Expression::HexEncode(e) => self.generate_hex_encode(e),
3709            Expression::Hll(e) => self.generate_hll(e),
3710            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3711            Expression::IncludeProperty(e) => self.generate_include_property(e),
3712            Expression::Index(e) => self.generate_index(e),
3713            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3714            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3715            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3716            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3717            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3718            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3719            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3720            Expression::Install(e) => self.generate_install(e),
3721            Expression::IntervalOp(e) => self.generate_interval_op(e),
3722            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3723            Expression::IntoClause(e) => self.generate_into_clause(e),
3724            Expression::Introducer(e) => self.generate_introducer(e),
3725            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3726            Expression::JSON(e) => self.generate_json(e),
3727            Expression::JSONArray(e) => self.generate_json_array(e),
3728            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3729            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3730            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3731            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3732            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3733            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3734            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3735            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3736            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3737            Expression::JSONExists(e) => self.generate_json_exists(e),
3738            Expression::JSONCast(e) => self.generate_json_cast(e),
3739            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3740            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3741            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3742            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3743            Expression::JSONFormat(e) => self.generate_json_format(e),
3744            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3745            Expression::JSONKeys(e) => self.generate_json_keys(e),
3746            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3747            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3748            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3749            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3750            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3751            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3752            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3753            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3754            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3755            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3756            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3757            Expression::JSONRemove(e) => self.generate_json_remove(e),
3758            Expression::JSONSchema(e) => self.generate_json_schema(e),
3759            Expression::JSONSet(e) => self.generate_json_set(e),
3760            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3761            Expression::JSONTable(e) => self.generate_json_table(e),
3762            Expression::JSONType(e) => self.generate_json_type(e),
3763            Expression::JSONValue(e) => self.generate_json_value(e),
3764            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3765            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3766            Expression::JoinHint(e) => self.generate_join_hint(e),
3767            Expression::JournalProperty(e) => self.generate_journal_property(e),
3768            Expression::LanguageProperty(e) => self.generate_language_property(e),
3769            Expression::Lateral(e) => self.generate_lateral(e),
3770            Expression::LikeProperty(e) => self.generate_like_property(e),
3771            Expression::Limit(e) => self.generate_limit(e),
3772            Expression::LimitOptions(e) => self.generate_limit_options(e),
3773            Expression::List(e) => self.generate_list(e),
3774            Expression::ToMap(e) => self.generate_tomap(e),
3775            Expression::Localtime(e) => self.generate_localtime(e),
3776            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3777            Expression::LocationProperty(e) => self.generate_location_property(e),
3778            Expression::Lock(e) => self.generate_lock(e),
3779            Expression::LockProperty(e) => self.generate_lock_property(e),
3780            Expression::LockingProperty(e) => self.generate_locking_property(e),
3781            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3782            Expression::LogProperty(e) => self.generate_log_property(e),
3783            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3784            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3785            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3786            Expression::MakeInterval(e) => self.generate_make_interval(e),
3787            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3788            Expression::Map(e) => self.generate_map(e),
3789            Expression::MapCat(e) => self.generate_map_cat(e),
3790            Expression::MapDelete(e) => self.generate_map_delete(e),
3791            Expression::MapInsert(e) => self.generate_map_insert(e),
3792            Expression::MapPick(e) => self.generate_map_pick(e),
3793            Expression::MaskingPolicyColumnConstraint(e) => {
3794                self.generate_masking_policy_column_constraint(e)
3795            }
3796            Expression::MatchAgainst(e) => self.generate_match_against(e),
3797            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3798            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3799            Expression::Merge(e) => self.generate_merge(e),
3800            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3801            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3802            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3803            Expression::Minhash(e) => self.generate_minhash(e),
3804            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3805            Expression::Monthname(e) => self.generate_monthname(e),
3806            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3807            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3808            Expression::Normal(e) => self.generate_normal(e),
3809            Expression::Normalize(e) => self.generate_normalize(e),
3810            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3811            Expression::Nullif(e) => self.generate_nullif(e),
3812            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3813            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3814            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3815            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3816            Expression::Offset(e) => self.generate_offset(e),
3817            Expression::Qualify(e) => self.generate_qualify(e),
3818            Expression::OnCluster(e) => self.generate_on_cluster(e),
3819            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3820            Expression::OnCondition(e) => self.generate_on_condition(e),
3821            Expression::OnConflict(e) => self.generate_on_conflict(e),
3822            Expression::OnProperty(e) => self.generate_on_property(e),
3823            Expression::Opclass(e) => self.generate_opclass(e),
3824            Expression::OpenJSON(e) => self.generate_open_json(e),
3825            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3826            Expression::Operator(e) => self.generate_operator(e),
3827            Expression::OrderBy(e) => self.generate_order_by(e),
3828            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3829            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3830            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3831            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3832            Expression::ParseIp(e) => self.generate_parse_ip(e),
3833            Expression::ParseJSON(e) => self.generate_parse_json(e),
3834            Expression::ParseTime(e) => self.generate_parse_time(e),
3835            Expression::ParseUrl(e) => self.generate_parse_url(e),
3836            Expression::Partition(e) => self.generate_partition_expr(e),
3837            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3838            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3839            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3840            Expression::PartitionByRangePropertyDynamic(e) => {
3841                self.generate_partition_by_range_property_dynamic(e)
3842            }
3843            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3844            Expression::PartitionList(e) => self.generate_partition_list(e),
3845            Expression::PartitionRange(e) => self.generate_partition_range(e),
3846            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3847            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3848            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3849            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3850            Expression::PeriodForSystemTimeConstraint(e) => {
3851                self.generate_period_for_system_time_constraint(e)
3852            }
3853            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3854            Expression::PivotAny(e) => self.generate_pivot_any(e),
3855            Expression::Predict(e) => self.generate_predict(e),
3856            Expression::PreviousDay(e) => self.generate_previous_day(e),
3857            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3858            Expression::PrimaryKeyColumnConstraint(e) => {
3859                self.generate_primary_key_column_constraint(e)
3860            }
3861            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3862            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3863            Expression::OptionsProperty(e) => self.generate_options_property(e),
3864            Expression::Properties(e) => self.generate_properties(e),
3865            Expression::Property(e) => self.generate_property(e),
3866            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3867            Expression::Put(e) => self.generate_put(e),
3868            Expression::Quantile(e) => self.generate_quantile(e),
3869            Expression::QueryBand(e) => self.generate_query_band(e),
3870            Expression::QueryOption(e) => self.generate_query_option(e),
3871            Expression::QueryTransform(e) => self.generate_query_transform(e),
3872            Expression::Randn(e) => self.generate_randn(e),
3873            Expression::Randstr(e) => self.generate_randstr(e),
3874            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3875            Expression::RangeN(e) => self.generate_range_n(e),
3876            Expression::ReadCSV(e) => self.generate_read_csv(e),
3877            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3878            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3879            Expression::Reduce(e) => self.generate_reduce(e),
3880            Expression::Reference(e) => self.generate_reference(e),
3881            Expression::Refresh(e) => self.generate_refresh(e),
3882            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3883            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3884            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3885            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3886            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3887            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3888            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3889            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3890            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3891            Expression::RegrCount(e) => self.generate_regr_count(e),
3892            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3893            Expression::RegrR2(e) => self.generate_regr_r2(e),
3894            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3895            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3896            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3897            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3898            Expression::RegrValx(e) => self.generate_regr_valx(e),
3899            Expression::RegrValy(e) => self.generate_regr_valy(e),
3900            Expression::RemoteWithConnectionModelProperty(e) => {
3901                self.generate_remote_with_connection_model_property(e)
3902            }
3903            Expression::RenameColumn(e) => self.generate_rename_column(e),
3904            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3905            Expression::Returning(e) => self.generate_returning(e),
3906            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3907            Expression::Rollback(e) => self.generate_rollback(e),
3908            Expression::Rollup(e) => self.generate_rollup(e),
3909            Expression::RowFormatDelimitedProperty(e) => {
3910                self.generate_row_format_delimited_property(e)
3911            }
3912            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3913            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3914            Expression::SHA2(e) => self.generate_sha2(e),
3915            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3916            Expression::SafeAdd(e) => self.generate_safe_add(e),
3917            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3918            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3919            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3920            Expression::SampleProperty(e) => self.generate_sample_property(e),
3921            Expression::Schema(e) => self.generate_schema(e),
3922            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3923            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3924            Expression::Search(e) => self.generate_search(e),
3925            Expression::SearchIp(e) => self.generate_search_ip(e),
3926            Expression::SecurityProperty(e) => self.generate_security_property(e),
3927            Expression::SemanticView(e) => self.generate_semantic_view(e),
3928            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3929            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3930            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3931            Expression::Set(e) => self.generate_set(e),
3932            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3933            Expression::SetItem(e) => self.generate_set_item(e),
3934            Expression::SetOperation(e) => self.generate_set_operation(e),
3935            Expression::SetProperty(e) => self.generate_set_property(e),
3936            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3937            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3938            Expression::Slice(e) => self.generate_slice(e),
3939            Expression::SortArray(e) => self.generate_sort_array(e),
3940            Expression::SortBy(e) => self.generate_sort_by(e),
3941            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3942            Expression::SplitPart(e) => self.generate_split_part(e),
3943            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3944            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3945            Expression::StDistance(e) => self.generate_st_distance(e),
3946            Expression::StPoint(e) => self.generate_st_point(e),
3947            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3948            Expression::StandardHash(e) => self.generate_standard_hash(e),
3949            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3950            Expression::StrPosition(e) => self.generate_str_position(e),
3951            Expression::StrToDate(e) => self.generate_str_to_date(e),
3952            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3953            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3954            Expression::StrToMap(e) => self.generate_str_to_map(e),
3955            Expression::StrToTime(e) => self.generate_str_to_time(e),
3956            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3957            Expression::StringToArray(e) => self.generate_string_to_array(e),
3958            Expression::Struct(e) => self.generate_struct(e),
3959            Expression::Stuff(e) => self.generate_stuff(e),
3960            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3961            Expression::Summarize(e) => self.generate_summarize(e),
3962            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3963            Expression::TableAlias(e) => self.generate_table_alias(e),
3964            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3965            Expression::RowsFrom(e) => self.generate_rows_from(e),
3966            Expression::TableSample(e) => self.generate_table_sample(e),
3967            Expression::Tag(e) => self.generate_tag(e),
3968            Expression::Tags(e) => self.generate_tags(e),
3969            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3970            Expression::Time(e) => self.generate_time_func(e),
3971            Expression::TimeAdd(e) => self.generate_time_add(e),
3972            Expression::TimeDiff(e) => self.generate_time_diff(e),
3973            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3974            Expression::TimeSlice(e) => self.generate_time_slice(e),
3975            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3976            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3977            Expression::TimeSub(e) => self.generate_time_sub(e),
3978            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3979            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3980            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3981            Expression::TimeUnit(e) => self.generate_time_unit(e),
3982            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3983            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3984            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3985            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3986            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3987            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3988            Expression::ToBinary(e) => self.generate_to_binary(e),
3989            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3990            Expression::ToChar(e) => self.generate_to_char(e),
3991            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3992            Expression::ToDouble(e) => self.generate_to_double(e),
3993            Expression::ToFile(e) => self.generate_to_file(e),
3994            Expression::ToNumber(e) => self.generate_to_number(e),
3995            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3996            Expression::Transaction(e) => self.generate_transaction(e),
3997            Expression::Transform(e) => self.generate_transform(e),
3998            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3999            Expression::TransientProperty(e) => self.generate_transient_property(e),
4000            Expression::Translate(e) => self.generate_translate(e),
4001            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
4002            Expression::TruncateTable(e) => self.generate_truncate_table(e),
4003            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
4004            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
4005            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
4006            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
4007            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
4008            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
4009            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
4010            Expression::Unhex(e) => self.generate_unhex(e),
4011            Expression::UnicodeString(e) => self.generate_unicode_string(e),
4012            Expression::Uniform(e) => self.generate_uniform(e),
4013            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
4014            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
4015            Expression::RollupProperty(e) => self.generate_rollup_property(e),
4016            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
4017            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
4018            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
4019            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
4020            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
4021            Expression::UtcTime(e) => self.generate_utc_time(e),
4022            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
4023            Expression::Uuid(e) => self.generate_uuid(e),
4024            Expression::Var(v) => {
4025                if matches!(self.config.dialect, Some(DialectType::MySQL))
4026                    && v.this.len() > 2
4027                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
4028                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
4029                {
4030                    return self.generate_identifier(&Identifier {
4031                        name: v.this.clone(),
4032                        quoted: true,
4033                        trailing_comments: Vec::new(),
4034                        span: None,
4035                    });
4036                }
4037                self.write(&v.this);
4038                Ok(())
4039            }
4040            Expression::Variadic(e) => {
4041                self.write_keyword("VARIADIC");
4042                self.write_space();
4043                self.generate_expression(&e.this)?;
4044                Ok(())
4045            }
4046            Expression::VarMap(e) => self.generate_var_map(e),
4047            Expression::VectorSearch(e) => self.generate_vector_search(e),
4048            Expression::Version(e) => self.generate_version(e),
4049            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
4050            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
4051            Expression::WatermarkColumnConstraint(e) => {
4052                self.generate_watermark_column_constraint(e)
4053            }
4054            Expression::Week(e) => self.generate_week(e),
4055            Expression::When(e) => self.generate_when(e),
4056            Expression::Whens(e) => self.generate_whens(e),
4057            Expression::Where(e) => self.generate_where(e),
4058            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4059            Expression::Window(e) => self.generate_window(e),
4060            Expression::WindowSpec(e) => self.generate_window_spec(e),
4061            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4062            Expression::WithFill(e) => self.generate_with_fill(e),
4063            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4064            Expression::WithOperator(e) => self.generate_with_operator(e),
4065            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4066            Expression::WithSchemaBindingProperty(e) => {
4067                self.generate_with_schema_binding_property(e)
4068            }
4069            Expression::WithSystemVersioningProperty(e) => {
4070                self.generate_with_system_versioning_property(e)
4071            }
4072            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4073            Expression::XMLElement(e) => self.generate_xml_element(e),
4074            Expression::XMLGet(e) => self.generate_xml_get(e),
4075            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4076            Expression::XMLTable(e) => self.generate_xml_table(e),
4077            Expression::Xor(e) => self.generate_xor(e),
4078            Expression::Zipf(e) => self.generate_zipf(e),
4079            _ => self.write_unsupported_comment("unsupported expression"),
4080        }
4081    }
4082
4083    fn should_handle_tsql_distinct_null_ordering(&self, select: &Select) -> bool {
4084        matches!(
4085            self.config.dialect,
4086            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4087        ) && !self.config.null_ordering_supported
4088            && select.distinct
4089            && select.order_by.as_ref().is_some_and(|order_by| {
4090                order_by
4091                    .expressions
4092                    .iter()
4093                    .any(Self::ordered_requires_tsql_null_ordering_emulation)
4094            })
4095    }
4096
4097    fn ordered_requires_tsql_null_ordering_emulation(ordered: &Ordered) -> bool {
4098        let Some(nulls_first) = ordered.nulls_first else {
4099            return false;
4100        };
4101
4102        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
4103        let target_default_nulls_first = !ordered.desc;
4104
4105        nulls_first != target_default_nulls_first && !random_ordering
4106    }
4107
4108    fn projection_output_identifier(expression: &Expression) -> Option<Identifier> {
4109        match expression {
4110            Expression::Alias(alias) if !alias.alias.name.is_empty() => Some(alias.alias.clone()),
4111            Expression::Column(column) => Some(column.name.clone()),
4112            Expression::Identifier(identifier) => Some(identifier.clone()),
4113            _ => None,
4114        }
4115    }
4116
4117    fn identifier_names_match(left: &Identifier, right: &Identifier) -> bool {
4118        if left.quoted || right.quoted {
4119            left.name == right.name
4120        } else {
4121            left.name.eq_ignore_ascii_case(&right.name)
4122        }
4123    }
4124
4125    fn expression_matches_identifier(expression: &Expression, identifier: &Identifier) -> bool {
4126        match expression {
4127            Expression::Column(column) if column.table.is_none() => {
4128                Self::identifier_names_match(&column.name, identifier)
4129            }
4130            Expression::Identifier(other) => Self::identifier_names_match(other, identifier),
4131            _ => false,
4132        }
4133    }
4134
4135    fn column_expression(identifier: Identifier) -> Expression {
4136        Expression::Column(Box::new(Column {
4137            name: identifier,
4138            table: None,
4139            join_mark: false,
4140            trailing_comments: Vec::new(),
4141            span: None,
4142            inferred_type: None,
4143        }))
4144    }
4145
4146    fn positional_ordering_index(expression: &Expression) -> Option<usize> {
4147        match expression {
4148            Expression::Literal(lit) => match lit.as_ref() {
4149                Literal::Number(n) => {
4150                    let value = n.parse::<usize>().ok()?;
4151                    value.checked_sub(1)
4152                }
4153                _ => None,
4154            },
4155            _ => None,
4156        }
4157    }
4158
4159    fn projection_sort_expression(expression: &Expression) -> Option<Expression> {
4160        let expression = match expression {
4161            Expression::Alias(alias) => &alias.this,
4162            other => other,
4163        };
4164
4165        match expression {
4166            Expression::Star(_)
4167            | Expression::Literal(_)
4168            | Expression::Null(_)
4169            | Expression::Boolean(_) => None,
4170            _ => Some(expression.clone()),
4171        }
4172    }
4173
4174    fn resolve_positional_order_projection(
4175        projections: &[Expression],
4176        index: usize,
4177    ) -> Option<Expression> {
4178        Self::projection_sort_expression(projections.get(index)?)
4179    }
4180
4181    fn is_direct_grouping_expression(expression: &Expression) -> bool {
4182        match expression {
4183            Expression::Paren(paren) => Self::is_direct_grouping_expression(&paren.this),
4184            Expression::Grouping(_) | Expression::GroupingId(_) => true,
4185            Expression::Function(function) => {
4186                function.name.eq_ignore_ascii_case("GROUPING")
4187                    || function.name.eq_ignore_ascii_case("GROUPING_ID")
4188            }
4189            _ => false,
4190        }
4191    }
4192
4193    fn group_by_has_multiple_grouping_levels(group_by: &GroupBy) -> bool {
4194        group_by
4195            .expressions
4196            .iter()
4197            .any(|expression| match expression {
4198                Expression::Cube(_) | Expression::Rollup(_) => true,
4199                Expression::GroupingSets(grouping_sets) => grouping_sets.expressions.len() > 1,
4200                Expression::Function(function)
4201                    if function.name.eq_ignore_ascii_case("CUBE")
4202                        || function.name.eq_ignore_ascii_case("ROLLUP") =>
4203                {
4204                    !function.args.is_empty()
4205                }
4206                Expression::Function(function)
4207                    if function.name.eq_ignore_ascii_case("GROUPING SETS") =>
4208                {
4209                    function.args.len() > 1
4210                        || function.args.first().is_some_and(|expression| {
4211                            matches!(expression, Expression::Cube(_) | Expression::Rollup(_))
4212                                || matches!(expression,
4213                                    Expression::Function(nested)
4214                                        if nested.name.eq_ignore_ascii_case("CUBE")
4215                                            || nested.name.eq_ignore_ascii_case("ROLLUP")
4216                                )
4217                        })
4218                }
4219                _ => false,
4220            })
4221    }
4222
4223    fn set_output_identifier_at(expression: &Expression, index: usize) -> Option<Identifier> {
4224        match expression {
4225            Expression::Select(select) => {
4226                Self::projection_output_identifier(select.expressions.get(index)?)
4227            }
4228            Expression::Union(union) => Self::set_output_identifier_at(&union.left, index),
4229            Expression::Intersect(intersect) => {
4230                Self::set_output_identifier_at(&intersect.left, index)
4231            }
4232            Expression::Except(except) => Self::set_output_identifier_at(&except.left, index),
4233            Expression::Subquery(subquery) => subquery
4234                .column_aliases
4235                .get(index)
4236                .cloned()
4237                .or_else(|| Self::set_output_identifier_at(&subquery.this, index)),
4238            _ => None,
4239        }
4240    }
4241
4242    fn resolve_single_subquery_star_projection(
4243        select: &Select,
4244        index: usize,
4245    ) -> Option<Expression> {
4246        if select.expressions.len() != 1
4247            || !matches!(select.expressions.first(), Some(Expression::Star(_)))
4248        {
4249            return None;
4250        }
4251
4252        let from = select.from.as_ref()?;
4253        if from.expressions.len() != 1 {
4254            return None;
4255        }
4256
4257        let Expression::Subquery(subquery) = from.expressions.first()? else {
4258            return None;
4259        };
4260
4261        subquery
4262            .column_aliases
4263            .get(index)
4264            .cloned()
4265            .or_else(|| Self::set_output_identifier_at(&subquery.this, index))
4266            .map(Self::column_expression)
4267    }
4268
4269    fn sole_tsql_ordering_source_qualifier(select: &Select) -> Option<Identifier> {
4270        if !select.joins.is_empty() {
4271            return None;
4272        }
4273
4274        let from = select.from.as_ref()?;
4275        if from.expressions.len() != 1 {
4276            return None;
4277        }
4278
4279        match from.expressions.first()? {
4280            Expression::Table(table) => {
4281                Some(table.alias.clone().unwrap_or_else(|| table.name.clone()))
4282            }
4283            Expression::Subquery(subquery) => subquery.alias.clone(),
4284            _ => None,
4285        }
4286    }
4287
4288    fn resolve_duplicate_tsql_ordering_column(
4289        select: &Select,
4290        expression: &Expression,
4291    ) -> Option<Expression> {
4292        let Expression::Column(order_column) = expression else {
4293            return None;
4294        };
4295        if order_column.table.is_some() {
4296            return None;
4297        }
4298
4299        let matching_projections: Vec<_> = select
4300            .expressions
4301            .iter()
4302            .filter(|projection| {
4303                Self::projection_output_identifier(projection).is_some_and(|identifier| {
4304                    Self::identifier_names_match(&identifier, &order_column.name)
4305                })
4306            })
4307            .filter_map(Self::projection_sort_expression)
4308            .collect();
4309
4310        if matching_projections.len() < 2
4311            || matching_projections[1..]
4312                .iter()
4313                .any(|projection| projection != &matching_projections[0])
4314        {
4315            return None;
4316        }
4317
4318        let Expression::Column(projected_column) = &matching_projections[0] else {
4319            return None;
4320        };
4321        if !Self::identifier_names_match(&projected_column.name, &order_column.name) {
4322            return None;
4323        }
4324
4325        let qualifier = projected_column
4326            .table
4327            .clone()
4328            .or_else(|| Self::sole_tsql_ordering_source_qualifier(select))?;
4329        let mut resolved = order_column.as_ref().clone();
4330        resolved.table = Some(qualifier);
4331        Some(Expression::Column(Box::new(resolved)))
4332    }
4333
4334    fn resolve_tsql_null_ordering_for_select(&self, select: &Select) -> Option<Select> {
4335        if !matches!(
4336            self.config.dialect,
4337            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4338        ) || self.config.null_ordering_supported
4339        {
4340            return None;
4341        }
4342
4343        let order_by = select.order_by.as_ref()?;
4344        let grouping_order_is_constant = matches!(self.config.dialect, Some(DialectType::Fabric))
4345            && select
4346                .group_by
4347                .as_ref()
4348                .is_some_and(|group_by| !Self::group_by_has_multiple_grouping_levels(group_by));
4349        let mut resolved_expressions = Vec::with_capacity(order_by.expressions.len());
4350        let mut changed = false;
4351
4352        for mut ordered in order_by.expressions.iter().cloned() {
4353            if Self::ordered_requires_tsql_null_ordering_emulation(&ordered) {
4354                if let Some(index) = Self::positional_ordering_index(&ordered.this) {
4355                    if let Some(resolved) =
4356                        Self::resolve_positional_order_projection(&select.expressions, index)
4357                            .or_else(|| {
4358                                Self::resolve_single_subquery_star_projection(select, index)
4359                            })
4360                    {
4361                        if resolved != ordered.this {
4362                            ordered.this = resolved;
4363                            changed = true;
4364                        }
4365                    }
4366                }
4367
4368                if let Some(resolved) =
4369                    Self::resolve_duplicate_tsql_ordering_column(select, &ordered.this)
4370                {
4371                    ordered.this = resolved;
4372                    changed = true;
4373                }
4374            }
4375
4376            // Fabric rejects ORDER BY expressions that fold to constants (Msg 408).
4377            // With one effective grouping level, a direct GROUPING/GROUPING_ID
4378            // result is constant and contributes no ordering, so omit it.
4379            if grouping_order_is_constant && Self::is_direct_grouping_expression(&ordered.this) {
4380                changed = true;
4381                continue;
4382            }
4383
4384            resolved_expressions.push(ordered);
4385        }
4386
4387        if changed {
4388            let mut resolved_select = select.clone();
4389            resolved_select.order_by = if resolved_expressions.is_empty() {
4390                None
4391            } else {
4392                Some(OrderBy {
4393                    expressions: resolved_expressions,
4394                    siblings: order_by.siblings,
4395                    comments: order_by.comments.clone(),
4396                })
4397            };
4398            Some(resolved_select)
4399        } else {
4400            None
4401        }
4402    }
4403
4404    fn resolve_distinct_order_projection(
4405        projections: &[Expression],
4406        order_expression: &Expression,
4407    ) -> Option<Expression> {
4408        for projection in projections {
4409            if let Expression::Alias(alias) = projection {
4410                if Self::expression_matches_identifier(order_expression, &alias.alias)
4411                    || &alias.this == order_expression
4412                {
4413                    return Some(alias.this.clone());
4414                }
4415            } else if projection == order_expression {
4416                return Some(projection.clone());
4417            } else if let Some(identifier) = Self::projection_output_identifier(projection) {
4418                if Self::expression_matches_identifier(order_expression, &identifier) {
4419                    return Some(projection.clone());
4420                }
4421            }
4422        }
4423
4424        None
4425    }
4426
4427    fn fresh_polyglot_alias(base: &str, index: usize, used_names: &mut Vec<String>) -> String {
4428        let mut suffix = 0;
4429        loop {
4430            let candidate = if suffix == 0 {
4431                format!("{base}_{index}")
4432            } else {
4433                format!("{base}_{index}_{suffix}")
4434            };
4435
4436            if !used_names
4437                .iter()
4438                .any(|name| name.eq_ignore_ascii_case(&candidate))
4439            {
4440                used_names.push(candidate.clone());
4441                return candidate;
4442            }
4443
4444            suffix += 1;
4445        }
4446    }
4447
4448    fn tsql_null_ordering_case(expression: Expression) -> Expression {
4449        Expression::Case(Box::new(Case {
4450            operand: None,
4451            whens: vec![(
4452                Expression::IsNull(Box::new(IsNull {
4453                    this: expression,
4454                    not: false,
4455                    postfix_form: false,
4456                })),
4457                Expression::number(1),
4458            )],
4459            else_: Some(Expression::number(0)),
4460            comments: Vec::new(),
4461            inferred_type: None,
4462        }))
4463    }
4464
4465    fn try_build_tsql_distinct_null_ordering_wrapper(&self, select: &Select) -> Option<Select> {
4466        let order_by = select.order_by.as_ref()?;
4467
4468        if order_by.siblings
4469            || select.distinct_on.is_some()
4470            || select.distribute_by.is_some()
4471            || select.cluster_by.is_some()
4472            || select.sort_by.is_some()
4473            || select.limit.is_some()
4474            || select.offset.is_some()
4475            || select.limit_by.is_some()
4476            || select.fetch.is_some()
4477            || select.top.is_some()
4478            || select.settings.is_some()
4479            || select.format.is_some()
4480            || select.kind.is_some()
4481            || select.hint.is_some()
4482            || select.into.is_some()
4483            || !select.locks.is_empty()
4484            || !select.for_xml.is_empty()
4485            || !select.for_json.is_empty()
4486            || !select.operation_modifiers.is_empty()
4487            || select.option.is_some()
4488            || select.exclude.is_some()
4489        {
4490            return None;
4491        }
4492
4493        let projection_identifiers: Vec<_> = select
4494            .expressions
4495            .iter()
4496            .map(Self::projection_output_identifier)
4497            .collect::<Option<_>>()?;
4498        let mut used_names: Vec<_> = projection_identifiers
4499            .iter()
4500            .map(|identifier| identifier.name.clone())
4501            .collect();
4502
4503        let mut inner = select.clone();
4504        inner.order_by = None;
4505
4506        let outer_with = inner.with.take();
4507        let outer_leading_comments = std::mem::take(&mut inner.leading_comments);
4508        let outer_post_select_comments = std::mem::take(&mut inner.post_select_comments);
4509
4510        let mut outer_order_expressions = Vec::with_capacity(order_by.expressions.len() * 2);
4511        for (index, ordered) in order_by.expressions.iter().enumerate() {
4512            if ordered.with_fill.is_some() {
4513                return None;
4514            }
4515
4516            let sort_expression =
4517                Self::resolve_distinct_order_projection(&select.expressions, &ordered.this)?;
4518
4519            if Self::ordered_requires_tsql_null_ordering_emulation(ordered) {
4520                let null_alias =
4521                    Self::fresh_polyglot_alias("_polyglot_order_null", index, &mut used_names);
4522                inner.expressions.push(
4523                    Self::tsql_null_ordering_case(sort_expression.clone())
4524                        .alias(null_alias.clone()),
4525                );
4526                outer_order_expressions.push(Ordered {
4527                    this: Expression::column(null_alias),
4528                    desc: ordered.nulls_first == Some(true),
4529                    nulls_first: None,
4530                    explicit_asc: false,
4531                    with_fill: None,
4532                });
4533            }
4534
4535            let key_alias =
4536                Self::fresh_polyglot_alias("_polyglot_order_key", index, &mut used_names);
4537            inner
4538                .expressions
4539                .push(sort_expression.alias(key_alias.clone()));
4540            outer_order_expressions.push(Ordered {
4541                this: Expression::column(key_alias),
4542                desc: ordered.desc,
4543                nulls_first: None,
4544                explicit_asc: ordered.explicit_asc,
4545                with_fill: None,
4546            });
4547        }
4548
4549        let subquery = Subquery {
4550            this: Expression::Select(Box::new(inner)),
4551            alias: Some(Identifier::new("_polyglot_distinct_order")),
4552            column_aliases: Vec::new(),
4553            alias_explicit_as: true,
4554            alias_keyword: None,
4555            order_by: None,
4556            limit: None,
4557            offset: None,
4558            distribute_by: None,
4559            sort_by: None,
4560            cluster_by: None,
4561            lateral: false,
4562            modifiers_inside: false,
4563            trailing_comments: Vec::new(),
4564            inferred_type: None,
4565        };
4566
4567        let mut outer = Select::new();
4568        outer.with = outer_with;
4569        outer.leading_comments = outer_leading_comments;
4570        outer.post_select_comments = outer_post_select_comments;
4571        outer.expressions = projection_identifiers
4572            .into_iter()
4573            .map(Self::column_expression)
4574            .collect();
4575        outer.from = Some(From {
4576            expressions: vec![Expression::Subquery(Box::new(subquery))],
4577        });
4578        outer.order_by = Some(OrderBy {
4579            expressions: outer_order_expressions,
4580            siblings: false,
4581            comments: order_by.comments.clone(),
4582        });
4583
4584        Some(outer)
4585    }
4586
4587    fn generate_select(&mut self, select: &Select) -> Result<()> {
4588        use crate::dialects::DialectType;
4589
4590        if let Some(resolved_select) = self.resolve_tsql_null_ordering_for_select(select) {
4591            return self.generate_select(&resolved_select);
4592        }
4593
4594        if self.should_handle_tsql_distinct_null_ordering(select) {
4595            if let Some(wrapped_select) = self.try_build_tsql_distinct_null_ordering_wrapper(select)
4596            {
4597                return self.generate_select(&wrapped_select);
4598            }
4599
4600            self.unsupported(
4601                "SELECT DISTINCT with emulated NULL ordering is not supported for TSQL/Fabric",
4602            )?;
4603        }
4604
4605        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4606        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4607        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4608        if let Some(exclude) = &select.exclude {
4609            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4610                // Build the inner select (same as original but without exclude)
4611                let mut inner_select = select.clone();
4612                inner_select.exclude = None;
4613                let inner_expr = Expression::Select(Box::new(inner_select));
4614
4615                // Build the subquery
4616                let subquery = crate::expressions::Subquery {
4617                    this: inner_expr,
4618                    alias: None,
4619                    column_aliases: Vec::new(),
4620                    alias_explicit_as: false,
4621                    alias_keyword: None,
4622                    order_by: None,
4623                    limit: None,
4624                    offset: None,
4625                    distribute_by: None,
4626                    sort_by: None,
4627                    cluster_by: None,
4628                    lateral: false,
4629                    modifiers_inside: false,
4630                    trailing_comments: Vec::new(),
4631                    inferred_type: None,
4632                };
4633
4634                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4635                let star = Expression::Star(crate::expressions::Star {
4636                    table: None,
4637                    except: Some(
4638                        exclude
4639                            .iter()
4640                            .map(|e| match e {
4641                                Expression::Column(col) => col.name.clone(),
4642                                Expression::Identifier(id) => id.clone(),
4643                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4644                            })
4645                            .collect(),
4646                    ),
4647                    replace: None,
4648                    rename: None,
4649                    trailing_comments: Vec::new(),
4650                    span: None,
4651                });
4652
4653                let outer_select = Select {
4654                    expressions: vec![star],
4655                    from: Some(crate::expressions::From {
4656                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4657                    }),
4658                    ..Select::new()
4659                };
4660
4661                return self.generate_select(&outer_select);
4662            }
4663        }
4664
4665        // Output leading comments before SELECT
4666        for comment in &select.leading_comments {
4667            self.write_formatted_comment(comment);
4668            self.write(" ");
4669        }
4670
4671        // WITH clause
4672        if let Some(with) = &select.with {
4673            self.generate_with(with)?;
4674            if self.config.pretty {
4675                self.write_newline();
4676                self.write_indent();
4677            } else {
4678                self.write_space();
4679            }
4680        }
4681
4682        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4683        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4684        for comment in &select.post_select_comments {
4685            self.write_formatted_comment(comment);
4686            self.write(" ");
4687        }
4688
4689        self.write_keyword("SELECT");
4690
4691        // Generate query hint if present /*+ ... */
4692        if let Some(hint) = &select.hint {
4693            self.generate_hint(hint)?;
4694        }
4695
4696        // For SQL Server, convert LIMIT to TOP (structural transformation)
4697        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4698        // TOP clause (SQL Server style - before DISTINCT)
4699        let use_top_from_limit = matches!(
4700            self.config.dialect,
4701            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4702        ) && select.top.is_none()
4703            && select
4704                .limit
4705                .as_ref()
4706                .is_some_and(|limit| !Self::is_noop_limit_expr(&limit.this))
4707            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4708
4709        // For TOP-supporting dialects: DISTINCT before TOP
4710        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4711        let is_top_dialect = matches!(
4712            self.config.dialect,
4713            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4714        );
4715        let keep_top_verbatim = !is_top_dialect
4716            && select.limit.is_none()
4717            && select
4718                .top
4719                .as_ref()
4720                .map_or(false, |top| top.percent || top.with_ties);
4721
4722        if select.distinct && (is_top_dialect || select.top.is_some()) {
4723            self.write_space();
4724            self.write_keyword("DISTINCT");
4725        }
4726
4727        if is_top_dialect || keep_top_verbatim {
4728            if let Some(top) = &select.top {
4729                self.write_space();
4730                self.write_keyword("TOP");
4731                if top.parenthesized {
4732                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4733                        self.write_space();
4734                        self.generate_expression(&top.this)?;
4735                    } else {
4736                        self.write(" (");
4737                        self.generate_expression(&top.this)?;
4738                        self.write(")");
4739                    }
4740                } else {
4741                    self.write_space();
4742                    self.generate_expression(&top.this)?;
4743                }
4744                if top.percent {
4745                    self.write_space();
4746                    self.write_keyword("PERCENT");
4747                }
4748                if top.with_ties {
4749                    self.write_space();
4750                    self.write_keyword("WITH TIES");
4751                }
4752            } else if use_top_from_limit {
4753                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4754                if let Some(limit) = &select.limit {
4755                    self.write_space();
4756                    self.write_keyword("TOP");
4757                    // Use parentheses for complex expressions, but not for simple literals
4758                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4759                    if is_simple_literal {
4760                        self.write_space();
4761                        self.generate_expression(&limit.this)?;
4762                    } else {
4763                        self.write(" (");
4764                        self.generate_expression(&limit.this)?;
4765                        self.write(")");
4766                    }
4767                }
4768            }
4769        }
4770
4771        if select.distinct && !is_top_dialect && select.top.is_none() {
4772            self.write_space();
4773            self.write_keyword("DISTINCT");
4774        }
4775
4776        // DISTINCT ON clause (PostgreSQL)
4777        if let Some(distinct_on) = &select.distinct_on {
4778            self.write_space();
4779            self.write_keyword("ON");
4780            self.write(" (");
4781            for (i, expr) in distinct_on.iter().enumerate() {
4782                if i > 0 {
4783                    self.write(", ");
4784                }
4785                self.generate_expression(expr)?;
4786            }
4787            self.write(")");
4788        }
4789
4790        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4791        for modifier in &select.operation_modifiers {
4792            self.write_space();
4793            self.write_keyword(modifier);
4794        }
4795
4796        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4797        if let Some(kind) = &select.kind {
4798            self.write_space();
4799            self.write_keyword("AS");
4800            self.write_space();
4801            self.write_keyword(kind);
4802        }
4803
4804        // Expressions (only if there are any)
4805        if !select.expressions.is_empty() {
4806            if self.config.pretty {
4807                self.write_newline();
4808                self.indent_level += 1;
4809            } else {
4810                self.write_space();
4811            }
4812        }
4813
4814        for (i, expr) in select.expressions.iter().enumerate() {
4815            if i > 0 {
4816                self.write(",");
4817                if self.config.pretty {
4818                    self.write_newline();
4819                } else {
4820                    self.write_space();
4821                }
4822            }
4823            if self.config.pretty {
4824                self.write_indent();
4825            }
4826            self.generate_expression(expr)?;
4827        }
4828
4829        if self.config.pretty && !select.expressions.is_empty() {
4830            self.indent_level -= 1;
4831        }
4832
4833        // Redshift-style EXCLUDE clause at the end of the projection list
4834        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4835        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4836        // (done after the full select is generated below)
4837        if let Some(exclude) = &select.exclude {
4838            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4839                self.write_space();
4840                self.write_keyword("EXCLUDE");
4841                self.write(" (");
4842                for (i, col) in exclude.iter().enumerate() {
4843                    if i > 0 {
4844                        self.write(", ");
4845                    }
4846                    self.generate_expression(col)?;
4847                }
4848                self.write(")");
4849            }
4850        }
4851
4852        // INTO clause (SELECT ... INTO table_name)
4853        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4854        if let Some(into) = &select.into {
4855            if self.config.pretty {
4856                self.write_newline();
4857                self.write_indent();
4858            } else {
4859                self.write_space();
4860            }
4861            if into.bulk_collect {
4862                self.write_keyword("BULK COLLECT INTO");
4863            } else {
4864                self.write_keyword("INTO");
4865            }
4866            if into.temporary {
4867                self.write_space();
4868                self.write_keyword("TEMPORARY");
4869            }
4870            if into.unlogged {
4871                self.write_space();
4872                self.write_keyword("UNLOGGED");
4873            }
4874            self.write_space();
4875            // If we have multiple expressions, output them comma-separated
4876            if !into.expressions.is_empty() {
4877                for (i, expr) in into.expressions.iter().enumerate() {
4878                    if i > 0 {
4879                        self.write(", ");
4880                    }
4881                    self.generate_expression(expr)?;
4882                }
4883            } else {
4884                self.generate_expression(&into.this)?;
4885            }
4886        }
4887
4888        // FROM clause
4889        if let Some(from) = &select.from {
4890            if self.config.pretty {
4891                self.write_newline();
4892                self.write_indent();
4893            } else {
4894                self.write_space();
4895            }
4896            self.write_keyword("FROM");
4897            self.write_space();
4898
4899            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4900            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4901            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4902            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4903            let has_tablesample = from
4904                .expressions
4905                .iter()
4906                .any(|e| matches!(e, Expression::TableSample(_)));
4907            let is_cross_join_dialect = matches!(
4908                self.config.dialect,
4909                Some(DialectType::BigQuery)
4910                    | Some(DialectType::Hive)
4911                    | Some(DialectType::Spark)
4912                    | Some(DialectType::Databricks)
4913                    | Some(DialectType::SQLite)
4914                    | Some(DialectType::ClickHouse)
4915            );
4916            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4917            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4918            let source_is_same_as_target = self.config.source_dialect.is_some()
4919                && self.config.source_dialect == self.config.dialect;
4920            let source_is_cross_join_dialect = matches!(
4921                self.config.source_dialect,
4922                Some(DialectType::BigQuery)
4923                    | Some(DialectType::Hive)
4924                    | Some(DialectType::Spark)
4925                    | Some(DialectType::Databricks)
4926                    | Some(DialectType::SQLite)
4927                    | Some(DialectType::ClickHouse)
4928            );
4929            let use_cross_join = !has_tablesample
4930                && is_cross_join_dialect
4931                && (source_is_same_as_target
4932                    || source_is_cross_join_dialect
4933                    || self.config.source_dialect.is_none());
4934
4935            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4936            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4937
4938            for (i, expr) in from.expressions.iter().enumerate() {
4939                if i > 0 {
4940                    if use_cross_join {
4941                        self.write(" CROSS JOIN ");
4942                    } else {
4943                        self.write(", ");
4944                    }
4945                }
4946                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4947                    self.write("(");
4948                    self.generate_expression(expr)?;
4949                    self.write(")");
4950                } else {
4951                    self.generate_expression(expr)?;
4952                }
4953                // Output leading comments that were on the table name before FROM
4954                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4955                let leading = Self::extract_table_leading_comments(expr);
4956                for comment in &leading {
4957                    self.write_space();
4958                    self.write_formatted_comment(comment);
4959                }
4960            }
4961        }
4962
4963        // JOINs - handle nested join structure for pretty printing
4964        // Deferred-condition joins "own" the non-deferred joins that follow them
4965        // until the next deferred join or end of list
4966        if self.config.pretty {
4967            self.generate_joins_with_nesting(&select.joins)?;
4968        } else {
4969            for join in &select.joins {
4970                self.generate_join(join)?;
4971            }
4972            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4973            for join in select.joins.iter().rev() {
4974                if join.deferred_condition {
4975                    self.generate_join_condition(join)?;
4976                }
4977            }
4978        }
4979
4980        // LATERAL VIEW clauses (Hive/Spark)
4981        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4982            self.generate_lateral_view(lateral_view, lv_idx)?;
4983        }
4984
4985        // PREWHERE (ClickHouse)
4986        if let Some(prewhere) = &select.prewhere {
4987            self.write_clause_condition("PREWHERE", prewhere)?;
4988        }
4989
4990        // WHERE
4991        if let Some(where_clause) = &select.where_clause {
4992            self.write_clause_condition("WHERE", &where_clause.this)?;
4993        }
4994
4995        // CONNECT BY (Oracle hierarchical queries)
4996        if let Some(connect) = &select.connect {
4997            self.generate_connect(connect)?;
4998        }
4999
5000        // GROUP BY
5001        if let Some(group_by) = &select.group_by {
5002            if self.config.pretty {
5003                // Output leading comments on their own lines before GROUP BY
5004                for comment in &group_by.comments {
5005                    self.write_newline();
5006                    self.write_indent();
5007                    self.write_formatted_comment(comment);
5008                }
5009                self.write_newline();
5010                self.write_indent();
5011            } else {
5012                self.write_space();
5013                // In non-pretty mode, output comments inline
5014                for comment in &group_by.comments {
5015                    self.write_formatted_comment(comment);
5016                    self.write_space();
5017                }
5018            }
5019            let clickhouse_bare_modifiers =
5020                matches!(self.config.dialect, Some(DialectType::ClickHouse))
5021                    && group_by.all.is_none()
5022                    && (group_by.totals || !group_by.expressions.is_empty())
5023                    && group_by.expressions.iter().all(|expr| match expr {
5024                        Expression::Cube(c) => c.expressions.is_empty(),
5025                        Expression::Rollup(r) => r.expressions.is_empty(),
5026                        _ => false,
5027                    });
5028
5029            if clickhouse_bare_modifiers {
5030                let trailing_cube = group_by
5031                    .expressions
5032                    .iter()
5033                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
5034                let trailing_rollup = group_by
5035                    .expressions
5036                    .iter()
5037                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
5038
5039                if trailing_cube {
5040                    self.write_keyword("WITH CUBE");
5041                } else if trailing_rollup {
5042                    self.write_keyword("WITH ROLLUP");
5043                }
5044
5045                if group_by.totals {
5046                    if trailing_cube || trailing_rollup {
5047                        self.write_space();
5048                    }
5049                    self.write_keyword("WITH TOTALS");
5050                }
5051            } else {
5052                self.write_keyword("GROUP BY");
5053                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
5054                match group_by.all {
5055                    Some(true) => {
5056                        self.write_space();
5057                        self.write_keyword("ALL");
5058                    }
5059                    Some(false) => {
5060                        self.write_space();
5061                        self.write_keyword("DISTINCT");
5062                    }
5063                    None => {}
5064                }
5065                if !group_by.expressions.is_empty() {
5066                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
5067                    // These are represented as Cube/Rollup expressions with empty expressions at the end
5068                    let mut trailing_cube = false;
5069                    let mut trailing_rollup = false;
5070                    let mut plain_expressions: Vec<&Expression> = Vec::new();
5071                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
5072                    let mut cube_expressions: Vec<&Expression> = Vec::new();
5073                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
5074
5075                    for expr in &group_by.expressions {
5076                        match expr {
5077                            Expression::Cube(c) if c.expressions.is_empty() => {
5078                                trailing_cube = true;
5079                            }
5080                            Expression::Rollup(r) if r.expressions.is_empty() => {
5081                                trailing_rollup = true;
5082                            }
5083                            Expression::Function(f) if f.name == "CUBE" => {
5084                                cube_expressions.push(expr);
5085                            }
5086                            Expression::Function(f) if f.name == "ROLLUP" => {
5087                                rollup_expressions.push(expr);
5088                            }
5089                            Expression::Function(f) if f.name == "GROUPING SETS" => {
5090                                grouping_sets_expressions.push(expr);
5091                            }
5092                            _ => {
5093                                plain_expressions.push(expr);
5094                            }
5095                        }
5096                    }
5097
5098                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
5099                    let mut regular_expressions: Vec<&Expression> = Vec::new();
5100                    regular_expressions.extend(plain_expressions);
5101                    regular_expressions.extend(grouping_sets_expressions);
5102                    regular_expressions.extend(cube_expressions);
5103                    regular_expressions.extend(rollup_expressions);
5104
5105                    if self.config.pretty {
5106                        self.write_newline();
5107                        self.indent_level += 1;
5108                        self.write_indent();
5109                    } else {
5110                        self.write_space();
5111                    }
5112
5113                    for (i, expr) in regular_expressions.iter().enumerate() {
5114                        if i > 0 {
5115                            if self.config.pretty {
5116                                self.write(",");
5117                                self.write_newline();
5118                                self.write_indent();
5119                            } else {
5120                                self.write(", ");
5121                            }
5122                        }
5123                        self.generate_expression(expr)?;
5124                    }
5125
5126                    if self.config.pretty {
5127                        self.indent_level -= 1;
5128                    }
5129
5130                    // Output trailing WITH CUBE or WITH ROLLUP
5131                    if trailing_cube {
5132                        self.write_space();
5133                        self.write_keyword("WITH CUBE");
5134                    } else if trailing_rollup {
5135                        self.write_space();
5136                        self.write_keyword("WITH ROLLUP");
5137                    }
5138                }
5139
5140                // ClickHouse: WITH TOTALS
5141                if group_by.totals {
5142                    self.write_space();
5143                    self.write_keyword("WITH TOTALS");
5144                }
5145            }
5146        }
5147
5148        // HAVING
5149        if let Some(having) = &select.having {
5150            if self.config.pretty {
5151                // Output leading comments on their own lines before HAVING
5152                for comment in &having.comments {
5153                    self.write_newline();
5154                    self.write_indent();
5155                    self.write_formatted_comment(comment);
5156                }
5157            } else {
5158                for comment in &having.comments {
5159                    self.write_space();
5160                    self.write_formatted_comment(comment);
5161                }
5162            }
5163            self.write_clause_condition("HAVING", &having.this)?;
5164        }
5165
5166        // QUALIFY and WINDOW clause ordering depends on input SQL
5167        if select.qualify_after_window {
5168            // WINDOW before QUALIFY (DuckDB style)
5169            if let Some(windows) = &select.windows {
5170                self.write_window_clause(windows)?;
5171            }
5172            if let Some(qualify) = &select.qualify {
5173                self.write_clause_condition("QUALIFY", &qualify.this)?;
5174            }
5175        } else {
5176            // QUALIFY before WINDOW (Snowflake/BigQuery default)
5177            if let Some(qualify) = &select.qualify {
5178                self.write_clause_condition("QUALIFY", &qualify.this)?;
5179            }
5180            if let Some(windows) = &select.windows {
5181                self.write_window_clause(windows)?;
5182            }
5183        }
5184
5185        // DISTRIBUTE BY (Hive/Spark)
5186        if let Some(distribute_by) = &select.distribute_by {
5187            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
5188        }
5189
5190        // CLUSTER BY (Hive/Spark)
5191        if let Some(cluster_by) = &select.cluster_by {
5192            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
5193        }
5194
5195        // SORT BY (Hive/Spark - comes before ORDER BY)
5196        if let Some(sort_by) = &select.sort_by {
5197            self.write_order_clause("SORT BY", &sort_by.expressions)?;
5198        }
5199
5200        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
5201        if let Some(order_by) = &select.order_by {
5202            if self.config.pretty {
5203                // Output leading comments on their own lines before ORDER BY
5204                for comment in &order_by.comments {
5205                    self.write_newline();
5206                    self.write_indent();
5207                    self.write_formatted_comment(comment);
5208                }
5209            } else {
5210                for comment in &order_by.comments {
5211                    self.write_space();
5212                    self.write_formatted_comment(comment);
5213                }
5214            }
5215            let keyword = if order_by.siblings {
5216                "ORDER SIBLINGS BY"
5217            } else {
5218                "ORDER BY"
5219            };
5220            self.write_order_clause(keyword, &order_by.expressions)?;
5221        }
5222
5223        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
5224        if select.order_by.is_none()
5225            && select.fetch.is_some()
5226            && matches!(
5227                self.config.dialect,
5228                Some(DialectType::TSQL) | Some(DialectType::Fabric)
5229            )
5230        {
5231            if self.config.pretty {
5232                self.write_newline();
5233                self.write_indent();
5234            } else {
5235                self.write_space();
5236            }
5237            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
5238        }
5239
5240        // LIMIT and OFFSET
5241        // PostgreSQL and others use: LIMIT count OFFSET offset
5242        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
5243        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
5244        let is_presto_like = matches!(
5245            self.config.dialect,
5246            Some(DialectType::Presto) | Some(DialectType::Trino)
5247        );
5248
5249        if is_presto_like && select.offset.is_some() {
5250            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
5251            if let Some(offset) = &select.offset {
5252                if self.config.pretty {
5253                    self.write_newline();
5254                    self.write_indent();
5255                } else {
5256                    self.write_space();
5257                }
5258                self.write_keyword("OFFSET");
5259                self.write_space();
5260                self.write_limit_expr(&offset.this)?;
5261                if offset.rows == Some(true) {
5262                    self.write_space();
5263                    self.write_keyword("ROWS");
5264                }
5265            }
5266            if let Some(limit) = &select.limit {
5267                if self.config.pretty {
5268                    self.write_newline();
5269                    self.write_indent();
5270                } else {
5271                    self.write_space();
5272                }
5273                self.write_keyword("LIMIT");
5274                self.write_space();
5275                self.write_limit_expr(&limit.this)?;
5276                if limit.percent {
5277                    self.write_space();
5278                    self.write_keyword("PERCENT");
5279                }
5280                // Emit any comments that were captured from before the LIMIT keyword
5281                for comment in &limit.comments {
5282                    self.write(" ");
5283                    self.write_formatted_comment(comment);
5284                }
5285            }
5286        } else {
5287            // Check if FETCH will be converted to LIMIT (used for ordering)
5288            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
5289                !fetch.percent
5290                    && !fetch.with_ties
5291                    && fetch.count.is_some()
5292                    && matches!(
5293                        self.config.dialect,
5294                        Some(DialectType::Spark)
5295                            | Some(DialectType::Hive)
5296                            | Some(DialectType::DuckDB)
5297                            | Some(DialectType::SQLite)
5298                            | Some(DialectType::MySQL)
5299                            | Some(DialectType::BigQuery)
5300                            | Some(DialectType::Databricks)
5301                            | Some(DialectType::StarRocks)
5302                            | Some(DialectType::Doris)
5303                            | Some(DialectType::Athena)
5304                            | Some(DialectType::ClickHouse)
5305                            | Some(DialectType::Redshift)
5306                    )
5307            });
5308
5309            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
5310            if let Some(limit) = &select.limit {
5311                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
5312                if !matches!(
5313                    self.config.dialect,
5314                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5315                ) {
5316                    if self.config.pretty {
5317                        self.write_newline();
5318                        self.write_indent();
5319                    } else {
5320                        self.write_space();
5321                    }
5322                    self.write_keyword("LIMIT");
5323                    self.write_space();
5324                    self.write_limit_expr(&limit.this)?;
5325                    if limit.percent {
5326                        self.write_space();
5327                        self.write_keyword("PERCENT");
5328                    }
5329                    // Emit any comments that were captured from before the LIMIT keyword
5330                    for comment in &limit.comments {
5331                        self.write(" ");
5332                        self.write_formatted_comment(comment);
5333                    }
5334                }
5335            }
5336
5337            // Convert TOP to LIMIT for non-TOP dialects
5338            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
5339                if let Some(top) = &select.top {
5340                    if !top.percent && !top.with_ties {
5341                        if self.config.pretty {
5342                            self.write_newline();
5343                            self.write_indent();
5344                        } else {
5345                            self.write_space();
5346                        }
5347                        self.write_keyword("LIMIT");
5348                        self.write_space();
5349                        self.generate_expression(&top.this)?;
5350                    }
5351                }
5352            }
5353
5354            // If FETCH will be converted to LIMIT and there's also OFFSET,
5355            // emit LIMIT from FETCH BEFORE the OFFSET
5356            if fetch_as_limit && select.offset.is_some() {
5357                if let Some(fetch) = &select.fetch {
5358                    if self.config.pretty {
5359                        self.write_newline();
5360                        self.write_indent();
5361                    } else {
5362                        self.write_space();
5363                    }
5364                    self.write_keyword("LIMIT");
5365                    self.write_space();
5366                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5367                }
5368            }
5369
5370            // OFFSET
5371            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
5372            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
5373            if let Some(offset) = &select.offset {
5374                if self.config.pretty {
5375                    self.write_newline();
5376                    self.write_indent();
5377                } else {
5378                    self.write_space();
5379                }
5380                if matches!(
5381                    self.config.dialect,
5382                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5383                ) {
5384                    // SQL Server 2012+ OFFSET ... FETCH syntax
5385                    self.write_keyword("OFFSET");
5386                    self.write_space();
5387                    self.write_limit_expr(&offset.this)?;
5388                    self.write_space();
5389                    self.write_keyword("ROWS");
5390                    // If there was a real LIMIT, use FETCH NEXT ... ROWS ONLY.
5391                    // PostgreSQL LIMIT NULL / LIMIT ALL mean "no limit", so
5392                    // T-SQL/Fabric should keep the OFFSET without FETCH.
5393                    if let Some(limit) = &select.limit {
5394                        if !Self::is_noop_limit_expr(&limit.this) {
5395                            self.write_space();
5396                            self.write_keyword("FETCH NEXT");
5397                            self.write_space();
5398                            self.write_limit_expr(&limit.this)?;
5399                            self.write_space();
5400                            self.write_keyword("ROWS ONLY");
5401                        }
5402                    }
5403                } else {
5404                    self.write_keyword("OFFSET");
5405                    self.write_space();
5406                    self.write_limit_expr(&offset.this)?;
5407                    // Output ROWS keyword if it was in the original SQL
5408                    if offset.rows == Some(true) {
5409                        self.write_space();
5410                        self.write_keyword("ROWS");
5411                    }
5412                }
5413            }
5414        }
5415
5416        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
5417        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5418            if let Some(limit_by) = &select.limit_by {
5419                if !limit_by.is_empty() {
5420                    self.write_space();
5421                    self.write_keyword("BY");
5422                    self.write_space();
5423                    for (i, expr) in limit_by.iter().enumerate() {
5424                        if i > 0 {
5425                            self.write(", ");
5426                        }
5427                        self.generate_expression(expr)?;
5428                    }
5429                }
5430            }
5431        }
5432
5433        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
5434        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5435            if let Some(settings) = &select.settings {
5436                if self.config.pretty {
5437                    self.write_newline();
5438                    self.write_indent();
5439                } else {
5440                    self.write_space();
5441                }
5442                self.write_keyword("SETTINGS");
5443                self.write_space();
5444                for (i, expr) in settings.iter().enumerate() {
5445                    if i > 0 {
5446                        self.write(", ");
5447                    }
5448                    self.generate_expression(expr)?;
5449                }
5450            }
5451
5452            if let Some(format_expr) = &select.format {
5453                if self.config.pretty {
5454                    self.write_newline();
5455                    self.write_indent();
5456                } else {
5457                    self.write_space();
5458                }
5459                self.write_keyword("FORMAT");
5460                self.write_space();
5461                self.generate_expression(format_expr)?;
5462            }
5463        }
5464
5465        // FETCH FIRST/NEXT
5466        if let Some(fetch) = &select.fetch {
5467            // Check if we already emitted LIMIT from FETCH before OFFSET
5468            let fetch_already_as_limit = select.offset.is_some()
5469                && !fetch.percent
5470                && !fetch.with_ties
5471                && fetch.count.is_some()
5472                && matches!(
5473                    self.config.dialect,
5474                    Some(DialectType::Spark)
5475                        | Some(DialectType::Hive)
5476                        | Some(DialectType::DuckDB)
5477                        | Some(DialectType::SQLite)
5478                        | Some(DialectType::MySQL)
5479                        | Some(DialectType::BigQuery)
5480                        | Some(DialectType::Databricks)
5481                        | Some(DialectType::StarRocks)
5482                        | Some(DialectType::Doris)
5483                        | Some(DialectType::Athena)
5484                        | Some(DialectType::ClickHouse)
5485                        | Some(DialectType::Redshift)
5486                );
5487
5488            if fetch_already_as_limit {
5489                // Already emitted as LIMIT before OFFSET, skip
5490            } else {
5491                if self.config.pretty {
5492                    self.write_newline();
5493                    self.write_indent();
5494                } else {
5495                    self.write_space();
5496                }
5497
5498                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
5499                let use_limit = !fetch.percent
5500                    && !fetch.with_ties
5501                    && fetch.count.is_some()
5502                    && matches!(
5503                        self.config.dialect,
5504                        Some(DialectType::Spark)
5505                            | Some(DialectType::Hive)
5506                            | Some(DialectType::DuckDB)
5507                            | Some(DialectType::SQLite)
5508                            | Some(DialectType::MySQL)
5509                            | Some(DialectType::BigQuery)
5510                            | Some(DialectType::Databricks)
5511                            | Some(DialectType::StarRocks)
5512                            | Some(DialectType::Doris)
5513                            | Some(DialectType::Athena)
5514                            | Some(DialectType::ClickHouse)
5515                            | Some(DialectType::Redshift)
5516                    );
5517
5518                if use_limit {
5519                    self.write_keyword("LIMIT");
5520                    self.write_space();
5521                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5522                } else {
5523                    self.write_keyword("FETCH");
5524                    self.write_space();
5525                    self.write_keyword(&fetch.direction);
5526                    if let Some(ref count) = fetch.count {
5527                        self.write_space();
5528                        self.generate_expression(count)?;
5529                    }
5530                    if fetch.percent {
5531                        self.write_space();
5532                        self.write_keyword("PERCENT");
5533                    }
5534                    if fetch.rows {
5535                        self.write_space();
5536                        self.write_keyword("ROWS");
5537                    }
5538                    if fetch.with_ties {
5539                        self.write_space();
5540                        self.write_keyword("WITH TIES");
5541                    } else {
5542                        self.write_space();
5543                        self.write_keyword("ONLY");
5544                    }
5545                }
5546            } // close fetch_already_as_limit else
5547        }
5548
5549        // SAMPLE / TABLESAMPLE
5550        if let Some(sample) = &select.sample {
5551            use crate::dialects::DialectType;
5552            if self.config.pretty {
5553                self.write_newline();
5554            } else {
5555                self.write_space();
5556            }
5557
5558            if sample.is_using_sample {
5559                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5560                self.write_keyword("USING SAMPLE");
5561                self.generate_sample_body(sample)?;
5562            } else {
5563                self.write_keyword("TABLESAMPLE");
5564
5565                // Snowflake defaults to BERNOULLI when no explicit method is given
5566                let snowflake_bernoulli =
5567                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5568                        && !sample.explicit_method;
5569                if snowflake_bernoulli {
5570                    self.write_space();
5571                    self.write_keyword("BERNOULLI");
5572                }
5573
5574                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5575                if matches!(sample.method, SampleMethod::Bucket) {
5576                    self.write_space();
5577                    self.write("(");
5578                    self.write_keyword("BUCKET");
5579                    self.write_space();
5580                    if let Some(ref num) = sample.bucket_numerator {
5581                        self.generate_expression(num)?;
5582                    }
5583                    self.write_space();
5584                    self.write_keyword("OUT OF");
5585                    self.write_space();
5586                    if let Some(ref denom) = sample.bucket_denominator {
5587                        self.generate_expression(denom)?;
5588                    }
5589                    if let Some(ref field) = sample.bucket_field {
5590                        self.write_space();
5591                        self.write_keyword("ON");
5592                        self.write_space();
5593                        self.generate_expression(field)?;
5594                    }
5595                    self.write(")");
5596                } else if sample.unit_after_size {
5597                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5598                    if sample.explicit_method && sample.method_before_size {
5599                        self.write_space();
5600                        match sample.method {
5601                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5602                            SampleMethod::System => self.write_keyword("SYSTEM"),
5603                            SampleMethod::Block => self.write_keyword("BLOCK"),
5604                            SampleMethod::Row => self.write_keyword("ROW"),
5605                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5606                            _ => {}
5607                        }
5608                    }
5609                    self.write(" (");
5610                    self.generate_expression(&sample.size)?;
5611                    self.write_space();
5612                    match sample.method {
5613                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5614                        SampleMethod::Row => self.write_keyword("ROWS"),
5615                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5616                        _ => {
5617                            self.write_keyword("PERCENT");
5618                        }
5619                    }
5620                    self.write(")");
5621                } else {
5622                    // Syntax: TABLESAMPLE METHOD (size)
5623                    self.write_space();
5624                    match sample.method {
5625                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5626                        SampleMethod::System => self.write_keyword("SYSTEM"),
5627                        SampleMethod::Block => self.write_keyword("BLOCK"),
5628                        SampleMethod::Row => self.write_keyword("ROW"),
5629                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5630                        SampleMethod::Bucket => {}
5631                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5632                    }
5633                    self.write(" (");
5634                    self.generate_expression(&sample.size)?;
5635                    if matches!(sample.method, SampleMethod::Percent) {
5636                        self.write_space();
5637                        self.write_keyword("PERCENT");
5638                    }
5639                    self.write(")");
5640                }
5641            }
5642
5643            if let Some(seed) = &sample.seed {
5644                self.write_space();
5645                // Databricks/Spark use REPEATABLE, not SEED
5646                let use_seed = sample.use_seed_keyword
5647                    && !matches!(
5648                        self.config.dialect,
5649                        Some(crate::dialects::DialectType::Databricks)
5650                            | Some(crate::dialects::DialectType::Spark)
5651                    );
5652                if use_seed {
5653                    self.write_keyword("SEED");
5654                } else {
5655                    self.write_keyword("REPEATABLE");
5656                }
5657                self.write(" (");
5658                self.generate_expression(seed)?;
5659                self.write(")");
5660            }
5661        }
5662
5663        // FOR UPDATE/SHARE locks
5664        // Skip locking clauses for dialects that don't support them
5665        if self.config.locking_reads_supported {
5666            for lock in &select.locks {
5667                if self.config.pretty {
5668                    self.write_newline();
5669                    self.write_indent();
5670                } else {
5671                    self.write_space();
5672                }
5673                self.generate_lock(lock)?;
5674            }
5675        }
5676
5677        // FOR XML clause (T-SQL)
5678        if !select.for_xml.is_empty() {
5679            if self.config.pretty {
5680                self.write_newline();
5681                self.write_indent();
5682            } else {
5683                self.write_space();
5684            }
5685            self.write_keyword("FOR XML");
5686            for (i, opt) in select.for_xml.iter().enumerate() {
5687                if self.config.pretty {
5688                    if i > 0 {
5689                        self.write(",");
5690                    }
5691                    self.write_newline();
5692                    self.write_indent();
5693                    self.write("  "); // extra indent for options
5694                } else {
5695                    if i > 0 {
5696                        self.write(",");
5697                    }
5698                    self.write_space();
5699                }
5700                self.generate_for_xml_option(opt)?;
5701            }
5702        }
5703
5704        // FOR JSON clause (T-SQL)
5705        if !select.for_json.is_empty()
5706            && matches!(
5707                self.config.dialect,
5708                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5709            )
5710        {
5711            if self.config.pretty {
5712                self.write_newline();
5713                self.write_indent();
5714            } else {
5715                self.write_space();
5716            }
5717            self.write_keyword("FOR JSON");
5718            for (i, opt) in select.for_json.iter().enumerate() {
5719                if self.config.pretty {
5720                    if i > 0 {
5721                        self.write(",");
5722                    }
5723                    self.write_newline();
5724                    self.write_indent();
5725                    self.write("  "); // extra indent for options
5726                } else {
5727                    if i > 0 {
5728                        self.write(",");
5729                    }
5730                    self.write_space();
5731                }
5732                self.generate_for_xml_option(opt)?;
5733            }
5734        }
5735
5736        // TSQL: OPTION clause
5737        if let Some(ref option) = select.option {
5738            if matches!(
5739                self.config.dialect,
5740                Some(crate::dialects::DialectType::TSQL)
5741                    | Some(crate::dialects::DialectType::Fabric)
5742            ) {
5743                self.write_space();
5744                self.write(option);
5745            }
5746        }
5747
5748        Ok(())
5749    }
5750
5751    /// Generate a single FOR XML option
5752    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5753        match opt {
5754            Expression::QueryOption(qo) => {
5755                // Extract the option name from Var
5756                if let Expression::Var(var) = &*qo.this {
5757                    self.write(&var.this);
5758                } else {
5759                    self.generate_expression(&qo.this)?;
5760                }
5761                // If there's an expression (like PATH('element')), output it in parens
5762                if let Some(expr) = &qo.expression {
5763                    self.write("(");
5764                    self.generate_expression(expr)?;
5765                    self.write(")");
5766                }
5767            }
5768            _ => {
5769                self.generate_expression(opt)?;
5770            }
5771        }
5772        Ok(())
5773    }
5774
5775    fn generate_with(&mut self, with: &With) -> Result<()> {
5776        use crate::dialects::DialectType;
5777
5778        // Output leading comments before WITH
5779        for comment in &with.leading_comments {
5780            self.write_formatted_comment(comment);
5781            self.write(" ");
5782        }
5783        self.write_keyword("WITH");
5784        if with.recursive && self.config.cte_recursive_keyword_required {
5785            self.write_space();
5786            self.write_keyword("RECURSIVE");
5787        }
5788        self.write_space();
5789
5790        // BigQuery doesn't support column aliases in CTE definitions
5791        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5792
5793        for (i, cte) in with.ctes.iter().enumerate() {
5794            if i > 0 {
5795                self.write(",");
5796                if self.config.pretty {
5797                    self.write_space();
5798                } else {
5799                    self.write(" ");
5800                }
5801            }
5802            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5803                self.generate_expression(&cte.this)?;
5804                self.write_space();
5805                self.write_keyword("AS");
5806                self.write_space();
5807                self.generate_identifier(&cte.alias)?;
5808                continue;
5809            }
5810            self.generate_identifier(&cte.alias)?;
5811            // Output CTE comments after alias name, before AS
5812            for comment in &cte.comments {
5813                self.write_space();
5814                self.write_formatted_comment(comment);
5815            }
5816            if !cte.columns.is_empty() && !skip_cte_columns {
5817                self.write("(");
5818                for (j, col) in cte.columns.iter().enumerate() {
5819                    if j > 0 {
5820                        self.write(", ");
5821                    }
5822                    self.generate_identifier(col)?;
5823                }
5824                self.write(")");
5825            }
5826            // USING KEY (columns) for DuckDB recursive CTEs
5827            if !cte.key_expressions.is_empty() {
5828                self.write_space();
5829                self.write_keyword("USING KEY");
5830                self.write(" (");
5831                for (i, key) in cte.key_expressions.iter().enumerate() {
5832                    if i > 0 {
5833                        self.write(", ");
5834                    }
5835                    self.generate_identifier(key)?;
5836                }
5837                self.write(")");
5838            }
5839            self.write_space();
5840            self.write_keyword("AS");
5841            // MATERIALIZED / NOT MATERIALIZED
5842            if let Some(materialized) = cte.materialized {
5843                self.write_space();
5844                if materialized {
5845                    self.write_keyword("MATERIALIZED");
5846                } else {
5847                    self.write_keyword("NOT MATERIALIZED");
5848                }
5849            }
5850            self.write(" (");
5851            if self.config.pretty {
5852                self.write_newline();
5853                self.indent_level += 1;
5854                self.write_indent();
5855            }
5856            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5857            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5858            let wrap_values_in_select = matches!(
5859                self.config.dialect,
5860                Some(DialectType::Spark) | Some(DialectType::Databricks)
5861            ) && matches!(&cte.this, Expression::Values(_));
5862
5863            if wrap_values_in_select {
5864                self.write_keyword("SELECT");
5865                self.write(" * ");
5866                self.write_keyword("FROM");
5867                self.write_space();
5868            }
5869            self.generate_expression(&cte.this)?;
5870            if self.config.pretty {
5871                self.write_newline();
5872                self.indent_level -= 1;
5873                self.write_indent();
5874            }
5875            self.write(")");
5876        }
5877
5878        // Generate SEARCH/CYCLE clause if present
5879        if let Some(search) = &with.search {
5880            self.write_space();
5881            self.generate_expression(search)?;
5882        }
5883
5884        Ok(())
5885    }
5886
5887    /// Generate joins with proper nesting structure for pretty printing.
5888    /// Deferred-condition joins "own" the non-deferred joins that follow them
5889    /// within the same nesting_group.
5890    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5891        let mut i = 0;
5892        while i < joins.len() {
5893            if joins[i].deferred_condition {
5894                let parent_group = joins[i].nesting_group;
5895
5896                // This join owns the following non-deferred joins in the same nesting_group
5897                // First output the join keyword and table (without condition)
5898                self.generate_join_without_condition(&joins[i])?;
5899
5900                // Find the range of child joins: same nesting_group and not deferred
5901                let child_start = i + 1;
5902                let mut child_end = child_start;
5903                while child_end < joins.len()
5904                    && !joins[child_end].deferred_condition
5905                    && joins[child_end].nesting_group == parent_group
5906                {
5907                    child_end += 1;
5908                }
5909
5910                // Output child joins with extra indentation
5911                if child_start < child_end {
5912                    self.indent_level += 1;
5913                    for j in child_start..child_end {
5914                        self.generate_join(&joins[j])?;
5915                    }
5916                    self.indent_level -= 1;
5917                }
5918
5919                // Output the deferred condition at the parent level
5920                self.generate_join_condition(&joins[i])?;
5921
5922                i = child_end;
5923            } else {
5924                // Regular join (no nesting)
5925                self.generate_join(&joins[i])?;
5926                i += 1;
5927            }
5928        }
5929        Ok(())
5930    }
5931
5932    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5933    /// Used for deferred-condition joins where the condition is output after child joins.
5934    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5935        // Save and temporarily clear the condition to prevent generate_join from outputting it
5936        // We achieve this by creating a modified copy
5937        let mut join_copy = join.clone();
5938        join_copy.on = None;
5939        join_copy.using = Vec::new();
5940        join_copy.deferred_condition = false;
5941        self.generate_join(&join_copy)
5942    }
5943
5944    fn generate_join(&mut self, join: &Join) -> Result<()> {
5945        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5946        if join.kind == JoinKind::Implicit {
5947            self.write(",");
5948            if self.config.pretty {
5949                self.write_newline();
5950                self.write_indent();
5951            } else {
5952                self.write_space();
5953            }
5954            self.generate_expression(&join.this)?;
5955            return Ok(());
5956        }
5957
5958        if self.config.pretty {
5959            self.write_newline();
5960            self.write_indent();
5961        } else {
5962            self.write_space();
5963        }
5964
5965        // Helper: format hint suffix (e.g., " LOOP" or "")
5966        // Only include join hints for dialects that support them
5967        let hint_str = if self.config.join_hints {
5968            join.join_hint
5969                .as_ref()
5970                .map(|h| format!(" {}", h))
5971                .unwrap_or_default()
5972        } else {
5973            String::new()
5974        };
5975
5976        let clickhouse_join_keyword =
5977            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5978                if let Some(hint) = &join.join_hint {
5979                    let mut global = false;
5980                    let mut strictness: Option<&'static str> = None;
5981                    for part in hint.split_whitespace() {
5982                        if part.eq_ignore_ascii_case("GLOBAL") {
5983                            global = true;
5984                        } else if part.eq_ignore_ascii_case("ALL") {
5985                            strictness = Some("ALL");
5986                        } else if part.eq_ignore_ascii_case("ANY") {
5987                            strictness = Some("ANY");
5988                        } else if part.eq_ignore_ascii_case("ASOF") {
5989                            strictness = Some("ASOF");
5990                        } else if part.eq_ignore_ascii_case("SEMI") {
5991                            strictness = Some("SEMI");
5992                        } else if part.eq_ignore_ascii_case("ANTI") {
5993                            strictness = Some("ANTI");
5994                        }
5995                    }
5996
5997                    if global || strictness.is_some() {
5998                        let join_type = match join.kind {
5999                            JoinKind::Left => {
6000                                if join.use_outer_keyword {
6001                                    "LEFT OUTER"
6002                                } else if join.use_inner_keyword {
6003                                    "LEFT INNER"
6004                                } else {
6005                                    "LEFT"
6006                                }
6007                            }
6008                            JoinKind::Right => {
6009                                if join.use_outer_keyword {
6010                                    "RIGHT OUTER"
6011                                } else if join.use_inner_keyword {
6012                                    "RIGHT INNER"
6013                                } else {
6014                                    "RIGHT"
6015                                }
6016                            }
6017                            JoinKind::Full => {
6018                                if join.use_outer_keyword {
6019                                    "FULL OUTER"
6020                                } else {
6021                                    "FULL"
6022                                }
6023                            }
6024                            JoinKind::Inner => {
6025                                if join.use_inner_keyword {
6026                                    "INNER"
6027                                } else {
6028                                    ""
6029                                }
6030                            }
6031                            _ => "",
6032                        };
6033
6034                        let mut parts = Vec::new();
6035                        if global {
6036                            parts.push("GLOBAL");
6037                        }
6038                        if !join_type.is_empty() {
6039                            parts.push(join_type);
6040                        }
6041                        if let Some(strict) = strictness {
6042                            parts.push(strict);
6043                        }
6044                        parts.push("JOIN");
6045                        Some(parts.join(" "))
6046                    } else {
6047                        None
6048                    }
6049                } else {
6050                    None
6051                }
6052            } else {
6053                None
6054            };
6055
6056        // Output any comments associated with this join
6057        // In pretty mode, comments go on their own line before the join keyword
6058        // In non-pretty mode, comments go inline before the join keyword
6059        if !join.comments.is_empty() {
6060            if self.config.pretty {
6061                // In pretty mode, go back before the newline+indent we just wrote
6062                // and output comments on their own lines
6063                // We need to output comments BEFORE the join keyword on separate lines
6064                // Trim the trailing newline+indent we already wrote
6065                let trimmed = self.output.trim_end().len();
6066                self.output.truncate(trimmed);
6067                for comment in &join.comments {
6068                    self.write_newline();
6069                    self.write_indent();
6070                    self.write_formatted_comment(comment);
6071                }
6072                self.write_newline();
6073                self.write_indent();
6074            } else {
6075                for comment in &join.comments {
6076                    self.write_formatted_comment(comment);
6077                    self.write_space();
6078                }
6079            }
6080        }
6081
6082        let directed_str = if join.directed { " DIRECTED" } else { "" };
6083
6084        if let Some(keyword) = clickhouse_join_keyword {
6085            self.write_keyword(&keyword);
6086        } else {
6087            match join.kind {
6088                JoinKind::Inner => {
6089                    if join.use_inner_keyword {
6090                        if hint_str.is_empty() && directed_str.is_empty() {
6091                            self.write_keyword("INNER JOIN");
6092                        } else {
6093                            self.write_keyword("INNER");
6094                            if !hint_str.is_empty() {
6095                                self.write_keyword(&hint_str);
6096                            }
6097                            if !directed_str.is_empty() {
6098                                self.write_keyword(directed_str);
6099                            }
6100                            self.write_keyword(" JOIN");
6101                        }
6102                    } else {
6103                        if !hint_str.is_empty() {
6104                            self.write_keyword(hint_str.trim());
6105                            self.write_keyword(" ");
6106                        }
6107                        if !directed_str.is_empty() {
6108                            self.write_keyword("DIRECTED ");
6109                        }
6110                        self.write_keyword("JOIN");
6111                    }
6112                }
6113                JoinKind::Left => {
6114                    if join.use_outer_keyword {
6115                        if hint_str.is_empty() && directed_str.is_empty() {
6116                            self.write_keyword("LEFT OUTER JOIN");
6117                        } else {
6118                            self.write_keyword("LEFT OUTER");
6119                            if !hint_str.is_empty() {
6120                                self.write_keyword(&hint_str);
6121                            }
6122                            if !directed_str.is_empty() {
6123                                self.write_keyword(directed_str);
6124                            }
6125                            self.write_keyword(" JOIN");
6126                        }
6127                    } else if join.use_inner_keyword {
6128                        if hint_str.is_empty() && directed_str.is_empty() {
6129                            self.write_keyword("LEFT INNER JOIN");
6130                        } else {
6131                            self.write_keyword("LEFT INNER");
6132                            if !hint_str.is_empty() {
6133                                self.write_keyword(&hint_str);
6134                            }
6135                            if !directed_str.is_empty() {
6136                                self.write_keyword(directed_str);
6137                            }
6138                            self.write_keyword(" JOIN");
6139                        }
6140                    } else {
6141                        if hint_str.is_empty() && directed_str.is_empty() {
6142                            self.write_keyword("LEFT JOIN");
6143                        } else {
6144                            self.write_keyword("LEFT");
6145                            if !hint_str.is_empty() {
6146                                self.write_keyword(&hint_str);
6147                            }
6148                            if !directed_str.is_empty() {
6149                                self.write_keyword(directed_str);
6150                            }
6151                            self.write_keyword(" JOIN");
6152                        }
6153                    }
6154                }
6155                JoinKind::Right => {
6156                    if join.use_outer_keyword {
6157                        if hint_str.is_empty() && directed_str.is_empty() {
6158                            self.write_keyword("RIGHT OUTER JOIN");
6159                        } else {
6160                            self.write_keyword("RIGHT OUTER");
6161                            if !hint_str.is_empty() {
6162                                self.write_keyword(&hint_str);
6163                            }
6164                            if !directed_str.is_empty() {
6165                                self.write_keyword(directed_str);
6166                            }
6167                            self.write_keyword(" JOIN");
6168                        }
6169                    } else if join.use_inner_keyword {
6170                        if hint_str.is_empty() && directed_str.is_empty() {
6171                            self.write_keyword("RIGHT INNER JOIN");
6172                        } else {
6173                            self.write_keyword("RIGHT INNER");
6174                            if !hint_str.is_empty() {
6175                                self.write_keyword(&hint_str);
6176                            }
6177                            if !directed_str.is_empty() {
6178                                self.write_keyword(directed_str);
6179                            }
6180                            self.write_keyword(" JOIN");
6181                        }
6182                    } else {
6183                        if hint_str.is_empty() && directed_str.is_empty() {
6184                            self.write_keyword("RIGHT JOIN");
6185                        } else {
6186                            self.write_keyword("RIGHT");
6187                            if !hint_str.is_empty() {
6188                                self.write_keyword(&hint_str);
6189                            }
6190                            if !directed_str.is_empty() {
6191                                self.write_keyword(directed_str);
6192                            }
6193                            self.write_keyword(" JOIN");
6194                        }
6195                    }
6196                }
6197                JoinKind::Full => {
6198                    if join.use_outer_keyword {
6199                        if hint_str.is_empty() && directed_str.is_empty() {
6200                            self.write_keyword("FULL OUTER JOIN");
6201                        } else {
6202                            self.write_keyword("FULL OUTER");
6203                            if !hint_str.is_empty() {
6204                                self.write_keyword(&hint_str);
6205                            }
6206                            if !directed_str.is_empty() {
6207                                self.write_keyword(directed_str);
6208                            }
6209                            self.write_keyword(" JOIN");
6210                        }
6211                    } else {
6212                        if hint_str.is_empty() && directed_str.is_empty() {
6213                            self.write_keyword("FULL JOIN");
6214                        } else {
6215                            self.write_keyword("FULL");
6216                            if !hint_str.is_empty() {
6217                                self.write_keyword(&hint_str);
6218                            }
6219                            if !directed_str.is_empty() {
6220                                self.write_keyword(directed_str);
6221                            }
6222                            self.write_keyword(" JOIN");
6223                        }
6224                    }
6225                }
6226                JoinKind::Outer => {
6227                    if directed_str.is_empty() {
6228                        self.write_keyword("OUTER JOIN");
6229                    } else {
6230                        self.write_keyword("OUTER");
6231                        self.write_keyword(directed_str);
6232                        self.write_keyword(" JOIN");
6233                    }
6234                }
6235                JoinKind::Cross => {
6236                    if directed_str.is_empty() {
6237                        self.write_keyword("CROSS JOIN");
6238                    } else {
6239                        self.write_keyword("CROSS");
6240                        self.write_keyword(directed_str);
6241                        self.write_keyword(" JOIN");
6242                    }
6243                }
6244                JoinKind::Natural => {
6245                    if join.use_inner_keyword {
6246                        if directed_str.is_empty() {
6247                            self.write_keyword("NATURAL INNER JOIN");
6248                        } else {
6249                            self.write_keyword("NATURAL INNER");
6250                            self.write_keyword(directed_str);
6251                            self.write_keyword(" JOIN");
6252                        }
6253                    } else {
6254                        if directed_str.is_empty() {
6255                            self.write_keyword("NATURAL JOIN");
6256                        } else {
6257                            self.write_keyword("NATURAL");
6258                            self.write_keyword(directed_str);
6259                            self.write_keyword(" JOIN");
6260                        }
6261                    }
6262                }
6263                JoinKind::NaturalLeft => {
6264                    if join.use_outer_keyword {
6265                        if directed_str.is_empty() {
6266                            self.write_keyword("NATURAL LEFT OUTER JOIN");
6267                        } else {
6268                            self.write_keyword("NATURAL LEFT OUTER");
6269                            self.write_keyword(directed_str);
6270                            self.write_keyword(" JOIN");
6271                        }
6272                    } else {
6273                        if directed_str.is_empty() {
6274                            self.write_keyword("NATURAL LEFT JOIN");
6275                        } else {
6276                            self.write_keyword("NATURAL LEFT");
6277                            self.write_keyword(directed_str);
6278                            self.write_keyword(" JOIN");
6279                        }
6280                    }
6281                }
6282                JoinKind::NaturalRight => {
6283                    if join.use_outer_keyword {
6284                        if directed_str.is_empty() {
6285                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
6286                        } else {
6287                            self.write_keyword("NATURAL RIGHT OUTER");
6288                            self.write_keyword(directed_str);
6289                            self.write_keyword(" JOIN");
6290                        }
6291                    } else {
6292                        if directed_str.is_empty() {
6293                            self.write_keyword("NATURAL RIGHT JOIN");
6294                        } else {
6295                            self.write_keyword("NATURAL RIGHT");
6296                            self.write_keyword(directed_str);
6297                            self.write_keyword(" JOIN");
6298                        }
6299                    }
6300                }
6301                JoinKind::NaturalFull => {
6302                    if join.use_outer_keyword {
6303                        if directed_str.is_empty() {
6304                            self.write_keyword("NATURAL FULL OUTER JOIN");
6305                        } else {
6306                            self.write_keyword("NATURAL FULL OUTER");
6307                            self.write_keyword(directed_str);
6308                            self.write_keyword(" JOIN");
6309                        }
6310                    } else {
6311                        if directed_str.is_empty() {
6312                            self.write_keyword("NATURAL FULL JOIN");
6313                        } else {
6314                            self.write_keyword("NATURAL FULL");
6315                            self.write_keyword(directed_str);
6316                            self.write_keyword(" JOIN");
6317                        }
6318                    }
6319                }
6320                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
6321                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
6322                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
6323                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
6324                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
6325                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
6326                JoinKind::CrossApply => {
6327                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
6328                    if matches!(
6329                        self.config.dialect,
6330                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6331                    ) {
6332                        self.write_keyword("CROSS APPLY");
6333                    } else {
6334                        self.write_keyword("INNER JOIN LATERAL");
6335                    }
6336                }
6337                JoinKind::OuterApply => {
6338                    // OUTER APPLY -> LEFT 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("OUTER APPLY");
6344                    } else {
6345                        self.write_keyword("LEFT JOIN LATERAL");
6346                    }
6347                }
6348                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
6349                JoinKind::AsOfLeft => {
6350                    if join.use_outer_keyword {
6351                        self.write_keyword("ASOF LEFT OUTER JOIN");
6352                    } else {
6353                        self.write_keyword("ASOF LEFT JOIN");
6354                    }
6355                }
6356                JoinKind::AsOfRight => {
6357                    if join.use_outer_keyword {
6358                        self.write_keyword("ASOF RIGHT OUTER JOIN");
6359                    } else {
6360                        self.write_keyword("ASOF RIGHT JOIN");
6361                    }
6362                }
6363                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
6364                JoinKind::LeftLateral => {
6365                    if join.use_outer_keyword {
6366                        self.write_keyword("LEFT OUTER LATERAL JOIN");
6367                    } else {
6368                        self.write_keyword("LEFT LATERAL JOIN");
6369                    }
6370                }
6371                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
6372                JoinKind::Implicit => {
6373                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
6374                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
6375                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
6376                    use crate::dialects::DialectType;
6377                    let is_cj_dialect = matches!(
6378                        self.config.dialect,
6379                        Some(DialectType::BigQuery)
6380                            | Some(DialectType::Hive)
6381                            | Some(DialectType::Spark)
6382                            | Some(DialectType::Databricks)
6383                    );
6384                    let source_is_same = self.config.source_dialect.is_some()
6385                        && self.config.source_dialect == self.config.dialect;
6386                    let source_is_cj = matches!(
6387                        self.config.source_dialect,
6388                        Some(DialectType::BigQuery)
6389                            | Some(DialectType::Hive)
6390                            | Some(DialectType::Spark)
6391                            | Some(DialectType::Databricks)
6392                    );
6393                    if is_cj_dialect
6394                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
6395                    {
6396                        self.write_keyword("CROSS JOIN");
6397                    } else {
6398                        // Implicit join uses comma: FROM a, b
6399                        // We already wrote a space before the match, so replace with comma
6400                        // by removing trailing space and writing ", "
6401                        self.output.truncate(self.output.trim_end().len());
6402                        self.write(",");
6403                    }
6404                }
6405                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
6406                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
6407                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
6408                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
6409            }
6410        }
6411
6412        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
6413        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
6414            match &join.this {
6415                Expression::Tuple(t) if t.expressions.is_empty() => {}
6416                Expression::Tuple(t) => {
6417                    self.write_space();
6418                    for (i, item) in t.expressions.iter().enumerate() {
6419                        if i > 0 {
6420                            self.write(", ");
6421                        }
6422                        self.generate_expression(item)?;
6423                    }
6424                }
6425                other => {
6426                    self.write_space();
6427                    self.generate_expression(other)?;
6428                }
6429            }
6430        } else {
6431            self.write_space();
6432            self.generate_expression(&join.this)?;
6433        }
6434
6435        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
6436        if !join.deferred_condition {
6437            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
6438            if let Some(match_cond) = &join.match_condition {
6439                self.write_space();
6440                self.write_keyword("MATCH_CONDITION");
6441                self.write(" (");
6442                self.generate_expression(match_cond)?;
6443                self.write(")");
6444            }
6445
6446            if let Some(on) = &join.on {
6447                if self.config.pretty {
6448                    self.write_newline();
6449                    self.indent_level += 1;
6450                    self.write_indent();
6451                    self.write_keyword("ON");
6452                    self.write_space();
6453                    self.generate_join_on_condition(on)?;
6454                    self.indent_level -= 1;
6455                } else {
6456                    self.write_space();
6457                    self.write_keyword("ON");
6458                    self.write_space();
6459                    self.generate_expression(on)?;
6460                }
6461            }
6462
6463            if !join.using.is_empty() {
6464                if self.config.pretty {
6465                    self.write_newline();
6466                    self.indent_level += 1;
6467                    self.write_indent();
6468                    self.write_keyword("USING");
6469                    self.write(" (");
6470                    for (i, col) in join.using.iter().enumerate() {
6471                        if i > 0 {
6472                            self.write(", ");
6473                        }
6474                        self.generate_identifier(col)?;
6475                    }
6476                    self.write(")");
6477                    self.indent_level -= 1;
6478                } else {
6479                    self.write_space();
6480                    self.write_keyword("USING");
6481                    self.write(" (");
6482                    for (i, col) in join.using.iter().enumerate() {
6483                        if i > 0 {
6484                            self.write(", ");
6485                        }
6486                        self.generate_identifier(col)?;
6487                    }
6488                    self.write(")");
6489                }
6490            }
6491        }
6492
6493        // Generate PIVOT/UNPIVOT expressions that follow this join
6494        for pivot in &join.pivots {
6495            self.write_space();
6496            self.generate_expression(pivot)?;
6497        }
6498
6499        Ok(())
6500    }
6501
6502    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
6503    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
6504        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
6505        if let Some(match_cond) = &join.match_condition {
6506            self.write_space();
6507            self.write_keyword("MATCH_CONDITION");
6508            self.write(" (");
6509            self.generate_expression(match_cond)?;
6510            self.write(")");
6511        }
6512
6513        if let Some(on) = &join.on {
6514            if self.config.pretty {
6515                self.write_newline();
6516                self.indent_level += 1;
6517                self.write_indent();
6518                self.write_keyword("ON");
6519                self.write_space();
6520                // In pretty mode, split AND conditions onto separate lines
6521                self.generate_join_on_condition(on)?;
6522                self.indent_level -= 1;
6523            } else {
6524                self.write_space();
6525                self.write_keyword("ON");
6526                self.write_space();
6527                self.generate_expression(on)?;
6528            }
6529        }
6530
6531        if !join.using.is_empty() {
6532            if self.config.pretty {
6533                self.write_newline();
6534                self.indent_level += 1;
6535                self.write_indent();
6536                self.write_keyword("USING");
6537                self.write(" (");
6538                for (i, col) in join.using.iter().enumerate() {
6539                    if i > 0 {
6540                        self.write(", ");
6541                    }
6542                    self.generate_identifier(col)?;
6543                }
6544                self.write(")");
6545                self.indent_level -= 1;
6546            } else {
6547                self.write_space();
6548                self.write_keyword("USING");
6549                self.write(" (");
6550                for (i, col) in join.using.iter().enumerate() {
6551                    if i > 0 {
6552                        self.write(", ");
6553                    }
6554                    self.generate_identifier(col)?;
6555                }
6556                self.write(")");
6557            }
6558        }
6559
6560        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6561        for pivot in &join.pivots {
6562            self.write_space();
6563            self.generate_expression(pivot)?;
6564        }
6565
6566        Ok(())
6567    }
6568
6569    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6570    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6571        if let Expression::And(and_op) = expr {
6572            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6573                self.generate_expression(conditions[0])?;
6574                for condition in conditions.iter().skip(1) {
6575                    self.write_newline();
6576                    self.write_indent();
6577                    self.write_keyword("AND");
6578                    self.write_space();
6579                    self.generate_expression(condition)?;
6580                }
6581                return Ok(());
6582            }
6583        }
6584
6585        self.generate_expression(expr)
6586    }
6587
6588    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6589        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6590        self.write("(");
6591        self.generate_expression(&jt.left)?;
6592
6593        // Generate all joins
6594        for join in &jt.joins {
6595            self.generate_join(join)?;
6596        }
6597
6598        // Generate LATERAL VIEW clauses (Hive/Spark)
6599        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6600            self.generate_lateral_view(lv, lv_idx)?;
6601        }
6602
6603        self.write(")");
6604
6605        // Alias
6606        if let Some(alias) = &jt.alias {
6607            self.write_space();
6608            self.write_keyword("AS");
6609            self.write_space();
6610            self.generate_identifier(alias)?;
6611        }
6612
6613        Ok(())
6614    }
6615
6616    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6617        use crate::dialects::DialectType;
6618
6619        if self.config.pretty {
6620            self.write_newline();
6621            self.write_indent();
6622        } else {
6623            self.write_space();
6624        }
6625
6626        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6627        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6628        let use_lateral_join = matches!(
6629            self.config.dialect,
6630            Some(DialectType::PostgreSQL)
6631                | Some(DialectType::DuckDB)
6632                | Some(DialectType::Snowflake)
6633                | Some(DialectType::TSQL)
6634                | Some(DialectType::Presto)
6635                | Some(DialectType::Trino)
6636                | Some(DialectType::Athena)
6637        );
6638
6639        // Check if target dialect should use UNNEST instead of EXPLODE
6640        let use_unnest = matches!(
6641            self.config.dialect,
6642            Some(DialectType::DuckDB)
6643                | Some(DialectType::Presto)
6644                | Some(DialectType::Trino)
6645                | Some(DialectType::Athena)
6646        );
6647
6648        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6649        let (is_posexplode, is_inline, func_args) = match &lv.this {
6650            Expression::Explode(uf) => {
6651                // Expression::Explode is the dedicated EXPLODE expression type
6652                (false, false, vec![uf.this.clone()])
6653            }
6654            Expression::Unnest(uf) => {
6655                let mut args = vec![uf.this.clone()];
6656                args.extend(uf.expressions.clone());
6657                (false, false, args)
6658            }
6659            Expression::Function(func) => {
6660                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6661                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6662                {
6663                    (true, false, func.args.clone())
6664                } else if func.name.eq_ignore_ascii_case("INLINE") {
6665                    (false, true, func.args.clone())
6666                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6667                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6668                {
6669                    (false, false, func.args.clone())
6670                } else {
6671                    (false, false, vec![])
6672                }
6673            }
6674            _ => (false, false, vec![]),
6675        };
6676
6677        if use_lateral_join {
6678            // Convert to CROSS JOIN for PostgreSQL-like dialects
6679            if lv.outer {
6680                self.write_keyword("LEFT JOIN LATERAL");
6681            } else {
6682                self.write_keyword("CROSS JOIN");
6683            }
6684            self.write_space();
6685
6686            if use_unnest && !func_args.is_empty() {
6687                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6688                // For DuckDB, also convert ARRAY(y) -> [y]
6689                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6690                    // DuckDB: ARRAY(y) -> [y]
6691                    func_args
6692                        .iter()
6693                        .map(|a| {
6694                            if let Expression::Function(ref f) = a {
6695                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6696                                    return Expression::ArrayFunc(Box::new(
6697                                        crate::expressions::ArrayConstructor {
6698                                            expressions: f.args.clone(),
6699                                            bracket_notation: true,
6700                                            use_list_keyword: false,
6701                                        },
6702                                    ));
6703                                }
6704                            }
6705                            a.clone()
6706                        })
6707                        .collect::<Vec<_>>()
6708                } else if matches!(
6709                    self.config.dialect,
6710                    Some(DialectType::Presto)
6711                        | Some(DialectType::Trino)
6712                        | Some(DialectType::Athena)
6713                ) {
6714                    // Presto: ARRAY(y) -> ARRAY[y]
6715                    func_args
6716                        .iter()
6717                        .map(|a| {
6718                            if let Expression::Function(ref f) = a {
6719                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6720                                    return Expression::ArrayFunc(Box::new(
6721                                        crate::expressions::ArrayConstructor {
6722                                            expressions: f.args.clone(),
6723                                            bracket_notation: true,
6724                                            use_list_keyword: false,
6725                                        },
6726                                    ));
6727                                }
6728                            }
6729                            a.clone()
6730                        })
6731                        .collect::<Vec<_>>()
6732                } else {
6733                    func_args
6734                };
6735
6736                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6737                if is_posexplode {
6738                    self.write_keyword("LATERAL");
6739                    self.write(" (");
6740                    self.write_keyword("SELECT");
6741                    self.write_space();
6742
6743                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6744                    // column_aliases[0] is the position column, rest are data columns
6745                    let pos_alias = if !lv.column_aliases.is_empty() {
6746                        lv.column_aliases[0].clone()
6747                    } else {
6748                        Identifier::new("pos")
6749                    };
6750                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6751                        lv.column_aliases[1..].to_vec()
6752                    } else {
6753                        vec![Identifier::new("col")]
6754                    };
6755
6756                    // pos - 1 AS pos
6757                    self.generate_identifier(&pos_alias)?;
6758                    self.write(" - 1");
6759                    self.write_space();
6760                    self.write_keyword("AS");
6761                    self.write_space();
6762                    self.generate_identifier(&pos_alias)?;
6763
6764                    // , col [, key, value ...]
6765                    for data_col in &data_aliases {
6766                        self.write(", ");
6767                        self.generate_identifier(data_col)?;
6768                    }
6769
6770                    self.write_space();
6771                    self.write_keyword("FROM");
6772                    self.write_space();
6773                    self.write_keyword("UNNEST");
6774                    self.write("(");
6775                    for (i, arg) in unnest_args.iter().enumerate() {
6776                        if i > 0 {
6777                            self.write(", ");
6778                        }
6779                        self.generate_expression(arg)?;
6780                    }
6781                    self.write(")");
6782                    self.write_space();
6783                    self.write_keyword("WITH ORDINALITY");
6784                    self.write_space();
6785                    self.write_keyword("AS");
6786                    self.write_space();
6787
6788                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6789                    let table_alias_ident = lv
6790                        .table_alias
6791                        .clone()
6792                        .unwrap_or_else(|| Identifier::new("t"));
6793                    self.generate_identifier(&table_alias_ident)?;
6794                    self.write("(");
6795                    for (i, data_col) in data_aliases.iter().enumerate() {
6796                        if i > 0 {
6797                            self.write(", ");
6798                        }
6799                        self.generate_identifier(data_col)?;
6800                    }
6801                    self.write(", ");
6802                    self.generate_identifier(&pos_alias)?;
6803                    self.write("))");
6804                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6805                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6806                    self.write_keyword("LATERAL");
6807                    self.write(" (");
6808                    self.write_keyword("SELECT");
6809                    self.write_space();
6810                    self.write_keyword("UNNEST");
6811                    self.write("(");
6812                    for (i, arg) in unnest_args.iter().enumerate() {
6813                        if i > 0 {
6814                            self.write(", ");
6815                        }
6816                        self.generate_expression(arg)?;
6817                    }
6818                    self.write(", ");
6819                    self.write_keyword("max_depth");
6820                    self.write(" => 2))");
6821
6822                    // Add table and column aliases
6823                    if let Some(alias) = &lv.table_alias {
6824                        self.write_space();
6825                        self.write_keyword("AS");
6826                        self.write_space();
6827                        self.generate_identifier(alias)?;
6828                        if !lv.column_aliases.is_empty() {
6829                            self.write("(");
6830                            for (i, col) in lv.column_aliases.iter().enumerate() {
6831                                if i > 0 {
6832                                    self.write(", ");
6833                                }
6834                                self.generate_identifier(col)?;
6835                            }
6836                            self.write(")");
6837                        }
6838                    } else if !lv.column_aliases.is_empty() {
6839                        // Auto-generate alias like _u_N
6840                        self.write_space();
6841                        self.write_keyword("AS");
6842                        self.write_space();
6843                        self.write(&format!("_u_{}", lv_index));
6844                        self.write("(");
6845                        for (i, col) in lv.column_aliases.iter().enumerate() {
6846                            if i > 0 {
6847                                self.write(", ");
6848                            }
6849                            self.generate_identifier(col)?;
6850                        }
6851                        self.write(")");
6852                    }
6853                } else {
6854                    self.write_keyword("UNNEST");
6855                    self.write("(");
6856                    for (i, arg) in unnest_args.iter().enumerate() {
6857                        if i > 0 {
6858                            self.write(", ");
6859                        }
6860                        self.generate_expression(arg)?;
6861                    }
6862                    self.write(")");
6863
6864                    // Add table and column aliases for non-POSEXPLODE
6865                    if let Some(alias) = &lv.table_alias {
6866                        self.write_space();
6867                        self.write_keyword("AS");
6868                        self.write_space();
6869                        self.generate_identifier(alias)?;
6870                        if !lv.column_aliases.is_empty() {
6871                            self.write("(");
6872                            for (i, col) in lv.column_aliases.iter().enumerate() {
6873                                if i > 0 {
6874                                    self.write(", ");
6875                                }
6876                                self.generate_identifier(col)?;
6877                            }
6878                            self.write(")");
6879                        }
6880                    } else if !lv.column_aliases.is_empty() {
6881                        self.write_space();
6882                        self.write_keyword("AS");
6883                        self.write(" t(");
6884                        for (i, col) in lv.column_aliases.iter().enumerate() {
6885                            if i > 0 {
6886                                self.write(", ");
6887                            }
6888                            self.generate_identifier(col)?;
6889                        }
6890                        self.write(")");
6891                    }
6892                }
6893            } else {
6894                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6895                if !lv.outer {
6896                    self.write_keyword("LATERAL");
6897                    self.write_space();
6898                }
6899                self.generate_expression(&lv.this)?;
6900
6901                // Add table and column aliases
6902                if let Some(alias) = &lv.table_alias {
6903                    self.write_space();
6904                    self.write_keyword("AS");
6905                    self.write_space();
6906                    self.generate_identifier(alias)?;
6907                    if !lv.column_aliases.is_empty() {
6908                        self.write("(");
6909                        for (i, col) in lv.column_aliases.iter().enumerate() {
6910                            if i > 0 {
6911                                self.write(", ");
6912                            }
6913                            self.generate_identifier(col)?;
6914                        }
6915                        self.write(")");
6916                    }
6917                } else if !lv.column_aliases.is_empty() {
6918                    self.write_space();
6919                    self.write_keyword("AS");
6920                    self.write(" t(");
6921                    for (i, col) in lv.column_aliases.iter().enumerate() {
6922                        if i > 0 {
6923                            self.write(", ");
6924                        }
6925                        self.generate_identifier(col)?;
6926                    }
6927                    self.write(")");
6928                }
6929            }
6930
6931            // For LEFT JOIN LATERAL, need ON TRUE
6932            if lv.outer {
6933                self.write_space();
6934                self.write_keyword("ON TRUE");
6935            }
6936        } else {
6937            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6938            self.write_keyword("LATERAL VIEW");
6939            if lv.outer {
6940                self.write_space();
6941                self.write_keyword("OUTER");
6942            }
6943            if self.config.pretty {
6944                self.write_newline();
6945                self.write_indent();
6946            } else {
6947                self.write_space();
6948            }
6949            self.generate_expression(&lv.this)?;
6950
6951            // Table alias
6952            if let Some(alias) = &lv.table_alias {
6953                self.write_space();
6954                self.generate_identifier(alias)?;
6955            }
6956
6957            // Column aliases
6958            if !lv.column_aliases.is_empty() {
6959                self.write_space();
6960                self.write_keyword("AS");
6961                self.write_space();
6962                for (i, col) in lv.column_aliases.iter().enumerate() {
6963                    if i > 0 {
6964                        self.write(", ");
6965                    }
6966                    self.generate_identifier(col)?;
6967                }
6968            }
6969        }
6970
6971        Ok(())
6972    }
6973
6974    fn should_wrap_set_operation_modifiers(
6975        &self,
6976        order_by: &Option<OrderBy>,
6977        limit: &Option<Box<Expression>>,
6978        offset: &Option<Box<Expression>>,
6979    ) -> bool {
6980        let has_row_limit = limit.is_some() || offset.is_some();
6981        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6982            order_by
6983                .expressions
6984                .iter()
6985                .any(|ordered| ordered.nulls_first.is_some())
6986        });
6987
6988        (has_row_limit || has_emulated_null_ordering)
6989            && matches!(
6990                self.config.dialect,
6991                Some(DialectType::TSQL) | Some(DialectType::Fabric)
6992            )
6993    }
6994
6995    fn generate_tsql_wrapped_set_operation(
6996        &mut self,
6997        inner: Expression,
6998        with: Option<With>,
6999        order_by: Option<OrderBy>,
7000        limit: Option<Box<Expression>>,
7001        offset: Option<Box<Expression>>,
7002    ) -> Result<()> {
7003        let subquery = Subquery {
7004            this: inner,
7005            alias: Some(Identifier::new("_l_0".to_string())),
7006            column_aliases: Vec::new(),
7007            alias_explicit_as: true,
7008            alias_keyword: None,
7009            order_by: None,
7010            limit: None,
7011            offset: None,
7012            lateral: false,
7013            modifiers_inside: false,
7014            trailing_comments: Vec::new(),
7015            distribute_by: None,
7016            sort_by: None,
7017            cluster_by: None,
7018            inferred_type: None,
7019        };
7020
7021        let mut outer_select = Select {
7022            expressions: vec![Expression::Star(Star {
7023                table: None,
7024                except: None,
7025                replace: None,
7026                rename: None,
7027                trailing_comments: Vec::new(),
7028                span: None,
7029            })],
7030            from: Some(From {
7031                expressions: vec![Expression::Subquery(Box::new(subquery))],
7032            }),
7033            with,
7034            order_by,
7035            limit: limit.map(|limit| Limit {
7036                this: *limit,
7037                percent: false,
7038                comments: Vec::new(),
7039            }),
7040            offset: offset.map(|offset| Offset {
7041                this: *offset,
7042                rows: Some(true),
7043            }),
7044            ..Select::new()
7045        };
7046
7047        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
7048            outer_select.order_by = Some(Self::dummy_tsql_order_by());
7049        }
7050
7051        self.generate_select(&outer_select)
7052    }
7053
7054    pub(crate) fn dummy_tsql_order_by() -> OrderBy {
7055        let null_select = Expression::Select(Box::new(Select {
7056            expressions: vec![Expression::Null(Null)],
7057            ..Select::new()
7058        }));
7059
7060        OrderBy {
7061            expressions: vec![Ordered {
7062                this: Expression::Subquery(Box::new(Subquery {
7063                    this: null_select,
7064                    alias: None,
7065                    column_aliases: Vec::new(),
7066                    alias_explicit_as: false,
7067                    alias_keyword: None,
7068                    order_by: None,
7069                    limit: None,
7070                    offset: None,
7071                    lateral: false,
7072                    modifiers_inside: false,
7073                    trailing_comments: Vec::new(),
7074                    distribute_by: None,
7075                    sort_by: None,
7076                    cluster_by: None,
7077                    inferred_type: None,
7078                })),
7079                desc: false,
7080                nulls_first: None,
7081                explicit_asc: false,
7082                with_fill: None,
7083            }],
7084            siblings: false,
7085            comments: Vec::new(),
7086        }
7087    }
7088
7089    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
7090        if self.should_wrap_set_operation_modifiers(
7091            &outermost.order_by,
7092            &outermost.limit,
7093            &outermost.offset,
7094        ) {
7095            let mut inner = outermost.clone();
7096            let with = inner.with.take();
7097            let order_by = inner.order_by.take();
7098            let limit = inner.limit.take();
7099            let offset = inner.offset.take();
7100
7101            return self.generate_tsql_wrapped_set_operation(
7102                Expression::Union(Box::new(inner)),
7103                with,
7104                order_by,
7105                limit,
7106                offset,
7107            );
7108        }
7109
7110        // Collect the left-recursive chain of Union nodes iteratively.
7111        // This avoids stack overflow for deeply nested chains like
7112        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
7113        // where the parser builds: Union(Union(Union(A, B), C), D)
7114        let mut chain: Vec<&Union> = vec![outermost];
7115        let mut leftmost: &Expression = &outermost.left;
7116        while let Expression::Union(inner) = leftmost {
7117            chain.push(inner);
7118            leftmost = &inner.left;
7119        }
7120        // chain[0] = outermost, chain[last] = innermost
7121        // leftmost = innermost.left (a non-Union expression, typically Select)
7122
7123        // WITH clause (only on outermost)
7124        if let Some(with) = &outermost.with {
7125            self.generate_with(with)?;
7126            self.write_space();
7127        }
7128
7129        // Generate the base (leftmost) expression
7130        self.generate_expression(leftmost)?;
7131
7132        // Generate each union step from innermost to outermost
7133        for union in chain.iter().rev() {
7134            self.generate_union_step(union)?;
7135        }
7136        Ok(())
7137    }
7138
7139    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
7140    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
7141        if self.config.pretty {
7142            self.write_newline();
7143            self.write_indent();
7144        } else {
7145            self.write_space();
7146        }
7147
7148        // BigQuery set operation modifiers: [side] [kind] UNION
7149        if let Some(side) = &union.side {
7150            self.write_keyword(side);
7151            self.write_space();
7152        }
7153        if let Some(kind) = &union.kind {
7154            self.write_keyword(kind);
7155            self.write_space();
7156        }
7157
7158        self.write_keyword("UNION");
7159        if union.all {
7160            self.write_space();
7161            self.write_keyword("ALL");
7162        } else if union.distinct {
7163            self.write_space();
7164            self.write_keyword("DISTINCT");
7165        }
7166
7167        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7168        // DuckDB: BY NAME
7169        if union.corresponding || union.by_name {
7170            self.write_space();
7171            self.write_keyword("BY NAME");
7172        }
7173        if !union.on_columns.is_empty() {
7174            self.write_space();
7175            self.write_keyword("ON");
7176            self.write(" (");
7177            for (i, col) in union.on_columns.iter().enumerate() {
7178                if i > 0 {
7179                    self.write(", ");
7180                }
7181                self.generate_expression(col)?;
7182            }
7183            self.write(")");
7184        }
7185
7186        if self.config.pretty {
7187            self.write_newline();
7188            self.write_indent();
7189        } else {
7190            self.write_space();
7191        }
7192        self.generate_expression(&union.right)?;
7193        // ORDER BY, LIMIT, OFFSET for the set operation
7194        if let Some(order_by) = &union.order_by {
7195            if self.config.pretty {
7196                self.write_newline();
7197            } else {
7198                self.write_space();
7199            }
7200            self.write_keyword("ORDER BY");
7201            self.write_space();
7202            for (i, ordered) in order_by.expressions.iter().enumerate() {
7203                if i > 0 {
7204                    self.write(", ");
7205                }
7206                self.generate_ordered(ordered)?;
7207            }
7208        }
7209        if let Some(limit) = &union.limit {
7210            if self.config.pretty {
7211                self.write_newline();
7212            } else {
7213                self.write_space();
7214            }
7215            self.write_keyword("LIMIT");
7216            self.write_space();
7217            self.generate_expression(limit)?;
7218        }
7219        if let Some(offset) = &union.offset {
7220            if self.config.pretty {
7221                self.write_newline();
7222            } else {
7223                self.write_space();
7224            }
7225            self.write_keyword("OFFSET");
7226            self.write_space();
7227            self.generate_expression(offset)?;
7228        }
7229        // DISTRIBUTE BY (Hive/Spark)
7230        if let Some(distribute_by) = &union.distribute_by {
7231            self.write_space();
7232            self.write_keyword("DISTRIBUTE BY");
7233            self.write_space();
7234            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7235                if i > 0 {
7236                    self.write(", ");
7237                }
7238                self.generate_expression(expr)?;
7239            }
7240        }
7241        // SORT BY (Hive/Spark)
7242        if let Some(sort_by) = &union.sort_by {
7243            self.write_space();
7244            self.write_keyword("SORT BY");
7245            self.write_space();
7246            for (i, ord) in sort_by.expressions.iter().enumerate() {
7247                if i > 0 {
7248                    self.write(", ");
7249                }
7250                self.generate_ordered(ord)?;
7251            }
7252        }
7253        // CLUSTER BY (Hive/Spark)
7254        if let Some(cluster_by) = &union.cluster_by {
7255            self.write_space();
7256            self.write_keyword("CLUSTER BY");
7257            self.write_space();
7258            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7259                if i > 0 {
7260                    self.write(", ");
7261                }
7262                self.generate_ordered(ord)?;
7263            }
7264        }
7265        Ok(())
7266    }
7267
7268    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
7269        if self.should_wrap_set_operation_modifiers(
7270            &outermost.order_by,
7271            &outermost.limit,
7272            &outermost.offset,
7273        ) {
7274            let mut inner = outermost.clone();
7275            let with = inner.with.take();
7276            let order_by = inner.order_by.take();
7277            let limit = inner.limit.take();
7278            let offset = inner.offset.take();
7279
7280            return self.generate_tsql_wrapped_set_operation(
7281                Expression::Intersect(Box::new(inner)),
7282                with,
7283                order_by,
7284                limit,
7285                offset,
7286            );
7287        }
7288
7289        // Collect the left-recursive chain iteratively to avoid stack overflow
7290        let mut chain: Vec<&Intersect> = vec![outermost];
7291        let mut leftmost: &Expression = &outermost.left;
7292        while let Expression::Intersect(inner) = leftmost {
7293            chain.push(inner);
7294            leftmost = &inner.left;
7295        }
7296
7297        if let Some(with) = &outermost.with {
7298            self.generate_with(with)?;
7299            self.write_space();
7300        }
7301
7302        self.generate_expression(leftmost)?;
7303
7304        for intersect in chain.iter().rev() {
7305            self.generate_intersect_step(intersect)?;
7306        }
7307        Ok(())
7308    }
7309
7310    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
7311    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
7312        if self.config.pretty {
7313            self.write_newline();
7314            self.write_indent();
7315        } else {
7316            self.write_space();
7317        }
7318
7319        // BigQuery set operation modifiers: [side] [kind] INTERSECT
7320        if let Some(side) = &intersect.side {
7321            self.write_keyword(side);
7322            self.write_space();
7323        }
7324        if let Some(kind) = &intersect.kind {
7325            self.write_keyword(kind);
7326            self.write_space();
7327        }
7328
7329        self.write_keyword("INTERSECT");
7330        if intersect.all {
7331            if !self.config.except_intersect_support_all_clause {
7332                self.unsupported("INTERSECT ALL is not supported")?;
7333            }
7334            self.write_space();
7335            self.write_keyword("ALL");
7336        } else if intersect.distinct {
7337            self.write_space();
7338            self.write_keyword("DISTINCT");
7339        }
7340
7341        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7342        // DuckDB: BY NAME
7343        if intersect.corresponding || intersect.by_name {
7344            self.write_space();
7345            self.write_keyword("BY NAME");
7346        }
7347        if !intersect.on_columns.is_empty() {
7348            self.write_space();
7349            self.write_keyword("ON");
7350            self.write(" (");
7351            for (i, col) in intersect.on_columns.iter().enumerate() {
7352                if i > 0 {
7353                    self.write(", ");
7354                }
7355                self.generate_expression(col)?;
7356            }
7357            self.write(")");
7358        }
7359
7360        if self.config.pretty {
7361            self.write_newline();
7362            self.write_indent();
7363        } else {
7364            self.write_space();
7365        }
7366        self.generate_expression(&intersect.right)?;
7367        // ORDER BY, LIMIT, OFFSET for the set operation
7368        if let Some(order_by) = &intersect.order_by {
7369            if self.config.pretty {
7370                self.write_newline();
7371            } else {
7372                self.write_space();
7373            }
7374            self.write_keyword("ORDER BY");
7375            self.write_space();
7376            for (i, ordered) in order_by.expressions.iter().enumerate() {
7377                if i > 0 {
7378                    self.write(", ");
7379                }
7380                self.generate_ordered(ordered)?;
7381            }
7382        }
7383        if let Some(limit) = &intersect.limit {
7384            if self.config.pretty {
7385                self.write_newline();
7386            } else {
7387                self.write_space();
7388            }
7389            self.write_keyword("LIMIT");
7390            self.write_space();
7391            self.generate_expression(limit)?;
7392        }
7393        if let Some(offset) = &intersect.offset {
7394            if self.config.pretty {
7395                self.write_newline();
7396            } else {
7397                self.write_space();
7398            }
7399            self.write_keyword("OFFSET");
7400            self.write_space();
7401            self.generate_expression(offset)?;
7402        }
7403        // DISTRIBUTE BY (Hive/Spark)
7404        if let Some(distribute_by) = &intersect.distribute_by {
7405            self.write_space();
7406            self.write_keyword("DISTRIBUTE BY");
7407            self.write_space();
7408            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7409                if i > 0 {
7410                    self.write(", ");
7411                }
7412                self.generate_expression(expr)?;
7413            }
7414        }
7415        // SORT BY (Hive/Spark)
7416        if let Some(sort_by) = &intersect.sort_by {
7417            self.write_space();
7418            self.write_keyword("SORT BY");
7419            self.write_space();
7420            for (i, ord) in sort_by.expressions.iter().enumerate() {
7421                if i > 0 {
7422                    self.write(", ");
7423                }
7424                self.generate_ordered(ord)?;
7425            }
7426        }
7427        // CLUSTER BY (Hive/Spark)
7428        if let Some(cluster_by) = &intersect.cluster_by {
7429            self.write_space();
7430            self.write_keyword("CLUSTER BY");
7431            self.write_space();
7432            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7433                if i > 0 {
7434                    self.write(", ");
7435                }
7436                self.generate_ordered(ord)?;
7437            }
7438        }
7439        Ok(())
7440    }
7441
7442    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
7443        if self.should_wrap_set_operation_modifiers(
7444            &outermost.order_by,
7445            &outermost.limit,
7446            &outermost.offset,
7447        ) {
7448            let mut inner = outermost.clone();
7449            let with = inner.with.take();
7450            let order_by = inner.order_by.take();
7451            let limit = inner.limit.take();
7452            let offset = inner.offset.take();
7453
7454            return self.generate_tsql_wrapped_set_operation(
7455                Expression::Except(Box::new(inner)),
7456                with,
7457                order_by,
7458                limit,
7459                offset,
7460            );
7461        }
7462
7463        // Collect the left-recursive chain iteratively to avoid stack overflow
7464        let mut chain: Vec<&Except> = vec![outermost];
7465        let mut leftmost: &Expression = &outermost.left;
7466        while let Expression::Except(inner) = leftmost {
7467            chain.push(inner);
7468            leftmost = &inner.left;
7469        }
7470
7471        if let Some(with) = &outermost.with {
7472            self.generate_with(with)?;
7473            self.write_space();
7474        }
7475
7476        self.generate_expression(leftmost)?;
7477
7478        for except in chain.iter().rev() {
7479            self.generate_except_step(except)?;
7480        }
7481        Ok(())
7482    }
7483
7484    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
7485    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
7486        use crate::dialects::DialectType;
7487
7488        if self.config.pretty {
7489            self.write_newline();
7490            self.write_indent();
7491        } else {
7492            self.write_space();
7493        }
7494
7495        // BigQuery set operation modifiers: [side] [kind] EXCEPT
7496        if let Some(side) = &except.side {
7497            self.write_keyword(side);
7498            self.write_space();
7499        }
7500        if let Some(kind) = &except.kind {
7501            self.write_keyword(kind);
7502            self.write_space();
7503        }
7504
7505        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
7506        match self.config.dialect {
7507            Some(DialectType::Oracle) if !except.all => {
7508                self.write_keyword("MINUS");
7509            }
7510            Some(DialectType::ClickHouse) => {
7511                self.write_keyword("EXCEPT");
7512                let preserve_all = self.config.source_dialect.is_none()
7513                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
7514                if except.all && preserve_all {
7515                    self.write_space();
7516                    self.write_keyword("ALL");
7517                }
7518                if except.distinct {
7519                    self.write_space();
7520                    self.write_keyword("DISTINCT");
7521                }
7522            }
7523            Some(DialectType::BigQuery) => {
7524                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
7525                self.write_keyword("EXCEPT");
7526                if except.all {
7527                    self.write_space();
7528                    self.write_keyword("ALL");
7529                } else {
7530                    self.write_space();
7531                    self.write_keyword("DISTINCT");
7532                }
7533            }
7534            _ => {
7535                self.write_keyword("EXCEPT");
7536                if except.all {
7537                    if !self.config.except_intersect_support_all_clause {
7538                        self.unsupported("EXCEPT ALL is not supported")?;
7539                    }
7540                    self.write_space();
7541                    self.write_keyword("ALL");
7542                } else if except.distinct {
7543                    self.write_space();
7544                    self.write_keyword("DISTINCT");
7545                }
7546            }
7547        }
7548
7549        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7550        // DuckDB: BY NAME
7551        if except.corresponding || except.by_name {
7552            self.write_space();
7553            self.write_keyword("BY NAME");
7554        }
7555        if !except.on_columns.is_empty() {
7556            self.write_space();
7557            self.write_keyword("ON");
7558            self.write(" (");
7559            for (i, col) in except.on_columns.iter().enumerate() {
7560                if i > 0 {
7561                    self.write(", ");
7562                }
7563                self.generate_expression(col)?;
7564            }
7565            self.write(")");
7566        }
7567
7568        if self.config.pretty {
7569            self.write_newline();
7570            self.write_indent();
7571        } else {
7572            self.write_space();
7573        }
7574        self.generate_expression(&except.right)?;
7575        // ORDER BY, LIMIT, OFFSET for the set operation
7576        if let Some(order_by) = &except.order_by {
7577            if self.config.pretty {
7578                self.write_newline();
7579            } else {
7580                self.write_space();
7581            }
7582            self.write_keyword("ORDER BY");
7583            self.write_space();
7584            for (i, ordered) in order_by.expressions.iter().enumerate() {
7585                if i > 0 {
7586                    self.write(", ");
7587                }
7588                self.generate_ordered(ordered)?;
7589            }
7590        }
7591        if let Some(limit) = &except.limit {
7592            if self.config.pretty {
7593                self.write_newline();
7594            } else {
7595                self.write_space();
7596            }
7597            self.write_keyword("LIMIT");
7598            self.write_space();
7599            self.generate_expression(limit)?;
7600        }
7601        if let Some(offset) = &except.offset {
7602            if self.config.pretty {
7603                self.write_newline();
7604            } else {
7605                self.write_space();
7606            }
7607            self.write_keyword("OFFSET");
7608            self.write_space();
7609            self.generate_expression(offset)?;
7610        }
7611        // DISTRIBUTE BY (Hive/Spark)
7612        if let Some(distribute_by) = &except.distribute_by {
7613            self.write_space();
7614            self.write_keyword("DISTRIBUTE BY");
7615            self.write_space();
7616            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7617                if i > 0 {
7618                    self.write(", ");
7619                }
7620                self.generate_expression(expr)?;
7621            }
7622        }
7623        // SORT BY (Hive/Spark)
7624        if let Some(sort_by) = &except.sort_by {
7625            self.write_space();
7626            self.write_keyword("SORT BY");
7627            self.write_space();
7628            for (i, ord) in sort_by.expressions.iter().enumerate() {
7629                if i > 0 {
7630                    self.write(", ");
7631                }
7632                self.generate_ordered(ord)?;
7633            }
7634        }
7635        // CLUSTER BY (Hive/Spark)
7636        if let Some(cluster_by) = &except.cluster_by {
7637            self.write_space();
7638            self.write_keyword("CLUSTER BY");
7639            self.write_space();
7640            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7641                if i > 0 {
7642                    self.write(", ");
7643                }
7644                self.generate_ordered(ord)?;
7645            }
7646        }
7647        Ok(())
7648    }
7649
7650    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7651        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7652        let prepend_query_cte = if insert.with.is_none() {
7653            use crate::dialects::DialectType;
7654            let should_prepend = matches!(
7655                self.config.dialect,
7656                Some(DialectType::TSQL)
7657                    | Some(DialectType::Fabric)
7658                    | Some(DialectType::Spark)
7659                    | Some(DialectType::Databricks)
7660                    | Some(DialectType::Hive)
7661            );
7662            if should_prepend {
7663                if let Some(Expression::Select(select)) = &insert.query {
7664                    select.with.clone()
7665                } else {
7666                    None
7667                }
7668            } else {
7669                None
7670            }
7671        } else {
7672            None
7673        };
7674
7675        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7676        if let Some(with) = &insert.with {
7677            self.generate_with(with)?;
7678            self.write_space();
7679        } else if let Some(with) = &prepend_query_cte {
7680            self.generate_with(with)?;
7681            self.write_space();
7682        }
7683
7684        // Output leading comments before INSERT
7685        for comment in &insert.leading_comments {
7686            self.write_formatted_comment(comment);
7687            self.write(" ");
7688        }
7689
7690        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7691        if let Some(dir) = &insert.directory {
7692            self.write_keyword("INSERT OVERWRITE");
7693            if dir.local {
7694                self.write_space();
7695                self.write_keyword("LOCAL");
7696            }
7697            self.write_space();
7698            self.write_keyword("DIRECTORY");
7699            self.write_space();
7700            self.write("'");
7701            self.write(&dir.path);
7702            self.write("'");
7703
7704            // ROW FORMAT clause
7705            if let Some(row_format) = &dir.row_format {
7706                self.write_space();
7707                self.write_keyword("ROW FORMAT");
7708                if row_format.delimited {
7709                    self.write_space();
7710                    self.write_keyword("DELIMITED");
7711                }
7712                if let Some(val) = &row_format.fields_terminated_by {
7713                    self.write_space();
7714                    self.write_keyword("FIELDS TERMINATED BY");
7715                    self.write_space();
7716                    self.generate_string_literal(val)?;
7717                }
7718                if let Some(val) = &row_format.collection_items_terminated_by {
7719                    self.write_space();
7720                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7721                    self.write_space();
7722                    self.write("'");
7723                    self.write(val);
7724                    self.write("'");
7725                }
7726                if let Some(val) = &row_format.map_keys_terminated_by {
7727                    self.write_space();
7728                    self.write_keyword("MAP KEYS TERMINATED BY");
7729                    self.write_space();
7730                    self.write("'");
7731                    self.write(val);
7732                    self.write("'");
7733                }
7734                if let Some(val) = &row_format.lines_terminated_by {
7735                    self.write_space();
7736                    self.write_keyword("LINES TERMINATED BY");
7737                    self.write_space();
7738                    self.write("'");
7739                    self.write(val);
7740                    self.write("'");
7741                }
7742                if let Some(val) = &row_format.null_defined_as {
7743                    self.write_space();
7744                    self.write_keyword("NULL DEFINED AS");
7745                    self.write_space();
7746                    self.write("'");
7747                    self.write(val);
7748                    self.write("'");
7749                }
7750            }
7751
7752            // STORED AS clause
7753            if let Some(format) = &dir.stored_as {
7754                self.write_space();
7755                self.write_keyword("STORED AS");
7756                self.write_space();
7757                self.write_keyword(format);
7758            }
7759
7760            // Query (SELECT statement)
7761            if let Some(query) = &insert.query {
7762                self.write_space();
7763                self.generate_expression(query)?;
7764            }
7765
7766            return Ok(());
7767        }
7768
7769        if insert.is_replace {
7770            // MySQL/SQLite REPLACE INTO statement
7771            self.write_keyword("REPLACE INTO");
7772        } else if insert.overwrite {
7773            // Use dialect-specific INSERT OVERWRITE format
7774            self.write_keyword("INSERT");
7775            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7776            if let Some(ref hint) = insert.hint {
7777                self.generate_hint(hint)?;
7778            }
7779            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7780        } else if let Some(ref action) = insert.conflict_action {
7781            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7782            self.write_keyword("INSERT OR");
7783            self.write_space();
7784            self.write_keyword(action);
7785            self.write_space();
7786            self.write_keyword("INTO");
7787        } else if insert.ignore {
7788            // MySQL INSERT IGNORE syntax
7789            self.write_keyword("INSERT IGNORE INTO");
7790        } else {
7791            self.write_keyword("INSERT");
7792            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7793            if let Some(ref hint) = insert.hint {
7794                self.generate_hint(hint)?;
7795            }
7796            self.write_space();
7797            self.write_keyword("INTO");
7798        }
7799        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7800        if let Some(ref func) = insert.function_target {
7801            self.write_space();
7802            self.write_keyword("FUNCTION");
7803            self.write_space();
7804            self.generate_expression(func)?;
7805        } else {
7806            self.write_space();
7807            self.generate_table(&insert.table)?;
7808        }
7809
7810        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7811        if let Some(ref alias) = insert.alias {
7812            self.write_space();
7813            if insert.alias_explicit_as {
7814                self.write_keyword("AS");
7815                self.write_space();
7816            }
7817            self.generate_identifier(alias)?;
7818        }
7819
7820        // IF EXISTS clause (Hive)
7821        if insert.if_exists {
7822            self.write_space();
7823            self.write_keyword("IF EXISTS");
7824        }
7825
7826        // REPLACE WHERE clause (Databricks)
7827        if let Some(ref replace_where) = insert.replace_where {
7828            if self.config.pretty {
7829                self.write_newline();
7830                self.write_indent();
7831            } else {
7832                self.write_space();
7833            }
7834            self.write_keyword("REPLACE WHERE");
7835            self.write_space();
7836            self.generate_expression(replace_where)?;
7837        }
7838
7839        // Generate PARTITION clause if present
7840        if !insert.partition.is_empty() {
7841            self.write_space();
7842            self.write_keyword("PARTITION");
7843            self.write("(");
7844            for (i, (col, val)) in insert.partition.iter().enumerate() {
7845                if i > 0 {
7846                    self.write(", ");
7847                }
7848                self.generate_identifier(col)?;
7849                if let Some(v) = val {
7850                    self.write(" = ");
7851                    self.generate_expression(v)?;
7852                }
7853            }
7854            self.write(")");
7855        }
7856
7857        // ClickHouse: PARTITION BY expr
7858        if let Some(ref partition_by) = insert.partition_by {
7859            self.write_space();
7860            self.write_keyword("PARTITION BY");
7861            self.write_space();
7862            self.generate_expression(partition_by)?;
7863        }
7864
7865        // ClickHouse: SETTINGS key = val, ...
7866        if !insert.settings.is_empty() {
7867            self.write_space();
7868            self.write_keyword("SETTINGS");
7869            self.write_space();
7870            for (i, setting) in insert.settings.iter().enumerate() {
7871                if i > 0 {
7872                    self.write(", ");
7873                }
7874                self.generate_expression(setting)?;
7875            }
7876        }
7877
7878        if !insert.columns.is_empty() {
7879            if insert.alias.is_some() && insert.alias_explicit_as {
7880                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7881                self.write("(");
7882            } else {
7883                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7884                self.write(" (");
7885            }
7886            for (i, col) in insert.columns.iter().enumerate() {
7887                if i > 0 {
7888                    self.write(", ");
7889                }
7890                self.generate_identifier(col)?;
7891            }
7892            self.write(")");
7893        }
7894
7895        // OUTPUT clause (TSQL)
7896        if let Some(ref output) = insert.output {
7897            self.generate_output_clause(output)?;
7898        }
7899
7900        // BY NAME modifier (DuckDB)
7901        if insert.by_name {
7902            self.write_space();
7903            self.write_keyword("BY NAME");
7904        }
7905
7906        if insert.default_values {
7907            self.write_space();
7908            self.write_keyword("DEFAULT VALUES");
7909        } else if let Some(query) = &insert.query {
7910            if self.config.pretty {
7911                self.write_newline();
7912            } else {
7913                self.write_space();
7914            }
7915            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7916            if prepend_query_cte.is_some() {
7917                if let Expression::Select(select) = query {
7918                    let mut select_no_with = select.clone();
7919                    select_no_with.with = None;
7920                    self.generate_select(&select_no_with)?;
7921                } else {
7922                    self.generate_expression(query)?;
7923                }
7924            } else {
7925                self.generate_expression(query)?;
7926            }
7927        } else if !insert.values.is_empty() {
7928            if self.config.pretty {
7929                // Pretty printing: VALUES on new line, each tuple indented
7930                self.write_newline();
7931                self.write_keyword("VALUES");
7932                self.write_newline();
7933                self.indent_level += 1;
7934                for (i, row) in insert.values.iter().enumerate() {
7935                    if i > 0 {
7936                        self.write(",");
7937                        self.write_newline();
7938                    }
7939                    self.write_indent();
7940                    self.write("(");
7941                    for (j, val) in row.iter().enumerate() {
7942                        if j > 0 {
7943                            self.write(", ");
7944                        }
7945                        self.generate_expression(val)?;
7946                    }
7947                    self.write(")");
7948                }
7949                self.indent_level -= 1;
7950            } else {
7951                // Non-pretty: single line
7952                self.write_space();
7953                self.write_keyword("VALUES");
7954                for (i, row) in insert.values.iter().enumerate() {
7955                    if i > 0 {
7956                        self.write(",");
7957                    }
7958                    self.write(" (");
7959                    for (j, val) in row.iter().enumerate() {
7960                        if j > 0 {
7961                            self.write(", ");
7962                        }
7963                        self.generate_expression(val)?;
7964                    }
7965                    self.write(")");
7966                }
7967            }
7968        }
7969
7970        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7971        if let Some(ref source) = insert.source {
7972            self.write_space();
7973            self.write_keyword("TABLE");
7974            self.write_space();
7975            self.generate_expression(source)?;
7976        }
7977
7978        // Source alias (MySQL: VALUES (...) AS new_data)
7979        if let Some(alias) = &insert.source_alias {
7980            self.write_space();
7981            self.write_keyword("AS");
7982            self.write_space();
7983            self.generate_identifier(alias)?;
7984        }
7985
7986        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7987        if let Some(on_conflict) = &insert.on_conflict {
7988            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7989                self.write_space();
7990                self.generate_expression(on_conflict)?;
7991            }
7992        }
7993
7994        // RETURNING clause
7995        if !insert.returning.is_empty() {
7996            self.write_space();
7997            self.write_keyword("RETURNING");
7998            self.write_space();
7999            for (i, expr) in insert.returning.iter().enumerate() {
8000                if i > 0 {
8001                    self.write(", ");
8002                }
8003                self.generate_expression(expr)?;
8004            }
8005        }
8006
8007        Ok(())
8008    }
8009
8010    fn generate_update(&mut self, update: &Update) -> Result<()> {
8011        // Output leading comments before UPDATE
8012        for comment in &update.leading_comments {
8013            self.write_formatted_comment(comment);
8014            self.write(" ");
8015        }
8016
8017        // WITH clause (CTEs)
8018        if let Some(ref with) = update.with {
8019            self.generate_with(with)?;
8020            self.write_space();
8021        }
8022
8023        self.write_keyword("UPDATE");
8024        if let Some(hint) = &update.hint {
8025            self.generate_hint(hint)?;
8026        }
8027        self.write_space();
8028        self.generate_table(&update.table)?;
8029
8030        let mysql_like_update_from = matches!(
8031            self.config.dialect,
8032            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
8033        ) && update.from_clause.is_some();
8034
8035        let mut set_pairs = update.set.clone();
8036
8037        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
8038        let mut pre_set_joins = update.table_joins.clone();
8039        if mysql_like_update_from {
8040            let target_name = update
8041                .table
8042                .alias
8043                .as_ref()
8044                .map(|a| a.name.clone())
8045                .unwrap_or_else(|| update.table.name.name.clone());
8046
8047            for (col, _) in &mut set_pairs {
8048                if !col.name.contains('.') {
8049                    col.name = format!("{}.{}", target_name, col.name);
8050                }
8051            }
8052
8053            if let Some(from_clause) = &update.from_clause {
8054                for table_expr in &from_clause.expressions {
8055                    pre_set_joins.push(crate::expressions::Join {
8056                        this: table_expr.clone(),
8057                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8058                            value: true,
8059                        })),
8060                        using: Vec::new(),
8061                        kind: crate::expressions::JoinKind::Inner,
8062                        use_inner_keyword: false,
8063                        use_outer_keyword: false,
8064                        deferred_condition: false,
8065                        join_hint: None,
8066                        match_condition: None,
8067                        pivots: Vec::new(),
8068                        comments: Vec::new(),
8069                        nesting_group: 0,
8070                        directed: false,
8071                    });
8072                }
8073            }
8074            for join in &update.from_joins {
8075                let mut join = join.clone();
8076                if join.on.is_none() && join.using.is_empty() {
8077                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8078                        value: true,
8079                    }));
8080                }
8081                pre_set_joins.push(join);
8082            }
8083        }
8084
8085        // Extra tables for multi-table UPDATE (MySQL syntax)
8086        for extra_table in &update.extra_tables {
8087            self.write(", ");
8088            self.generate_table(extra_table)?;
8089        }
8090
8091        // JOINs attached to the table list (MySQL multi-table syntax)
8092        for join in &pre_set_joins {
8093            // generate_join already adds a leading space
8094            self.generate_join(join)?;
8095        }
8096
8097        // Teradata: FROM clause comes before SET
8098        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
8099        if teradata_from_before_set && !mysql_like_update_from {
8100            if let Some(ref from_clause) = update.from_clause {
8101                self.write_space();
8102                self.write_keyword("FROM");
8103                self.write_space();
8104                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8105                    if i > 0 {
8106                        self.write(", ");
8107                    }
8108                    self.generate_expression(table_expr)?;
8109                }
8110            }
8111            for join in &update.from_joins {
8112                self.generate_join(join)?;
8113            }
8114        }
8115
8116        self.write_space();
8117        self.write_keyword("SET");
8118        self.write_space();
8119
8120        for (i, (col, val)) in set_pairs.iter().enumerate() {
8121            if i > 0 {
8122                self.write(", ");
8123            }
8124            self.generate_identifier(col)?;
8125            self.write(" = ");
8126            self.generate_expression(val)?;
8127        }
8128
8129        // OUTPUT clause (TSQL)
8130        if let Some(ref output) = update.output {
8131            self.generate_output_clause(output)?;
8132        }
8133
8134        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
8135        if !mysql_like_update_from && !teradata_from_before_set {
8136            if let Some(ref from_clause) = update.from_clause {
8137                self.write_space();
8138                self.write_keyword("FROM");
8139                self.write_space();
8140                // Generate each table in the FROM clause
8141                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8142                    if i > 0 {
8143                        self.write(", ");
8144                    }
8145                    self.generate_expression(table_expr)?;
8146                }
8147            }
8148        }
8149
8150        if !mysql_like_update_from && !teradata_from_before_set {
8151            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
8152            for join in &update.from_joins {
8153                self.generate_join(join)?;
8154            }
8155        }
8156
8157        if let Some(where_clause) = &update.where_clause {
8158            self.write_space();
8159            self.write_keyword("WHERE");
8160            self.write_space();
8161            self.generate_expression(&where_clause.this)?;
8162        }
8163
8164        // RETURNING clause
8165        if !update.returning.is_empty() {
8166            self.write_space();
8167            self.write_keyword("RETURNING");
8168            self.write_space();
8169            for (i, expr) in update.returning.iter().enumerate() {
8170                if i > 0 {
8171                    self.write(", ");
8172                }
8173                self.generate_expression(expr)?;
8174            }
8175        }
8176
8177        // ORDER BY clause (MySQL)
8178        if let Some(ref order_by) = update.order_by {
8179            self.write_space();
8180            self.generate_order_by(order_by)?;
8181        }
8182
8183        // LIMIT clause (MySQL)
8184        if let Some(ref limit) = update.limit {
8185            self.write_space();
8186            self.write_keyword("LIMIT");
8187            self.write_space();
8188            self.generate_expression(limit)?;
8189        }
8190
8191        Ok(())
8192    }
8193
8194    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
8195        // Output WITH clause if present
8196        if let Some(with) = &delete.with {
8197            self.generate_with(with)?;
8198            self.write_space();
8199        }
8200
8201        // Output leading comments before DELETE
8202        for comment in &delete.leading_comments {
8203            self.write_formatted_comment(comment);
8204            self.write(" ");
8205        }
8206
8207        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
8208        if !delete.tables.is_empty() && !delete.tables_from_using {
8209            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
8210            self.write_keyword("DELETE");
8211            if let Some(hint) = &delete.hint {
8212                self.generate_hint(hint)?;
8213            }
8214            self.write_space();
8215            for (i, tbl) in delete.tables.iter().enumerate() {
8216                if i > 0 {
8217                    self.write(", ");
8218                }
8219                self.generate_table(tbl)?;
8220            }
8221            // TSQL: OUTPUT clause between target table and FROM
8222            if let Some(ref output) = delete.output {
8223                self.generate_output_clause(output)?;
8224            }
8225            self.write_space();
8226            self.write_keyword("FROM");
8227            self.write_space();
8228            self.generate_table(&delete.table)?;
8229        } else if !delete.tables.is_empty() && delete.tables_from_using {
8230            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
8231            self.write_keyword("DELETE");
8232            if let Some(hint) = &delete.hint {
8233                self.generate_hint(hint)?;
8234            }
8235            self.write_space();
8236            self.write_keyword("FROM");
8237            self.write_space();
8238            for (i, tbl) in delete.tables.iter().enumerate() {
8239                if i > 0 {
8240                    self.write(", ");
8241                }
8242                self.generate_table(tbl)?;
8243            }
8244        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
8245            // BigQuery-style DELETE without FROM keyword
8246            self.write_keyword("DELETE");
8247            if let Some(hint) = &delete.hint {
8248                self.generate_hint(hint)?;
8249            }
8250            self.write_space();
8251            self.generate_table(&delete.table)?;
8252        } else {
8253            self.write_keyword("DELETE");
8254            if let Some(hint) = &delete.hint {
8255                self.generate_hint(hint)?;
8256            }
8257            self.write_space();
8258            self.write_keyword("FROM");
8259            self.write_space();
8260            self.generate_table(&delete.table)?;
8261        }
8262
8263        // ClickHouse: ON CLUSTER clause
8264        if let Some(ref on_cluster) = delete.on_cluster {
8265            self.write_space();
8266            self.generate_on_cluster(on_cluster)?;
8267        }
8268
8269        // FORCE INDEX hint (MySQL)
8270        if let Some(ref idx) = delete.force_index {
8271            self.write_space();
8272            self.write_keyword("FORCE INDEX");
8273            self.write(" (");
8274            self.write(idx);
8275            self.write(")");
8276        }
8277
8278        // Optional alias
8279        if let Some(ref alias) = delete.alias {
8280            self.write_space();
8281            if delete.alias_explicit_as
8282                || matches!(self.config.dialect, Some(DialectType::BigQuery))
8283            {
8284                self.write_keyword("AS");
8285                self.write_space();
8286            }
8287            self.generate_identifier(alias)?;
8288        }
8289
8290        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
8291        if !delete.tables_from_using {
8292            for join in &delete.joins {
8293                self.generate_join(join)?;
8294            }
8295        }
8296
8297        // USING clause (PostgreSQL/DuckDB/MySQL)
8298        if !delete.using.is_empty() {
8299            self.write_space();
8300            self.write_keyword("USING");
8301            for (i, table) in delete.using.iter().enumerate() {
8302                if i > 0 {
8303                    self.write(",");
8304                }
8305                self.write_space();
8306                // Check if the table has subquery hints (DuckDB USING with subquery)
8307                if !table.hints.is_empty() && table.name.is_empty() {
8308                    // Subquery in USING: (VALUES ...) AS alias(cols)
8309                    self.generate_expression(&table.hints[0])?;
8310                    if let Some(ref alias) = table.alias {
8311                        self.write_space();
8312                        if table.alias_explicit_as {
8313                            self.write_keyword("AS");
8314                            self.write_space();
8315                        }
8316                        self.generate_identifier(alias)?;
8317                        if !table.column_aliases.is_empty() {
8318                            self.write("(");
8319                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
8320                                if j > 0 {
8321                                    self.write(", ");
8322                                }
8323                                self.generate_identifier(col_alias)?;
8324                            }
8325                            self.write(")");
8326                        }
8327                    }
8328                } else {
8329                    self.generate_table(table)?;
8330                }
8331            }
8332        }
8333
8334        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
8335        if delete.tables_from_using {
8336            for join in &delete.joins {
8337                self.generate_join(join)?;
8338            }
8339        }
8340
8341        // OUTPUT clause (TSQL) - only if not already emitted in the early position
8342        let output_already_emitted =
8343            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
8344        if !output_already_emitted {
8345            if let Some(ref output) = delete.output {
8346                self.generate_output_clause(output)?;
8347            }
8348        }
8349
8350        if let Some(where_clause) = &delete.where_clause {
8351            self.write_space();
8352            self.write_keyword("WHERE");
8353            self.write_space();
8354            self.generate_expression(&where_clause.this)?;
8355        }
8356
8357        // ORDER BY clause (MySQL)
8358        if let Some(ref order_by) = delete.order_by {
8359            self.write_space();
8360            self.generate_order_by(order_by)?;
8361        }
8362
8363        // LIMIT clause (MySQL)
8364        if let Some(ref limit) = delete.limit {
8365            self.write_space();
8366            self.write_keyword("LIMIT");
8367            self.write_space();
8368            self.generate_expression(limit)?;
8369        }
8370
8371        // RETURNING clause (PostgreSQL)
8372        if !delete.returning.is_empty() {
8373            self.write_space();
8374            self.write_keyword("RETURNING");
8375            self.write_space();
8376            for (i, expr) in delete.returning.iter().enumerate() {
8377                if i > 0 {
8378                    self.write(", ");
8379                }
8380                self.generate_expression(expr)?;
8381            }
8382        }
8383
8384        Ok(())
8385    }
8386
8387    // ==================== DDL Generation ====================
8388
8389    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
8390        // Athena: Determine if this is Hive-style DDL or Trino-style DML
8391        // CREATE TABLE AS SELECT uses Trino (double quotes)
8392        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
8393        let saved_athena_hive_context = self.athena_hive_context;
8394        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
8395        if matches!(
8396            self.config.dialect,
8397            Some(crate::dialects::DialectType::Athena)
8398        ) {
8399            // Use Hive context if:
8400            // 1. It's an EXTERNAL table, OR
8401            // 2. There's no AS SELECT clause
8402            let is_external = ct
8403                .table_modifier
8404                .as_ref()
8405                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
8406                .unwrap_or(false);
8407            let has_as_select = ct.as_select.is_some();
8408            self.athena_hive_context = is_external || !has_as_select;
8409        }
8410
8411        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
8412        if matches!(
8413            self.config.dialect,
8414            Some(crate::dialects::DialectType::TSQL)
8415        ) {
8416            if let Some(ref query) = ct.as_select {
8417                // Output WITH CTE clause if present
8418                if let Some(with_cte) = &ct.with_cte {
8419                    self.generate_with(with_cte)?;
8420                    self.write_space();
8421                }
8422
8423                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
8424                self.write_keyword("SELECT");
8425                self.write(" * ");
8426                self.write_keyword("INTO");
8427                self.write_space();
8428
8429                // If temporary, prefix with # for TSQL temp table
8430                if ct.temporary {
8431                    self.write("#");
8432                }
8433                self.generate_table(&ct.name)?;
8434
8435                self.write_space();
8436                self.write_keyword("FROM");
8437                self.write(" (");
8438                // For TSQL, add aliases to select columns to preserve column names
8439                let aliased_query = Self::add_column_aliases_to_query(query.clone());
8440                self.generate_expression(&aliased_query)?;
8441                self.write(") ");
8442                self.write_keyword("AS");
8443                self.write(" temp");
8444                return Ok(());
8445            }
8446        }
8447
8448        // Output WITH CTE clause if present
8449        if let Some(with_cte) = &ct.with_cte {
8450            self.generate_with(with_cte)?;
8451            self.write_space();
8452        }
8453
8454        // Output leading comments before CREATE
8455        for comment in &ct.leading_comments {
8456            self.write_formatted_comment(comment);
8457            self.write(" ");
8458        }
8459        self.write_keyword("CREATE");
8460
8461        if ct.or_replace {
8462            self.write_space();
8463            self.write_keyword("OR REPLACE");
8464        }
8465
8466        if ct.temporary {
8467            self.write_space();
8468            // Oracle uses GLOBAL TEMPORARY TABLE syntax
8469            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
8470                self.write_keyword("GLOBAL TEMPORARY");
8471            } else {
8472                self.write_keyword("TEMPORARY");
8473            }
8474        }
8475
8476        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
8477        let is_dictionary = ct
8478            .table_modifier
8479            .as_ref()
8480            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
8481            .unwrap_or(false);
8482        if let Some(ref modifier) = ct.table_modifier {
8483            // TRANSIENT is Snowflake-specific - skip for other dialects
8484            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
8485                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
8486            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
8487            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
8488                || modifier.eq_ignore_ascii_case("SET")
8489                || modifier.eq_ignore_ascii_case("MULTISET")
8490                || modifier.to_ascii_uppercase().contains("VOLATILE")
8491                || modifier.to_ascii_uppercase().starts_with("SET ")
8492                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
8493            let skip_teradata =
8494                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
8495            if !skip_transient && !skip_teradata {
8496                self.write_space();
8497                self.write_keyword(modifier);
8498            }
8499        }
8500
8501        if !is_dictionary {
8502            self.write_space();
8503            self.write_keyword("TABLE");
8504        }
8505
8506        if ct.if_not_exists {
8507            self.write_space();
8508            self.write_keyword("IF NOT EXISTS");
8509        }
8510
8511        self.write_space();
8512        self.generate_table(&ct.name)?;
8513
8514        // ClickHouse: UUID 'xxx' clause after table name
8515        if let Some(ref uuid) = ct.uuid {
8516            self.write_space();
8517            self.write_keyword("UUID");
8518            self.write(" '");
8519            self.write(uuid);
8520            self.write("'");
8521        }
8522
8523        // ClickHouse: ON CLUSTER clause
8524        if let Some(ref on_cluster) = ct.on_cluster {
8525            self.write_space();
8526            self.generate_on_cluster(on_cluster)?;
8527        }
8528
8529        // Teradata: options after table name before column list (comma-separated)
8530        if matches!(
8531            self.config.dialect,
8532            Some(crate::dialects::DialectType::Teradata)
8533        ) && !ct.teradata_post_name_options.is_empty()
8534        {
8535            for opt in &ct.teradata_post_name_options {
8536                self.write(", ");
8537                self.write(opt);
8538            }
8539        }
8540
8541        // Snowflake: COPY GRANTS clause
8542        if ct.copy_grants {
8543            self.write_space();
8544            self.write_keyword("COPY GRANTS");
8545        }
8546
8547        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
8548        if let Some(ref using_template) = ct.using_template {
8549            self.write_space();
8550            self.write_keyword("USING TEMPLATE");
8551            self.write_space();
8552            self.generate_expression(using_template)?;
8553            return Ok(());
8554        }
8555
8556        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
8557        // When explicit columns or constraints are present, the source must be emitted
8558        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
8559        if is_clickhouse {
8560            if let Some(ref clone_source) = ct.clone_source {
8561                if ct.columns.is_empty() && ct.constraints.is_empty() {
8562                    self.write_space();
8563                    self.write_keyword("AS");
8564                    self.write_space();
8565                    self.generate_table(clone_source)?;
8566                }
8567            }
8568        }
8569
8570        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8571        if !is_clickhouse {
8572            if let Some(ref clone_source) = ct.clone_source {
8573                self.write_space();
8574                if ct.is_copy && self.config.supports_table_copy {
8575                    // BigQuery uses COPY
8576                    self.write_keyword("COPY");
8577                } else if ct.shallow_clone {
8578                    self.write_keyword("SHALLOW CLONE");
8579                } else if ct.deep_clone {
8580                    self.write_keyword("DEEP CLONE");
8581                } else {
8582                    self.write_keyword("CLONE");
8583                }
8584                self.write_space();
8585                self.generate_table(clone_source)?;
8586                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8587                if let Some(ref at_clause) = ct.clone_at_clause {
8588                    self.write_space();
8589                    self.generate_expression(at_clause)?;
8590                }
8591                return Ok(());
8592            }
8593        }
8594
8595        // Handle PARTITION OF property
8596        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8597        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8598        if let Some(ref partition_of) = ct.partition_of {
8599            self.write_space();
8600
8601            // Extract the PartitionedOfProperty parts to generate them separately
8602            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8603                // Output: PARTITION OF <table>
8604                self.write_keyword("PARTITION OF");
8605                self.write_space();
8606                self.generate_expression(&pop.this)?;
8607
8608                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8609                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8610                    self.write(" (");
8611                    let mut first = true;
8612                    for col in &ct.columns {
8613                        if !first {
8614                            self.write(", ");
8615                        }
8616                        first = false;
8617                        self.generate_column_def(col)?;
8618                    }
8619                    for constraint in &ct.constraints {
8620                        if !first {
8621                            self.write(", ");
8622                        }
8623                        first = false;
8624                        self.generate_table_constraint(constraint)?;
8625                    }
8626                    self.write(")");
8627                }
8628
8629                // Output partition bound spec: FOR VALUES ... or DEFAULT
8630                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8631                    self.write_space();
8632                    self.write_keyword("FOR VALUES");
8633                    self.write_space();
8634                    self.generate_expression(&pop.expression)?;
8635                } else {
8636                    self.write_space();
8637                    self.write_keyword("DEFAULT");
8638                }
8639            } else {
8640                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8641                self.generate_expression(partition_of)?;
8642
8643                // Output columns/constraints if present
8644                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8645                    self.write(" (");
8646                    let mut first = true;
8647                    for col in &ct.columns {
8648                        if !first {
8649                            self.write(", ");
8650                        }
8651                        first = false;
8652                        self.generate_column_def(col)?;
8653                    }
8654                    for constraint in &ct.constraints {
8655                        if !first {
8656                            self.write(", ");
8657                        }
8658                        first = false;
8659                        self.generate_table_constraint(constraint)?;
8660                    }
8661                    self.write(")");
8662                }
8663            }
8664
8665            // Output table properties (e.g., PARTITION BY RANGE(population))
8666            for prop in &ct.properties {
8667                self.write_space();
8668                self.generate_expression(prop)?;
8669            }
8670
8671            return Ok(());
8672        }
8673
8674        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8675        // This matches Python sqlglot's behavior for SQLite dialect
8676        self.sqlite_inline_pk_columns.clear();
8677        if matches!(
8678            self.config.dialect,
8679            Some(crate::dialects::DialectType::SQLite)
8680        ) {
8681            for constraint in &ct.constraints {
8682                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8683                    // Only inline if: single column, no constraint name, and column exists in table
8684                    if columns.len() == 1 && name.is_none() {
8685                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8686                        // Check if this column exists in the table
8687                        if ct
8688                            .columns
8689                            .iter()
8690                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8691                        {
8692                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8693                        }
8694                    }
8695                }
8696            }
8697        }
8698
8699        // Output columns if present (even for CTAS with columns)
8700        if !ct.columns.is_empty() {
8701            if self.config.pretty {
8702                // Pretty print: each column on new line
8703                self.write(" (");
8704                self.write_newline();
8705                self.indent_level += 1;
8706                for (i, col) in ct.columns.iter().enumerate() {
8707                    if i > 0 {
8708                        self.write(",");
8709                        self.write_newline();
8710                    }
8711                    self.write_indent();
8712                    self.generate_column_def(col)?;
8713                }
8714                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8715                for constraint in &ct.constraints {
8716                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8717                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8718                        if columns.len() == 1
8719                            && name.is_none()
8720                            && self
8721                                .sqlite_inline_pk_columns
8722                                .contains(&columns[0].name.to_ascii_lowercase())
8723                        {
8724                            continue;
8725                        }
8726                    }
8727                    self.write(",");
8728                    self.write_newline();
8729                    self.write_indent();
8730                    self.generate_table_constraint(constraint)?;
8731                }
8732                self.indent_level -= 1;
8733                self.write_newline();
8734                self.write(")");
8735            } else {
8736                self.write(" (");
8737                for (i, col) in ct.columns.iter().enumerate() {
8738                    if i > 0 {
8739                        self.write(", ");
8740                    }
8741                    self.generate_column_def(col)?;
8742                }
8743                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8744                let mut first_constraint = true;
8745                for constraint in &ct.constraints {
8746                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8747                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8748                        if columns.len() == 1
8749                            && name.is_none()
8750                            && self
8751                                .sqlite_inline_pk_columns
8752                                .contains(&columns[0].name.to_ascii_lowercase())
8753                        {
8754                            continue;
8755                        }
8756                    }
8757                    if first_constraint {
8758                        self.write(", ");
8759                        first_constraint = false;
8760                    } else {
8761                        self.write(", ");
8762                    }
8763                    self.generate_table_constraint(constraint)?;
8764                }
8765                self.write(")");
8766            }
8767        } else if !ct.constraints.is_empty() {
8768            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8769            let has_like_only = ct
8770                .constraints
8771                .iter()
8772                .all(|c| matches!(c, TableConstraint::Like { .. }));
8773            let has_tags_only = ct
8774                .constraints
8775                .iter()
8776                .all(|c| matches!(c, TableConstraint::Tags(_)));
8777            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8778            // Most dialects: CREATE TABLE A LIKE B (no parens)
8779            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8780            let is_pg_like = matches!(
8781                self.config.dialect,
8782                Some(crate::dialects::DialectType::PostgreSQL)
8783                    | Some(crate::dialects::DialectType::CockroachDB)
8784                    | Some(crate::dialects::DialectType::Materialize)
8785                    | Some(crate::dialects::DialectType::RisingWave)
8786                    | Some(crate::dialects::DialectType::Redshift)
8787                    | Some(crate::dialects::DialectType::Presto)
8788                    | Some(crate::dialects::DialectType::Trino)
8789                    | Some(crate::dialects::DialectType::Athena)
8790            );
8791            let use_parens = if has_like_only {
8792                is_pg_like
8793            } else {
8794                !has_tags_only
8795            };
8796            if self.config.pretty && use_parens {
8797                self.write(" (");
8798                self.write_newline();
8799                self.indent_level += 1;
8800                for (i, constraint) in ct.constraints.iter().enumerate() {
8801                    if i > 0 {
8802                        self.write(",");
8803                        self.write_newline();
8804                    }
8805                    self.write_indent();
8806                    self.generate_table_constraint(constraint)?;
8807                }
8808                self.indent_level -= 1;
8809                self.write_newline();
8810                self.write(")");
8811            } else {
8812                if use_parens {
8813                    self.write(" (");
8814                } else {
8815                    self.write_space();
8816                }
8817                for (i, constraint) in ct.constraints.iter().enumerate() {
8818                    if i > 0 {
8819                        self.write(", ");
8820                    }
8821                    self.generate_table_constraint(constraint)?;
8822                }
8823                if use_parens {
8824                    self.write(")");
8825                }
8826            }
8827        }
8828
8829        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8830            if let Some(ref clone_source) = ct.clone_source {
8831                self.write_space();
8832                self.write_keyword("AS");
8833                self.write_space();
8834                self.generate_table(clone_source)?;
8835            }
8836        }
8837
8838        // TSQL ON filegroup or ON filegroup (partition_column) clause
8839        if let Some(ref on_prop) = ct.on_property {
8840            self.write(" ");
8841            self.write_keyword("ON");
8842            self.write(" ");
8843            self.generate_expression(&on_prop.this)?;
8844        }
8845
8846        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8847        if !ct.with_partition_columns.is_empty() {
8848            if self.config.pretty {
8849                self.write_newline();
8850            } else {
8851                self.write_space();
8852            }
8853            self.write_keyword("WITH PARTITION COLUMNS");
8854            self.write(" (");
8855            if self.config.pretty {
8856                self.write_newline();
8857                self.indent_level += 1;
8858                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8859                    if i > 0 {
8860                        self.write(",");
8861                        self.write_newline();
8862                    }
8863                    self.write_indent();
8864                    self.generate_column_def(col)?;
8865                }
8866                self.indent_level -= 1;
8867                self.write_newline();
8868            } else {
8869                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8870                    if i > 0 {
8871                        self.write(", ");
8872                    }
8873                    self.generate_column_def(col)?;
8874                }
8875            }
8876            self.write(")");
8877        }
8878
8879        // BigQuery: WITH CONNECTION `project.region.connection`
8880        if let Some(ref conn) = ct.with_connection {
8881            if self.config.pretty {
8882                self.write_newline();
8883            } else {
8884                self.write_space();
8885            }
8886            self.write_keyword("WITH CONNECTION");
8887            self.write_space();
8888            self.generate_table(conn)?;
8889        }
8890
8891        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8892        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8893        if !is_clickhouse {
8894            for prop in &ct.properties {
8895                if let Expression::SchemaCommentProperty(_) = prop {
8896                    if self.config.pretty {
8897                        self.write_newline();
8898                    } else {
8899                        self.write_space();
8900                    }
8901                    self.generate_expression(prop)?;
8902                }
8903            }
8904        }
8905
8906        // WITH properties (output after columns if columns exist, otherwise before AS)
8907        if !ct.with_properties.is_empty() {
8908            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8909            let is_snowflake_special_table = matches!(
8910                self.config.dialect,
8911                Some(crate::dialects::DialectType::Snowflake)
8912            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8913                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8914            if is_snowflake_special_table {
8915                for (key, value) in &ct.with_properties {
8916                    self.write_space();
8917                    self.write(key);
8918                    self.write("=");
8919                    self.write(value);
8920                }
8921            } else if self.config.pretty {
8922                self.write_newline();
8923                self.write_keyword("WITH");
8924                self.write(" (");
8925                self.write_newline();
8926                self.indent_level += 1;
8927                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8928                    if i > 0 {
8929                        self.write(",");
8930                        self.write_newline();
8931                    }
8932                    self.write_indent();
8933                    self.write(key);
8934                    self.write("=");
8935                    self.write(value);
8936                }
8937                self.indent_level -= 1;
8938                self.write_newline();
8939                self.write(")");
8940            } else {
8941                self.write_space();
8942                self.write_keyword("WITH");
8943                self.write(" (");
8944                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8945                    if i > 0 {
8946                        self.write(", ");
8947                    }
8948                    self.write(key);
8949                    self.write("=");
8950                    self.write(value);
8951                }
8952                self.write(")");
8953            }
8954        }
8955
8956        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8957            if is_clickhouse && ct.as_select.is_some() {
8958                let mut pre = Vec::new();
8959                let mut post = Vec::new();
8960                for prop in &ct.properties {
8961                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8962                        post.push(prop);
8963                    } else {
8964                        pre.push(prop);
8965                    }
8966                }
8967                (pre, post)
8968            } else {
8969                (ct.properties.iter().collect(), Vec::new())
8970            };
8971
8972        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8973        for prop in pre_as_properties {
8974            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8975            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8976                continue;
8977            }
8978            if self.config.pretty {
8979                self.write_newline();
8980            } else {
8981                self.write_space();
8982            }
8983            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8984            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8985            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8986            if let Expression::Properties(props) = prop {
8987                let is_hive_dialect = matches!(
8988                    self.config.dialect,
8989                    Some(crate::dialects::DialectType::Hive)
8990                        | Some(crate::dialects::DialectType::Spark)
8991                        | Some(crate::dialects::DialectType::Databricks)
8992                        | Some(crate::dialects::DialectType::Athena)
8993                );
8994                let is_doris_starrocks = matches!(
8995                    self.config.dialect,
8996                    Some(crate::dialects::DialectType::Doris)
8997                        | Some(crate::dialects::DialectType::StarRocks)
8998                );
8999                if is_hive_dialect {
9000                    self.generate_tblproperties_clause(&props.expressions)?;
9001                } else if is_doris_starrocks {
9002                    self.generate_properties_clause(&props.expressions)?;
9003                } else {
9004                    self.generate_options_clause(&props.expressions)?;
9005                }
9006            } else {
9007                self.generate_expression(prop)?;
9008            }
9009        }
9010
9011        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
9012        for prop in &ct.post_table_properties {
9013            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
9014                self.write(" WITH(");
9015                self.generate_system_versioning_content(svp)?;
9016                self.write(")");
9017            } else if let Expression::Properties(props) = prop {
9018                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
9019                let is_doris_starrocks = matches!(
9020                    self.config.dialect,
9021                    Some(crate::dialects::DialectType::Doris)
9022                        | Some(crate::dialects::DialectType::StarRocks)
9023                );
9024                self.write_space();
9025                if is_doris_starrocks {
9026                    self.generate_properties_clause(&props.expressions)?;
9027                } else {
9028                    self.generate_options_clause(&props.expressions)?;
9029                }
9030            } else {
9031                self.write_space();
9032                self.generate_expression(prop)?;
9033            }
9034        }
9035
9036        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
9037        // Only output for StarRocks target
9038        if let Some(ref rollup) = ct.rollup {
9039            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
9040                self.write_space();
9041                self.generate_rollup_property(rollup)?;
9042            }
9043        }
9044
9045        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
9046        // Only output for MySQL-compatible dialects; strip for others during transpilation
9047        // COMMENT is also used by Hive/Spark so we selectively preserve it
9048        let is_mysql_compatible = matches!(
9049            self.config.dialect,
9050            Some(DialectType::MySQL)
9051                | Some(DialectType::TiDB)
9052                | Some(DialectType::SingleStore)
9053                | Some(DialectType::Doris)
9054                | Some(DialectType::StarRocks)
9055                | None
9056        );
9057        let is_hive_compatible = matches!(
9058            self.config.dialect,
9059            Some(DialectType::Hive)
9060                | Some(DialectType::Spark)
9061                | Some(DialectType::Databricks)
9062                | Some(DialectType::Athena)
9063        );
9064        let mysql_pretty_options =
9065            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
9066        for (key, value) in &ct.mysql_table_options {
9067            // Skip non-MySQL-specific options for non-MySQL targets
9068            let should_output = if is_mysql_compatible {
9069                true
9070            } else if is_hive_compatible && key == "COMMENT" {
9071                true // COMMENT is valid in Hive/Spark table definitions
9072            } else {
9073                false
9074            };
9075            if should_output {
9076                if mysql_pretty_options {
9077                    self.write_newline();
9078                    self.write_indent();
9079                } else {
9080                    self.write_space();
9081                }
9082                self.write_keyword(key);
9083                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
9084                if key == "COMMENT" && !self.config.schema_comment_with_eq {
9085                    self.write_space();
9086                } else {
9087                    self.write("=");
9088                }
9089                self.write(value);
9090            }
9091        }
9092
9093        for option in &ct.tidb_table_options {
9094            if self.config.pretty {
9095                self.write_newline();
9096                self.write_indent();
9097            } else {
9098                self.write_space();
9099            }
9100            self.generate_tidb_table_option(option, false)?;
9101        }
9102
9103        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
9104        if ct.temporary
9105            && matches!(
9106                self.config.dialect,
9107                Some(DialectType::Spark) | Some(DialectType::Databricks)
9108            )
9109            && ct.as_select.is_none()
9110        {
9111            self.write_space();
9112            self.write_keyword("USING PARQUET");
9113        }
9114
9115        // PostgreSQL INHERITS clause
9116        if !ct.inherits.is_empty() {
9117            self.write_space();
9118            self.write_keyword("INHERITS");
9119            self.write(" (");
9120            for (i, parent) in ct.inherits.iter().enumerate() {
9121                if i > 0 {
9122                    self.write(", ");
9123                }
9124                self.generate_table(parent)?;
9125            }
9126            self.write(")");
9127        }
9128
9129        // CREATE TABLE AS SELECT
9130        if let Some(ref query) = ct.as_select {
9131            self.write_space();
9132            self.write_keyword("AS");
9133            self.write_space();
9134            let source_is_clickhouse =
9135                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
9136            let wrap_as_select =
9137                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
9138            if wrap_as_select {
9139                self.write("(");
9140            }
9141            self.generate_expression(query)?;
9142            if wrap_as_select {
9143                self.write(")");
9144            }
9145
9146            // Teradata: WITH DATA / WITH NO DATA
9147            if let Some(with_data) = ct.with_data {
9148                self.write_space();
9149                self.write_keyword("WITH");
9150                if !with_data {
9151                    self.write_space();
9152                    self.write_keyword("NO");
9153                }
9154                self.write_space();
9155                self.write_keyword("DATA");
9156            }
9157
9158            // Teradata: AND STATISTICS / AND NO STATISTICS
9159            if let Some(with_statistics) = ct.with_statistics {
9160                self.write_space();
9161                self.write_keyword("AND");
9162                if !with_statistics {
9163                    self.write_space();
9164                    self.write_keyword("NO");
9165                }
9166                self.write_space();
9167                self.write_keyword("STATISTICS");
9168            }
9169
9170            // Teradata: Index specifications
9171            for index in &ct.teradata_indexes {
9172                self.write_space();
9173                match index.kind {
9174                    TeradataIndexKind::NoPrimary => {
9175                        self.write_keyword("NO PRIMARY INDEX");
9176                    }
9177                    TeradataIndexKind::Primary => {
9178                        self.write_keyword("PRIMARY INDEX");
9179                    }
9180                    TeradataIndexKind::PrimaryAmp => {
9181                        self.write_keyword("PRIMARY AMP INDEX");
9182                    }
9183                    TeradataIndexKind::Unique => {
9184                        self.write_keyword("UNIQUE INDEX");
9185                    }
9186                    TeradataIndexKind::UniquePrimary => {
9187                        self.write_keyword("UNIQUE PRIMARY INDEX");
9188                    }
9189                    TeradataIndexKind::Secondary => {
9190                        self.write_keyword("INDEX");
9191                    }
9192                }
9193                // Output index name if present
9194                if let Some(ref name) = index.name {
9195                    self.write_space();
9196                    self.write(name);
9197                }
9198                // Output columns if present
9199                if !index.columns.is_empty() {
9200                    self.write(" (");
9201                    for (i, col) in index.columns.iter().enumerate() {
9202                        if i > 0 {
9203                            self.write(", ");
9204                        }
9205                        self.write(col);
9206                    }
9207                    self.write(")");
9208                }
9209            }
9210
9211            // Teradata: ON COMMIT behavior for volatile tables
9212            if let Some(ref on_commit) = ct.on_commit {
9213                self.write_space();
9214                self.write_keyword("ON COMMIT");
9215                self.write_space();
9216                match on_commit {
9217                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9218                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9219                }
9220            }
9221
9222            if !post_as_properties.is_empty() {
9223                for prop in post_as_properties {
9224                    self.write_space();
9225                    self.generate_expression(prop)?;
9226                }
9227            }
9228
9229            // Restore Athena Hive context before early return
9230            self.athena_hive_context = saved_athena_hive_context;
9231            return Ok(());
9232        }
9233
9234        // ON COMMIT behavior (for non-CTAS tables)
9235        if let Some(ref on_commit) = ct.on_commit {
9236            self.write_space();
9237            self.write_keyword("ON COMMIT");
9238            self.write_space();
9239            match on_commit {
9240                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9241                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9242            }
9243        }
9244
9245        // Restore Athena Hive context
9246        self.athena_hive_context = saved_athena_hive_context;
9247
9248        Ok(())
9249    }
9250
9251    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
9252    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
9253    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
9254        // Output column name
9255        self.generate_identifier(&col.name)?;
9256        // Output data type if known
9257        if !matches!(col.data_type, DataType::Unknown) {
9258            self.write_space();
9259            self.generate_data_type(&col.data_type)?;
9260        }
9261        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
9262        for constraint in &col.constraints {
9263            if let ColumnConstraint::Path(path_expr) = constraint {
9264                self.write_space();
9265                self.write_keyword("PATH");
9266                self.write_space();
9267                self.generate_expression(path_expr)?;
9268            }
9269        }
9270        Ok(())
9271    }
9272
9273    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
9274        // Check if this is a TSQL computed column (no data type)
9275        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9276            && col
9277                .constraints
9278                .iter()
9279                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9280        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
9281        let omit_computed_type = !self.config.computed_column_with_type
9282            && col
9283                .constraints
9284                .iter()
9285                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9286
9287        // Check if this is a partition column spec (no data type, type is Unknown)
9288        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
9289        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
9290
9291        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
9292        // Also check the no_type flag for SQLite columns without types
9293        let has_no_type = col.no_type
9294            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9295                && col.constraints.is_empty());
9296
9297        self.generate_identifier(&col.name)?;
9298
9299        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
9300        let serial_expansion = if matches!(
9301            self.config.dialect,
9302            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
9303        ) {
9304            if let DataType::Custom { ref name } = col.data_type {
9305                if name.eq_ignore_ascii_case("SERIAL") {
9306                    Some("INT")
9307                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
9308                    Some("BIGINT")
9309                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
9310                    Some("SMALLINT")
9311                } else {
9312                    None
9313                }
9314            } else {
9315                None
9316            }
9317        } else {
9318            None
9319        };
9320
9321        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
9322        {
9323            self.write_space();
9324            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
9325            // since ClickHouse uses explicit Nullable() in its type system.
9326            let saved_nullable_depth = self.clickhouse_nullable_depth;
9327            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9328                self.clickhouse_nullable_depth = -1;
9329            }
9330            if let Some(int_type) = serial_expansion {
9331                // SERIAL -> INT (+ constraints added below)
9332                self.write_keyword(int_type);
9333            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9334                // For DuckDB: convert unsigned integer types to their unsigned equivalents
9335                let unsigned_type = match &col.data_type {
9336                    DataType::Int { .. } => Some("UINTEGER"),
9337                    DataType::BigInt { .. } => Some("UBIGINT"),
9338                    DataType::SmallInt { .. } => Some("USMALLINT"),
9339                    DataType::TinyInt { .. } => Some("UTINYINT"),
9340                    _ => None,
9341                };
9342                if let Some(utype) = unsigned_type {
9343                    self.write_keyword(utype);
9344                } else {
9345                    self.generate_data_type(&col.data_type)?;
9346                }
9347            } else {
9348                self.generate_data_type(&col.data_type)?;
9349            }
9350            self.clickhouse_nullable_depth = saved_nullable_depth;
9351        }
9352
9353        // MySQL type modifiers (must come right after data type)
9354        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
9355        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9356            self.write_space();
9357            self.write_keyword("UNSIGNED");
9358        }
9359        if col.zerofill {
9360            self.write_space();
9361            self.write_keyword("ZEROFILL");
9362        }
9363
9364        // Teradata column attributes (must come right after data type, in specific order)
9365        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
9366
9367        if let Some(ref charset) = col.character_set {
9368            self.write_space();
9369            self.write_keyword("CHARACTER SET");
9370            self.write_space();
9371            self.write(charset);
9372        }
9373
9374        if col.uppercase {
9375            self.write_space();
9376            self.write_keyword("UPPERCASE");
9377        }
9378
9379        if let Some(casespecific) = col.casespecific {
9380            self.write_space();
9381            if casespecific {
9382                self.write_keyword("CASESPECIFIC");
9383            } else {
9384                self.write_keyword("NOT CASESPECIFIC");
9385            }
9386        }
9387
9388        if let Some(ref format) = col.format {
9389            self.write_space();
9390            self.write_keyword("FORMAT");
9391            self.write(" '");
9392            self.write(format);
9393            self.write("'");
9394        }
9395
9396        if let Some(ref title) = col.title {
9397            self.write_space();
9398            self.write_keyword("TITLE");
9399            self.write(" '");
9400            self.write(title);
9401            self.write("'");
9402        }
9403
9404        if let Some(length) = col.inline_length {
9405            self.write_space();
9406            self.write_keyword("INLINE LENGTH");
9407            self.write(" ");
9408            self.write(&length.to_string());
9409        }
9410
9411        if let Some(ref compress) = col.compress {
9412            self.write_space();
9413            self.write_keyword("COMPRESS");
9414            if !compress.is_empty() {
9415                // Single string literal: output without parentheses (Teradata syntax)
9416                if compress.len() == 1 {
9417                    if let Expression::Literal(lit) = &compress[0] {
9418                        if let Literal::String(_) = lit.as_ref() {
9419                            self.write_space();
9420                            self.generate_expression(&compress[0])?;
9421                        }
9422                    } else {
9423                        self.write(" (");
9424                        self.generate_expression(&compress[0])?;
9425                        self.write(")");
9426                    }
9427                } else {
9428                    self.write(" (");
9429                    for (i, val) in compress.iter().enumerate() {
9430                        if i > 0 {
9431                            self.write(", ");
9432                        }
9433                        self.generate_expression(val)?;
9434                    }
9435                    self.write(")");
9436                }
9437            }
9438        }
9439
9440        // Column constraints - output in original order if constraint_order is populated
9441        // Otherwise fall back to legacy fixed order for backward compatibility
9442        if !col.constraint_order.is_empty() {
9443            // Use constraint_order for original ordering
9444            // Track indices for constraints stored in the constraints Vec
9445            let mut references_idx = 0;
9446            let mut check_idx = 0;
9447            let mut generated_idx = 0;
9448            let mut collate_idx = 0;
9449            let mut comment_idx = 0;
9450            // The preprocessing in dialects/mod.rs now handles the correct ordering of
9451            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
9452            let defer_not_null_after_identity = false;
9453            let mut pending_not_null_after_identity = false;
9454
9455            for constraint_type in &col.constraint_order {
9456                match constraint_type {
9457                    ConstraintType::PrimaryKey => {
9458                        // Materialize doesn't support PRIMARY KEY column constraints
9459                        if col.primary_key
9460                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
9461                        {
9462                            if let Some(ref cname) = col.primary_key_constraint_name {
9463                                self.write_space();
9464                                self.write_keyword("CONSTRAINT");
9465                                self.write_space();
9466                                self.write(cname);
9467                            }
9468                            self.write_space();
9469                            self.write_keyword("PRIMARY KEY");
9470                            if let Some(ref order) = col.primary_key_order {
9471                                self.write_space();
9472                                match order {
9473                                    SortOrder::Asc => self.write_keyword("ASC"),
9474                                    SortOrder::Desc => self.write_keyword("DESC"),
9475                                }
9476                            }
9477                        }
9478                    }
9479                    ConstraintType::Unique => {
9480                        if col.unique {
9481                            if let Some(ref cname) = col.unique_constraint_name {
9482                                self.write_space();
9483                                self.write_keyword("CONSTRAINT");
9484                                self.write_space();
9485                                self.write(cname);
9486                            }
9487                            self.write_space();
9488                            self.write_keyword("UNIQUE");
9489                            // PostgreSQL 15+: NULLS NOT DISTINCT
9490                            if col.unique_nulls_not_distinct {
9491                                self.write(" NULLS NOT DISTINCT");
9492                            }
9493                        }
9494                    }
9495                    ConstraintType::NotNull => {
9496                        if col.nullable == Some(false) {
9497                            if defer_not_null_after_identity {
9498                                pending_not_null_after_identity = true;
9499                                continue;
9500                            }
9501                            if let Some(ref cname) = col.not_null_constraint_name {
9502                                self.write_space();
9503                                self.write_keyword("CONSTRAINT");
9504                                self.write_space();
9505                                self.write(cname);
9506                            }
9507                            self.write_space();
9508                            self.write_keyword("NOT NULL");
9509                        }
9510                    }
9511                    ConstraintType::Null => {
9512                        if col.nullable == Some(true) {
9513                            self.write_space();
9514                            self.write_keyword("NULL");
9515                        }
9516                    }
9517                    ConstraintType::Default => {
9518                        if let Some(ref default) = col.default {
9519                            self.write_space();
9520                            self.write_keyword("DEFAULT");
9521                            self.write_space();
9522                            self.generate_expression(default)?;
9523                        }
9524                    }
9525                    ConstraintType::AutoIncrement => {
9526                        if col.auto_increment {
9527                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
9528                            if matches!(
9529                                self.config.dialect,
9530                                Some(crate::dialects::DialectType::DuckDB)
9531                            ) {
9532                                // Skip - DuckDB uses sequences or rowid instead
9533                            } else if matches!(
9534                                self.config.dialect,
9535                                Some(crate::dialects::DialectType::Materialize)
9536                            ) {
9537                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
9538                                if !matches!(col.nullable, Some(false)) {
9539                                    self.write_space();
9540                                    self.write_keyword("NOT NULL");
9541                                }
9542                            } else if matches!(
9543                                self.config.dialect,
9544                                Some(crate::dialects::DialectType::PostgreSQL)
9545                            ) {
9546                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
9547                                self.write_space();
9548                                self.generate_auto_increment_keyword(col)?;
9549                            } else if matches!(
9550                                self.config.dialect,
9551                                Some(crate::dialects::DialectType::SQLite)
9552                            ) && !col.primary_key
9553                                && self
9554                                    .sqlite_inline_pk_columns
9555                                    .contains(&col.name.name.to_ascii_lowercase())
9556                            {
9557                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
9558                                // The table-level primary key is emitted later inline.
9559                            } else {
9560                                self.write_space();
9561                                self.generate_auto_increment_keyword(col)?;
9562                                if pending_not_null_after_identity {
9563                                    self.write_space();
9564                                    self.write_keyword("NOT NULL");
9565                                    pending_not_null_after_identity = false;
9566                                }
9567                            }
9568                        } // close else for DuckDB skip
9569                    }
9570                    ConstraintType::AutoRandom => {
9571                        if let Some(ref auto_random) = col.auto_random {
9572                            self.write_space();
9573                            self.generate_tidb_auto_random(auto_random)?;
9574                        }
9575                    }
9576                    ConstraintType::References => {
9577                        // Find next References constraint
9578                        while references_idx < col.constraints.len() {
9579                            if let ColumnConstraint::References(fk_ref) =
9580                                &col.constraints[references_idx]
9581                            {
9582                                // CONSTRAINT name if present
9583                                if let Some(ref name) = fk_ref.constraint_name {
9584                                    self.write_space();
9585                                    self.write_keyword("CONSTRAINT");
9586                                    self.write_space();
9587                                    self.write(name);
9588                                }
9589                                self.write_space();
9590                                if fk_ref.has_foreign_key_keywords {
9591                                    self.write_keyword("FOREIGN KEY");
9592                                    self.write_space();
9593                                }
9594                                self.write_keyword("REFERENCES");
9595                                self.write_space();
9596                                self.generate_table(&fk_ref.table)?;
9597                                if !fk_ref.columns.is_empty() {
9598                                    self.write(" (");
9599                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9600                                        if i > 0 {
9601                                            self.write(", ");
9602                                        }
9603                                        self.generate_identifier(c)?;
9604                                    }
9605                                    self.write(")");
9606                                }
9607                                self.generate_referential_actions(fk_ref)?;
9608                                references_idx += 1;
9609                                break;
9610                            }
9611                            references_idx += 1;
9612                        }
9613                    }
9614                    ConstraintType::Check => {
9615                        // Find next Check constraint
9616                        while check_idx < col.constraints.len() {
9617                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9618                                // Output CONSTRAINT name if present (only for first CHECK)
9619                                if check_idx == 0 {
9620                                    if let Some(ref cname) = col.check_constraint_name {
9621                                        self.write_space();
9622                                        self.write_keyword("CONSTRAINT");
9623                                        self.write_space();
9624                                        self.write(cname);
9625                                    }
9626                                }
9627                                self.write_space();
9628                                self.write_keyword("CHECK");
9629                                self.write(" (");
9630                                self.generate_expression(expr)?;
9631                                self.write(")");
9632                                check_idx += 1;
9633                                break;
9634                            }
9635                            check_idx += 1;
9636                        }
9637                    }
9638                    ConstraintType::GeneratedAsIdentity => {
9639                        // Find next GeneratedAsIdentity constraint
9640                        while generated_idx < col.constraints.len() {
9641                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9642                                &col.constraints[generated_idx]
9643                            {
9644                                self.write_space();
9645                                // Redshift uses IDENTITY(start, increment) syntax
9646                                if matches!(
9647                                    self.config.dialect,
9648                                    Some(crate::dialects::DialectType::Redshift)
9649                                ) {
9650                                    self.write_keyword("IDENTITY");
9651                                    self.write("(");
9652                                    if let Some(ref start) = gen.start {
9653                                        self.generate_expression(start)?;
9654                                    } else {
9655                                        self.write("0");
9656                                    }
9657                                    self.write(", ");
9658                                    if let Some(ref incr) = gen.increment {
9659                                        self.generate_expression(incr)?;
9660                                    } else {
9661                                        self.write("1");
9662                                    }
9663                                    self.write(")");
9664                                } else {
9665                                    self.write_keyword("GENERATED");
9666                                    if gen.always {
9667                                        self.write_space();
9668                                        self.write_keyword("ALWAYS");
9669                                    } else {
9670                                        self.write_space();
9671                                        self.write_keyword("BY DEFAULT");
9672                                        if gen.on_null {
9673                                            self.write_space();
9674                                            self.write_keyword("ON NULL");
9675                                        }
9676                                    }
9677                                    self.write_space();
9678                                    self.write_keyword("AS IDENTITY");
9679
9680                                    let has_options = gen.start.is_some()
9681                                        || gen.increment.is_some()
9682                                        || gen.minvalue.is_some()
9683                                        || gen.maxvalue.is_some()
9684                                        || gen.cycle.is_some();
9685                                    if has_options {
9686                                        self.write(" (");
9687                                        let mut first = true;
9688                                        if let Some(ref start) = gen.start {
9689                                            if !first {
9690                                                self.write(" ");
9691                                            }
9692                                            first = false;
9693                                            self.write_keyword("START WITH");
9694                                            self.write_space();
9695                                            self.generate_expression(start)?;
9696                                        }
9697                                        if let Some(ref incr) = gen.increment {
9698                                            if !first {
9699                                                self.write(" ");
9700                                            }
9701                                            first = false;
9702                                            self.write_keyword("INCREMENT BY");
9703                                            self.write_space();
9704                                            self.generate_expression(incr)?;
9705                                        }
9706                                        if let Some(ref minv) = gen.minvalue {
9707                                            if !first {
9708                                                self.write(" ");
9709                                            }
9710                                            first = false;
9711                                            self.write_keyword("MINVALUE");
9712                                            self.write_space();
9713                                            self.generate_expression(minv)?;
9714                                        }
9715                                        if let Some(ref maxv) = gen.maxvalue {
9716                                            if !first {
9717                                                self.write(" ");
9718                                            }
9719                                            first = false;
9720                                            self.write_keyword("MAXVALUE");
9721                                            self.write_space();
9722                                            self.generate_expression(maxv)?;
9723                                        }
9724                                        if let Some(cycle) = gen.cycle {
9725                                            if !first {
9726                                                self.write(" ");
9727                                            }
9728                                            if cycle {
9729                                                self.write_keyword("CYCLE");
9730                                            } else {
9731                                                self.write_keyword("NO CYCLE");
9732                                            }
9733                                        }
9734                                        self.write(")");
9735                                    }
9736                                }
9737                                generated_idx += 1;
9738                                break;
9739                            }
9740                            generated_idx += 1;
9741                        }
9742                    }
9743                    ConstraintType::Collate => {
9744                        // Find next Collate constraint
9745                        while collate_idx < col.constraints.len() {
9746                            if let ColumnConstraint::Collate(collation) =
9747                                &col.constraints[collate_idx]
9748                            {
9749                                self.write_space();
9750                                self.write_keyword("COLLATE");
9751                                self.write_space();
9752                                self.generate_identifier(collation)?;
9753                                collate_idx += 1;
9754                                break;
9755                            }
9756                            collate_idx += 1;
9757                        }
9758                    }
9759                    ConstraintType::Comment => {
9760                        // Find next Comment constraint
9761                        while comment_idx < col.constraints.len() {
9762                            if let ColumnConstraint::Comment(comment) =
9763                                &col.constraints[comment_idx]
9764                            {
9765                                self.write_space();
9766                                self.write_keyword("COMMENT");
9767                                self.write_space();
9768                                self.generate_string_literal(comment)?;
9769                                comment_idx += 1;
9770                                break;
9771                            }
9772                            comment_idx += 1;
9773                        }
9774                    }
9775                    ConstraintType::Tags => {
9776                        // Find next Tags constraint (Snowflake)
9777                        for constraint in &col.constraints {
9778                            if let ColumnConstraint::Tags(tags) = constraint {
9779                                self.write_space();
9780                                self.write_keyword("TAG");
9781                                self.write(" (");
9782                                for (i, expr) in tags.expressions.iter().enumerate() {
9783                                    if i > 0 {
9784                                        self.write(", ");
9785                                    }
9786                                    self.generate_expression(expr)?;
9787                                }
9788                                self.write(")");
9789                                break;
9790                            }
9791                        }
9792                    }
9793                    ConstraintType::ComputedColumn => {
9794                        // Find next ComputedColumn constraint
9795                        for constraint in &col.constraints {
9796                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9797                                self.write_space();
9798                                self.generate_computed_column_inline(cc)?;
9799                                break;
9800                            }
9801                        }
9802                    }
9803                    ConstraintType::GeneratedAsRow => {
9804                        // Find next GeneratedAsRow constraint
9805                        for constraint in &col.constraints {
9806                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9807                                self.write_space();
9808                                self.generate_generated_as_row_inline(gar)?;
9809                                break;
9810                            }
9811                        }
9812                    }
9813                    ConstraintType::OnUpdate => {
9814                        if let Some(ref expr) = col.on_update {
9815                            self.write_space();
9816                            self.write_keyword("ON UPDATE");
9817                            self.write_space();
9818                            self.generate_expression(expr)?;
9819                        }
9820                    }
9821                    ConstraintType::Encode => {
9822                        if let Some(ref encoding) = col.encoding {
9823                            self.write_space();
9824                            self.write_keyword("ENCODE");
9825                            self.write_space();
9826                            self.write(encoding);
9827                        }
9828                    }
9829                    ConstraintType::Path => {
9830                        // Find next Path constraint
9831                        for constraint in &col.constraints {
9832                            if let ColumnConstraint::Path(path_expr) = constraint {
9833                                self.write_space();
9834                                self.write_keyword("PATH");
9835                                self.write_space();
9836                                self.generate_expression(path_expr)?;
9837                                break;
9838                            }
9839                        }
9840                    }
9841                }
9842            }
9843            if pending_not_null_after_identity {
9844                self.write_space();
9845                self.write_keyword("NOT NULL");
9846            }
9847        } else {
9848            // Legacy fixed order for backward compatibility
9849            if col.primary_key {
9850                self.write_space();
9851                self.write_keyword("PRIMARY KEY");
9852                if let Some(ref order) = col.primary_key_order {
9853                    self.write_space();
9854                    match order {
9855                        SortOrder::Asc => self.write_keyword("ASC"),
9856                        SortOrder::Desc => self.write_keyword("DESC"),
9857                    }
9858                }
9859            }
9860
9861            if col.unique {
9862                self.write_space();
9863                self.write_keyword("UNIQUE");
9864                // PostgreSQL 15+: NULLS NOT DISTINCT
9865                if col.unique_nulls_not_distinct {
9866                    self.write(" NULLS NOT DISTINCT");
9867                }
9868            }
9869
9870            match col.nullable {
9871                Some(false) => {
9872                    self.write_space();
9873                    self.write_keyword("NOT NULL");
9874                }
9875                Some(true) => {
9876                    self.write_space();
9877                    self.write_keyword("NULL");
9878                }
9879                None => {}
9880            }
9881
9882            if let Some(ref default) = col.default {
9883                self.write_space();
9884                self.write_keyword("DEFAULT");
9885                self.write_space();
9886                self.generate_expression(default)?;
9887            }
9888
9889            if col.auto_increment {
9890                self.write_space();
9891                self.generate_auto_increment_keyword(col)?;
9892            }
9893
9894            if let Some(ref auto_random) = col.auto_random {
9895                self.write_space();
9896                self.generate_tidb_auto_random(auto_random)?;
9897            }
9898
9899            // Column-level constraints from Vec
9900            for constraint in &col.constraints {
9901                match constraint {
9902                    ColumnConstraint::References(fk_ref) => {
9903                        self.write_space();
9904                        if fk_ref.has_foreign_key_keywords {
9905                            self.write_keyword("FOREIGN KEY");
9906                            self.write_space();
9907                        }
9908                        self.write_keyword("REFERENCES");
9909                        self.write_space();
9910                        self.generate_table(&fk_ref.table)?;
9911                        if !fk_ref.columns.is_empty() {
9912                            self.write(" (");
9913                            for (i, c) in fk_ref.columns.iter().enumerate() {
9914                                if i > 0 {
9915                                    self.write(", ");
9916                                }
9917                                self.generate_identifier(c)?;
9918                            }
9919                            self.write(")");
9920                        }
9921                        self.generate_referential_actions(fk_ref)?;
9922                    }
9923                    ColumnConstraint::Check(expr) => {
9924                        self.write_space();
9925                        self.write_keyword("CHECK");
9926                        self.write(" (");
9927                        self.generate_expression(expr)?;
9928                        self.write(")");
9929                    }
9930                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9931                        self.write_space();
9932                        // Redshift uses IDENTITY(start, increment) syntax
9933                        if matches!(
9934                            self.config.dialect,
9935                            Some(crate::dialects::DialectType::Redshift)
9936                        ) {
9937                            self.write_keyword("IDENTITY");
9938                            self.write("(");
9939                            if let Some(ref start) = gen.start {
9940                                self.generate_expression(start)?;
9941                            } else {
9942                                self.write("0");
9943                            }
9944                            self.write(", ");
9945                            if let Some(ref incr) = gen.increment {
9946                                self.generate_expression(incr)?;
9947                            } else {
9948                                self.write("1");
9949                            }
9950                            self.write(")");
9951                        } else {
9952                            self.write_keyword("GENERATED");
9953                            if gen.always {
9954                                self.write_space();
9955                                self.write_keyword("ALWAYS");
9956                            } else {
9957                                self.write_space();
9958                                self.write_keyword("BY DEFAULT");
9959                                if gen.on_null {
9960                                    self.write_space();
9961                                    self.write_keyword("ON NULL");
9962                                }
9963                            }
9964                            self.write_space();
9965                            self.write_keyword("AS IDENTITY");
9966
9967                            let has_options = gen.start.is_some()
9968                                || gen.increment.is_some()
9969                                || gen.minvalue.is_some()
9970                                || gen.maxvalue.is_some()
9971                                || gen.cycle.is_some();
9972                            if has_options {
9973                                self.write(" (");
9974                                let mut first = true;
9975                                if let Some(ref start) = gen.start {
9976                                    if !first {
9977                                        self.write(" ");
9978                                    }
9979                                    first = false;
9980                                    self.write_keyword("START WITH");
9981                                    self.write_space();
9982                                    self.generate_expression(start)?;
9983                                }
9984                                if let Some(ref incr) = gen.increment {
9985                                    if !first {
9986                                        self.write(" ");
9987                                    }
9988                                    first = false;
9989                                    self.write_keyword("INCREMENT BY");
9990                                    self.write_space();
9991                                    self.generate_expression(incr)?;
9992                                }
9993                                if let Some(ref minv) = gen.minvalue {
9994                                    if !first {
9995                                        self.write(" ");
9996                                    }
9997                                    first = false;
9998                                    self.write_keyword("MINVALUE");
9999                                    self.write_space();
10000                                    self.generate_expression(minv)?;
10001                                }
10002                                if let Some(ref maxv) = gen.maxvalue {
10003                                    if !first {
10004                                        self.write(" ");
10005                                    }
10006                                    first = false;
10007                                    self.write_keyword("MAXVALUE");
10008                                    self.write_space();
10009                                    self.generate_expression(maxv)?;
10010                                }
10011                                if let Some(cycle) = gen.cycle {
10012                                    if !first {
10013                                        self.write(" ");
10014                                    }
10015                                    if cycle {
10016                                        self.write_keyword("CYCLE");
10017                                    } else {
10018                                        self.write_keyword("NO CYCLE");
10019                                    }
10020                                }
10021                                self.write(")");
10022                            }
10023                        }
10024                    }
10025                    ColumnConstraint::Collate(collation) => {
10026                        self.write_space();
10027                        self.write_keyword("COLLATE");
10028                        self.write_space();
10029                        self.generate_identifier(collation)?;
10030                    }
10031                    ColumnConstraint::Comment(comment) => {
10032                        self.write_space();
10033                        self.write_keyword("COMMENT");
10034                        self.write_space();
10035                        self.generate_string_literal(comment)?;
10036                    }
10037                    ColumnConstraint::Path(path_expr) => {
10038                        self.write_space();
10039                        self.write_keyword("PATH");
10040                        self.write_space();
10041                        self.generate_expression(path_expr)?;
10042                    }
10043                    _ => {} // Other constraints handled above
10044                }
10045            }
10046
10047            // Redshift: ENCODE encoding_type (legacy path)
10048            if let Some(ref encoding) = col.encoding {
10049                self.write_space();
10050                self.write_keyword("ENCODE");
10051                self.write_space();
10052                self.write(encoding);
10053            }
10054        }
10055
10056        // ClickHouse: CODEC(...)
10057        if let Some(ref codec) = col.codec {
10058            self.write_space();
10059            self.write_keyword("CODEC");
10060            self.write("(");
10061            self.write(codec);
10062            self.write(")");
10063        }
10064
10065        if let Some(visible) = col.visible {
10066            self.write_space();
10067            if visible {
10068                self.write_keyword("VISIBLE");
10069            } else {
10070                self.write_keyword("INVISIBLE");
10071            }
10072        }
10073
10074        // ClickHouse: EPHEMERAL [expr]
10075        if let Some(ref ephemeral) = col.ephemeral {
10076            self.write_space();
10077            self.write_keyword("EPHEMERAL");
10078            if let Some(ref expr) = ephemeral {
10079                self.write_space();
10080                self.generate_expression(expr)?;
10081            }
10082        }
10083
10084        // ClickHouse: MATERIALIZED expr
10085        if let Some(ref mat_expr) = col.materialized_expr {
10086            self.write_space();
10087            self.write_keyword("MATERIALIZED");
10088            self.write_space();
10089            self.generate_expression(mat_expr)?;
10090        }
10091
10092        // ClickHouse: ALIAS expr
10093        if let Some(ref alias_expr) = col.alias_expr {
10094            self.write_space();
10095            self.write_keyword("ALIAS");
10096            self.write_space();
10097            self.generate_expression(alias_expr)?;
10098        }
10099
10100        // ClickHouse: TTL expr
10101        if let Some(ref ttl_expr) = col.ttl_expr {
10102            self.write_space();
10103            self.write_keyword("TTL");
10104            self.write_space();
10105            self.generate_expression(ttl_expr)?;
10106        }
10107
10108        // TSQL: NOT FOR REPLICATION
10109        if col.not_for_replication
10110            && matches!(
10111                self.config.dialect,
10112                Some(crate::dialects::DialectType::TSQL)
10113                    | Some(crate::dialects::DialectType::Fabric)
10114            )
10115        {
10116            self.write_space();
10117            self.write_keyword("NOT FOR REPLICATION");
10118        }
10119
10120        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
10121        if !col.options.is_empty() {
10122            self.write_space();
10123            self.generate_options_clause(&col.options)?;
10124        }
10125
10126        // SQLite: Inline PRIMARY KEY from table constraint
10127        // This comes at the end, after all existing column constraints
10128        if !col.primary_key
10129            && self
10130                .sqlite_inline_pk_columns
10131                .contains(&col.name.name.to_ascii_lowercase())
10132        {
10133            self.write_space();
10134            self.write_keyword("PRIMARY KEY");
10135            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
10136                self.write_space();
10137                self.generate_auto_increment_keyword(col)?;
10138            }
10139        }
10140
10141        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
10142        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
10143        if serial_expansion.is_some() {
10144            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
10145                self.write_space();
10146                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
10147            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
10148                self.write_space();
10149                self.write_keyword("NOT NULL");
10150            }
10151        }
10152
10153        Ok(())
10154    }
10155
10156    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
10157        match constraint {
10158            TableConstraint::PrimaryKey {
10159                name,
10160                columns,
10161                include_columns,
10162                modifiers,
10163                has_constraint_keyword,
10164            } => {
10165                if let Some(ref n) = name {
10166                    if *has_constraint_keyword {
10167                        self.write_keyword("CONSTRAINT");
10168                        self.write_space();
10169                        self.generate_identifier(n)?;
10170                        self.write_space();
10171                    }
10172                }
10173                self.write_keyword("PRIMARY KEY");
10174                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10175                if let Some(ref clustered) = modifiers.clustered {
10176                    self.write_space();
10177                    self.write_keyword(clustered);
10178                }
10179                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
10180                if let Some(ref n) = name {
10181                    if !*has_constraint_keyword {
10182                        self.write_space();
10183                        self.generate_identifier(n)?;
10184                    }
10185                }
10186                self.write(" (");
10187                for (i, col) in columns.iter().enumerate() {
10188                    if i > 0 {
10189                        self.write(", ");
10190                    }
10191                    self.generate_identifier(col)?;
10192                }
10193                self.write(")");
10194                if !include_columns.is_empty() {
10195                    self.write_space();
10196                    self.write_keyword("INCLUDE");
10197                    self.write(" (");
10198                    for (i, col) in include_columns.iter().enumerate() {
10199                        if i > 0 {
10200                            self.write(", ");
10201                        }
10202                        self.generate_identifier(col)?;
10203                    }
10204                    self.write(")");
10205                }
10206                self.generate_constraint_modifiers(modifiers);
10207            }
10208            TableConstraint::Unique {
10209                name,
10210                columns,
10211                columns_parenthesized,
10212                modifiers,
10213                has_constraint_keyword,
10214                nulls_not_distinct,
10215            } => {
10216                if let Some(ref n) = name {
10217                    if *has_constraint_keyword {
10218                        self.write_keyword("CONSTRAINT");
10219                        self.write_space();
10220                        self.generate_identifier(n)?;
10221                        self.write_space();
10222                    }
10223                }
10224                self.write_keyword("UNIQUE");
10225                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10226                if let Some(ref clustered) = modifiers.clustered {
10227                    self.write_space();
10228                    self.write_keyword(clustered);
10229                }
10230                // PostgreSQL 15+: NULLS NOT DISTINCT
10231                if *nulls_not_distinct {
10232                    self.write(" NULLS NOT DISTINCT");
10233                }
10234                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
10235                if let Some(ref n) = name {
10236                    if !*has_constraint_keyword {
10237                        self.write_space();
10238                        self.generate_identifier(n)?;
10239                    }
10240                }
10241                if *columns_parenthesized {
10242                    self.write(" (");
10243                    for (i, col) in columns.iter().enumerate() {
10244                        if i > 0 {
10245                            self.write(", ");
10246                        }
10247                        self.generate_identifier(col)?;
10248                    }
10249                    self.write(")");
10250                } else {
10251                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
10252                    for col in columns.iter() {
10253                        self.write_space();
10254                        self.generate_identifier(col)?;
10255                    }
10256                }
10257                self.generate_constraint_modifiers(modifiers);
10258            }
10259            TableConstraint::ForeignKey {
10260                name,
10261                columns,
10262                references,
10263                on_delete,
10264                on_update,
10265                modifiers,
10266            } => {
10267                if let Some(ref n) = name {
10268                    self.write_keyword("CONSTRAINT");
10269                    self.write_space();
10270                    self.generate_identifier(n)?;
10271                    self.write_space();
10272                }
10273                self.write_keyword("FOREIGN KEY");
10274                self.write(" (");
10275                for (i, col) in columns.iter().enumerate() {
10276                    if i > 0 {
10277                        self.write(", ");
10278                    }
10279                    self.generate_identifier(col)?;
10280                }
10281                self.write(")");
10282                if let Some(ref refs) = references {
10283                    self.write(" ");
10284                    self.write_keyword("REFERENCES");
10285                    self.write_space();
10286                    self.generate_table(&refs.table)?;
10287                    if !refs.columns.is_empty() {
10288                        if self.config.pretty {
10289                            self.write(" (");
10290                            self.write_newline();
10291                            self.indent_level += 1;
10292                            for (i, col) in refs.columns.iter().enumerate() {
10293                                if i > 0 {
10294                                    self.write(",");
10295                                    self.write_newline();
10296                                }
10297                                self.write_indent();
10298                                self.generate_identifier(col)?;
10299                            }
10300                            self.indent_level -= 1;
10301                            self.write_newline();
10302                            self.write_indent();
10303                            self.write(")");
10304                        } else {
10305                            self.write(" (");
10306                            for (i, col) in refs.columns.iter().enumerate() {
10307                                if i > 0 {
10308                                    self.write(", ");
10309                                }
10310                                self.generate_identifier(col)?;
10311                            }
10312                            self.write(")");
10313                        }
10314                    }
10315                    self.generate_referential_actions(refs)?;
10316                } else {
10317                    // No REFERENCES - output ON DELETE/ON UPDATE directly
10318                    if let Some(ref action) = on_delete {
10319                        self.write_space();
10320                        self.write_keyword("ON DELETE");
10321                        self.write_space();
10322                        self.generate_referential_action(action);
10323                    }
10324                    if let Some(ref action) = on_update {
10325                        self.write_space();
10326                        self.write_keyword("ON UPDATE");
10327                        self.write_space();
10328                        self.generate_referential_action(action);
10329                    }
10330                }
10331                self.generate_constraint_modifiers(modifiers);
10332            }
10333            TableConstraint::Check {
10334                name,
10335                expression,
10336                modifiers,
10337            } => {
10338                if let Some(ref n) = name {
10339                    self.write_keyword("CONSTRAINT");
10340                    self.write_space();
10341                    self.generate_identifier(n)?;
10342                    self.write_space();
10343                }
10344                self.write_keyword("CHECK");
10345                self.write(" (");
10346                self.generate_expression(expression)?;
10347                self.write(")");
10348                self.generate_constraint_modifiers(modifiers);
10349            }
10350            TableConstraint::Assume { name, expression } => {
10351                if let Some(ref n) = name {
10352                    self.write_keyword("CONSTRAINT");
10353                    self.write_space();
10354                    self.generate_identifier(n)?;
10355                    self.write_space();
10356                }
10357                self.write_keyword("ASSUME");
10358                self.write(" (");
10359                self.generate_expression(expression)?;
10360                self.write(")");
10361            }
10362            TableConstraint::Default {
10363                name,
10364                expression,
10365                column,
10366            } => {
10367                if let Some(ref n) = name {
10368                    self.write_keyword("CONSTRAINT");
10369                    self.write_space();
10370                    self.generate_identifier(n)?;
10371                    self.write_space();
10372                }
10373                self.write_keyword("DEFAULT");
10374                self.write_space();
10375                self.generate_expression(expression)?;
10376                self.write_space();
10377                self.write_keyword("FOR");
10378                self.write_space();
10379                self.generate_identifier(column)?;
10380            }
10381            TableConstraint::Index {
10382                name,
10383                columns,
10384                key_parts,
10385                kind,
10386                modifiers,
10387                use_key_keyword,
10388                expression,
10389                index_type,
10390                granularity,
10391            } => {
10392                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
10393                if expression.is_some() {
10394                    self.write_keyword("INDEX");
10395                    if let Some(ref n) = name {
10396                        self.write_space();
10397                        self.generate_identifier(n)?;
10398                    }
10399                    if let Some(ref expr) = expression {
10400                        self.write_space();
10401                        self.generate_expression(expr)?;
10402                    }
10403                    if let Some(ref idx_type) = index_type {
10404                        self.write_space();
10405                        self.write_keyword("TYPE");
10406                        self.write_space();
10407                        self.generate_expression(idx_type)?;
10408                    }
10409                    if let Some(ref gran) = granularity {
10410                        self.write_space();
10411                        self.write_keyword("GRANULARITY");
10412                        self.write_space();
10413                        self.generate_expression(gran)?;
10414                    }
10415                } else {
10416                    // Standard INDEX syntax
10417                    // Determine the index keyword to use
10418                    // MySQL normalizes KEY to INDEX
10419                    use crate::dialects::DialectType;
10420                    let index_keyword = if *use_key_keyword
10421                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
10422                    {
10423                        "KEY"
10424                    } else {
10425                        "INDEX"
10426                    };
10427
10428                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
10429                    if let Some(ref k) = kind {
10430                        self.write_keyword(k);
10431                        // For UNIQUE, don't add INDEX/KEY keyword
10432                        if k != "UNIQUE" {
10433                            self.write_space();
10434                            self.write_keyword(index_keyword);
10435                        }
10436                    } else {
10437                        self.write_keyword(index_keyword);
10438                    }
10439
10440                    // Output USING before name if using_before_columns is true and there's no name
10441                    if modifiers.using_before_columns && name.is_none() {
10442                        if let Some(ref using) = modifiers.using {
10443                            self.write_space();
10444                            self.write_keyword("USING");
10445                            self.write_space();
10446                            self.write_keyword(using);
10447                        }
10448                    }
10449
10450                    // Output index name if present
10451                    if let Some(ref n) = name {
10452                        self.write_space();
10453                        self.generate_identifier(n)?;
10454                    }
10455
10456                    // Output USING after name but before columns if using_before_columns and there's a name
10457                    if modifiers.using_before_columns && name.is_some() {
10458                        if let Some(ref using) = modifiers.using {
10459                            self.write_space();
10460                            self.write_keyword("USING");
10461                            self.write_space();
10462                            self.write_keyword(using);
10463                        }
10464                    }
10465
10466                    // Output columns / functional key parts
10467                    self.write(" (");
10468                    if key_parts.is_empty() {
10469                        for (i, col) in columns.iter().enumerate() {
10470                            if i > 0 {
10471                                self.write(", ");
10472                            }
10473                            self.generate_identifier(col)?;
10474                        }
10475                    } else {
10476                        self.generate_index_key_parts(key_parts)?;
10477                    }
10478                    self.write(")");
10479
10480                    // Output USING after columns if not using_before_columns
10481                    if !modifiers.using_before_columns {
10482                        if let Some(ref using) = modifiers.using {
10483                            self.write_space();
10484                            self.write_keyword("USING");
10485                            self.write_space();
10486                            self.write_keyword(using);
10487                        }
10488                    }
10489
10490                    // Output other constraint modifiers (but skip USING since we already handled it)
10491                    self.generate_constraint_modifiers_without_using(modifiers);
10492                }
10493            }
10494            TableConstraint::Projection { name, expression } => {
10495                // ClickHouse: PROJECTION name (SELECT ...)
10496                self.write_keyword("PROJECTION");
10497                self.write_space();
10498                self.generate_identifier(name)?;
10499                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
10500                    if let Expression::Raw(raw) = expression {
10501                        if raw
10502                            .sql
10503                            .trim_start()
10504                            .to_ascii_uppercase()
10505                            .starts_with("INDEX ")
10506                        {
10507                            self.write_space();
10508                            self.write(raw.sql.trim());
10509                            return Ok(());
10510                        }
10511                    }
10512                }
10513                self.write(" (");
10514                self.generate_expression(expression)?;
10515                self.write(")");
10516            }
10517            TableConstraint::Like { source, options } => {
10518                self.write_keyword("LIKE");
10519                self.write_space();
10520                self.generate_table(source)?;
10521                for (action, prop) in options {
10522                    self.write_space();
10523                    match action {
10524                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
10525                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
10526                    }
10527                    self.write_space();
10528                    self.write_keyword(prop);
10529                }
10530            }
10531            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
10532                self.write_keyword("PERIOD FOR SYSTEM_TIME");
10533                self.write(" (");
10534                self.generate_identifier(start_col)?;
10535                self.write(", ");
10536                self.generate_identifier(end_col)?;
10537                self.write(")");
10538            }
10539            TableConstraint::Exclude {
10540                name,
10541                using,
10542                elements,
10543                include_columns,
10544                where_clause,
10545                with_params,
10546                using_index_tablespace,
10547                modifiers: _,
10548            } => {
10549                if let Some(ref n) = name {
10550                    self.write_keyword("CONSTRAINT");
10551                    self.write_space();
10552                    self.generate_identifier(n)?;
10553                    self.write_space();
10554                }
10555                self.write_keyword("EXCLUDE");
10556                if let Some(ref method) = using {
10557                    self.write_space();
10558                    self.write_keyword("USING");
10559                    self.write_space();
10560                    self.write(method);
10561                    self.write("(");
10562                } else {
10563                    self.write(" (");
10564                }
10565                for (i, elem) in elements.iter().enumerate() {
10566                    if i > 0 {
10567                        self.write(", ");
10568                    }
10569                    self.write(&elem.expression);
10570                    self.write_space();
10571                    self.write_keyword("WITH");
10572                    self.write_space();
10573                    self.write(&elem.operator);
10574                }
10575                self.write(")");
10576                if !include_columns.is_empty() {
10577                    self.write_space();
10578                    self.write_keyword("INCLUDE");
10579                    self.write(" (");
10580                    for (i, col) in include_columns.iter().enumerate() {
10581                        if i > 0 {
10582                            self.write(", ");
10583                        }
10584                        self.generate_identifier(col)?;
10585                    }
10586                    self.write(")");
10587                }
10588                if !with_params.is_empty() {
10589                    self.write_space();
10590                    self.write_keyword("WITH");
10591                    self.write(" (");
10592                    for (i, (key, val)) in with_params.iter().enumerate() {
10593                        if i > 0 {
10594                            self.write(", ");
10595                        }
10596                        self.write(key);
10597                        self.write("=");
10598                        self.write(val);
10599                    }
10600                    self.write(")");
10601                }
10602                if let Some(ref tablespace) = using_index_tablespace {
10603                    self.write_space();
10604                    self.write_keyword("USING INDEX TABLESPACE");
10605                    self.write_space();
10606                    self.write(tablespace);
10607                }
10608                if let Some(ref where_expr) = where_clause {
10609                    self.write_space();
10610                    self.write_keyword("WHERE");
10611                    self.write(" (");
10612                    self.generate_expression(where_expr)?;
10613                    self.write(")");
10614                }
10615            }
10616            TableConstraint::Tags(tags) => {
10617                self.write_keyword("TAG");
10618                self.write(" (");
10619                for (i, expr) in tags.expressions.iter().enumerate() {
10620                    if i > 0 {
10621                        self.write(", ");
10622                    }
10623                    self.generate_expression(expr)?;
10624                }
10625                self.write(")");
10626            }
10627            TableConstraint::InitiallyDeferred { deferred } => {
10628                self.write_keyword("INITIALLY");
10629                self.write_space();
10630                if *deferred {
10631                    self.write_keyword("DEFERRED");
10632                } else {
10633                    self.write_keyword("IMMEDIATE");
10634                }
10635            }
10636        }
10637        Ok(())
10638    }
10639
10640    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10641        // Output USING BTREE/HASH (MySQL) - comes first
10642        if let Some(using) = &modifiers.using {
10643            self.write_space();
10644            self.write_keyword("USING");
10645            self.write_space();
10646            self.write_keyword(using);
10647        }
10648        // Output ENFORCED/NOT ENFORCED
10649        if let Some(enforced) = modifiers.enforced {
10650            self.write_space();
10651            if enforced {
10652                self.write_keyword("ENFORCED");
10653            } else {
10654                self.write_keyword("NOT ENFORCED");
10655            }
10656        }
10657        // Output DEFERRABLE/NOT DEFERRABLE
10658        if let Some(deferrable) = modifiers.deferrable {
10659            self.write_space();
10660            if deferrable {
10661                self.write_keyword("DEFERRABLE");
10662            } else {
10663                self.write_keyword("NOT DEFERRABLE");
10664            }
10665        }
10666        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10667        if let Some(initially_deferred) = modifiers.initially_deferred {
10668            self.write_space();
10669            if initially_deferred {
10670                self.write_keyword("INITIALLY DEFERRED");
10671            } else {
10672                self.write_keyword("INITIALLY IMMEDIATE");
10673            }
10674        }
10675        // Output NORELY
10676        if modifiers.norely {
10677            self.write_space();
10678            self.write_keyword("NORELY");
10679        }
10680        // Output RELY
10681        if modifiers.rely {
10682            self.write_space();
10683            self.write_keyword("RELY");
10684        }
10685        // Output NOT VALID (PostgreSQL)
10686        if modifiers.not_valid {
10687            self.write_space();
10688            self.write_keyword("NOT VALID");
10689        }
10690        // Output ON CONFLICT (SQLite)
10691        if let Some(on_conflict) = &modifiers.on_conflict {
10692            self.write_space();
10693            self.write_keyword("ON CONFLICT");
10694            self.write_space();
10695            self.write_keyword(on_conflict);
10696        }
10697        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10698        if !modifiers.with_options.is_empty() {
10699            self.write_space();
10700            self.write_keyword("WITH");
10701            self.write(" (");
10702            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10703                if i > 0 {
10704                    self.write(", ");
10705                }
10706                self.write(key);
10707                self.write("=");
10708                self.write(value);
10709            }
10710            self.write(")");
10711        }
10712        // Output TSQL ON filegroup
10713        if let Some(ref fg) = modifiers.on_filegroup {
10714            self.write_space();
10715            self.write_keyword("ON");
10716            self.write_space();
10717            let _ = self.generate_identifier(fg);
10718        }
10719    }
10720
10721    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10722    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10723        // Output ENFORCED/NOT ENFORCED
10724        if let Some(enforced) = modifiers.enforced {
10725            self.write_space();
10726            if enforced {
10727                self.write_keyword("ENFORCED");
10728            } else {
10729                self.write_keyword("NOT ENFORCED");
10730            }
10731        }
10732        // Output DEFERRABLE/NOT DEFERRABLE
10733        if let Some(deferrable) = modifiers.deferrable {
10734            self.write_space();
10735            if deferrable {
10736                self.write_keyword("DEFERRABLE");
10737            } else {
10738                self.write_keyword("NOT DEFERRABLE");
10739            }
10740        }
10741        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10742        if let Some(initially_deferred) = modifiers.initially_deferred {
10743            self.write_space();
10744            if initially_deferred {
10745                self.write_keyword("INITIALLY DEFERRED");
10746            } else {
10747                self.write_keyword("INITIALLY IMMEDIATE");
10748            }
10749        }
10750        // Output NORELY
10751        if modifiers.norely {
10752            self.write_space();
10753            self.write_keyword("NORELY");
10754        }
10755        // Output RELY
10756        if modifiers.rely {
10757            self.write_space();
10758            self.write_keyword("RELY");
10759        }
10760        // Output NOT VALID (PostgreSQL)
10761        if modifiers.not_valid {
10762            self.write_space();
10763            self.write_keyword("NOT VALID");
10764        }
10765        // Output ON CONFLICT (SQLite)
10766        if let Some(on_conflict) = &modifiers.on_conflict {
10767            self.write_space();
10768            self.write_keyword("ON CONFLICT");
10769            self.write_space();
10770            self.write_keyword(on_conflict);
10771        }
10772        // Output MySQL index-specific modifiers
10773        self.generate_index_specific_modifiers(modifiers);
10774    }
10775
10776    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10777    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10778        if let Some(ref comment) = modifiers.comment {
10779            self.write_space();
10780            self.write_keyword("COMMENT");
10781            self.write(" '");
10782            self.write(comment);
10783            self.write("'");
10784        }
10785        if let Some(visible) = modifiers.visible {
10786            self.write_space();
10787            if visible {
10788                self.write_keyword("VISIBLE");
10789            } else {
10790                self.write_keyword("INVISIBLE");
10791            }
10792        }
10793        if let Some(ref attr) = modifiers.engine_attribute {
10794            self.write_space();
10795            self.write_keyword("ENGINE_ATTRIBUTE");
10796            self.write(" = '");
10797            self.write(attr);
10798            self.write("'");
10799        }
10800        if let Some(ref parser) = modifiers.with_parser {
10801            self.write_space();
10802            self.write_keyword("WITH PARSER");
10803            self.write_space();
10804            self.write(parser);
10805        }
10806    }
10807
10808    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10809        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10810        if !fk_ref.match_after_actions {
10811            if let Some(ref match_type) = fk_ref.match_type {
10812                self.write_space();
10813                self.write_keyword("MATCH");
10814                self.write_space();
10815                match match_type {
10816                    MatchType::Full => self.write_keyword("FULL"),
10817                    MatchType::Partial => self.write_keyword("PARTIAL"),
10818                    MatchType::Simple => self.write_keyword("SIMPLE"),
10819                }
10820            }
10821        }
10822
10823        // Output ON UPDATE and ON DELETE in the original order
10824        if fk_ref.on_update_first {
10825            if let Some(ref action) = fk_ref.on_update {
10826                self.write_space();
10827                self.write_keyword("ON UPDATE");
10828                self.write_space();
10829                self.generate_referential_action(action);
10830            }
10831            if let Some(ref action) = fk_ref.on_delete {
10832                self.write_space();
10833                self.write_keyword("ON DELETE");
10834                self.write_space();
10835                self.generate_referential_action(action);
10836            }
10837        } else {
10838            if let Some(ref action) = fk_ref.on_delete {
10839                self.write_space();
10840                self.write_keyword("ON DELETE");
10841                self.write_space();
10842                self.generate_referential_action(action);
10843            }
10844            if let Some(ref action) = fk_ref.on_update {
10845                self.write_space();
10846                self.write_keyword("ON UPDATE");
10847                self.write_space();
10848                self.generate_referential_action(action);
10849            }
10850        }
10851
10852        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10853        if fk_ref.match_after_actions {
10854            if let Some(ref match_type) = fk_ref.match_type {
10855                self.write_space();
10856                self.write_keyword("MATCH");
10857                self.write_space();
10858                match match_type {
10859                    MatchType::Full => self.write_keyword("FULL"),
10860                    MatchType::Partial => self.write_keyword("PARTIAL"),
10861                    MatchType::Simple => self.write_keyword("SIMPLE"),
10862                }
10863            }
10864        }
10865
10866        // DEFERRABLE / NOT DEFERRABLE
10867        if let Some(deferrable) = fk_ref.deferrable {
10868            self.write_space();
10869            if deferrable {
10870                self.write_keyword("DEFERRABLE");
10871            } else {
10872                self.write_keyword("NOT DEFERRABLE");
10873            }
10874        }
10875
10876        Ok(())
10877    }
10878
10879    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10880        match action {
10881            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10882            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10883            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10884            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10885            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10886        }
10887    }
10888
10889    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10890        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10891        if let Some(ref object_id_args) = dt.object_id_args {
10892            if matches!(
10893                self.config.dialect,
10894                Some(crate::dialects::DialectType::TSQL)
10895                    | Some(crate::dialects::DialectType::Fabric)
10896            ) {
10897                self.write_keyword("IF NOT OBJECT_ID");
10898                self.write("(");
10899                self.write(object_id_args);
10900                self.write(")");
10901                self.write_space();
10902                self.write_keyword("IS NULL BEGIN DROP TABLE");
10903                self.write_space();
10904                for (i, table) in dt.names.iter().enumerate() {
10905                    if i > 0 {
10906                        self.write(", ");
10907                    }
10908                    self.generate_table(table)?;
10909                }
10910                self.write("; ");
10911                self.write_keyword("END");
10912                return Ok(());
10913            }
10914        }
10915
10916        // Athena: DROP TABLE uses Hive engine (backticks)
10917        let saved_athena_hive_context = self.athena_hive_context;
10918        if matches!(
10919            self.config.dialect,
10920            Some(crate::dialects::DialectType::Athena)
10921        ) {
10922            self.athena_hive_context = true;
10923        }
10924
10925        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10926        for comment in &dt.leading_comments {
10927            self.write_formatted_comment(comment);
10928            self.write_space();
10929        }
10930        if dt.iceberg {
10931            self.write_keyword("DROP ICEBERG TABLE");
10932        } else {
10933            self.write_keyword("DROP TABLE");
10934        }
10935
10936        if dt.if_exists {
10937            self.write_space();
10938            self.write_keyword("IF EXISTS");
10939        }
10940
10941        self.write_space();
10942        for (i, table) in dt.names.iter().enumerate() {
10943            if i > 0 {
10944                self.write(", ");
10945            }
10946            self.generate_table(table)?;
10947        }
10948
10949        if dt.cascade_constraints {
10950            self.write_space();
10951            self.write_keyword("CASCADE CONSTRAINTS");
10952        } else if dt.cascade {
10953            self.write_space();
10954            self.write_keyword("CASCADE");
10955        }
10956
10957        if dt.restrict {
10958            self.write_space();
10959            self.write_keyword("RESTRICT");
10960        }
10961
10962        if dt.purge {
10963            self.write_space();
10964            self.write_keyword("PURGE");
10965        }
10966
10967        if dt.sync {
10968            self.write_space();
10969            self.write_keyword("SYNC");
10970        }
10971
10972        // Restore Athena Hive context
10973        self.athena_hive_context = saved_athena_hive_context;
10974
10975        Ok(())
10976    }
10977
10978    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10979        self.write_keyword("UNDROP");
10980        self.write_space();
10981        self.write_keyword(&u.kind);
10982        if u.if_exists {
10983            self.write_space();
10984            self.write_keyword("IF EXISTS");
10985        }
10986        self.write_space();
10987        self.generate_table(&u.name)?;
10988        if let Some(rename_to) = &u.rename_to {
10989            self.write_space();
10990            self.write_keyword("RENAME TO");
10991            self.write_space();
10992            self.generate_table(rename_to)?;
10993        }
10994        Ok(())
10995    }
10996
10997    fn generate_split_table(&mut self, split: &SplitTable) -> Result<()> {
10998        if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
10999            return self
11000                .write_unsupported_comment("SPLIT TABLE is only supported by the TiDB dialect");
11001        }
11002
11003        match &split.partition_scope {
11004            SplitTablePartitionScope::Table => self.write_keyword("SPLIT TABLE"),
11005            SplitTablePartitionScope::AllPartitions
11006            | SplitTablePartitionScope::Partitions { .. } => {
11007                self.write_keyword("SPLIT PARTITION TABLE")
11008            }
11009        }
11010        self.write_space();
11011        self.generate_table(&split.table)?;
11012        if let SplitTablePartitionScope::Partitions { names } = &split.partition_scope {
11013            self.write_space();
11014            self.write_keyword("PARTITION");
11015            self.write(" (");
11016            for (index, name) in names.iter().enumerate() {
11017                if index > 0 {
11018                    self.write(", ");
11019                }
11020                self.generate_identifier(name)?;
11021            }
11022            self.write(")");
11023        }
11024        if let Some(index) = &split.index {
11025            self.write_space();
11026            self.write_keyword("INDEX");
11027            self.write_space();
11028            self.generate_identifier(index)?;
11029        }
11030        match &split.mode {
11031            SplitTableMode::Between {
11032                lower,
11033                upper,
11034                regions,
11035            } => {
11036                self.write_space();
11037                self.write_keyword("BETWEEN");
11038                self.write_space();
11039                self.generate_tidb_split_values(lower)?;
11040                self.write_space();
11041                self.write_keyword("AND");
11042                self.write_space();
11043                self.generate_tidb_split_values(upper)?;
11044                self.write_space();
11045                self.write_keyword("REGIONS");
11046                self.write_space();
11047                self.write(&regions.to_string());
11048            }
11049            SplitTableMode::By { points } => {
11050                self.write_space();
11051                self.write_keyword("BY");
11052                self.write_space();
11053                for (index, point) in points.iter().enumerate() {
11054                    if index > 0 {
11055                        self.write(", ");
11056                    }
11057                    self.generate_tidb_split_values(point)?;
11058                }
11059            }
11060        }
11061        Ok(())
11062    }
11063
11064    fn generate_tidb_split_values(&mut self, values: &[Expression]) -> Result<()> {
11065        self.write("(");
11066        for (index, value) in values.iter().enumerate() {
11067            if index > 0 {
11068                self.write(", ");
11069            }
11070            self.generate_expression(value)?;
11071        }
11072        self.write(")");
11073        Ok(())
11074    }
11075
11076    fn generate_flashback_table(&mut self, flashback: &FlashbackTable) -> Result<()> {
11077        if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11078            return self.write_unsupported_comment(
11079                "FLASHBACK TABLE is only supported by the TiDB dialect",
11080            );
11081        }
11082        self.write_keyword("FLASHBACK TABLE");
11083        self.write_space();
11084        self.generate_table(&flashback.table)?;
11085        if let Some(rename_to) = &flashback.rename_to {
11086            self.write_space();
11087            self.write_keyword("TO");
11088            self.write_space();
11089            self.generate_identifier(rename_to)?;
11090        }
11091        Ok(())
11092    }
11093
11094    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
11095        // Athena: ALTER TABLE uses Hive engine (backticks)
11096        let saved_athena_hive_context = self.athena_hive_context;
11097        if matches!(
11098            self.config.dialect,
11099            Some(crate::dialects::DialectType::Athena)
11100        ) {
11101            self.athena_hive_context = true;
11102        }
11103
11104        self.write_keyword("ALTER");
11105        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
11106        if let Some(ref modifier) = at.table_modifier {
11107            if !matches!(
11108                self.config.dialect,
11109                Some(crate::dialects::DialectType::DuckDB)
11110            ) {
11111                self.write_space();
11112                self.write_keyword(modifier);
11113            }
11114        }
11115        self.write(" ");
11116        self.write_keyword("TABLE");
11117        if at.if_exists {
11118            self.write_space();
11119            self.write_keyword("IF EXISTS");
11120        }
11121        self.write_space();
11122        self.generate_table(&at.name)?;
11123
11124        // ClickHouse: ON CLUSTER clause
11125        if let Some(ref on_cluster) = at.on_cluster {
11126            self.write_space();
11127            self.generate_on_cluster(on_cluster)?;
11128        }
11129
11130        // Hive: PARTITION(key=value, ...) clause
11131        if let Some(ref partition) = at.partition {
11132            self.write_space();
11133            self.write_keyword("PARTITION");
11134            self.write("(");
11135            for (i, (key, value)) in partition.iter().enumerate() {
11136                if i > 0 {
11137                    self.write(", ");
11138                }
11139                self.generate_identifier(key)?;
11140                self.write(" = ");
11141                self.generate_expression(value)?;
11142            }
11143            self.write(")");
11144        }
11145
11146        // TSQL: WITH CHECK / WITH NOCHECK modifier
11147        if let Some(ref with_check) = at.with_check {
11148            self.write_space();
11149            self.write_keyword(with_check);
11150        }
11151
11152        if self.config.pretty {
11153            // In pretty mode, format actions with newlines and indentation
11154            self.write_newline();
11155            self.indent_level += 1;
11156            for (i, action) in at.actions.iter().enumerate() {
11157                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11158                let is_continuation = i > 0
11159                    && matches!(
11160                        (&at.actions[i - 1], action),
11161                        (
11162                            AlterTableAction::AddColumn { .. },
11163                            AlterTableAction::AddColumn { .. }
11164                        ) | (
11165                            AlterTableAction::AddConstraint(_),
11166                            AlterTableAction::AddConstraint(_)
11167                        )
11168                    );
11169                if i > 0 {
11170                    self.write(",");
11171                    self.write_newline();
11172                }
11173                self.write_indent();
11174                self.generate_alter_action_with_continuation(action, is_continuation)?;
11175            }
11176            self.indent_level -= 1;
11177        } else {
11178            for (i, action) in at.actions.iter().enumerate() {
11179                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11180                let is_continuation = i > 0
11181                    && matches!(
11182                        (&at.actions[i - 1], action),
11183                        (
11184                            AlterTableAction::AddColumn { .. },
11185                            AlterTableAction::AddColumn { .. }
11186                        ) | (
11187                            AlterTableAction::AddConstraint(_),
11188                            AlterTableAction::AddConstraint(_)
11189                        )
11190                    );
11191                if i > 0 {
11192                    self.write(",");
11193                }
11194                self.write_space();
11195                self.generate_alter_action_with_continuation(action, is_continuation)?;
11196            }
11197        }
11198
11199        // MySQL ALTER TABLE trailing options
11200        if let Some(ref algorithm) = at.algorithm {
11201            self.write(", ");
11202            self.write_keyword("ALGORITHM");
11203            self.write("=");
11204            self.write_keyword(algorithm);
11205        }
11206        if let Some(ref lock) = at.lock {
11207            self.write(", ");
11208            self.write_keyword("LOCK");
11209            self.write("=");
11210            self.write_keyword(lock);
11211        }
11212
11213        // Restore Athena Hive context
11214        self.athena_hive_context = saved_athena_hive_context;
11215
11216        Ok(())
11217    }
11218
11219    fn generate_alter_action_with_continuation(
11220        &mut self,
11221        action: &AlterTableAction,
11222        is_continuation: bool,
11223    ) -> Result<()> {
11224        match action {
11225            AlterTableAction::AddColumn {
11226                column,
11227                if_not_exists,
11228                position,
11229            } => {
11230                use crate::dialects::DialectType;
11231                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
11232                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
11233                // For other dialects, repeat ADD COLUMN for each
11234                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
11235                let is_tsql_like = matches!(
11236                    self.config.dialect,
11237                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
11238                );
11239                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
11240                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
11241
11242                if is_continuation && (is_snowflake || is_tsql_like) {
11243                    // Don't write ADD keyword for continuation in Snowflake/TSQL
11244                } else if is_snowflake {
11245                    self.write_keyword("ADD");
11246                    self.write_space();
11247                } else if is_athena {
11248                    // Athena uses ADD COLUMNS (col_def) syntax
11249                    self.write_keyword("ADD COLUMNS");
11250                    self.write(" (");
11251                } else if self.config.alter_table_include_column_keyword {
11252                    self.write_keyword("ADD COLUMN");
11253                    self.write_space();
11254                } else {
11255                    // Dialects like Oracle and TSQL don't use COLUMN keyword
11256                    self.write_keyword("ADD");
11257                    self.write_space();
11258                }
11259
11260                if *if_not_exists {
11261                    self.write_keyword("IF NOT EXISTS");
11262                    self.write_space();
11263                }
11264                self.generate_column_def(column)?;
11265
11266                // Close parenthesis for Athena
11267                if is_athena {
11268                    self.write(")");
11269                }
11270
11271                // Column position (FIRST or AFTER)
11272                if let Some(pos) = position {
11273                    self.write_space();
11274                    match pos {
11275                        ColumnPosition::First => self.write_keyword("FIRST"),
11276                        ColumnPosition::After(col_name) => {
11277                            self.write_keyword("AFTER");
11278                            self.write_space();
11279                            self.generate_identifier(col_name)?;
11280                        }
11281                    }
11282                }
11283            }
11284            AlterTableAction::DropColumn {
11285                name,
11286                if_exists,
11287                cascade,
11288            } => {
11289                self.write_keyword("DROP COLUMN");
11290                if *if_exists {
11291                    self.write_space();
11292                    self.write_keyword("IF EXISTS");
11293                }
11294                self.write_space();
11295                self.generate_identifier(name)?;
11296                if *cascade {
11297                    self.write_space();
11298                    self.write_keyword("CASCADE");
11299                }
11300            }
11301            AlterTableAction::DropColumns { names } => {
11302                self.write_keyword("DROP COLUMNS");
11303                self.write(" (");
11304                for (i, name) in names.iter().enumerate() {
11305                    if i > 0 {
11306                        self.write(", ");
11307                    }
11308                    self.generate_identifier(name)?;
11309                }
11310                self.write(")");
11311            }
11312            AlterTableAction::RenameColumn {
11313                old_name,
11314                new_name,
11315                if_exists,
11316            } => {
11317                self.write_keyword("RENAME COLUMN");
11318                if *if_exists {
11319                    self.write_space();
11320                    self.write_keyword("IF EXISTS");
11321                }
11322                self.write_space();
11323                self.generate_identifier(old_name)?;
11324                self.write_space();
11325                self.write_keyword("TO");
11326                self.write_space();
11327                self.generate_identifier(new_name)?;
11328            }
11329            AlterTableAction::AlterColumn {
11330                name,
11331                action,
11332                use_modify_keyword,
11333            } => {
11334                use crate::dialects::DialectType;
11335                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
11336                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
11337                let use_modify = *use_modify_keyword
11338                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
11339                        && matches!(action, AlterColumnAction::SetDataType { .. }));
11340                if use_modify {
11341                    self.write_keyword("MODIFY COLUMN");
11342                    self.write_space();
11343                    self.generate_identifier(name)?;
11344                    // For MODIFY COLUMN, output the type directly
11345                    if let AlterColumnAction::SetDataType {
11346                        data_type,
11347                        using: _,
11348                        collate,
11349                    } = action
11350                    {
11351                        self.write_space();
11352                        self.generate_data_type(data_type)?;
11353                        // Output COLLATE clause if present
11354                        if let Some(collate_name) = collate {
11355                            self.write_space();
11356                            self.write_keyword("COLLATE");
11357                            self.write_space();
11358                            // Output as single-quoted string
11359                            self.write(&format!("'{}'", collate_name));
11360                        }
11361                    } else {
11362                        self.write_space();
11363                        self.generate_alter_column_action(action)?;
11364                    }
11365                } else if matches!(self.config.dialect, Some(DialectType::Hive))
11366                    && matches!(action, AlterColumnAction::SetDataType { .. })
11367                {
11368                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
11369                    self.write_keyword("CHANGE COLUMN");
11370                    self.write_space();
11371                    self.generate_identifier(name)?;
11372                    self.write_space();
11373                    self.generate_identifier(name)?;
11374                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
11375                        self.write_space();
11376                        self.generate_data_type(data_type)?;
11377                    }
11378                } else {
11379                    self.write_keyword("ALTER COLUMN");
11380                    self.write_space();
11381                    self.generate_identifier(name)?;
11382                    self.write_space();
11383                    self.generate_alter_column_action(action)?;
11384                }
11385            }
11386            AlterTableAction::ModifyColumn {
11387                column,
11388                if_exists,
11389                position,
11390            } => {
11391                if !matches!(
11392                    self.config.dialect,
11393                    Some(DialectType::TiDB) | Some(DialectType::MySQL)
11394                ) {
11395                    return self.write_unsupported_comment(
11396                        "MODIFY COLUMN is only supported by MySQL-compatible dialects",
11397                    );
11398                }
11399                self.write_keyword("MODIFY COLUMN");
11400                self.write_space();
11401                if *if_exists {
11402                    self.write_keyword("IF EXISTS");
11403                    self.write_space();
11404                }
11405                self.generate_column_def(column)?;
11406                if let Some(position) = position {
11407                    self.write_space();
11408                    match position {
11409                        ColumnPosition::First => self.write_keyword("FIRST"),
11410                        ColumnPosition::After(name) => {
11411                            self.write_keyword("AFTER");
11412                            self.write_space();
11413                            self.generate_identifier(name)?;
11414                        }
11415                    }
11416                }
11417            }
11418            AlterTableAction::RenameTable(new_name) => {
11419                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
11420                let mysql_like = matches!(
11421                    self.config.dialect,
11422                    Some(DialectType::MySQL)
11423                        | Some(DialectType::Doris)
11424                        | Some(DialectType::StarRocks)
11425                        | Some(DialectType::SingleStore)
11426                );
11427                if mysql_like {
11428                    self.write_keyword("RENAME");
11429                } else {
11430                    self.write_keyword("RENAME TO");
11431                }
11432                self.write_space();
11433                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
11434                let rename_table_with_db = !matches!(
11435                    self.config.dialect,
11436                    Some(DialectType::Doris)
11437                        | Some(DialectType::DuckDB)
11438                        | Some(DialectType::BigQuery)
11439                        | Some(DialectType::PostgreSQL)
11440                );
11441                if !rename_table_with_db {
11442                    let mut stripped = new_name.clone();
11443                    stripped.schema = None;
11444                    stripped.catalog = None;
11445                    self.generate_table(&stripped)?;
11446                } else {
11447                    self.generate_table(new_name)?;
11448                }
11449            }
11450            AlterTableAction::AddConstraint(constraint) => {
11451                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
11452                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
11453                if !is_continuation {
11454                    self.write_keyword("ADD");
11455                    self.write_space();
11456                }
11457                self.generate_table_constraint(constraint)?;
11458            }
11459            AlterTableAction::DropConstraint { name, if_exists } => {
11460                self.write_keyword("DROP CONSTRAINT");
11461                if *if_exists {
11462                    self.write_space();
11463                    self.write_keyword("IF EXISTS");
11464                }
11465                self.write_space();
11466                self.generate_identifier(name)?;
11467            }
11468            AlterTableAction::DropForeignKey { name } => {
11469                self.write_keyword("DROP FOREIGN KEY");
11470                self.write_space();
11471                self.generate_identifier(name)?;
11472            }
11473            AlterTableAction::DropPartition {
11474                partitions,
11475                if_exists,
11476            } => {
11477                self.write_keyword("DROP");
11478                if *if_exists {
11479                    self.write_space();
11480                    self.write_keyword("IF EXISTS");
11481                }
11482                for (i, partition) in partitions.iter().enumerate() {
11483                    if i > 0 {
11484                        self.write(",");
11485                    }
11486                    self.write_space();
11487                    self.write_keyword("PARTITION");
11488                    // Check for special ClickHouse partition formats
11489                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
11490                        // ClickHouse: PARTITION <expression>
11491                        self.write_space();
11492                        self.generate_expression(&partition[0].1)?;
11493                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
11494                        // ClickHouse: PARTITION ALL
11495                        self.write_space();
11496                        self.write_keyword("ALL");
11497                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
11498                        // ClickHouse: PARTITION ID 'string'
11499                        self.write_space();
11500                        self.write_keyword("ID");
11501                        self.write_space();
11502                        self.generate_expression(&partition[0].1)?;
11503                    } else {
11504                        // Standard SQL: PARTITION(key=value, ...)
11505                        self.write("(");
11506                        for (j, (key, value)) in partition.iter().enumerate() {
11507                            if j > 0 {
11508                                self.write(", ");
11509                            }
11510                            self.generate_identifier(key)?;
11511                            self.write(" = ");
11512                            self.generate_expression(value)?;
11513                        }
11514                        self.write(")");
11515                    }
11516                }
11517            }
11518            AlterTableAction::Delete { where_clause } => {
11519                self.write_keyword("DELETE");
11520                self.write_space();
11521                self.write_keyword("WHERE");
11522                self.write_space();
11523                self.generate_expression(where_clause)?;
11524            }
11525            AlterTableAction::SwapWith(target) => {
11526                self.write_keyword("SWAP WITH");
11527                self.write_space();
11528                self.generate_table(target)?;
11529            }
11530            AlterTableAction::SetProperty { properties } => {
11531                use crate::dialects::DialectType;
11532                self.write_keyword("SET");
11533                // Trino/Presto use SET PROPERTIES syntax with spaces around =
11534                let is_trino_presto = matches!(
11535                    self.config.dialect,
11536                    Some(DialectType::Trino) | Some(DialectType::Presto)
11537                );
11538                if is_trino_presto {
11539                    self.write_space();
11540                    self.write_keyword("PROPERTIES");
11541                }
11542                let eq = if is_trino_presto { " = " } else { "=" };
11543                for (i, (key, value)) in properties.iter().enumerate() {
11544                    if i > 0 {
11545                        self.write(",");
11546                    }
11547                    self.write_space();
11548                    // Handle quoted property names for Trino
11549                    if key.contains(' ') {
11550                        self.generate_string_literal(key)?;
11551                    } else {
11552                        self.write(key);
11553                    }
11554                    self.write(eq);
11555                    self.generate_expression(value)?;
11556                }
11557            }
11558            AlterTableAction::UnsetProperty { properties } => {
11559                self.write_keyword("UNSET");
11560                for (i, name) in properties.iter().enumerate() {
11561                    if i > 0 {
11562                        self.write(",");
11563                    }
11564                    self.write_space();
11565                    self.write(name);
11566                }
11567            }
11568            AlterTableAction::ClusterBy { expressions } => {
11569                self.write_keyword("CLUSTER BY");
11570                self.write(" (");
11571                for (i, expr) in expressions.iter().enumerate() {
11572                    if i > 0 {
11573                        self.write(", ");
11574                    }
11575                    self.generate_expression(expr)?;
11576                }
11577                self.write(")");
11578            }
11579            AlterTableAction::SetTag { expressions } => {
11580                self.write_keyword("SET TAG");
11581                for (i, (key, value)) in expressions.iter().enumerate() {
11582                    if i > 0 {
11583                        self.write(",");
11584                    }
11585                    self.write_space();
11586                    self.write(key);
11587                    self.write(" = ");
11588                    self.generate_expression(value)?;
11589                }
11590            }
11591            AlterTableAction::UnsetTag { names } => {
11592                self.write_keyword("UNSET TAG");
11593                for (i, name) in names.iter().enumerate() {
11594                    if i > 0 {
11595                        self.write(",");
11596                    }
11597                    self.write_space();
11598                    self.write(name);
11599                }
11600            }
11601            AlterTableAction::SetOptions { expressions } => {
11602                self.write_keyword("SET");
11603                self.write(" (");
11604                for (i, expr) in expressions.iter().enumerate() {
11605                    if i > 0 {
11606                        self.write(", ");
11607                    }
11608                    self.generate_expression(expr)?;
11609                }
11610                self.write(")");
11611            }
11612            AlterTableAction::AlterIndex { name, visible } => {
11613                self.write_keyword("ALTER INDEX");
11614                self.write_space();
11615                self.generate_identifier(name)?;
11616                self.write_space();
11617                if *visible {
11618                    self.write_keyword("VISIBLE");
11619                } else {
11620                    self.write_keyword("INVISIBLE");
11621                }
11622            }
11623            AlterTableAction::SetAttribute { attribute } => {
11624                self.write_keyword("SET");
11625                self.write_space();
11626                self.write_keyword(attribute);
11627            }
11628            AlterTableAction::SetStageFileFormat { options } => {
11629                self.write_keyword("SET");
11630                self.write_space();
11631                self.write_keyword("STAGE_FILE_FORMAT");
11632                self.write(" = (");
11633                if let Some(opts) = options {
11634                    self.generate_space_separated_properties(opts)?;
11635                }
11636                self.write(")");
11637            }
11638            AlterTableAction::SetStageCopyOptions { options } => {
11639                self.write_keyword("SET");
11640                self.write_space();
11641                self.write_keyword("STAGE_COPY_OPTIONS");
11642                self.write(" = (");
11643                if let Some(opts) = options {
11644                    self.generate_space_separated_properties(opts)?;
11645                }
11646                self.write(")");
11647            }
11648            AlterTableAction::AddColumns { columns, cascade } => {
11649                // Oracle uses ADD (...) without COLUMNS keyword
11650                // Hive/Spark uses ADD COLUMNS (...)
11651                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
11652                if is_oracle {
11653                    self.write_keyword("ADD");
11654                } else {
11655                    self.write_keyword("ADD COLUMNS");
11656                }
11657                self.write(" (");
11658                for (i, col) in columns.iter().enumerate() {
11659                    if i > 0 {
11660                        self.write(", ");
11661                    }
11662                    self.generate_column_def(col)?;
11663                }
11664                self.write(")");
11665                if *cascade {
11666                    self.write_space();
11667                    self.write_keyword("CASCADE");
11668                }
11669            }
11670            AlterTableAction::ChangeColumn {
11671                old_name,
11672                new_name,
11673                data_type,
11674                comment,
11675                cascade,
11676            } => {
11677                use crate::dialects::DialectType;
11678                let is_spark = matches!(
11679                    self.config.dialect,
11680                    Some(DialectType::Spark) | Some(DialectType::Databricks)
11681                );
11682                let is_rename = old_name.name != new_name.name;
11683
11684                if is_spark {
11685                    if is_rename {
11686                        // Spark: RENAME COLUMN old TO new
11687                        self.write_keyword("RENAME COLUMN");
11688                        self.write_space();
11689                        self.generate_identifier(old_name)?;
11690                        self.write_space();
11691                        self.write_keyword("TO");
11692                        self.write_space();
11693                        self.generate_identifier(new_name)?;
11694                    } else if comment.is_some() {
11695                        // Spark: ALTER COLUMN old COMMENT 'comment'
11696                        self.write_keyword("ALTER COLUMN");
11697                        self.write_space();
11698                        self.generate_identifier(old_name)?;
11699                        self.write_space();
11700                        self.write_keyword("COMMENT");
11701                        self.write_space();
11702                        self.write("'");
11703                        self.write(comment.as_ref().unwrap());
11704                        self.write("'");
11705                    } else if data_type.is_some() {
11706                        // Spark: ALTER COLUMN old TYPE data_type
11707                        self.write_keyword("ALTER COLUMN");
11708                        self.write_space();
11709                        self.generate_identifier(old_name)?;
11710                        self.write_space();
11711                        self.write_keyword("TYPE");
11712                        self.write_space();
11713                        self.generate_data_type(data_type.as_ref().unwrap())?;
11714                    } else {
11715                        // Fallback to CHANGE COLUMN
11716                        self.write_keyword("CHANGE COLUMN");
11717                        self.write_space();
11718                        self.generate_identifier(old_name)?;
11719                        self.write_space();
11720                        self.generate_identifier(new_name)?;
11721                    }
11722                } else {
11723                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
11724                    if data_type.is_some() {
11725                        self.write_keyword("CHANGE COLUMN");
11726                    } else {
11727                        self.write_keyword("CHANGE");
11728                    }
11729                    self.write_space();
11730                    self.generate_identifier(old_name)?;
11731                    self.write_space();
11732                    self.generate_identifier(new_name)?;
11733                    if let Some(ref dt) = data_type {
11734                        self.write_space();
11735                        self.generate_data_type(dt)?;
11736                    }
11737                    if let Some(ref c) = comment {
11738                        self.write_space();
11739                        self.write_keyword("COMMENT");
11740                        self.write_space();
11741                        self.write("'");
11742                        self.write(c);
11743                        self.write("'");
11744                    }
11745                    if *cascade {
11746                        self.write_space();
11747                        self.write_keyword("CASCADE");
11748                    }
11749                }
11750            }
11751            AlterTableAction::AddPartition {
11752                partition,
11753                if_not_exists,
11754                location,
11755            } => {
11756                self.write_keyword("ADD");
11757                self.write_space();
11758                if *if_not_exists {
11759                    self.write_keyword("IF NOT EXISTS");
11760                    self.write_space();
11761                }
11762                self.generate_expression(partition)?;
11763                if let Some(ref loc) = location {
11764                    self.write_space();
11765                    self.write_keyword("LOCATION");
11766                    self.write_space();
11767                    self.generate_expression(loc)?;
11768                }
11769            }
11770            AlterTableAction::AlterSortKey {
11771                this,
11772                expressions,
11773                compound,
11774            } => {
11775                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11776                self.write_keyword("ALTER");
11777                if *compound {
11778                    self.write_space();
11779                    self.write_keyword("COMPOUND");
11780                }
11781                self.write_space();
11782                self.write_keyword("SORTKEY");
11783                self.write_space();
11784                if let Some(style) = this {
11785                    self.write_keyword(style);
11786                } else if !expressions.is_empty() {
11787                    self.write("(");
11788                    for (i, expr) in expressions.iter().enumerate() {
11789                        if i > 0 {
11790                            self.write(", ");
11791                        }
11792                        self.generate_expression(expr)?;
11793                    }
11794                    self.write(")");
11795                }
11796            }
11797            AlterTableAction::AlterDistStyle { style, distkey } => {
11798                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11799                self.write_keyword("ALTER");
11800                self.write_space();
11801                self.write_keyword("DISTSTYLE");
11802                self.write_space();
11803                self.write_keyword(style);
11804                if let Some(col) = distkey {
11805                    self.write_space();
11806                    self.write_keyword("DISTKEY");
11807                    self.write_space();
11808                    self.generate_identifier(col)?;
11809                }
11810            }
11811            AlterTableAction::SetTableProperties { properties } => {
11812                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11813                self.write_keyword("SET TABLE PROPERTIES");
11814                self.write(" (");
11815                for (i, (key, value)) in properties.iter().enumerate() {
11816                    if i > 0 {
11817                        self.write(", ");
11818                    }
11819                    self.generate_expression(key)?;
11820                    self.write(" = ");
11821                    self.generate_expression(value)?;
11822                }
11823                self.write(")");
11824            }
11825            AlterTableAction::SetLocation { location } => {
11826                // Redshift: SET LOCATION 's3://bucket/folder/'
11827                self.write_keyword("SET LOCATION");
11828                self.write_space();
11829                self.write("'");
11830                self.write(location);
11831                self.write("'");
11832            }
11833            AlterTableAction::SetFileFormat { format } => {
11834                // Redshift: SET FILE FORMAT AVRO
11835                self.write_keyword("SET FILE FORMAT");
11836                self.write_space();
11837                self.write_keyword(format);
11838            }
11839            AlterTableAction::ReplacePartition { partition, source } => {
11840                // ClickHouse: REPLACE PARTITION expr FROM source
11841                self.write_keyword("REPLACE PARTITION");
11842                self.write_space();
11843                self.generate_expression(partition)?;
11844                if let Some(src) = source {
11845                    self.write_space();
11846                    self.write_keyword("FROM");
11847                    self.write_space();
11848                    self.generate_expression(src)?;
11849                }
11850            }
11851            AlterTableAction::SetTiDBTableOption { option, force } => {
11852                self.generate_tidb_table_option(option, *force)?;
11853            }
11854            AlterTableAction::RemoveTiDBTtl { executable_comment } => {
11855                if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11856                    return self.write_unsupported_comment(
11857                        "REMOVE TTL is only supported by the TiDB dialect",
11858                    );
11859                }
11860                if *executable_comment {
11861                    self.write("/*T![ttl] REMOVE TTL */");
11862                } else {
11863                    self.write_keyword("REMOVE TTL");
11864                }
11865            }
11866            AlterTableAction::Raw { sql } => {
11867                self.write(sql);
11868            }
11869        }
11870        Ok(())
11871    }
11872
11873    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11874        match action {
11875            AlterColumnAction::SetDataType {
11876                data_type,
11877                using,
11878                collate,
11879            } => {
11880                use crate::dialects::DialectType;
11881                // Dialect-specific type change syntax:
11882                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11883                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11884                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11885                let is_no_prefix = matches!(
11886                    self.config.dialect,
11887                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11888                );
11889                let is_type_only = matches!(
11890                    self.config.dialect,
11891                    Some(DialectType::Redshift)
11892                        | Some(DialectType::Spark)
11893                        | Some(DialectType::Databricks)
11894                );
11895                if is_type_only {
11896                    self.write_keyword("TYPE");
11897                    self.write_space();
11898                } else if !is_no_prefix {
11899                    self.write_keyword("SET DATA TYPE");
11900                    self.write_space();
11901                }
11902                self.generate_data_type(data_type)?;
11903                if let Some(ref collation) = collate {
11904                    self.write_space();
11905                    self.write_keyword("COLLATE");
11906                    self.write_space();
11907                    self.write(collation);
11908                }
11909                if let Some(ref using_expr) = using {
11910                    self.write_space();
11911                    self.write_keyword("USING");
11912                    self.write_space();
11913                    self.generate_expression(using_expr)?;
11914                }
11915            }
11916            AlterColumnAction::SetDefault(expr) => {
11917                self.write_keyword("SET DEFAULT");
11918                self.write_space();
11919                self.generate_expression(expr)?;
11920            }
11921            AlterColumnAction::DropDefault => {
11922                self.write_keyword("DROP DEFAULT");
11923            }
11924            AlterColumnAction::SetNotNull => {
11925                self.write_keyword("SET NOT NULL");
11926            }
11927            AlterColumnAction::DropNotNull => {
11928                self.write_keyword("DROP NOT NULL");
11929            }
11930            AlterColumnAction::Comment(comment) => {
11931                self.write_keyword("COMMENT");
11932                self.write_space();
11933                self.generate_string_literal(comment)?;
11934            }
11935            AlterColumnAction::SetVisible => {
11936                self.write_keyword("SET VISIBLE");
11937            }
11938            AlterColumnAction::SetInvisible => {
11939                self.write_keyword("SET INVISIBLE");
11940            }
11941        }
11942        Ok(())
11943    }
11944
11945    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11946        self.write_keyword("CREATE");
11947
11948        if ci.unique {
11949            self.write_space();
11950            self.write_keyword("UNIQUE");
11951        }
11952
11953        // TSQL CLUSTERED/NONCLUSTERED modifier
11954        if let Some(ref clustered) = ci.clustered {
11955            self.write_space();
11956            self.write_keyword(clustered);
11957        }
11958
11959        self.write_space();
11960        self.write_keyword("INDEX");
11961
11962        // PostgreSQL CONCURRENTLY modifier
11963        if ci.concurrently {
11964            self.write_space();
11965            self.write_keyword("CONCURRENTLY");
11966        }
11967
11968        if ci.if_not_exists {
11969            self.write_space();
11970            self.write_keyword("IF NOT EXISTS");
11971        }
11972
11973        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11974        if !ci.name.name.is_empty() {
11975            self.write_space();
11976            self.generate_identifier(&ci.name)?;
11977        }
11978        self.write_space();
11979        self.write_keyword("ON");
11980        // Hive uses ON TABLE
11981        if matches!(self.config.dialect, Some(DialectType::Hive)) {
11982            self.write_space();
11983            self.write_keyword("TABLE");
11984        }
11985        self.write_space();
11986        self.generate_table(&ci.table)?;
11987
11988        // Column list (optional for COLUMNSTORE indexes)
11989        // Standard SQL convention: ON t(a) without space before paren
11990        if !ci.columns.is_empty() || !ci.key_parts.is_empty() || ci.using.is_some() {
11991            let space_before_paren = false;
11992
11993            if let Some(ref using) = ci.using {
11994                self.write_space();
11995                self.write_keyword("USING");
11996                self.write_space();
11997                self.write(using);
11998                if space_before_paren {
11999                    self.write(" (");
12000                } else {
12001                    self.write("(");
12002                }
12003            } else {
12004                if space_before_paren {
12005                    self.write(" (");
12006                } else {
12007                    self.write("(");
12008                }
12009            }
12010            if ci.key_parts.is_empty() {
12011                for (i, col) in ci.columns.iter().enumerate() {
12012                    if i > 0 {
12013                        self.write(", ");
12014                    }
12015                    self.generate_identifier(&col.column)?;
12016                    if let Some(ref opclass) = col.opclass {
12017                        self.write_space();
12018                        self.write(opclass);
12019                    }
12020                    if col.desc {
12021                        self.write_space();
12022                        self.write_keyword("DESC");
12023                    } else if col.asc {
12024                        self.write_space();
12025                        self.write_keyword("ASC");
12026                    }
12027                    if let Some(nulls_first) = col.nulls_first {
12028                        self.write_space();
12029                        self.write_keyword("NULLS");
12030                        self.write_space();
12031                        self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
12032                    }
12033                }
12034            } else {
12035                self.generate_index_key_parts(&ci.key_parts)?;
12036            }
12037            self.write(")");
12038        }
12039
12040        // PostgreSQL INCLUDE (col1, col2) clause
12041        if !ci.include_columns.is_empty() {
12042            self.write_space();
12043            self.write_keyword("INCLUDE");
12044            self.write(" (");
12045            for (i, col) in ci.include_columns.iter().enumerate() {
12046                if i > 0 {
12047                    self.write(", ");
12048                }
12049                self.generate_identifier(col)?;
12050            }
12051            self.write(")");
12052        }
12053
12054        // TSQL: WITH (option=value, ...) clause
12055        if !ci.with_options.is_empty() {
12056            self.write_space();
12057            self.write_keyword("WITH");
12058            self.write(" (");
12059            for (i, (key, value)) in ci.with_options.iter().enumerate() {
12060                if i > 0 {
12061                    self.write(", ");
12062                }
12063                self.write(key);
12064                self.write("=");
12065                self.write(value);
12066            }
12067            self.write(")");
12068        }
12069
12070        // PostgreSQL WHERE clause for partial indexes
12071        if let Some(ref where_clause) = ci.where_clause {
12072            self.write_space();
12073            self.write_keyword("WHERE");
12074            self.write_space();
12075            self.generate_expression(where_clause)?;
12076        }
12077
12078        // TSQL: ON filegroup or partition scheme clause
12079        if let Some(ref on_fg) = ci.on_filegroup {
12080            self.write_space();
12081            self.write_keyword("ON");
12082            self.write_space();
12083            self.write(on_fg);
12084        }
12085
12086        Ok(())
12087    }
12088
12089    fn generate_index_key_parts(&mut self, key_parts: &[IndexKeyPart]) -> Result<()> {
12090        for (i, part) in key_parts.iter().enumerate() {
12091            if i > 0 {
12092                self.write(", ");
12093            }
12094            self.generate_expression(&part.expression)?;
12095            if let Some(ref prefix_length) = part.prefix_length {
12096                self.write("(");
12097                self.write(prefix_length);
12098                self.write(")");
12099            }
12100            if let Some(ref opclass) = part.opclass {
12101                self.write_space();
12102                self.write(opclass);
12103            }
12104            if part.desc {
12105                self.write_space();
12106                self.write_keyword("DESC");
12107            } else if part.asc {
12108                self.write_space();
12109                self.write_keyword("ASC");
12110            }
12111            if let Some(nulls_first) = part.nulls_first {
12112                self.write_space();
12113                self.write_keyword("NULLS");
12114                self.write_space();
12115                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
12116            }
12117        }
12118        Ok(())
12119    }
12120
12121    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
12122        self.write_keyword("DROP INDEX");
12123
12124        if di.concurrently {
12125            self.write_space();
12126            self.write_keyword("CONCURRENTLY");
12127        }
12128
12129        if di.if_exists {
12130            self.write_space();
12131            self.write_keyword("IF EXISTS");
12132        }
12133
12134        self.write_space();
12135        self.generate_table(&di.name)?;
12136
12137        if let Some(ref table) = di.table {
12138            self.write_space();
12139            self.write_keyword("ON");
12140            self.write_space();
12141            self.generate_table(table)?;
12142        }
12143
12144        Ok(())
12145    }
12146
12147    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
12148        self.write_keyword("CREATE");
12149
12150        // MySQL: ALGORITHM=...
12151        if let Some(ref algorithm) = cv.algorithm {
12152            self.write_space();
12153            self.write_keyword("ALGORITHM");
12154            self.write("=");
12155            self.write_keyword(algorithm);
12156        }
12157
12158        // MySQL: DEFINER=...
12159        if let Some(ref definer) = cv.definer {
12160            self.write_space();
12161            self.write_keyword("DEFINER");
12162            self.write("=");
12163            self.write(definer);
12164        }
12165
12166        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
12167        if cv.security_sql_style && !cv.security_after_name {
12168            if let Some(ref security) = cv.security {
12169                self.write_space();
12170                self.write_keyword("SQL SECURITY");
12171                self.write_space();
12172                match security {
12173                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12174                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12175                    FunctionSecurity::None => self.write_keyword("NONE"),
12176                }
12177            }
12178        }
12179
12180        if cv.or_alter {
12181            self.write_space();
12182            self.write_keyword("OR ALTER");
12183        } else if cv.or_replace {
12184            self.write_space();
12185            self.write_keyword("OR REPLACE");
12186        }
12187
12188        if cv.temporary {
12189            self.write_space();
12190            self.write_keyword("TEMPORARY");
12191        }
12192
12193        if cv.materialized {
12194            self.write_space();
12195            self.write_keyword("MATERIALIZED");
12196        }
12197
12198        // Snowflake: SECURE VIEW
12199        if cv.secure {
12200            self.write_space();
12201            self.write_keyword("SECURE");
12202        }
12203
12204        self.write_space();
12205        self.write_keyword("VIEW");
12206
12207        if cv.if_not_exists {
12208            self.write_space();
12209            self.write_keyword("IF NOT EXISTS");
12210        }
12211
12212        self.write_space();
12213        self.generate_table(&cv.name)?;
12214
12215        // ClickHouse: ON CLUSTER clause
12216        if let Some(ref on_cluster) = cv.on_cluster {
12217            self.write_space();
12218            self.generate_on_cluster(on_cluster)?;
12219        }
12220
12221        // ClickHouse: TO destination_table
12222        if let Some(ref to_table) = cv.to_table {
12223            self.write_space();
12224            self.write_keyword("TO");
12225            self.write_space();
12226            self.generate_table(to_table)?;
12227        }
12228
12229        // For regular VIEW: columns come before COPY GRANTS
12230        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
12231        if !cv.materialized {
12232            // Regular VIEW: columns first
12233            if let Some(ref schema) = cv.schema {
12234                self.write(" (");
12235                for (i, expr) in schema.expressions.iter().enumerate() {
12236                    if i > 0 {
12237                        self.write(", ");
12238                    }
12239                    self.generate_expression(expr)?;
12240                }
12241                self.write(")");
12242            } else if !cv.columns.is_empty() {
12243                self.write(" (");
12244                for (i, col) in cv.columns.iter().enumerate() {
12245                    if i > 0 {
12246                        self.write(", ");
12247                    }
12248                    self.generate_identifier(&col.name)?;
12249                    // BigQuery: OPTIONS (key=value, ...) on view column
12250                    if !col.options.is_empty() {
12251                        self.write_space();
12252                        self.generate_options_clause(&col.options)?;
12253                    }
12254                    if let Some(ref comment) = col.comment {
12255                        self.write_space();
12256                        self.write_keyword("COMMENT");
12257                        self.write_space();
12258                        self.generate_string_literal(comment)?;
12259                    }
12260                }
12261                self.write(")");
12262            }
12263
12264            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
12265            // Also handles SQL SECURITY after view name (security_after_name)
12266            if !cv.security_sql_style || cv.security_after_name {
12267                if let Some(ref security) = cv.security {
12268                    self.write_space();
12269                    if cv.security_sql_style {
12270                        self.write_keyword("SQL SECURITY");
12271                    } else {
12272                        self.write_keyword("SECURITY");
12273                    }
12274                    self.write_space();
12275                    match security {
12276                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12277                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12278                        FunctionSecurity::None => self.write_keyword("NONE"),
12279                    }
12280                }
12281            }
12282
12283            // Snowflake: COPY GRANTS
12284            if cv.copy_grants {
12285                self.write_space();
12286                self.write_keyword("COPY GRANTS");
12287            }
12288        } else {
12289            // MATERIALIZED VIEW: COPY GRANTS first
12290            if cv.copy_grants {
12291                self.write_space();
12292                self.write_keyword("COPY GRANTS");
12293            }
12294
12295            // Doris: If we have a schema (typed columns), generate that instead
12296            if let Some(ref schema) = cv.schema {
12297                self.write(" (");
12298                for (i, expr) in schema.expressions.iter().enumerate() {
12299                    if i > 0 {
12300                        self.write(", ");
12301                    }
12302                    self.generate_expression(expr)?;
12303                }
12304                self.write(")");
12305            } else if !cv.columns.is_empty() {
12306                // Then columns (simple column names without types)
12307                self.write(" (");
12308                for (i, col) in cv.columns.iter().enumerate() {
12309                    if i > 0 {
12310                        self.write(", ");
12311                    }
12312                    self.generate_identifier(&col.name)?;
12313                    // BigQuery: OPTIONS (key=value, ...) on view column
12314                    if !col.options.is_empty() {
12315                        self.write_space();
12316                        self.generate_options_clause(&col.options)?;
12317                    }
12318                    if let Some(ref comment) = col.comment {
12319                        self.write_space();
12320                        self.write_keyword("COMMENT");
12321                        self.write_space();
12322                        self.generate_string_literal(comment)?;
12323                    }
12324                }
12325                self.write(")");
12326            }
12327
12328            // Doris: KEY (columns) for materialized views
12329            if let Some(ref unique_key) = cv.unique_key {
12330                self.write_space();
12331                self.write_keyword("KEY");
12332                self.write(" (");
12333                for (i, expr) in unique_key.expressions.iter().enumerate() {
12334                    if i > 0 {
12335                        self.write(", ");
12336                    }
12337                    self.generate_expression(expr)?;
12338                }
12339                self.write(")");
12340            }
12341        }
12342
12343        if let Some(ref row_access_policy) = cv.row_access_policy {
12344            self.write_space();
12345            self.write_keyword("WITH");
12346            self.write_space();
12347            self.write(row_access_policy);
12348        }
12349
12350        // Snowflake: COMMENT = 'text'
12351        if let Some(ref comment) = cv.comment {
12352            self.write_space();
12353            self.write_keyword("COMMENT");
12354            self.write("=");
12355            self.generate_string_literal(comment)?;
12356        }
12357
12358        // Snowflake: TAG (name='value', ...)
12359        if !cv.tags.is_empty() {
12360            self.write_space();
12361            self.write_keyword("TAG");
12362            self.write(" (");
12363            for (i, (name, value)) in cv.tags.iter().enumerate() {
12364                if i > 0 {
12365                    self.write(", ");
12366                }
12367                self.write(name);
12368                self.write("='");
12369                self.write(value);
12370                self.write("'");
12371            }
12372            self.write(")");
12373        }
12374
12375        // BigQuery: OPTIONS (key=value, ...)
12376        if !cv.options.is_empty() {
12377            self.write_space();
12378            self.generate_options_clause(&cv.options)?;
12379        }
12380
12381        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
12382        if let Some(ref build) = cv.build {
12383            self.write_space();
12384            self.write_keyword("BUILD");
12385            self.write_space();
12386            self.write_keyword(build);
12387        }
12388
12389        // Doris: REFRESH clause for materialized views
12390        if let Some(ref refresh) = cv.refresh {
12391            self.write_space();
12392            self.generate_refresh_trigger_property(refresh)?;
12393        }
12394
12395        // Redshift: AUTO REFRESH YES|NO for materialized views
12396        if let Some(auto_refresh) = cv.auto_refresh {
12397            self.write_space();
12398            self.write_keyword("AUTO REFRESH");
12399            self.write_space();
12400            if auto_refresh {
12401                self.write_keyword("YES");
12402            } else {
12403                self.write_keyword("NO");
12404            }
12405        }
12406
12407        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
12408        for prop in &cv.table_properties {
12409            self.write_space();
12410            self.generate_expression(prop)?;
12411        }
12412
12413        // ClickHouse: POPULATE / EMPTY before AS
12414        if let Some(ref population) = cv.clickhouse_population {
12415            self.write_space();
12416            self.write_keyword(population);
12417        }
12418
12419        // Only output AS clause if there's a real query (not just NULL placeholder)
12420        if !matches!(&cv.query, Expression::Null(_)) {
12421            self.write_space();
12422            self.write_keyword("AS");
12423            self.write_space();
12424
12425            // Teradata: LOCKING clause (between AS and query)
12426            if let Some(ref mode) = cv.locking_mode {
12427                self.write_keyword("LOCKING");
12428                self.write_space();
12429                self.write_keyword(mode);
12430                if let Some(ref access) = cv.locking_access {
12431                    self.write_space();
12432                    self.write_keyword("FOR");
12433                    self.write_space();
12434                    self.write_keyword(access);
12435                }
12436                self.write_space();
12437            }
12438
12439            if cv.query_parenthesized {
12440                self.write("(");
12441            }
12442            self.generate_expression(&cv.query)?;
12443            if cv.query_parenthesized {
12444                self.write(")");
12445            }
12446        }
12447
12448        // Redshift: WITH NO SCHEMA BINDING (after query)
12449        if cv.no_schema_binding {
12450            self.write_space();
12451            self.write_keyword("WITH NO SCHEMA BINDING");
12452        }
12453
12454        Ok(())
12455    }
12456
12457    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
12458        self.write_keyword("DROP");
12459
12460        if dv.materialized {
12461            self.write_space();
12462            self.write_keyword("MATERIALIZED");
12463        }
12464
12465        self.write_space();
12466        self.write_keyword("VIEW");
12467
12468        if dv.if_exists {
12469            self.write_space();
12470            self.write_keyword("IF EXISTS");
12471        }
12472
12473        self.write_space();
12474        self.generate_table(&dv.name)?;
12475
12476        Ok(())
12477    }
12478
12479    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
12480        match tr.target {
12481            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
12482            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
12483        }
12484        if tr.if_exists {
12485            self.write_space();
12486            self.write_keyword("IF EXISTS");
12487        }
12488        self.write_space();
12489        self.generate_table(&tr.table)?;
12490
12491        // ClickHouse: ON CLUSTER clause
12492        if let Some(ref on_cluster) = tr.on_cluster {
12493            self.write_space();
12494            self.generate_on_cluster(on_cluster)?;
12495        }
12496
12497        // Check if first table has a * (multi-table with star)
12498        if !tr.extra_tables.is_empty() {
12499            // Check if the first entry matches the main table (star case)
12500            let skip_first = if let Some(first) = tr.extra_tables.first() {
12501                first.table.name == tr.table.name && first.star
12502            } else {
12503                false
12504            };
12505
12506            // PostgreSQL normalizes away the * suffix (it's the default behavior)
12507            let strip_star = matches!(
12508                self.config.dialect,
12509                Some(crate::dialects::DialectType::PostgreSQL)
12510                    | Some(crate::dialects::DialectType::Redshift)
12511            );
12512            if skip_first && !strip_star {
12513                self.write("*");
12514            }
12515
12516            // Generate additional tables
12517            for (i, entry) in tr.extra_tables.iter().enumerate() {
12518                if i == 0 && skip_first {
12519                    continue; // Already handled the star for first table
12520                }
12521                self.write(", ");
12522                self.generate_table(&entry.table)?;
12523                if entry.star && !strip_star {
12524                    self.write("*");
12525                }
12526            }
12527        }
12528
12529        // RESTART/CONTINUE IDENTITY
12530        if let Some(identity) = &tr.identity {
12531            self.write_space();
12532            match identity {
12533                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
12534                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
12535            }
12536        }
12537
12538        if tr.cascade {
12539            self.write_space();
12540            self.write_keyword("CASCADE");
12541        }
12542
12543        if tr.restrict {
12544            self.write_space();
12545            self.write_keyword("RESTRICT");
12546        }
12547
12548        // Output Hive PARTITION clause
12549        if let Some(ref partition) = tr.partition {
12550            self.write_space();
12551            self.generate_expression(partition)?;
12552        }
12553
12554        Ok(())
12555    }
12556
12557    fn generate_use(&mut self, u: &Use) -> Result<()> {
12558        // Teradata uses "DATABASE <name>" instead of "USE <name>"
12559        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
12560            self.write_keyword("DATABASE");
12561            self.write_space();
12562            self.generate_identifier(&u.this)?;
12563            return Ok(());
12564        }
12565
12566        self.write_keyword("USE");
12567
12568        if let Some(kind) = &u.kind {
12569            self.write_space();
12570            match kind {
12571                UseKind::Database => self.write_keyword("DATABASE"),
12572                UseKind::Schema => self.write_keyword("SCHEMA"),
12573                UseKind::Role => self.write_keyword("ROLE"),
12574                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
12575                UseKind::Catalog => self.write_keyword("CATALOG"),
12576                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
12577            }
12578        }
12579
12580        self.write_space();
12581        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
12582        // without quoting, since these are keywords not identifiers
12583        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
12584            self.write(&u.this.name);
12585        } else {
12586            self.generate_identifier(&u.this)?;
12587        }
12588        Ok(())
12589    }
12590
12591    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
12592        self.write_keyword("CACHE");
12593        if c.lazy {
12594            self.write_space();
12595            self.write_keyword("LAZY");
12596        }
12597        self.write_space();
12598        self.write_keyword("TABLE");
12599        self.write_space();
12600        self.generate_identifier(&c.table)?;
12601
12602        // OPTIONS clause
12603        if !c.options.is_empty() {
12604            self.write_space();
12605            self.write_keyword("OPTIONS");
12606            self.write("(");
12607            for (i, (key, value)) in c.options.iter().enumerate() {
12608                if i > 0 {
12609                    self.write(", ");
12610                }
12611                self.generate_expression(key)?;
12612                self.write(" = ");
12613                self.generate_expression(value)?;
12614            }
12615            self.write(")");
12616        }
12617
12618        // AS query
12619        if let Some(query) = &c.query {
12620            self.write_space();
12621            self.write_keyword("AS");
12622            self.write_space();
12623            self.generate_expression(query)?;
12624        }
12625
12626        Ok(())
12627    }
12628
12629    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
12630        self.write_keyword("UNCACHE TABLE");
12631        if u.if_exists {
12632            self.write_space();
12633            self.write_keyword("IF EXISTS");
12634        }
12635        self.write_space();
12636        self.generate_identifier(&u.table)?;
12637        Ok(())
12638    }
12639
12640    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
12641        self.write_keyword("LOAD DATA");
12642        if l.local {
12643            self.write_space();
12644            self.write_keyword("LOCAL");
12645        }
12646        self.write_space();
12647        self.write_keyword("INPATH");
12648        self.write_space();
12649        self.write("'");
12650        self.write(&l.inpath);
12651        self.write("'");
12652
12653        if l.overwrite {
12654            self.write_space();
12655            self.write_keyword("OVERWRITE");
12656        }
12657
12658        self.write_space();
12659        self.write_keyword("INTO TABLE");
12660        self.write_space();
12661        self.generate_expression(&l.table)?;
12662
12663        // PARTITION clause
12664        if !l.partition.is_empty() {
12665            self.write_space();
12666            self.write_keyword("PARTITION");
12667            self.write("(");
12668            for (i, (col, val)) in l.partition.iter().enumerate() {
12669                if i > 0 {
12670                    self.write(", ");
12671                }
12672                self.generate_identifier(col)?;
12673                self.write(" = ");
12674                self.generate_expression(val)?;
12675            }
12676            self.write(")");
12677        }
12678
12679        // INPUTFORMAT clause
12680        if let Some(fmt) = &l.input_format {
12681            self.write_space();
12682            self.write_keyword("INPUTFORMAT");
12683            self.write_space();
12684            self.write("'");
12685            self.write(fmt);
12686            self.write("'");
12687        }
12688
12689        // SERDE clause
12690        if let Some(serde) = &l.serde {
12691            self.write_space();
12692            self.write_keyword("SERDE");
12693            self.write_space();
12694            self.write("'");
12695            self.write(serde);
12696            self.write("'");
12697        }
12698
12699        Ok(())
12700    }
12701
12702    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
12703        self.write_keyword("PRAGMA");
12704        self.write_space();
12705
12706        // Schema prefix if present
12707        if let Some(schema) = &p.schema {
12708            self.generate_identifier(schema)?;
12709            self.write(".");
12710        }
12711
12712        // Pragma name
12713        self.generate_identifier(&p.name)?;
12714
12715        // Value assignment or function call
12716        if p.use_assignment_syntax {
12717            self.write(" = ");
12718            if let Some(value) = &p.value {
12719                self.generate_expression(value)?;
12720            } else if let Some(arg) = p.args.first() {
12721                self.generate_expression(arg)?;
12722            }
12723        } else if !p.args.is_empty() {
12724            self.write("(");
12725            for (i, arg) in p.args.iter().enumerate() {
12726                if i > 0 {
12727                    self.write(", ");
12728                }
12729                self.generate_expression(arg)?;
12730            }
12731            self.write(")");
12732        }
12733
12734        Ok(())
12735    }
12736
12737    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
12738        self.write_keyword("GRANT");
12739        self.write_space();
12740
12741        // Privileges (with optional column lists)
12742        for (i, privilege) in g.privileges.iter().enumerate() {
12743            if i > 0 {
12744                self.write(", ");
12745            }
12746            self.write_keyword(&privilege.name);
12747            // Output column list if present: SELECT(col1, col2)
12748            if !privilege.columns.is_empty() {
12749                self.write("(");
12750                for (j, col) in privilege.columns.iter().enumerate() {
12751                    if j > 0 {
12752                        self.write(", ");
12753                    }
12754                    self.write(col);
12755                }
12756                self.write(")");
12757            }
12758        }
12759
12760        self.write_space();
12761        self.write_keyword("ON");
12762        self.write_space();
12763
12764        // Object kind (TABLE, SCHEMA, etc.)
12765        if let Some(kind) = &g.kind {
12766            self.write_keyword(kind);
12767            self.write_space();
12768        }
12769
12770        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12771        {
12772            use crate::dialects::DialectType;
12773            let should_upper = matches!(
12774                self.config.dialect,
12775                Some(DialectType::PostgreSQL)
12776                    | Some(DialectType::CockroachDB)
12777                    | Some(DialectType::Materialize)
12778                    | Some(DialectType::RisingWave)
12779            ) && (g.kind.as_deref() == Some("FUNCTION")
12780                || g.kind.as_deref() == Some("PROCEDURE"));
12781            if should_upper {
12782                use crate::expressions::Identifier;
12783                let upper_id = Identifier {
12784                    name: g.securable.name.to_ascii_uppercase(),
12785                    quoted: g.securable.quoted,
12786                    ..g.securable.clone()
12787                };
12788                self.generate_identifier(&upper_id)?;
12789            } else {
12790                self.generate_identifier(&g.securable)?;
12791            }
12792        }
12793
12794        // Function parameter types (if present)
12795        if !g.function_params.is_empty() {
12796            self.write("(");
12797            for (i, param) in g.function_params.iter().enumerate() {
12798                if i > 0 {
12799                    self.write(", ");
12800                }
12801                self.write(param);
12802            }
12803            self.write(")");
12804        }
12805
12806        self.write_space();
12807        self.write_keyword("TO");
12808        self.write_space();
12809
12810        // Principals
12811        for (i, principal) in g.principals.iter().enumerate() {
12812            if i > 0 {
12813                self.write(", ");
12814            }
12815            if principal.is_role {
12816                self.write_keyword("ROLE");
12817                self.write_space();
12818            } else if principal.is_group {
12819                self.write_keyword("GROUP");
12820                self.write_space();
12821            } else if principal.is_share {
12822                self.write_keyword("SHARE");
12823                self.write_space();
12824            }
12825            self.generate_identifier(&principal.name)?;
12826        }
12827
12828        // WITH GRANT OPTION
12829        if g.grant_option {
12830            self.write_space();
12831            self.write_keyword("WITH GRANT OPTION");
12832        }
12833
12834        // TSQL: AS principal
12835        if let Some(ref principal) = g.as_principal {
12836            self.write_space();
12837            self.write_keyword("AS");
12838            self.write_space();
12839            self.generate_identifier(principal)?;
12840        }
12841
12842        Ok(())
12843    }
12844
12845    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12846        self.write_keyword("REVOKE");
12847        self.write_space();
12848
12849        // GRANT OPTION FOR
12850        if r.grant_option {
12851            self.write_keyword("GRANT OPTION FOR");
12852            self.write_space();
12853        }
12854
12855        // Privileges (with optional column lists)
12856        for (i, privilege) in r.privileges.iter().enumerate() {
12857            if i > 0 {
12858                self.write(", ");
12859            }
12860            self.write_keyword(&privilege.name);
12861            // Output column list if present: SELECT(col1, col2)
12862            if !privilege.columns.is_empty() {
12863                self.write("(");
12864                for (j, col) in privilege.columns.iter().enumerate() {
12865                    if j > 0 {
12866                        self.write(", ");
12867                    }
12868                    self.write(col);
12869                }
12870                self.write(")");
12871            }
12872        }
12873
12874        self.write_space();
12875        self.write_keyword("ON");
12876        self.write_space();
12877
12878        // Object kind
12879        if let Some(kind) = &r.kind {
12880            self.write_keyword(kind);
12881            self.write_space();
12882        }
12883
12884        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12885        {
12886            use crate::dialects::DialectType;
12887            let should_upper = matches!(
12888                self.config.dialect,
12889                Some(DialectType::PostgreSQL)
12890                    | Some(DialectType::CockroachDB)
12891                    | Some(DialectType::Materialize)
12892                    | Some(DialectType::RisingWave)
12893            ) && (r.kind.as_deref() == Some("FUNCTION")
12894                || r.kind.as_deref() == Some("PROCEDURE"));
12895            if should_upper {
12896                use crate::expressions::Identifier;
12897                let upper_id = Identifier {
12898                    name: r.securable.name.to_ascii_uppercase(),
12899                    quoted: r.securable.quoted,
12900                    ..r.securable.clone()
12901                };
12902                self.generate_identifier(&upper_id)?;
12903            } else {
12904                self.generate_identifier(&r.securable)?;
12905            }
12906        }
12907
12908        // Function parameter types (if present)
12909        if !r.function_params.is_empty() {
12910            self.write("(");
12911            for (i, param) in r.function_params.iter().enumerate() {
12912                if i > 0 {
12913                    self.write(", ");
12914                }
12915                self.write(param);
12916            }
12917            self.write(")");
12918        }
12919
12920        self.write_space();
12921        self.write_keyword("FROM");
12922        self.write_space();
12923
12924        // Principals
12925        for (i, principal) in r.principals.iter().enumerate() {
12926            if i > 0 {
12927                self.write(", ");
12928            }
12929            if principal.is_role {
12930                self.write_keyword("ROLE");
12931                self.write_space();
12932            } else if principal.is_group {
12933                self.write_keyword("GROUP");
12934                self.write_space();
12935            } else if principal.is_share {
12936                self.write_keyword("SHARE");
12937                self.write_space();
12938            }
12939            self.generate_identifier(&principal.name)?;
12940        }
12941
12942        // CASCADE or RESTRICT
12943        if r.cascade {
12944            self.write_space();
12945            self.write_keyword("CASCADE");
12946        } else if r.restrict {
12947            self.write_space();
12948            self.write_keyword("RESTRICT");
12949        }
12950
12951        Ok(())
12952    }
12953
12954    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12955        self.write_keyword("COMMENT");
12956
12957        // IF EXISTS
12958        if c.exists {
12959            self.write_space();
12960            self.write_keyword("IF EXISTS");
12961        }
12962
12963        self.write_space();
12964        self.write_keyword("ON");
12965
12966        // MATERIALIZED
12967        if c.materialized {
12968            self.write_space();
12969            self.write_keyword("MATERIALIZED");
12970        }
12971
12972        self.write_space();
12973        self.write_keyword(&c.kind);
12974        self.write_space();
12975
12976        // Object name
12977        self.generate_expression(&c.this)?;
12978
12979        self.write_space();
12980        self.write_keyword("IS");
12981        self.write_space();
12982
12983        // Comment expression
12984        self.generate_expression(&c.expression)?;
12985
12986        Ok(())
12987    }
12988
12989    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
12990        self.write_keyword("SET");
12991
12992        for (i, item) in s.items.iter().enumerate() {
12993            if i > 0 {
12994                self.write(",");
12995            }
12996            self.write_space();
12997
12998            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
12999            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
13000            if let Some(ref kind) = item.kind {
13001                // For VARIABLE kind, only output the keyword for dialects that require it
13002                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
13003                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
13004                if has_variable_kind {
13005                    if matches!(
13006                        self.config.dialect,
13007                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
13008                    ) {
13009                        self.write_keyword("VARIABLE");
13010                        self.write_space();
13011                    }
13012                } else {
13013                    self.write_keyword(kind);
13014                    self.write_space();
13015                }
13016            }
13017
13018            // Check for special SET forms by name
13019            let name_str = match &item.name {
13020                Expression::Identifier(id) => Some(id.name.as_str()),
13021                _ => None,
13022            };
13023
13024            let is_transaction = name_str == Some("TRANSACTION");
13025            let is_session_authorization = name_str == Some("SESSION AUTHORIZATION");
13026            let is_character_set = name_str == Some("CHARACTER SET");
13027            let is_names = name_str == Some("NAMES");
13028            let is_collate = name_str == Some("COLLATE");
13029            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
13030            let is_value_only =
13031                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
13032
13033            if is_session_authorization {
13034                // PostgreSQL: SET [SESSION | LOCAL] SESSION AUTHORIZATION
13035                // user_name | DEFAULT (without an equals sign).
13036                self.write_keyword("SESSION AUTHORIZATION");
13037                self.write_space();
13038                self.generate_set_value(&item.value)?;
13039            } else if is_transaction {
13040                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
13041                self.write_keyword("TRANSACTION");
13042                if let Expression::Identifier(id) = &item.value {
13043                    if !id.name.is_empty() {
13044                        self.write_space();
13045                        self.write(&id.name);
13046                    }
13047                }
13048            } else if is_character_set {
13049                // Output: SET CHARACTER SET <charset>
13050                self.write_keyword("CHARACTER SET");
13051                self.write_space();
13052                self.generate_set_value(&item.value)?;
13053            } else if is_names {
13054                // Output: SET NAMES <charset>
13055                self.write_keyword("NAMES");
13056                self.write_space();
13057                self.generate_set_value(&item.value)?;
13058            } else if is_collate {
13059                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
13060                self.write_keyword("COLLATE");
13061                self.write_space();
13062                self.generate_set_value(&item.value)?;
13063            } else if is_identity_insert {
13064                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
13065                self.write_keyword("IDENTITY_INSERT");
13066                self.write_space();
13067                self.generate_identity_insert_value(&item.value)?;
13068            } else if has_variable_kind {
13069                // Output: SET [VARIABLE] <name> = <value>
13070                // VARIABLE keyword already written above if dialect requires it
13071                if let Some(ns) = name_str {
13072                    self.write(ns);
13073                } else {
13074                    self.generate_expression(&item.name)?;
13075                }
13076                self.write(" = ");
13077                self.generate_set_value(&item.value)?;
13078            } else if is_value_only {
13079                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
13080                self.generate_expression(&item.name)?;
13081            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
13082                // SET key value without = (TSQL style)
13083                self.generate_expression(&item.name)?;
13084                self.write_space();
13085                self.generate_set_value(&item.value)?;
13086            } else {
13087                // Standard: variable = value
13088                // SET item names should not be quoted (they are config parameter names, not column refs)
13089                match &item.name {
13090                    Expression::Identifier(id) => {
13091                        self.write(&id.name);
13092                    }
13093                    _ => {
13094                        self.generate_expression(&item.name)?;
13095                    }
13096                }
13097                self.write(" = ");
13098                self.generate_set_value(&item.value)?;
13099            }
13100        }
13101
13102        Ok(())
13103    }
13104
13105    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
13106        if let Expression::Tuple(tuple) = value {
13107            if tuple.expressions.len() == 2 {
13108                self.generate_expression(&tuple.expressions[0])?;
13109                self.write_space();
13110                self.generate_set_value(&tuple.expressions[1])?;
13111                return Ok(());
13112            }
13113        }
13114
13115        self.generate_set_value(value)
13116    }
13117
13118    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
13119    /// directly to avoid reserved keyword quoting.
13120    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
13121        if let Expression::Identifier(id) = value {
13122            match id.name.as_str() {
13123                "DEFAULT" | "ON" | "OFF" => {
13124                    self.write_keyword(&id.name);
13125                    return Ok(());
13126                }
13127                _ => {}
13128            }
13129        }
13130        self.generate_expression(value)
13131    }
13132
13133    // ==================== Phase 4: Additional DDL Generation ====================
13134
13135    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
13136        self.write_keyword("ALTER");
13137        // MySQL modifiers before VIEW
13138        if let Some(ref algorithm) = av.algorithm {
13139            self.write_space();
13140            self.write_keyword("ALGORITHM");
13141            self.write(" = ");
13142            self.write_keyword(algorithm);
13143        }
13144        if let Some(ref definer) = av.definer {
13145            self.write_space();
13146            self.write_keyword("DEFINER");
13147            self.write(" = ");
13148            self.write(definer);
13149        }
13150        if let Some(ref sql_security) = av.sql_security {
13151            self.write_space();
13152            self.write_keyword("SQL SECURITY");
13153            self.write(" = ");
13154            self.write_keyword(sql_security);
13155        }
13156        self.write_space();
13157        self.write_keyword("VIEW");
13158        self.write_space();
13159        self.generate_table(&av.name)?;
13160
13161        // Hive: Column aliases with optional COMMENT
13162        if !av.columns.is_empty() {
13163            self.write(" (");
13164            for (i, col) in av.columns.iter().enumerate() {
13165                if i > 0 {
13166                    self.write(", ");
13167                }
13168                self.generate_identifier(&col.name)?;
13169                if let Some(ref comment) = col.comment {
13170                    self.write_space();
13171                    self.write_keyword("COMMENT");
13172                    self.write(" ");
13173                    self.generate_string_literal(comment)?;
13174                }
13175            }
13176            self.write(")");
13177        }
13178
13179        // TSQL: WITH option before actions
13180        if let Some(ref opt) = av.with_option {
13181            self.write_space();
13182            self.write_keyword("WITH");
13183            self.write_space();
13184            self.write_keyword(opt);
13185        }
13186
13187        for action in &av.actions {
13188            self.write_space();
13189            match action {
13190                AlterViewAction::Rename(new_name) => {
13191                    self.write_keyword("RENAME TO");
13192                    self.write_space();
13193                    self.generate_table(new_name)?;
13194                }
13195                AlterViewAction::OwnerTo(owner) => {
13196                    self.write_keyword("OWNER TO");
13197                    self.write_space();
13198                    self.generate_identifier(owner)?;
13199                }
13200                AlterViewAction::SetSchema(schema) => {
13201                    self.write_keyword("SET SCHEMA");
13202                    self.write_space();
13203                    self.generate_identifier(schema)?;
13204                }
13205                AlterViewAction::SetAuthorization(auth) => {
13206                    self.write_keyword("SET AUTHORIZATION");
13207                    self.write_space();
13208                    self.write(auth);
13209                }
13210                AlterViewAction::AlterColumn { name, action } => {
13211                    self.write_keyword("ALTER COLUMN");
13212                    self.write_space();
13213                    self.generate_identifier(name)?;
13214                    self.write_space();
13215                    self.generate_alter_column_action(action)?;
13216                }
13217                AlterViewAction::AsSelect(query) => {
13218                    self.write_keyword("AS");
13219                    self.write_space();
13220                    self.generate_expression(query)?;
13221                }
13222                AlterViewAction::SetTblproperties(props) => {
13223                    self.write_keyword("SET TBLPROPERTIES");
13224                    self.write(" (");
13225                    for (i, (key, value)) in props.iter().enumerate() {
13226                        if i > 0 {
13227                            self.write(", ");
13228                        }
13229                        self.generate_string_literal(key)?;
13230                        self.write("=");
13231                        self.generate_string_literal(value)?;
13232                    }
13233                    self.write(")");
13234                }
13235                AlterViewAction::UnsetTblproperties(keys) => {
13236                    self.write_keyword("UNSET TBLPROPERTIES");
13237                    self.write(" (");
13238                    for (i, key) in keys.iter().enumerate() {
13239                        if i > 0 {
13240                            self.write(", ");
13241                        }
13242                        self.generate_string_literal(key)?;
13243                    }
13244                    self.write(")");
13245                }
13246            }
13247        }
13248
13249        Ok(())
13250    }
13251
13252    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
13253        self.write_keyword("ALTER INDEX");
13254        self.write_space();
13255        self.generate_identifier(&ai.name)?;
13256
13257        if let Some(table) = &ai.table {
13258            self.write_space();
13259            self.write_keyword("ON");
13260            self.write_space();
13261            self.generate_table(table)?;
13262        }
13263
13264        for action in &ai.actions {
13265            self.write_space();
13266            match action {
13267                AlterIndexAction::Rename(new_name) => {
13268                    self.write_keyword("RENAME TO");
13269                    self.write_space();
13270                    self.generate_identifier(new_name)?;
13271                }
13272                AlterIndexAction::SetTablespace(tablespace) => {
13273                    self.write_keyword("SET TABLESPACE");
13274                    self.write_space();
13275                    self.generate_identifier(tablespace)?;
13276                }
13277                AlterIndexAction::Visible(visible) => {
13278                    if *visible {
13279                        self.write_keyword("VISIBLE");
13280                    } else {
13281                        self.write_keyword("INVISIBLE");
13282                    }
13283                }
13284            }
13285        }
13286
13287        Ok(())
13288    }
13289
13290    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
13291        // Output leading comments
13292        for comment in &cs.leading_comments {
13293            self.write_formatted_comment(comment);
13294            self.write_space();
13295        }
13296
13297        // Athena: CREATE SCHEMA uses Hive engine (backticks)
13298        let saved_athena_hive_context = self.athena_hive_context;
13299        if matches!(
13300            self.config.dialect,
13301            Some(crate::dialects::DialectType::Athena)
13302        ) {
13303            self.athena_hive_context = true;
13304        }
13305
13306        self.write_keyword("CREATE SCHEMA");
13307
13308        if cs.if_not_exists {
13309            self.write_space();
13310            self.write_keyword("IF NOT EXISTS");
13311        }
13312
13313        self.write_space();
13314        for (i, part) in cs.name.iter().enumerate() {
13315            if i > 0 {
13316                self.write(".");
13317            }
13318            self.generate_identifier(part)?;
13319        }
13320
13321        if let Some(ref clone_parts) = cs.clone_from {
13322            self.write_keyword(" CLONE ");
13323            for (i, part) in clone_parts.iter().enumerate() {
13324                if i > 0 {
13325                    self.write(".");
13326                }
13327                self.generate_identifier(part)?;
13328            }
13329        }
13330
13331        if let Some(ref at_clause) = cs.at_clause {
13332            self.write_space();
13333            self.generate_expression(at_clause)?;
13334        }
13335
13336        if let Some(auth) = &cs.authorization {
13337            self.write_space();
13338            self.write_keyword("AUTHORIZATION");
13339            self.write_space();
13340            self.generate_identifier(auth)?;
13341        }
13342
13343        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
13344        // Separate WITH properties from other properties
13345        let with_properties: Vec<_> = cs
13346            .properties
13347            .iter()
13348            .filter(|p| matches!(p, Expression::Property(_)))
13349            .collect();
13350        let other_properties: Vec<_> = cs
13351            .properties
13352            .iter()
13353            .filter(|p| !matches!(p, Expression::Property(_)))
13354            .collect();
13355
13356        // Generate WITH (props) if we have Property expressions
13357        if !with_properties.is_empty() {
13358            self.write_space();
13359            self.write_keyword("WITH");
13360            self.write(" (");
13361            for (i, prop) in with_properties.iter().enumerate() {
13362                if i > 0 {
13363                    self.write(", ");
13364                }
13365                self.generate_expression(prop)?;
13366            }
13367            self.write(")");
13368        }
13369
13370        // Generate other properties (like DEFAULT COLLATE)
13371        for prop in other_properties {
13372            self.write_space();
13373            self.generate_expression(prop)?;
13374        }
13375
13376        // Restore Athena Hive context
13377        self.athena_hive_context = saved_athena_hive_context;
13378
13379        Ok(())
13380    }
13381
13382    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
13383        self.write_keyword("DROP SCHEMA");
13384
13385        if ds.if_exists {
13386            self.write_space();
13387            self.write_keyword("IF EXISTS");
13388        }
13389
13390        self.write_space();
13391        self.generate_identifier(&ds.name)?;
13392
13393        if ds.cascade {
13394            self.write_space();
13395            self.write_keyword("CASCADE");
13396        }
13397
13398        Ok(())
13399    }
13400
13401    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
13402        self.write_keyword("DROP NAMESPACE");
13403
13404        if dn.if_exists {
13405            self.write_space();
13406            self.write_keyword("IF EXISTS");
13407        }
13408
13409        self.write_space();
13410        self.generate_identifier(&dn.name)?;
13411
13412        if dn.cascade {
13413            self.write_space();
13414            self.write_keyword("CASCADE");
13415        }
13416
13417        Ok(())
13418    }
13419
13420    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
13421        self.write_keyword("CREATE DATABASE");
13422
13423        if cd.if_not_exists {
13424            self.write_space();
13425            self.write_keyword("IF NOT EXISTS");
13426        }
13427
13428        self.write_space();
13429        self.generate_identifier(&cd.name)?;
13430
13431        if let Some(ref clone_src) = cd.clone_from {
13432            self.write_keyword(" CLONE ");
13433            self.generate_identifier(clone_src)?;
13434        }
13435
13436        // AT/BEFORE clause for time travel (Snowflake)
13437        if let Some(ref at_clause) = cd.at_clause {
13438            self.write_space();
13439            self.generate_expression(at_clause)?;
13440        }
13441
13442        for option in &cd.options {
13443            self.write_space();
13444            match option {
13445                DatabaseOption::CharacterSet(charset) => {
13446                    self.write_keyword("CHARACTER SET");
13447                    self.write(" = ");
13448                    self.write(&format!("'{}'", charset));
13449                }
13450                DatabaseOption::Collate(collate) => {
13451                    self.write_keyword("COLLATE");
13452                    self.write(" = ");
13453                    self.write(&format!("'{}'", collate));
13454                }
13455                DatabaseOption::Owner(owner) => {
13456                    self.write_keyword("OWNER");
13457                    self.write(" = ");
13458                    self.generate_identifier(owner)?;
13459                }
13460                DatabaseOption::Template(template) => {
13461                    self.write_keyword("TEMPLATE");
13462                    self.write(" = ");
13463                    self.generate_identifier(template)?;
13464                }
13465                DatabaseOption::Encoding(encoding) => {
13466                    self.write_keyword("ENCODING");
13467                    self.write(" = ");
13468                    self.write(&format!("'{}'", encoding));
13469                }
13470                DatabaseOption::Location(location) => {
13471                    self.write_keyword("LOCATION");
13472                    self.write(" = ");
13473                    self.write(&format!("'{}'", location));
13474                }
13475            }
13476        }
13477
13478        Ok(())
13479    }
13480
13481    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
13482        self.write_keyword("DROP DATABASE");
13483
13484        if dd.if_exists {
13485            self.write_space();
13486            self.write_keyword("IF EXISTS");
13487        }
13488
13489        self.write_space();
13490        self.generate_identifier(&dd.name)?;
13491
13492        if dd.sync {
13493            self.write_space();
13494            self.write_keyword("SYNC");
13495        }
13496
13497        Ok(())
13498    }
13499
13500    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
13501        self.write_keyword("CREATE");
13502
13503        if cf.or_alter {
13504            self.write_space();
13505            self.write_keyword("OR ALTER");
13506        } else if cf.or_replace {
13507            self.write_space();
13508            self.write_keyword("OR REPLACE");
13509        }
13510
13511        if cf.temporary {
13512            self.write_space();
13513            self.write_keyword("TEMPORARY");
13514        }
13515
13516        self.write_space();
13517        if cf.is_table_function {
13518            self.write_keyword("TABLE FUNCTION");
13519        } else {
13520            self.write_keyword("FUNCTION");
13521        }
13522
13523        if cf.if_not_exists {
13524            self.write_space();
13525            self.write_keyword("IF NOT EXISTS");
13526        }
13527
13528        self.write_space();
13529        self.generate_table(&cf.name)?;
13530        if cf.has_parens {
13531            let func_multiline = self.config.pretty
13532                && matches!(
13533                    self.config.dialect,
13534                    Some(crate::dialects::DialectType::TSQL)
13535                        | Some(crate::dialects::DialectType::Fabric)
13536                )
13537                && !cf.parameters.is_empty();
13538            if func_multiline {
13539                self.write("(\n");
13540                self.indent_level += 2;
13541                self.write_indent();
13542                self.generate_function_parameters(&cf.parameters)?;
13543                self.write("\n");
13544                self.indent_level -= 2;
13545                self.write(")");
13546            } else {
13547                self.write("(");
13548                self.generate_function_parameters(&cf.parameters)?;
13549                self.write(")");
13550            }
13551        }
13552
13553        // Output RETURNS clause (always comes first after parameters)
13554        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
13555        let use_multiline = self.config.pretty
13556            && matches!(
13557                self.config.dialect,
13558                Some(crate::dialects::DialectType::BigQuery)
13559                    | Some(crate::dialects::DialectType::TSQL)
13560                    | Some(crate::dialects::DialectType::Fabric)
13561            );
13562
13563        if cf.language_first {
13564            // LANGUAGE first, then SQL data access, then RETURNS
13565            if let Some(lang) = &cf.language {
13566                if use_multiline {
13567                    self.write_newline();
13568                } else {
13569                    self.write_space();
13570                }
13571                self.write_keyword("LANGUAGE");
13572                self.write_space();
13573                self.write(lang);
13574            }
13575
13576            // SQL data access comes after LANGUAGE in this case
13577            if let Some(sql_data) = &cf.sql_data_access {
13578                self.write_space();
13579                match sql_data {
13580                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13581                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13582                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13583                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13584                }
13585            }
13586
13587            if let Some(ref rtb) = cf.returns_table_body {
13588                if use_multiline {
13589                    self.write_newline();
13590                } else {
13591                    self.write_space();
13592                }
13593                self.write_keyword("RETURNS");
13594                self.write_space();
13595                self.write(rtb);
13596            } else if let Some(return_type) = &cf.return_type {
13597                if use_multiline {
13598                    self.write_newline();
13599                } else {
13600                    self.write_space();
13601                }
13602                self.write_keyword("RETURNS");
13603                self.write_space();
13604                self.generate_function_return_type(return_type)?;
13605            }
13606        } else {
13607            // RETURNS first (default)
13608            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
13609            let is_duckdb = matches!(
13610                self.config.dialect,
13611                Some(crate::dialects::DialectType::DuckDB)
13612            );
13613            if let Some(ref rtb) = cf.returns_table_body {
13614                if !(is_duckdb && rtb.is_empty()) {
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.write(rtb);
13623                }
13624            } else if let Some(return_type) = &cf.return_type {
13625                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
13626                if !is_duckdb {
13627                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
13628                    if use_multiline {
13629                        self.write_newline();
13630                    } else {
13631                        self.write_space();
13632                    }
13633                    self.write_keyword("RETURNS");
13634                    self.write_space();
13635                    if is_table_return {
13636                        self.write_keyword("TABLE");
13637                    } else {
13638                        self.generate_function_return_type(return_type)?;
13639                    }
13640                }
13641            }
13642        }
13643
13644        // If we have property_order, use it to output properties in original order
13645        if !cf.property_order.is_empty() {
13646            // For BigQuery, OPTIONS must come before AS - reorder if needed
13647            let is_bigquery = matches!(
13648                self.config.dialect,
13649                Some(crate::dialects::DialectType::BigQuery)
13650            );
13651            let is_postgres = matches!(
13652                self.config.dialect,
13653                Some(crate::dialects::DialectType::PostgreSQL)
13654            );
13655            let property_order = if is_bigquery {
13656                // Move Options before As if both are present
13657                let mut reordered = Vec::new();
13658                let mut has_as = false;
13659                let mut has_options = false;
13660                for prop in &cf.property_order {
13661                    match prop {
13662                        FunctionPropertyKind::As => has_as = true,
13663                        FunctionPropertyKind::Options => has_options = true,
13664                        _ => {}
13665                    }
13666                }
13667                if has_as && has_options {
13668                    // Output all props except As and Options, then Options, then As
13669                    for prop in &cf.property_order {
13670                        if *prop != FunctionPropertyKind::As
13671                            && *prop != FunctionPropertyKind::Options
13672                        {
13673                            reordered.push(*prop);
13674                        }
13675                    }
13676                    reordered.push(FunctionPropertyKind::Options);
13677                    reordered.push(FunctionPropertyKind::As);
13678                    reordered
13679                } else {
13680                    cf.property_order.clone()
13681                }
13682            } else if is_postgres
13683                && cf.property_order.contains(&FunctionPropertyKind::As)
13684                && cf.property_order.contains(&FunctionPropertyKind::NullInput)
13685            {
13686                let mut reordered: Vec<_> = cf
13687                    .property_order
13688                    .iter()
13689                    .copied()
13690                    .filter(|prop| *prop != FunctionPropertyKind::As)
13691                    .collect();
13692                reordered.push(FunctionPropertyKind::As);
13693                reordered
13694            } else {
13695                cf.property_order.clone()
13696            };
13697
13698            for prop in &property_order {
13699                match prop {
13700                    FunctionPropertyKind::Set => {
13701                        self.generate_function_set_options(cf)?;
13702                    }
13703                    FunctionPropertyKind::As => {
13704                        self.generate_function_body(cf)?;
13705                    }
13706                    FunctionPropertyKind::Using => {
13707                        self.generate_function_using_resources(cf)?;
13708                    }
13709                    FunctionPropertyKind::Language => {
13710                        if !cf.language_first {
13711                            // Only output here if not already output above
13712                            if let Some(lang) = &cf.language {
13713                                // Only BigQuery uses multiline formatting
13714                                let use_multiline = self.config.pretty
13715                                    && matches!(
13716                                        self.config.dialect,
13717                                        Some(crate::dialects::DialectType::BigQuery)
13718                                    );
13719                                if use_multiline {
13720                                    self.write_newline();
13721                                } else {
13722                                    self.write_space();
13723                                }
13724                                self.write_keyword("LANGUAGE");
13725                                self.write_space();
13726                                self.write(lang);
13727                            }
13728                        }
13729                    }
13730                    FunctionPropertyKind::Determinism => {
13731                        self.generate_function_determinism(cf)?;
13732                    }
13733                    FunctionPropertyKind::NullInput => {
13734                        self.generate_function_null_input(cf)?;
13735                    }
13736                    FunctionPropertyKind::Security => {
13737                        self.generate_function_security(cf)?;
13738                    }
13739                    FunctionPropertyKind::SqlDataAccess => {
13740                        if !cf.language_first {
13741                            // Only output here if not already output above
13742                            self.generate_function_sql_data_access(cf)?;
13743                        }
13744                    }
13745                    FunctionPropertyKind::Options => {
13746                        if !cf.options.is_empty() {
13747                            self.write_space();
13748                            self.generate_options_clause(&cf.options)?;
13749                        }
13750                    }
13751                    FunctionPropertyKind::Environment => {
13752                        if !cf.environment.is_empty() {
13753                            self.write_space();
13754                            self.generate_environment_clause(&cf.environment)?;
13755                        }
13756                    }
13757                    FunctionPropertyKind::Handler => {
13758                        if let Some(ref h) = cf.handler {
13759                            self.write_space();
13760                            self.write_keyword("HANDLER");
13761                            if cf.handler_uses_eq {
13762                                self.write(" = ");
13763                            } else {
13764                                self.write_space();
13765                            }
13766                            self.write("'");
13767                            self.write(h);
13768                            self.write("'");
13769                        }
13770                    }
13771                    FunctionPropertyKind::RuntimeVersion => {
13772                        if let Some(ref runtime_version) = cf.runtime_version {
13773                            self.write_space();
13774                            self.write_keyword("RUNTIME_VERSION");
13775                            self.write("='");
13776                            self.write(runtime_version);
13777                            self.write("'");
13778                        }
13779                    }
13780                    FunctionPropertyKind::Packages => {
13781                        if let Some(ref packages) = cf.packages {
13782                            self.write_space();
13783                            self.write_keyword("PACKAGES");
13784                            self.write("=(");
13785                            for (i, package) in packages.iter().enumerate() {
13786                                if i > 0 {
13787                                    self.write(", ");
13788                                }
13789                                self.write("'");
13790                                self.write(package);
13791                                self.write("'");
13792                            }
13793                            self.write(")");
13794                        }
13795                    }
13796                    FunctionPropertyKind::ParameterStyle => {
13797                        if let Some(ref ps) = cf.parameter_style {
13798                            self.write_space();
13799                            self.write_keyword("PARAMETER STYLE");
13800                            self.write_space();
13801                            self.write_keyword(ps);
13802                        }
13803                    }
13804                }
13805            }
13806
13807            // Output OPTIONS if not tracked in property_order (legacy)
13808            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13809            {
13810                self.write_space();
13811                self.generate_options_clause(&cf.options)?;
13812            }
13813
13814            // Output ENVIRONMENT if not tracked in property_order (legacy)
13815            if !cf.environment.is_empty()
13816                && !cf
13817                    .property_order
13818                    .contains(&FunctionPropertyKind::Environment)
13819            {
13820                self.write_space();
13821                self.generate_environment_clause(&cf.environment)?;
13822            }
13823        } else {
13824            // Legacy behavior when property_order is empty
13825            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13826            if matches!(
13827                self.config.dialect,
13828                Some(crate::dialects::DialectType::BigQuery)
13829            ) {
13830                self.generate_function_determinism(cf)?;
13831            }
13832
13833            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13834            let use_multiline = self.config.pretty
13835                && matches!(
13836                    self.config.dialect,
13837                    Some(crate::dialects::DialectType::BigQuery)
13838                );
13839
13840            if !cf.language_first {
13841                if let Some(lang) = &cf.language {
13842                    if use_multiline {
13843                        self.write_newline();
13844                    } else {
13845                        self.write_space();
13846                    }
13847                    self.write_keyword("LANGUAGE");
13848                    self.write_space();
13849                    self.write(lang);
13850                }
13851
13852                // SQL data access characteristic comes after LANGUAGE
13853                self.generate_function_sql_data_access(cf)?;
13854            }
13855
13856            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13857            if !matches!(
13858                self.config.dialect,
13859                Some(crate::dialects::DialectType::BigQuery)
13860            ) {
13861                self.generate_function_determinism(cf)?;
13862            }
13863
13864            self.generate_function_null_input(cf)?;
13865            self.generate_function_security(cf)?;
13866            self.generate_function_set_options(cf)?;
13867
13868            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13869            if !cf.options.is_empty() {
13870                self.write_space();
13871                self.generate_options_clause(&cf.options)?;
13872            }
13873
13874            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13875            if !cf.environment.is_empty() {
13876                self.write_space();
13877                self.generate_environment_clause(&cf.environment)?;
13878            }
13879
13880            if let Some(ref h) = cf.handler {
13881                self.write_space();
13882                self.write_keyword("HANDLER");
13883                if cf.handler_uses_eq {
13884                    self.write(" = ");
13885                } else {
13886                    self.write_space();
13887                }
13888                self.write("'");
13889                self.write(h);
13890                self.write("'");
13891            }
13892
13893            if let Some(ref runtime_version) = cf.runtime_version {
13894                self.write_space();
13895                self.write_keyword("RUNTIME_VERSION");
13896                self.write("='");
13897                self.write(runtime_version);
13898                self.write("'");
13899            }
13900
13901            if let Some(ref packages) = cf.packages {
13902                self.write_space();
13903                self.write_keyword("PACKAGES");
13904                self.write("=(");
13905                for (i, package) in packages.iter().enumerate() {
13906                    if i > 0 {
13907                        self.write(", ");
13908                    }
13909                    self.write("'");
13910                    self.write(package);
13911                    self.write("'");
13912                }
13913                self.write(")");
13914            }
13915
13916            self.generate_function_body(cf)?;
13917            self.generate_function_using_resources(cf)?;
13918        }
13919
13920        Ok(())
13921    }
13922
13923    fn generate_function_return_type(&mut self, return_type: &DataType) -> Result<()> {
13924        if matches!(
13925            self.config.dialect,
13926            Some(crate::dialects::DialectType::PostgreSQL)
13927        ) {
13928            if let DataType::Custom { name } = return_type {
13929                if name.eq_ignore_ascii_case("integer") {
13930                    self.write_keyword("INT");
13931                    return Ok(());
13932                }
13933            }
13934        }
13935
13936        self.generate_data_type(return_type)
13937    }
13938
13939    /// Generate SET options for CREATE FUNCTION
13940    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13941        for opt in &cf.set_options {
13942            self.write_space();
13943            self.write_keyword("SET");
13944            self.write_space();
13945            self.write(&opt.name);
13946            match &opt.value {
13947                FunctionSetValue::Value { value, use_to } => {
13948                    if *use_to {
13949                        self.write(" TO ");
13950                    } else {
13951                        self.write(" = ");
13952                    }
13953                    self.write(value);
13954                }
13955                FunctionSetValue::FromCurrent => {
13956                    self.write_space();
13957                    self.write_keyword("FROM CURRENT");
13958                }
13959            }
13960        }
13961        Ok(())
13962    }
13963
13964    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13965        if cf.using_resources.is_empty() {
13966            return Ok(());
13967        }
13968
13969        self.write_space();
13970        self.write_keyword("USING");
13971        for resource in &cf.using_resources {
13972            self.write_space();
13973            self.write_keyword(&resource.kind);
13974            self.write_space();
13975            self.generate_string_literal(&resource.uri)?;
13976        }
13977        Ok(())
13978    }
13979
13980    /// Generate function body (AS clause)
13981    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
13982        if let Some(body) = &cf.body {
13983            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
13984            self.write_space();
13985            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
13986            let use_multiline = self.config.pretty
13987                && matches!(
13988                    self.config.dialect,
13989                    Some(crate::dialects::DialectType::BigQuery)
13990                );
13991            match body {
13992                FunctionBody::Block(block) => {
13993                    self.write_keyword("AS");
13994                    if matches!(
13995                        self.config.dialect,
13996                        Some(crate::dialects::DialectType::TSQL)
13997                    ) {
13998                        self.write(" BEGIN ");
13999                        self.write(block);
14000                        self.write(" END");
14001                    } else if matches!(
14002                        self.config.dialect,
14003                        Some(crate::dialects::DialectType::PostgreSQL)
14004                    ) {
14005                        self.write(" $$");
14006                        self.write(block);
14007                        self.write("$$");
14008                    } else {
14009                        // Escape content for single-quoted output
14010                        let escaped = self.escape_block_for_single_quote(block);
14011                        // In BigQuery pretty mode, body content goes on new line
14012                        if use_multiline {
14013                            self.write_newline();
14014                        } else {
14015                            self.write(" ");
14016                        }
14017                        self.write("'");
14018                        self.write(&escaped);
14019                        self.write("'");
14020                    }
14021                }
14022                FunctionBody::StringLiteral(s) => {
14023                    self.write_keyword("AS");
14024                    // In BigQuery pretty mode, body content goes on new line
14025                    if use_multiline {
14026                        self.write_newline();
14027                    } else {
14028                        self.write(" ");
14029                    }
14030                    self.generate_string_literal(s)?;
14031                }
14032                FunctionBody::Expression(expr) => {
14033                    self.write_keyword("AS");
14034                    self.write_space();
14035                    self.generate_expression(expr)?;
14036                }
14037                FunctionBody::External(name) => {
14038                    self.write_keyword("EXTERNAL NAME");
14039                    self.write(" '");
14040                    self.write(name);
14041                    self.write("'");
14042                }
14043                FunctionBody::Return(expr) => {
14044                    if matches!(
14045                        self.config.dialect,
14046                        Some(crate::dialects::DialectType::DuckDB)
14047                    ) {
14048                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
14049                        self.write_keyword("AS");
14050                        self.write_space();
14051                        // Check both returns_table_body marker and return_type = Custom "TABLE"
14052                        let is_table_return = cf.returns_table_body.is_some()
14053                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
14054                        if is_table_return {
14055                            self.write_keyword("TABLE");
14056                            self.write_space();
14057                        }
14058                        self.generate_expression(expr)?;
14059                    } else {
14060                        if self.config.create_function_return_as {
14061                            self.write_keyword("AS");
14062                            // TSQL pretty: newline between AS and RETURN
14063                            if self.config.pretty
14064                                && matches!(
14065                                    self.config.dialect,
14066                                    Some(crate::dialects::DialectType::TSQL)
14067                                        | Some(crate::dialects::DialectType::Fabric)
14068                                )
14069                            {
14070                                self.write_newline();
14071                            } else {
14072                                self.write_space();
14073                            }
14074                        }
14075                        self.write_keyword("RETURN");
14076                        self.write_space();
14077                        self.generate_expression(expr)?;
14078                    }
14079                }
14080                FunctionBody::Statements(stmts) => {
14081                    self.write_keyword("AS");
14082                    self.write(" BEGIN ");
14083                    for (i, stmt) in stmts.iter().enumerate() {
14084                        if i > 0 {
14085                            self.write(" ");
14086                        }
14087                        self.generate_expression(stmt)?;
14088                        self.write(";");
14089                    }
14090                    self.write(" END");
14091                }
14092                FunctionBody::RawBlock(text) => {
14093                    self.write_newline();
14094                    self.write(text);
14095                }
14096                FunctionBody::DollarQuoted { content, tag } => {
14097                    self.write_keyword("AS");
14098                    self.write(" ");
14099                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14100                    let supports_dollar_quoting = matches!(
14101                        self.config.dialect,
14102                        Some(crate::dialects::DialectType::PostgreSQL)
14103                            | Some(crate::dialects::DialectType::Databricks)
14104                            | Some(crate::dialects::DialectType::Redshift)
14105                            | Some(crate::dialects::DialectType::DuckDB)
14106                    );
14107                    if supports_dollar_quoting {
14108                        // Output in dollar-quoted format
14109                        self.write("$");
14110                        if let Some(t) = tag {
14111                            self.write(t);
14112                        }
14113                        self.write("$");
14114                        self.write(content);
14115                        self.write("$");
14116                        if let Some(t) = tag {
14117                            self.write(t);
14118                        }
14119                        self.write("$");
14120                    } else {
14121                        // Convert to single-quoted string for other dialects
14122                        let escaped = self.escape_block_for_single_quote(content);
14123                        self.write("'");
14124                        self.write(&escaped);
14125                        self.write("'");
14126                    }
14127                }
14128            }
14129        }
14130        Ok(())
14131    }
14132
14133    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
14134    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
14135        if let Some(det) = cf.deterministic {
14136            self.write_space();
14137            if matches!(
14138                self.config.dialect,
14139                Some(crate::dialects::DialectType::BigQuery)
14140            ) {
14141                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
14142                if det {
14143                    self.write_keyword("DETERMINISTIC");
14144                } else {
14145                    self.write_keyword("NOT DETERMINISTIC");
14146                }
14147            } else {
14148                // PostgreSQL and others use IMMUTABLE/VOLATILE
14149                if det {
14150                    self.write_keyword("IMMUTABLE");
14151                } else {
14152                    self.write_keyword("VOLATILE");
14153                }
14154            }
14155        }
14156        Ok(())
14157    }
14158
14159    /// Generate null input handling clause
14160    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
14161        if let Some(returns_null) = cf.returns_null_on_null_input {
14162            self.write_space();
14163            if returns_null {
14164                if cf.strict {
14165                    self.write_keyword("STRICT");
14166                } else {
14167                    self.write_keyword("RETURNS NULL ON NULL INPUT");
14168                }
14169            } else {
14170                self.write_keyword("CALLED ON NULL INPUT");
14171            }
14172        }
14173        Ok(())
14174    }
14175
14176    /// Generate security clause
14177    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
14178        if let Some(security) = &cf.security {
14179            self.write_space();
14180            // MySQL uses SQL SECURITY prefix
14181            if matches!(
14182                self.config.dialect,
14183                Some(crate::dialects::DialectType::MySQL)
14184            ) {
14185                self.write_keyword("SQL SECURITY");
14186            } else {
14187                self.write_keyword("SECURITY");
14188            }
14189            self.write_space();
14190            match security {
14191                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14192                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14193                FunctionSecurity::None => self.write_keyword("NONE"),
14194            }
14195        }
14196        Ok(())
14197    }
14198
14199    /// Generate SQL data access clause
14200    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
14201        if let Some(sql_data) = &cf.sql_data_access {
14202            self.write_space();
14203            match sql_data {
14204                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
14205                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
14206                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
14207                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
14208            }
14209        }
14210        Ok(())
14211    }
14212
14213    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
14214        for (i, param) in params.iter().enumerate() {
14215            if i > 0 {
14216                self.write(", ");
14217            }
14218
14219            if let Some(mode) = &param.mode {
14220                if let Some(text) = &param.mode_text {
14221                    self.write(text);
14222                } else {
14223                    match mode {
14224                        ParameterMode::In => self.write_keyword("IN"),
14225                        ParameterMode::Out => self.write_keyword("OUT"),
14226                        ParameterMode::InOut => self.write_keyword("INOUT"),
14227                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
14228                    }
14229                }
14230                self.write_space();
14231            }
14232
14233            if let Some(name) = &param.name {
14234                self.generate_identifier(name)?;
14235                // Skip space and type for empty Custom types (e.g., DuckDB macros)
14236                let skip_type =
14237                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
14238                if !skip_type {
14239                    self.write_space();
14240                    self.generate_data_type(&param.data_type)?;
14241                }
14242            } else {
14243                self.generate_data_type(&param.data_type)?;
14244            }
14245
14246            if let Some(default) = &param.default {
14247                if self.config.parameter_default_equals {
14248                    self.write(" = ");
14249                } else {
14250                    self.write(" DEFAULT ");
14251                }
14252                self.generate_expression(default)?;
14253            }
14254        }
14255
14256        Ok(())
14257    }
14258
14259    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
14260        self.write_keyword("DROP FUNCTION");
14261
14262        if df.if_exists {
14263            self.write_space();
14264            self.write_keyword("IF EXISTS");
14265        }
14266
14267        self.write_space();
14268        self.generate_table(&df.name)?;
14269
14270        if let Some(params) = &df.parameters {
14271            self.write(" (");
14272            for (i, dt) in params.iter().enumerate() {
14273                if i > 0 {
14274                    self.write(", ");
14275                }
14276                self.generate_data_type(dt)?;
14277            }
14278            self.write(")");
14279        }
14280
14281        if df.cascade {
14282            self.write_space();
14283            self.write_keyword("CASCADE");
14284        }
14285
14286        Ok(())
14287    }
14288
14289    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
14290        self.write_keyword("CREATE");
14291
14292        if cp.or_alter {
14293            self.write_space();
14294            self.write_keyword("OR ALTER");
14295        } else if cp.or_replace {
14296            self.write_space();
14297            self.write_keyword("OR REPLACE");
14298        }
14299
14300        self.write_space();
14301        if cp.use_proc_keyword {
14302            self.write_keyword("PROC");
14303        } else {
14304            self.write_keyword("PROCEDURE");
14305        }
14306
14307        if cp.if_not_exists {
14308            self.write_space();
14309            self.write_keyword("IF NOT EXISTS");
14310        }
14311
14312        self.write_space();
14313        self.generate_table(&cp.name)?;
14314        if cp.has_parens {
14315            self.write("(");
14316            self.generate_function_parameters(&cp.parameters)?;
14317            self.write(")");
14318        } else if !cp.parameters.is_empty() {
14319            // TSQL: unparenthesized parameters
14320            self.write_space();
14321            self.generate_function_parameters(&cp.parameters)?;
14322        }
14323
14324        // RETURNS clause (Snowflake)
14325        if let Some(return_type) = &cp.return_type {
14326            self.write_space();
14327            self.write_keyword("RETURNS");
14328            self.write_space();
14329            self.generate_data_type(return_type)?;
14330        }
14331
14332        // EXECUTE AS clause (Snowflake)
14333        if let Some(execute_as) = &cp.execute_as {
14334            self.write_space();
14335            self.write_keyword("EXECUTE AS");
14336            self.write_space();
14337            self.write_keyword(execute_as);
14338        }
14339
14340        if let Some(lang) = &cp.language {
14341            self.write_space();
14342            self.write_keyword("LANGUAGE");
14343            self.write_space();
14344            self.write(lang);
14345        }
14346
14347        if let Some(security) = &cp.security {
14348            self.write_space();
14349            self.write_keyword("SECURITY");
14350            self.write_space();
14351            match security {
14352                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14353                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14354                FunctionSecurity::None => self.write_keyword("NONE"),
14355            }
14356        }
14357
14358        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
14359        if !cp.with_options.is_empty() {
14360            self.write_space();
14361            self.write_keyword("WITH");
14362            self.write_space();
14363            for (i, opt) in cp.with_options.iter().enumerate() {
14364                if i > 0 {
14365                    self.write(", ");
14366                }
14367                self.write(opt);
14368            }
14369        }
14370
14371        if let Some(body) = &cp.body {
14372            self.write_space();
14373            match body {
14374                FunctionBody::Block(block) => {
14375                    self.write_keyword("AS");
14376                    if matches!(
14377                        self.config.dialect,
14378                        Some(crate::dialects::DialectType::TSQL)
14379                    ) {
14380                        self.write(" BEGIN ");
14381                        self.write(block);
14382                        self.write(" END");
14383                    } else if matches!(
14384                        self.config.dialect,
14385                        Some(crate::dialects::DialectType::PostgreSQL)
14386                    ) {
14387                        self.write(" $$");
14388                        self.write(block);
14389                        self.write("$$");
14390                    } else {
14391                        // Escape content for single-quoted output
14392                        let escaped = self.escape_block_for_single_quote(block);
14393                        self.write(" '");
14394                        self.write(&escaped);
14395                        self.write("'");
14396                    }
14397                }
14398                FunctionBody::StringLiteral(s) => {
14399                    self.write_keyword("AS");
14400                    self.write_space();
14401                    self.generate_string_literal(s)?;
14402                }
14403                FunctionBody::Expression(expr) => {
14404                    self.write_keyword("AS");
14405                    self.write_space();
14406                    self.generate_expression(expr)?;
14407                }
14408                FunctionBody::External(name) => {
14409                    self.write_keyword("EXTERNAL NAME");
14410                    self.write(" '");
14411                    self.write(name);
14412                    self.write("'");
14413                }
14414                FunctionBody::Return(expr) => {
14415                    self.write_keyword("RETURN");
14416                    self.write_space();
14417                    self.generate_expression(expr)?;
14418                }
14419                FunctionBody::Statements(stmts) => {
14420                    self.write_keyword("AS");
14421                    self.write(" BEGIN ");
14422                    for (i, stmt) in stmts.iter().enumerate() {
14423                        if i > 0 {
14424                            self.write(" ");
14425                        }
14426                        self.generate_expression(stmt)?;
14427                        self.write(";");
14428                    }
14429                    self.write(" END");
14430                }
14431                FunctionBody::RawBlock(text) => {
14432                    self.write_newline();
14433                    self.write(text);
14434                }
14435                FunctionBody::DollarQuoted { content, tag } => {
14436                    self.write_keyword("AS");
14437                    self.write(" ");
14438                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14439                    let supports_dollar_quoting = matches!(
14440                        self.config.dialect,
14441                        Some(crate::dialects::DialectType::PostgreSQL)
14442                            | Some(crate::dialects::DialectType::Databricks)
14443                            | Some(crate::dialects::DialectType::Redshift)
14444                            | Some(crate::dialects::DialectType::DuckDB)
14445                    );
14446                    if supports_dollar_quoting {
14447                        // Output in dollar-quoted format
14448                        self.write("$");
14449                        if let Some(t) = tag {
14450                            self.write(t);
14451                        }
14452                        self.write("$");
14453                        self.write(content);
14454                        self.write("$");
14455                        if let Some(t) = tag {
14456                            self.write(t);
14457                        }
14458                        self.write("$");
14459                    } else {
14460                        // Convert to single-quoted string for other dialects
14461                        let escaped = self.escape_block_for_single_quote(content);
14462                        self.write("'");
14463                        self.write(&escaped);
14464                        self.write("'");
14465                    }
14466                }
14467            }
14468        }
14469
14470        Ok(())
14471    }
14472
14473    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
14474        self.write_keyword("DROP PROCEDURE");
14475
14476        if dp.if_exists {
14477            self.write_space();
14478            self.write_keyword("IF EXISTS");
14479        }
14480
14481        self.write_space();
14482        self.generate_table(&dp.name)?;
14483
14484        if let Some(params) = &dp.parameters {
14485            self.write(" (");
14486            for (i, dt) in params.iter().enumerate() {
14487                if i > 0 {
14488                    self.write(", ");
14489                }
14490                self.generate_data_type(dt)?;
14491            }
14492            self.write(")");
14493        }
14494
14495        if dp.cascade {
14496            self.write_space();
14497            self.write_keyword("CASCADE");
14498        }
14499
14500        Ok(())
14501    }
14502
14503    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
14504        self.write_keyword("CREATE");
14505
14506        if cs.or_replace {
14507            self.write_space();
14508            self.write_keyword("OR REPLACE");
14509        }
14510
14511        if cs.temporary {
14512            self.write_space();
14513            self.write_keyword("TEMPORARY");
14514        }
14515
14516        self.write_space();
14517        self.write_keyword("SEQUENCE");
14518
14519        if cs.if_not_exists {
14520            self.write_space();
14521            self.write_keyword("IF NOT EXISTS");
14522        }
14523
14524        self.write_space();
14525        self.generate_table(&cs.name)?;
14526
14527        // Output AS <type> if present
14528        if let Some(as_type) = &cs.as_type {
14529            self.write_space();
14530            self.write_keyword("AS");
14531            self.write_space();
14532            self.generate_data_type(as_type)?;
14533        }
14534
14535        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
14536        if let Some(comment) = &cs.comment {
14537            self.write_space();
14538            self.write_keyword("COMMENT");
14539            self.write("=");
14540            self.generate_string_literal(comment)?;
14541        }
14542
14543        // If property_order is available, use it to preserve original order
14544        if !cs.property_order.is_empty() {
14545            for prop in &cs.property_order {
14546                match prop {
14547                    SeqPropKind::Start => {
14548                        if let Some(start) = cs.start {
14549                            self.write_space();
14550                            self.write_keyword("START WITH");
14551                            self.write(&format!(" {}", start));
14552                        }
14553                    }
14554                    SeqPropKind::Increment => {
14555                        if let Some(inc) = cs.increment {
14556                            self.write_space();
14557                            self.write_keyword("INCREMENT BY");
14558                            self.write(&format!(" {}", inc));
14559                        }
14560                    }
14561                    SeqPropKind::Minvalue => {
14562                        if let Some(min) = &cs.minvalue {
14563                            self.write_space();
14564                            match min {
14565                                SequenceBound::Value(v) => {
14566                                    self.write_keyword("MINVALUE");
14567                                    self.write(&format!(" {}", v));
14568                                }
14569                                SequenceBound::None => {
14570                                    self.write_keyword("NO MINVALUE");
14571                                }
14572                            }
14573                        }
14574                    }
14575                    SeqPropKind::Maxvalue => {
14576                        if let Some(max) = &cs.maxvalue {
14577                            self.write_space();
14578                            match max {
14579                                SequenceBound::Value(v) => {
14580                                    self.write_keyword("MAXVALUE");
14581                                    self.write(&format!(" {}", v));
14582                                }
14583                                SequenceBound::None => {
14584                                    self.write_keyword("NO MAXVALUE");
14585                                }
14586                            }
14587                        }
14588                    }
14589                    SeqPropKind::Cache => {
14590                        if let Some(cache) = cs.cache {
14591                            self.write_space();
14592                            self.write_keyword("CACHE");
14593                            self.write(&format!(" {}", cache));
14594                        }
14595                    }
14596                    SeqPropKind::NoCache => {
14597                        self.write_space();
14598                        self.write_keyword("NO CACHE");
14599                    }
14600                    SeqPropKind::NoCacheWord => {
14601                        self.write_space();
14602                        self.write_keyword("NOCACHE");
14603                    }
14604                    SeqPropKind::Cycle => {
14605                        self.write_space();
14606                        self.write_keyword("CYCLE");
14607                    }
14608                    SeqPropKind::NoCycle => {
14609                        self.write_space();
14610                        self.write_keyword("NO CYCLE");
14611                    }
14612                    SeqPropKind::NoCycleWord => {
14613                        self.write_space();
14614                        self.write_keyword("NOCYCLE");
14615                    }
14616                    SeqPropKind::OwnedBy => {
14617                        // Skip OWNED BY NONE (it's a no-op)
14618                        if !cs.owned_by_none {
14619                            if let Some(owned) = &cs.owned_by {
14620                                self.write_space();
14621                                self.write_keyword("OWNED BY");
14622                                self.write_space();
14623                                self.generate_table(owned)?;
14624                            }
14625                        }
14626                    }
14627                    SeqPropKind::Order => {
14628                        self.write_space();
14629                        self.write_keyword("ORDER");
14630                    }
14631                    SeqPropKind::NoOrder => {
14632                        self.write_space();
14633                        self.write_keyword("NOORDER");
14634                    }
14635                    SeqPropKind::Comment => {
14636                        // COMMENT is output above, before property_order iteration
14637                    }
14638                    SeqPropKind::Sharing => {
14639                        if let Some(val) = &cs.sharing {
14640                            self.write_space();
14641                            self.write(&format!("SHARING={}", val));
14642                        }
14643                    }
14644                    SeqPropKind::Keep => {
14645                        self.write_space();
14646                        self.write_keyword("KEEP");
14647                    }
14648                    SeqPropKind::NoKeep => {
14649                        self.write_space();
14650                        self.write_keyword("NOKEEP");
14651                    }
14652                    SeqPropKind::Scale => {
14653                        self.write_space();
14654                        self.write_keyword("SCALE");
14655                        if let Some(modifier) = &cs.scale_modifier {
14656                            if !modifier.is_empty() {
14657                                self.write_space();
14658                                self.write_keyword(modifier);
14659                            }
14660                        }
14661                    }
14662                    SeqPropKind::NoScale => {
14663                        self.write_space();
14664                        self.write_keyword("NOSCALE");
14665                    }
14666                    SeqPropKind::Shard => {
14667                        self.write_space();
14668                        self.write_keyword("SHARD");
14669                        if let Some(modifier) = &cs.shard_modifier {
14670                            if !modifier.is_empty() {
14671                                self.write_space();
14672                                self.write_keyword(modifier);
14673                            }
14674                        }
14675                    }
14676                    SeqPropKind::NoShard => {
14677                        self.write_space();
14678                        self.write_keyword("NOSHARD");
14679                    }
14680                    SeqPropKind::Session => {
14681                        self.write_space();
14682                        self.write_keyword("SESSION");
14683                    }
14684                    SeqPropKind::Global => {
14685                        self.write_space();
14686                        self.write_keyword("GLOBAL");
14687                    }
14688                    SeqPropKind::NoMinvalueWord => {
14689                        self.write_space();
14690                        self.write_keyword("NOMINVALUE");
14691                    }
14692                    SeqPropKind::NoMaxvalueWord => {
14693                        self.write_space();
14694                        self.write_keyword("NOMAXVALUE");
14695                    }
14696                }
14697            }
14698        } else {
14699            // Fallback: default order for backwards compatibility
14700            if let Some(inc) = cs.increment {
14701                self.write_space();
14702                self.write_keyword("INCREMENT BY");
14703                self.write(&format!(" {}", inc));
14704            }
14705
14706            if let Some(min) = &cs.minvalue {
14707                self.write_space();
14708                match min {
14709                    SequenceBound::Value(v) => {
14710                        self.write_keyword("MINVALUE");
14711                        self.write(&format!(" {}", v));
14712                    }
14713                    SequenceBound::None => {
14714                        self.write_keyword("NO MINVALUE");
14715                    }
14716                }
14717            }
14718
14719            if let Some(max) = &cs.maxvalue {
14720                self.write_space();
14721                match max {
14722                    SequenceBound::Value(v) => {
14723                        self.write_keyword("MAXVALUE");
14724                        self.write(&format!(" {}", v));
14725                    }
14726                    SequenceBound::None => {
14727                        self.write_keyword("NO MAXVALUE");
14728                    }
14729                }
14730            }
14731
14732            if let Some(start) = cs.start {
14733                self.write_space();
14734                self.write_keyword("START WITH");
14735                self.write(&format!(" {}", start));
14736            }
14737
14738            if let Some(cache) = cs.cache {
14739                self.write_space();
14740                self.write_keyword("CACHE");
14741                self.write(&format!(" {}", cache));
14742            }
14743
14744            if cs.cycle {
14745                self.write_space();
14746                self.write_keyword("CYCLE");
14747            }
14748
14749            if let Some(owned) = &cs.owned_by {
14750                self.write_space();
14751                self.write_keyword("OWNED BY");
14752                self.write_space();
14753                self.generate_table(owned)?;
14754            }
14755        }
14756
14757        Ok(())
14758    }
14759
14760    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
14761        self.write_keyword("DROP SEQUENCE");
14762
14763        if ds.if_exists {
14764            self.write_space();
14765            self.write_keyword("IF EXISTS");
14766        }
14767
14768        self.write_space();
14769        self.generate_table(&ds.name)?;
14770
14771        if ds.cascade {
14772            self.write_space();
14773            self.write_keyword("CASCADE");
14774        }
14775
14776        Ok(())
14777    }
14778
14779    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
14780        self.write_keyword("ALTER SEQUENCE");
14781
14782        if als.if_exists {
14783            self.write_space();
14784            self.write_keyword("IF EXISTS");
14785        }
14786
14787        self.write_space();
14788        self.generate_table(&als.name)?;
14789
14790        if let Some(inc) = als.increment {
14791            self.write_space();
14792            self.write_keyword("INCREMENT BY");
14793            self.write(&format!(" {}", inc));
14794        }
14795
14796        if let Some(min) = &als.minvalue {
14797            self.write_space();
14798            match min {
14799                SequenceBound::Value(v) => {
14800                    self.write_keyword("MINVALUE");
14801                    self.write(&format!(" {}", v));
14802                }
14803                SequenceBound::None => {
14804                    self.write_keyword("NO MINVALUE");
14805                }
14806            }
14807        }
14808
14809        if let Some(max) = &als.maxvalue {
14810            self.write_space();
14811            match max {
14812                SequenceBound::Value(v) => {
14813                    self.write_keyword("MAXVALUE");
14814                    self.write(&format!(" {}", v));
14815                }
14816                SequenceBound::None => {
14817                    self.write_keyword("NO MAXVALUE");
14818                }
14819            }
14820        }
14821
14822        if let Some(start) = als.start {
14823            self.write_space();
14824            self.write_keyword("START WITH");
14825            self.write(&format!(" {}", start));
14826        }
14827
14828        if let Some(restart) = &als.restart {
14829            self.write_space();
14830            self.write_keyword("RESTART");
14831            if let Some(val) = restart {
14832                self.write_keyword(" WITH");
14833                self.write(&format!(" {}", val));
14834            }
14835        }
14836
14837        if let Some(cache) = als.cache {
14838            self.write_space();
14839            self.write_keyword("CACHE");
14840            self.write(&format!(" {}", cache));
14841        }
14842
14843        if let Some(cycle) = als.cycle {
14844            self.write_space();
14845            if cycle {
14846                self.write_keyword("CYCLE");
14847            } else {
14848                self.write_keyword("NO CYCLE");
14849            }
14850        }
14851
14852        if let Some(owned) = &als.owned_by {
14853            self.write_space();
14854            self.write_keyword("OWNED BY");
14855            self.write_space();
14856            if let Some(table) = owned {
14857                self.generate_table(table)?;
14858            } else {
14859                self.write_keyword("NONE");
14860            }
14861        }
14862
14863        Ok(())
14864    }
14865
14866    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14867        self.write_keyword("CREATE");
14868
14869        if ct.or_alter {
14870            self.write_space();
14871            self.write_keyword("OR ALTER");
14872        } else if ct.or_replace {
14873            self.write_space();
14874            self.write_keyword("OR REPLACE");
14875        }
14876
14877        if ct.constraint {
14878            self.write_space();
14879            self.write_keyword("CONSTRAINT");
14880        }
14881
14882        self.write_space();
14883        self.write_keyword("TRIGGER");
14884        self.write_space();
14885        self.generate_identifier(&ct.name)?;
14886
14887        self.write_space();
14888        match ct.timing {
14889            TriggerTiming::Before => self.write_keyword("BEFORE"),
14890            TriggerTiming::After => self.write_keyword("AFTER"),
14891            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14892        }
14893
14894        // Events
14895        for (i, event) in ct.events.iter().enumerate() {
14896            if i > 0 {
14897                self.write_keyword(" OR");
14898            }
14899            self.write_space();
14900            match event {
14901                TriggerEvent::Insert => self.write_keyword("INSERT"),
14902                TriggerEvent::Update(cols) => {
14903                    self.write_keyword("UPDATE");
14904                    if let Some(cols) = cols {
14905                        self.write_space();
14906                        self.write_keyword("OF");
14907                        for (j, col) in cols.iter().enumerate() {
14908                            if j > 0 {
14909                                self.write(",");
14910                            }
14911                            self.write_space();
14912                            self.generate_identifier(col)?;
14913                        }
14914                    }
14915                }
14916                TriggerEvent::Delete => self.write_keyword("DELETE"),
14917                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14918            }
14919        }
14920
14921        self.write_space();
14922        self.write_keyword("ON");
14923        self.write_space();
14924        self.generate_table(&ct.table)?;
14925
14926        // Referencing clause
14927        if let Some(ref_clause) = &ct.referencing {
14928            self.write_space();
14929            self.write_keyword("REFERENCING");
14930            if let Some(old_table) = &ref_clause.old_table {
14931                self.write_space();
14932                self.write_keyword("OLD TABLE AS");
14933                self.write_space();
14934                self.generate_identifier(old_table)?;
14935            }
14936            if let Some(new_table) = &ref_clause.new_table {
14937                self.write_space();
14938                self.write_keyword("NEW TABLE AS");
14939                self.write_space();
14940                self.generate_identifier(new_table)?;
14941            }
14942            if let Some(old_row) = &ref_clause.old_row {
14943                self.write_space();
14944                self.write_keyword("OLD ROW AS");
14945                self.write_space();
14946                self.generate_identifier(old_row)?;
14947            }
14948            if let Some(new_row) = &ref_clause.new_row {
14949                self.write_space();
14950                self.write_keyword("NEW ROW AS");
14951                self.write_space();
14952                self.generate_identifier(new_row)?;
14953            }
14954        }
14955
14956        // Deferrable options for constraint triggers (must come before FOR EACH)
14957        if let Some(deferrable) = ct.deferrable {
14958            self.write_space();
14959            if deferrable {
14960                self.write_keyword("DEFERRABLE");
14961            } else {
14962                self.write_keyword("NOT DEFERRABLE");
14963            }
14964        }
14965
14966        if let Some(initially) = ct.initially_deferred {
14967            self.write_space();
14968            self.write_keyword("INITIALLY");
14969            self.write_space();
14970            if initially {
14971                self.write_keyword("DEFERRED");
14972            } else {
14973                self.write_keyword("IMMEDIATE");
14974            }
14975        }
14976
14977        if let Some(for_each) = ct.for_each {
14978            self.write_space();
14979            self.write_keyword("FOR EACH");
14980            self.write_space();
14981            match for_each {
14982                TriggerForEach::Row => self.write_keyword("ROW"),
14983                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
14984            }
14985        }
14986
14987        // When clause
14988        if let Some(when) = &ct.when {
14989            self.write_space();
14990            self.write_keyword("WHEN");
14991            if ct.when_paren {
14992                self.write(" (");
14993                self.generate_expression(when)?;
14994                self.write(")");
14995            } else {
14996                self.write_space();
14997                self.generate_expression(when)?;
14998            }
14999        }
15000
15001        // Body
15002        self.write_space();
15003        match &ct.body {
15004            TriggerBody::Execute { function, args } => {
15005                self.write_keyword("EXECUTE FUNCTION");
15006                self.write_space();
15007                self.generate_table(function)?;
15008                self.write("(");
15009                for (i, arg) in args.iter().enumerate() {
15010                    if i > 0 {
15011                        self.write(", ");
15012                    }
15013                    self.generate_expression(arg)?;
15014                }
15015                self.write(")");
15016            }
15017            TriggerBody::Block(block) => {
15018                self.write_keyword("BEGIN");
15019                self.write_space();
15020                self.write(block);
15021                self.write_space();
15022                self.write_keyword("END");
15023            }
15024        }
15025
15026        Ok(())
15027    }
15028
15029    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
15030        self.write_keyword("DROP TRIGGER");
15031
15032        if dt.if_exists {
15033            self.write_space();
15034            self.write_keyword("IF EXISTS");
15035        }
15036
15037        self.write_space();
15038        self.generate_identifier(&dt.name)?;
15039
15040        if let Some(table) = &dt.table {
15041            self.write_space();
15042            self.write_keyword("ON");
15043            self.write_space();
15044            self.generate_table(table)?;
15045        }
15046
15047        if dt.cascade {
15048            self.write_space();
15049            self.write_keyword("CASCADE");
15050        }
15051
15052        Ok(())
15053    }
15054
15055    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
15056        self.write_keyword("CREATE TYPE");
15057
15058        if ct.if_not_exists {
15059            self.write_space();
15060            self.write_keyword("IF NOT EXISTS");
15061        }
15062
15063        self.write_space();
15064        self.generate_table(&ct.name)?;
15065
15066        if let TypeDefinition::Base {
15067            input,
15068            output,
15069            internallength,
15070        } = &ct.definition
15071        {
15072            if input.is_empty() && output.is_empty() && internallength.is_none() {
15073                return Ok(());
15074            }
15075        }
15076
15077        self.write_space();
15078        self.write_keyword("AS");
15079        self.write_space();
15080
15081        match &ct.definition {
15082            TypeDefinition::Enum(values) => {
15083                self.write_keyword("ENUM");
15084                self.write(" (");
15085                for (i, val) in values.iter().enumerate() {
15086                    if i > 0 {
15087                        self.write(", ");
15088                    }
15089                    self.write(&format!("'{}'", val));
15090                }
15091                self.write(")");
15092            }
15093            TypeDefinition::Composite(attrs) => {
15094                self.write("(");
15095                for (i, attr) in attrs.iter().enumerate() {
15096                    if i > 0 {
15097                        self.write(", ");
15098                    }
15099                    self.generate_identifier(&attr.name)?;
15100                    self.write_space();
15101                    self.generate_data_type(&attr.data_type)?;
15102                    if let Some(collate) = &attr.collate {
15103                        self.write_space();
15104                        self.write_keyword("COLLATE");
15105                        self.write_space();
15106                        self.generate_identifier(collate)?;
15107                    }
15108                }
15109                self.write(")");
15110            }
15111            TypeDefinition::Range {
15112                subtype,
15113                subtype_diff,
15114                canonical,
15115            } => {
15116                self.write_keyword("RANGE");
15117                self.write(" (");
15118                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15119                    self.write("subtype");
15120                } else {
15121                    self.write_keyword("SUBTYPE");
15122                }
15123                self.write(" = ");
15124                self.generate_data_type(subtype)?;
15125                if let Some(diff) = subtype_diff {
15126                    self.write(", ");
15127                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15128                        self.write("subtype_diff");
15129                    } else {
15130                        self.write_keyword("SUBTYPE_DIFF");
15131                    }
15132                    self.write(" = ");
15133                    self.write(diff);
15134                }
15135                if let Some(canon) = canonical {
15136                    self.write(", ");
15137                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15138                        self.write("canonical");
15139                    } else {
15140                        self.write_keyword("CANONICAL");
15141                    }
15142                    self.write(" = ");
15143                    self.write(canon);
15144                }
15145                self.write(")");
15146            }
15147            TypeDefinition::Base {
15148                input,
15149                output,
15150                internallength,
15151            } => {
15152                self.write("(");
15153                self.write_keyword("INPUT");
15154                self.write(" = ");
15155                self.write(input);
15156                self.write(", ");
15157                self.write_keyword("OUTPUT");
15158                self.write(" = ");
15159                self.write(output);
15160                if let Some(len) = internallength {
15161                    self.write(", ");
15162                    self.write_keyword("INTERNALLENGTH");
15163                    self.write(" = ");
15164                    self.write(&len.to_string());
15165                }
15166                self.write(")");
15167            }
15168            TypeDefinition::Domain {
15169                base_type,
15170                default,
15171                constraints,
15172            } => {
15173                self.generate_data_type(base_type)?;
15174                if let Some(def) = default {
15175                    self.write_space();
15176                    self.write_keyword("DEFAULT");
15177                    self.write_space();
15178                    self.generate_expression(def)?;
15179                }
15180                for constr in constraints {
15181                    self.write_space();
15182                    if let Some(name) = &constr.name {
15183                        self.write_keyword("CONSTRAINT");
15184                        self.write_space();
15185                        self.generate_identifier(name)?;
15186                        self.write_space();
15187                    }
15188                    self.write_keyword("CHECK");
15189                    self.write(" (");
15190                    self.generate_expression(&constr.check)?;
15191                    self.write(")");
15192                }
15193            }
15194        }
15195
15196        Ok(())
15197    }
15198
15199    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
15200        self.write_keyword("CREATE");
15201        if task.or_replace {
15202            self.write_space();
15203            self.write_keyword("OR REPLACE");
15204        }
15205        self.write_space();
15206        self.write_keyword("TASK");
15207        if task.if_not_exists {
15208            self.write_space();
15209            self.write_keyword("IF NOT EXISTS");
15210        }
15211        self.write_space();
15212        self.write(&task.name);
15213        if !task.properties.is_empty() {
15214            // Properties already include leading whitespace from tokens_to_sql
15215            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
15216                self.write_space();
15217            }
15218            self.write(&task.properties);
15219        }
15220        self.write_space();
15221        self.write_keyword("AS");
15222        self.write_space();
15223        self.generate_expression(&task.body)?;
15224        Ok(())
15225    }
15226
15227    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
15228        self.write_keyword("BEGIN TRY");
15229        self.generate_tsql_block_statements(&try_catch.try_body)?;
15230        self.write_keyword("END TRY");
15231
15232        if let Some(catch_body) = &try_catch.catch_body {
15233            if self.config.pretty {
15234                self.write_newline();
15235                self.write_indent();
15236            } else {
15237                self.write_space();
15238            }
15239            self.write_keyword("BEGIN CATCH");
15240            self.generate_tsql_block_statements(catch_body)?;
15241            self.write_keyword("END CATCH");
15242        }
15243
15244        Ok(())
15245    }
15246
15247    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
15248        if statements.is_empty() {
15249            self.write_space();
15250            return Ok(());
15251        }
15252
15253        if self.config.pretty {
15254            self.indent_level += 1;
15255            for stmt in statements {
15256                self.write_newline();
15257                self.write_indent();
15258                self.generate_expression(stmt)?;
15259                self.write(";");
15260            }
15261            self.indent_level -= 1;
15262            self.write_newline();
15263            self.write_indent();
15264        } else {
15265            self.write_space();
15266            for (i, stmt) in statements.iter().enumerate() {
15267                if i > 0 {
15268                    self.write_space();
15269                }
15270                self.generate_expression(stmt)?;
15271                self.write(";");
15272            }
15273            self.write_space();
15274        }
15275
15276        Ok(())
15277    }
15278
15279    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
15280        self.write_keyword("DROP TYPE");
15281
15282        if dt.if_exists {
15283            self.write_space();
15284            self.write_keyword("IF EXISTS");
15285        }
15286
15287        self.write_space();
15288        self.generate_table(&dt.name)?;
15289
15290        if dt.cascade {
15291            self.write_space();
15292            self.write_keyword("CASCADE");
15293        }
15294
15295        Ok(())
15296    }
15297
15298    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
15299        // Athena: DESCRIBE uses Hive engine (backticks)
15300        let saved_athena_hive_context = self.athena_hive_context;
15301        if matches!(
15302            self.config.dialect,
15303            Some(crate::dialects::DialectType::Athena)
15304        ) {
15305            self.athena_hive_context = true;
15306        }
15307
15308        // Output leading comments before DESCRIBE
15309        for comment in &d.leading_comments {
15310            self.write_formatted_comment(comment);
15311            self.write(" ");
15312        }
15313
15314        self.write_keyword("DESCRIBE");
15315
15316        if d.extended {
15317            self.write_space();
15318            self.write_keyword("EXTENDED");
15319        } else if d.formatted {
15320            self.write_space();
15321            self.write_keyword("FORMATTED");
15322        }
15323
15324        // Output style like ANALYZE, HISTORY
15325        if let Some(ref style) = d.style {
15326            self.write_space();
15327            self.write_keyword(style);
15328        }
15329
15330        // Handle object kind (TABLE, VIEW) based on dialect
15331        let should_output_kind = match self.config.dialect {
15332            // Spark doesn't use TABLE/VIEW after DESCRIBE
15333            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
15334                false
15335            }
15336            // Snowflake always includes TABLE
15337            Some(DialectType::Snowflake) => true,
15338            _ => d.kind.is_some(),
15339        };
15340        if should_output_kind {
15341            if let Some(ref kind) = d.kind {
15342                self.write_space();
15343                self.write_keyword(kind);
15344            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15345                self.write_space();
15346                self.write_keyword("TABLE");
15347            }
15348        }
15349
15350        self.write_space();
15351        self.generate_expression(&d.target)?;
15352
15353        // Output parenthesized parameter types for PROCEDURE/FUNCTION
15354        if !d.params.is_empty() {
15355            self.write("(");
15356            for (i, param) in d.params.iter().enumerate() {
15357                if i > 0 {
15358                    self.write(", ");
15359                }
15360                self.write(param);
15361            }
15362            self.write(")");
15363        }
15364
15365        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
15366        if let Some(ref partition) = d.partition {
15367            self.write_space();
15368            self.generate_expression(partition)?;
15369        }
15370
15371        // Databricks: AS JSON
15372        if d.as_json {
15373            self.write_space();
15374            self.write_keyword("AS JSON");
15375        }
15376
15377        // Output properties like type=stage
15378        for (name, value) in &d.properties {
15379            self.write_space();
15380            self.write(name);
15381            self.write("=");
15382            self.write(value);
15383        }
15384
15385        // Restore Athena Hive context
15386        self.athena_hive_context = saved_athena_hive_context;
15387
15388        Ok(())
15389    }
15390
15391    /// Generate SHOW statement (Snowflake, MySQL, etc.)
15392    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
15393    fn generate_show(&mut self, s: &Show) -> Result<()> {
15394        self.write_keyword("SHOW");
15395        self.write_space();
15396
15397        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
15398        // where TERSE is syntactically valid but has no effect on output
15399        let show_terse = s.terse
15400            && !matches!(
15401                s.this.as_str(),
15402                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
15403            );
15404        if show_terse {
15405            self.write_keyword("TERSE");
15406            self.write_space();
15407        }
15408
15409        // Object type (USERS, TABLES, DATABASES, etc.)
15410        self.write_keyword(&s.this);
15411
15412        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
15413        if let Some(ref target_expr) = s.target {
15414            self.write_space();
15415            self.generate_expression(target_expr)?;
15416        }
15417
15418        // HISTORY keyword
15419        if s.history {
15420            self.write_space();
15421            self.write_keyword("HISTORY");
15422        }
15423
15424        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
15425        if let Some(ref for_target) = s.for_target {
15426            self.write_space();
15427            self.write_keyword("FOR");
15428            self.write_space();
15429            self.generate_expression(for_target)?;
15430        }
15431
15432        // Determine ordering based on dialect:
15433        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
15434        // - MySQL: IN, FROM, LIKE (when FROM is present)
15435        use crate::dialects::DialectType;
15436        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
15437        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
15438        let mysql_tables_scope_as_from = is_mysql
15439            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
15440            && s.scope_kind.as_deref() == Some("SCHEMA")
15441            && s.scope.is_some()
15442            && s.from.is_none();
15443
15444        if !is_snowflake && s.from.is_some() {
15445            // MySQL ordering: IN, FROM, LIKE
15446
15447            // IN scope_kind [scope]
15448            if let Some(ref scope_kind) = s.scope_kind {
15449                self.write_space();
15450                self.write_keyword("IN");
15451                self.write_space();
15452                self.write_keyword(scope_kind);
15453                if let Some(ref scope) = s.scope {
15454                    self.write_space();
15455                    self.generate_expression(scope)?;
15456                }
15457            } else if let Some(ref scope) = s.scope {
15458                self.write_space();
15459                self.write_keyword("IN");
15460                self.write_space();
15461                self.generate_expression(scope)?;
15462            }
15463
15464            // FROM clause
15465            if let Some(ref from) = s.from {
15466                self.write_space();
15467                self.write_keyword("FROM");
15468                self.write_space();
15469                self.generate_expression(from)?;
15470            }
15471
15472            // Second FROM clause (db name)
15473            if let Some(ref db) = s.db {
15474                self.write_space();
15475                self.write_keyword("FROM");
15476                self.write_space();
15477                self.generate_expression(db)?;
15478            }
15479
15480            // LIKE pattern
15481            if let Some(ref like) = s.like {
15482                self.write_space();
15483                self.write_keyword("LIKE");
15484                self.write_space();
15485                self.generate_expression(like)?;
15486            }
15487        } else {
15488            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
15489
15490            // LIKE pattern
15491            if let Some(ref like) = s.like {
15492                self.write_space();
15493                self.write_keyword("LIKE");
15494                self.write_space();
15495                self.generate_expression(like)?;
15496            }
15497
15498            // IN scope_kind [scope]
15499            if mysql_tables_scope_as_from {
15500                self.write_space();
15501                self.write_keyword("FROM");
15502                self.write_space();
15503                self.generate_expression(s.scope.as_ref().unwrap())?;
15504            } else if let Some(ref scope_kind) = s.scope_kind {
15505                self.write_space();
15506                self.write_keyword("IN");
15507                self.write_space();
15508                self.write_keyword(scope_kind);
15509                if let Some(ref scope) = s.scope {
15510                    self.write_space();
15511                    self.generate_expression(scope)?;
15512                }
15513            } else if let Some(ref scope) = s.scope {
15514                self.write_space();
15515                self.write_keyword("IN");
15516                self.write_space();
15517                self.generate_expression(scope)?;
15518            }
15519        }
15520
15521        // STARTS WITH pattern
15522        if let Some(ref starts_with) = s.starts_with {
15523            self.write_space();
15524            self.write_keyword("STARTS WITH");
15525            self.write_space();
15526            self.generate_expression(starts_with)?;
15527        }
15528
15529        // LIMIT clause
15530        if let Some(ref limit) = s.limit {
15531            self.write_space();
15532            self.generate_limit(limit)?;
15533        }
15534
15535        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
15536        if is_snowflake {
15537            if let Some(ref from) = s.from {
15538                self.write_space();
15539                self.write_keyword("FROM");
15540                self.write_space();
15541                self.generate_expression(from)?;
15542            }
15543        }
15544
15545        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
15546        if let Some(ref where_clause) = s.where_clause {
15547            self.write_space();
15548            self.write_keyword("WHERE");
15549            self.write_space();
15550            self.generate_expression(where_clause)?;
15551        }
15552
15553        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
15554        if let Some(is_mutex) = s.mutex {
15555            self.write_space();
15556            if is_mutex {
15557                self.write_keyword("MUTEX");
15558            } else {
15559                self.write_keyword("STATUS");
15560            }
15561        }
15562
15563        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
15564        if !s.privileges.is_empty() {
15565            self.write_space();
15566            self.write_keyword("WITH PRIVILEGES");
15567            self.write_space();
15568            for (i, priv_name) in s.privileges.iter().enumerate() {
15569                if i > 0 {
15570                    self.write(", ");
15571                }
15572                self.write_keyword(priv_name);
15573            }
15574        }
15575
15576        Ok(())
15577    }
15578
15579    // ==================== End DDL Generation ====================
15580
15581    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
15582        use crate::dialects::DialectType;
15583        match lit {
15584            Literal::String(s) => {
15585                self.generate_string_literal(s)?;
15586            }
15587            Literal::Number(n) => {
15588                if matches!(self.config.dialect, Some(DialectType::MySQL))
15589                    && n.len() > 2
15590                    && (n.starts_with("0x") || n.starts_with("0X"))
15591                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
15592                {
15593                    return self.generate_identifier(&Identifier {
15594                        name: n.clone(),
15595                        quoted: true,
15596                        trailing_comments: Vec::new(),
15597                        span: None,
15598                    });
15599                }
15600                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
15601                // for dialects that don't support them (MySQL interprets as identifier).
15602                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
15603                let n = if n.contains('_')
15604                    && !matches!(
15605                        self.config.dialect,
15606                        Some(DialectType::ClickHouse)
15607                            | Some(DialectType::DuckDB)
15608                            | Some(DialectType::PostgreSQL)
15609                            | Some(DialectType::Hive)
15610                            | Some(DialectType::Spark)
15611                            | Some(DialectType::Databricks)
15612                    ) {
15613                    std::borrow::Cow::Owned(n.replace('_', ""))
15614                } else {
15615                    std::borrow::Cow::Borrowed(n.as_str())
15616                };
15617                // Normalize numbers starting with decimal point to have leading zero
15618                // e.g., .25 -> 0.25 (matches sqlglot behavior)
15619                if n.starts_with('.') {
15620                    self.write("0");
15621                    self.write(&n);
15622                } else if n.starts_with("-.") {
15623                    // Handle negative numbers like -.25 -> -0.25
15624                    self.write("-0");
15625                    self.write(&n[1..]);
15626                } else {
15627                    self.write(&n);
15628                }
15629            }
15630            Literal::HexString(h) => {
15631                // Most dialects use lowercase x'...' for hex literals.
15632                match self.config.dialect {
15633                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15634                        self.write("0x");
15635                        self.write(h);
15636                        return Ok(());
15637                    }
15638                    Some(DialectType::Spark)
15639                    | Some(DialectType::Databricks)
15640                    | Some(DialectType::Teradata) => self.write("X'"),
15641                    _ => self.write("x'"),
15642                }
15643                self.write(h);
15644                self.write("'");
15645            }
15646            Literal::HexNumber(h) => {
15647                // Hex number (0xA) - integer in hex notation (from BigQuery)
15648                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
15649                // For other dialects, convert to decimal integer
15650                match self.config.dialect {
15651                    Some(DialectType::BigQuery)
15652                    | Some(DialectType::ClickHouse)
15653                    | Some(DialectType::TSQL)
15654                    | Some(DialectType::Fabric) => {
15655                        self.write("0x");
15656                        self.write(h);
15657                    }
15658                    _ => {
15659                        // Convert hex to decimal
15660                        if let Ok(val) = u64::from_str_radix(h, 16) {
15661                            self.write(&val.to_string());
15662                        } else {
15663                            // Fallback: keep as 0x notation
15664                            self.write("0x");
15665                            self.write(h);
15666                        }
15667                    }
15668                }
15669            }
15670            Literal::BitString(b) => {
15671                // Bit string B'0101...'
15672                self.write("B'");
15673                self.write(b);
15674                self.write("'");
15675            }
15676            Literal::ByteString(b) => {
15677                // Byte string b'...' (BigQuery style)
15678                self.write("b'");
15679                // Escape special characters for output
15680                self.write_escaped_byte_string(b);
15681                self.write("'");
15682            }
15683            Literal::NationalString(s) => {
15684                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
15685                // Other dialects strip the N prefix and output as regular string
15686                let keep_n_prefix = matches!(
15687                    self.config.dialect,
15688                    Some(DialectType::TSQL)
15689                        | Some(DialectType::Oracle)
15690                        | Some(DialectType::MySQL)
15691                        | None
15692                );
15693                if keep_n_prefix {
15694                    self.write("N'");
15695                } else {
15696                    self.write("'");
15697                }
15698                self.write(s);
15699                self.write("'");
15700            }
15701            Literal::Date(d) => {
15702                self.generate_date_literal(d)?;
15703            }
15704            Literal::Time(t) => {
15705                self.generate_time_literal(t)?;
15706            }
15707            Literal::Timestamp(ts) => {
15708                self.generate_timestamp_literal(ts)?;
15709            }
15710            Literal::Datetime(dt) => {
15711                self.generate_datetime_literal(dt)?;
15712            }
15713            Literal::TripleQuotedString(s, _quote_char) => {
15714                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
15715                if matches!(
15716                    self.config.dialect,
15717                    Some(crate::dialects::DialectType::BigQuery)
15718                        | Some(crate::dialects::DialectType::DuckDB)
15719                        | Some(crate::dialects::DialectType::Snowflake)
15720                        | Some(crate::dialects::DialectType::Spark)
15721                        | Some(crate::dialects::DialectType::Hive)
15722                        | Some(crate::dialects::DialectType::Presto)
15723                        | Some(crate::dialects::DialectType::Trino)
15724                        | Some(crate::dialects::DialectType::PostgreSQL)
15725                        | Some(crate::dialects::DialectType::MySQL)
15726                        | Some(crate::dialects::DialectType::Redshift)
15727                        | Some(crate::dialects::DialectType::TSQL)
15728                        | Some(crate::dialects::DialectType::Oracle)
15729                        | Some(crate::dialects::DialectType::ClickHouse)
15730                        | Some(crate::dialects::DialectType::Databricks)
15731                        | Some(crate::dialects::DialectType::SQLite)
15732                ) {
15733                    self.generate_string_literal(s)?;
15734                } else {
15735                    // Preserve triple-quoted string syntax for generic/unknown dialects
15736                    let quotes = format!("{0}{0}{0}", _quote_char);
15737                    self.write(&quotes);
15738                    self.write(s);
15739                    self.write(&quotes);
15740                }
15741            }
15742            Literal::EscapeString(s) => {
15743                // PostgreSQL escape string: e'...' or E'...'
15744                // Token text format is "e:content" or "E:content"
15745                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
15746                use crate::dialects::DialectType;
15747                let content = if let Some(c) = s.strip_prefix("e:") {
15748                    c
15749                } else if let Some(c) = s.strip_prefix("E:") {
15750                    c
15751                } else {
15752                    s.as_str()
15753                };
15754
15755                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
15756                if matches!(
15757                    self.config.dialect,
15758                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
15759                ) {
15760                    self.write("'");
15761                    self.write(&content.replace('\'', "''"));
15762                    self.write("'");
15763                } else {
15764                    // Some dialects use lowercase e' prefix
15765                    let prefix = if matches!(
15766                        self.config.dialect,
15767                        Some(DialectType::SingleStore)
15768                            | Some(DialectType::DuckDB)
15769                            | Some(DialectType::PostgreSQL)
15770                            | Some(DialectType::CockroachDB)
15771                            | Some(DialectType::Materialize)
15772                            | Some(DialectType::RisingWave)
15773                    ) {
15774                        "e'"
15775                    } else {
15776                        "E'"
15777                    };
15778
15779                    // Normalize \' to '' for output
15780                    let normalized = content.replace("\\'", "''");
15781                    self.write(prefix);
15782                    self.write(&normalized);
15783                    self.write("'");
15784                }
15785            }
15786            Literal::DollarString(s) => {
15787                // Convert dollar-quoted strings to single-quoted strings
15788                // (like Python sqlglot's rawstring_sql)
15789                use crate::dialects::DialectType;
15790                // Extract content from tag\x00content format
15791                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
15792                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
15793                let escape_backslash = matches!(
15794                    self.config.dialect,
15795                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
15796                );
15797                // Step 2: Determine quote escaping style
15798                // Snowflake: ' -> \' (backslash escape)
15799                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
15800                let use_backslash_quote =
15801                    matches!(self.config.dialect, Some(DialectType::Snowflake));
15802
15803                let mut escaped = String::with_capacity(content.len() + 4);
15804                for ch in content.chars() {
15805                    if escape_backslash && ch == '\\' {
15806                        // Escape backslash first (before quote escaping)
15807                        escaped.push('\\');
15808                        escaped.push('\\');
15809                    } else if ch == '\'' {
15810                        if use_backslash_quote {
15811                            escaped.push('\\');
15812                            escaped.push('\'');
15813                        } else {
15814                            escaped.push('\'');
15815                            escaped.push('\'');
15816                        }
15817                    } else {
15818                        escaped.push(ch);
15819                    }
15820                }
15821                self.write("'");
15822                self.write(&escaped);
15823                self.write("'");
15824            }
15825            Literal::RawString(s) => {
15826                // Raw strings (r"..." or r'...') contain literal backslashes.
15827                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15828                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15829                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15830                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15831                use crate::dialects::DialectType;
15832
15833                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15834                let escape_backslash = matches!(
15835                    self.config.dialect,
15836                    Some(DialectType::BigQuery)
15837                        | Some(DialectType::MySQL)
15838                        | Some(DialectType::SingleStore)
15839                        | Some(DialectType::TiDB)
15840                        | Some(DialectType::Hive)
15841                        | Some(DialectType::Spark)
15842                        | Some(DialectType::Databricks)
15843                        | Some(DialectType::Drill)
15844                        | Some(DialectType::Snowflake)
15845                        | Some(DialectType::Redshift)
15846                        | Some(DialectType::ClickHouse)
15847                );
15848
15849                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15850                // These escape quotes as \' instead of ''
15851                let backslash_escapes_quote = matches!(
15852                    self.config.dialect,
15853                    Some(DialectType::BigQuery)
15854                        | Some(DialectType::Hive)
15855                        | Some(DialectType::Spark)
15856                        | Some(DialectType::Databricks)
15857                        | Some(DialectType::Drill)
15858                        | Some(DialectType::Snowflake)
15859                        | Some(DialectType::Redshift)
15860                );
15861
15862                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15863                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15864                let supports_escape_sequences = escape_backslash;
15865
15866                let mut escaped = String::with_capacity(s.len() + 4);
15867                for ch in s.chars() {
15868                    if escape_backslash && ch == '\\' {
15869                        // Double the backslash for the target dialect
15870                        escaped.push('\\');
15871                        escaped.push('\\');
15872                    } else if ch == '\'' {
15873                        if backslash_escapes_quote {
15874                            // Use backslash to escape the quote: \'
15875                            escaped.push('\\');
15876                            escaped.push('\'');
15877                        } else {
15878                            // Use SQL standard quote doubling: ''
15879                            escaped.push('\'');
15880                            escaped.push('\'');
15881                        }
15882                    } else if supports_escape_sequences {
15883                        // Apply ESCAPED_SEQUENCES mapping for special chars
15884                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15885                        match ch {
15886                            '\n' => {
15887                                escaped.push('\\');
15888                                escaped.push('n');
15889                            }
15890                            '\r' => {
15891                                escaped.push('\\');
15892                                escaped.push('r');
15893                            }
15894                            '\t' => {
15895                                escaped.push('\\');
15896                                escaped.push('t');
15897                            }
15898                            '\x07' => {
15899                                escaped.push('\\');
15900                                escaped.push('a');
15901                            }
15902                            '\x08' => {
15903                                escaped.push('\\');
15904                                escaped.push('b');
15905                            }
15906                            '\x0C' => {
15907                                escaped.push('\\');
15908                                escaped.push('f');
15909                            }
15910                            '\x0B' => {
15911                                escaped.push('\\');
15912                                escaped.push('v');
15913                            }
15914                            _ => escaped.push(ch),
15915                        }
15916                    } else {
15917                        escaped.push(ch);
15918                    }
15919                }
15920                self.write("'");
15921                self.write(&escaped);
15922                self.write("'");
15923            }
15924        }
15925        Ok(())
15926    }
15927
15928    /// Generate a DATE literal with dialect-specific formatting
15929    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15930        use crate::dialects::DialectType;
15931
15932        match self.config.dialect {
15933            // SQL Server / Fabric use CONVERT or CAST
15934            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15935                self.write("CAST('");
15936                self.write(d);
15937                self.write("' AS DATE)");
15938            }
15939            // BigQuery uses CAST syntax for type literals
15940            // DATE 'value' -> CAST('value' AS DATE)
15941            Some(DialectType::BigQuery) => {
15942                self.write("CAST('");
15943                self.write(d);
15944                self.write("' AS DATE)");
15945            }
15946            // Exasol uses CAST syntax for DATE literals
15947            // DATE 'value' -> CAST('value' AS DATE)
15948            Some(DialectType::Exasol) => {
15949                self.write("CAST('");
15950                self.write(d);
15951                self.write("' AS DATE)");
15952            }
15953            // Snowflake uses CAST syntax for DATE literals
15954            // DATE 'value' -> CAST('value' AS DATE)
15955            Some(DialectType::Snowflake) => {
15956                self.write("CAST('");
15957                self.write(d);
15958                self.write("' AS DATE)");
15959            }
15960            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15961            Some(DialectType::PostgreSQL)
15962            | Some(DialectType::MySQL)
15963            | Some(DialectType::SingleStore)
15964            | Some(DialectType::TiDB)
15965            | Some(DialectType::Redshift) => {
15966                self.write("CAST('");
15967                self.write(d);
15968                self.write("' AS DATE)");
15969            }
15970            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15971            Some(DialectType::DuckDB)
15972            | Some(DialectType::Presto)
15973            | Some(DialectType::Trino)
15974            | Some(DialectType::Athena)
15975            | Some(DialectType::Spark)
15976            | Some(DialectType::Databricks)
15977            | Some(DialectType::Hive) => {
15978                self.write("CAST('");
15979                self.write(d);
15980                self.write("' AS DATE)");
15981            }
15982            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
15983            Some(DialectType::Oracle) => {
15984                self.write("TO_DATE('");
15985                self.write(d);
15986                self.write("', 'YYYY-MM-DD')");
15987            }
15988            // Standard SQL: DATE '...'
15989            _ => {
15990                self.write_keyword("DATE");
15991                self.write(" '");
15992                self.write(d);
15993                self.write("'");
15994            }
15995        }
15996        Ok(())
15997    }
15998
15999    /// Generate a TIME literal with dialect-specific formatting
16000    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
16001        use crate::dialects::DialectType;
16002
16003        match self.config.dialect {
16004            // SQL Server uses CONVERT or CAST
16005            Some(DialectType::TSQL) => {
16006                self.write("CAST('");
16007                self.write(t);
16008                self.write("' AS TIME)");
16009            }
16010            Some(DialectType::Fabric) => {
16011                self.write("CAST('");
16012                self.write(t);
16013                self.write("' AS TIME(6))");
16014            }
16015            // Standard SQL: TIME '...'
16016            _ => {
16017                self.write_keyword("TIME");
16018                self.write(" '");
16019                self.write(t);
16020                self.write("'");
16021            }
16022        }
16023        Ok(())
16024    }
16025
16026    /// Generate a date expression for Dremio, converting DATE literals to CAST
16027    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
16028        use crate::expressions::Literal;
16029
16030        match expr {
16031            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
16032                let Literal::Date(d) = lit.as_ref() else {
16033                    unreachable!()
16034                };
16035                // DATE 'value' -> CAST('value' AS DATE)
16036                self.write("CAST('");
16037                self.write(d);
16038                self.write("' AS DATE)");
16039            }
16040            _ => {
16041                // For all other expressions, generate normally
16042                self.generate_expression(expr)?;
16043            }
16044        }
16045        Ok(())
16046    }
16047
16048    /// Generate a TIMESTAMP literal with dialect-specific formatting
16049    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
16050        use crate::dialects::DialectType;
16051
16052        match self.config.dialect {
16053            // SQL Server uses CONVERT or CAST
16054            Some(DialectType::TSQL) => {
16055                self.write("CAST('");
16056                self.write(ts);
16057                self.write("' AS DATETIME2)");
16058            }
16059            // BigQuery uses CAST syntax for type literals
16060            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16061            Some(DialectType::BigQuery) => {
16062                self.write("CAST('");
16063                self.write(ts);
16064                self.write("' AS TIMESTAMP)");
16065            }
16066            // Snowflake uses CAST syntax for TIMESTAMP literals
16067            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16068            Some(DialectType::Snowflake) => {
16069                self.write("CAST('");
16070                self.write(ts);
16071                self.write("' AS TIMESTAMP)");
16072            }
16073            // Dremio uses CAST syntax for TIMESTAMP literals
16074            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16075            Some(DialectType::Dremio) => {
16076                self.write("CAST('");
16077                self.write(ts);
16078                self.write("' AS TIMESTAMP)");
16079            }
16080            // Exasol uses CAST syntax for TIMESTAMP literals
16081            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16082            Some(DialectType::Exasol) => {
16083                self.write("CAST('");
16084                self.write(ts);
16085                self.write("' AS TIMESTAMP)");
16086            }
16087            // Oracle prefers TO_TIMESTAMP function call
16088            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
16089            Some(DialectType::Oracle) => {
16090                self.write("TO_TIMESTAMP('");
16091                self.write(ts);
16092                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
16093            }
16094            // Presto/Trino: always use CAST for TIMESTAMP literals
16095            Some(DialectType::Presto) | Some(DialectType::Trino) => {
16096                if Self::timestamp_has_timezone(ts) {
16097                    self.write("CAST('");
16098                    self.write(ts);
16099                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
16100                } else {
16101                    self.write("CAST('");
16102                    self.write(ts);
16103                    self.write("' AS TIMESTAMP)");
16104                }
16105            }
16106            // ClickHouse: CAST('...' AS Nullable(DateTime))
16107            Some(DialectType::ClickHouse) => {
16108                self.write("CAST('");
16109                self.write(ts);
16110                self.write("' AS Nullable(DateTime))");
16111            }
16112            // Spark: CAST('...' AS TIMESTAMP)
16113            Some(DialectType::Spark) => {
16114                self.write("CAST('");
16115                self.write(ts);
16116                self.write("' AS TIMESTAMP)");
16117            }
16118            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
16119            // but TIMESTAMP '...' for special values like 'epoch'
16120            Some(DialectType::Redshift) => {
16121                if ts == "epoch" {
16122                    self.write_keyword("TIMESTAMP");
16123                    self.write(" '");
16124                    self.write(ts);
16125                    self.write("'");
16126                } else {
16127                    self.write("CAST('");
16128                    self.write(ts);
16129                    self.write("' AS TIMESTAMP)");
16130                }
16131            }
16132            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
16133            Some(DialectType::PostgreSQL)
16134            | Some(DialectType::Hive)
16135            | Some(DialectType::SQLite)
16136            | Some(DialectType::DuckDB)
16137            | Some(DialectType::Athena)
16138            | Some(DialectType::Drill)
16139            | Some(DialectType::Teradata) => {
16140                self.write("CAST('");
16141                self.write(ts);
16142                self.write("' AS TIMESTAMP)");
16143            }
16144            // MySQL/StarRocks: CAST('...' AS DATETIME)
16145            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
16146                self.write("CAST('");
16147                self.write(ts);
16148                self.write("' AS DATETIME)");
16149            }
16150            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
16151            Some(DialectType::Databricks) => {
16152                self.write("CAST('");
16153                self.write(ts);
16154                self.write("' AS TIMESTAMP_NTZ)");
16155            }
16156            // Standard SQL: TIMESTAMP '...'
16157            _ => {
16158                self.write_keyword("TIMESTAMP");
16159                self.write(" '");
16160                self.write(ts);
16161                self.write("'");
16162            }
16163        }
16164        Ok(())
16165    }
16166
16167    /// Check if a timestamp string contains a timezone identifier
16168    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
16169    fn timestamp_has_timezone(ts: &str) -> bool {
16170        // Check for common IANA timezone patterns: Continent/City format
16171        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
16172        // Also handles: UTC, GMT, Etc/GMT+0, etc.
16173        let ts_lower = ts.to_ascii_lowercase();
16174
16175        // Check for Continent/City pattern (most common)
16176        let continent_prefixes = [
16177            "africa/",
16178            "america/",
16179            "antarctica/",
16180            "arctic/",
16181            "asia/",
16182            "atlantic/",
16183            "australia/",
16184            "europe/",
16185            "indian/",
16186            "pacific/",
16187            "etc/",
16188            "brazil/",
16189            "canada/",
16190            "chile/",
16191            "mexico/",
16192            "us/",
16193        ];
16194
16195        for prefix in &continent_prefixes {
16196            if ts_lower.contains(prefix) {
16197                return true;
16198            }
16199        }
16200
16201        // Check for standalone timezone abbreviations at the end
16202        // These typically appear after the time portion
16203        let tz_abbrevs = [
16204            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
16205            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
16206            " sgt", " aest", " aedt", " acst", " acdt", " awst",
16207        ];
16208
16209        for abbrev in &tz_abbrevs {
16210            if ts_lower.ends_with(abbrev) {
16211                return true;
16212            }
16213        }
16214
16215        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
16216        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
16217        // Look for pattern: space followed by + or - and digits (optionally with :)
16218        let trimmed = ts.trim();
16219        if let Some(last_space) = trimmed.rfind(' ') {
16220            let suffix = &trimmed[last_space + 1..];
16221            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
16222                // Check if rest is numeric (possibly with : for hh:mm format)
16223                let rest = &suffix[1..];
16224                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
16225                    return true;
16226                }
16227            }
16228        }
16229
16230        false
16231    }
16232
16233    /// Generate a DATETIME literal with dialect-specific formatting
16234    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
16235        use crate::dialects::DialectType;
16236
16237        match self.config.dialect {
16238            // BigQuery uses CAST syntax for type literals
16239            // DATETIME 'value' -> CAST('value' AS DATETIME)
16240            Some(DialectType::BigQuery) => {
16241                self.write("CAST('");
16242                self.write(dt);
16243                self.write("' AS DATETIME)");
16244            }
16245            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
16246            Some(DialectType::DuckDB) => {
16247                self.write("CAST('");
16248                self.write(dt);
16249                self.write("' AS TIMESTAMP)");
16250            }
16251            // DATETIME is primarily a BigQuery type
16252            // Output as DATETIME '...' for dialects that support it
16253            _ => {
16254                self.write_keyword("DATETIME");
16255                self.write(" '");
16256                self.write(dt);
16257                self.write("'");
16258            }
16259        }
16260        Ok(())
16261    }
16262
16263    /// Generate a string literal with dialect-specific escaping
16264    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
16265        use crate::dialects::DialectType;
16266
16267        match self.config.dialect {
16268            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
16269            // and backslash escaping for special characters like newlines
16270            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
16271            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
16272                // Hive/Spark use backslash escaping for quotes (\') and special chars
16273                self.write("'");
16274                for c in s.chars() {
16275                    match c {
16276                        '\'' => self.write("\\'"),
16277                        '\\' => self.write("\\\\"),
16278                        '\n' => self.write("\\n"),
16279                        '\r' => self.write("\\r"),
16280                        '\t' => self.write("\\t"),
16281                        '\0' => self.write("\\0"),
16282                        _ => self.output.push(c),
16283                    }
16284                }
16285                self.write("'");
16286            }
16287            Some(DialectType::Drill) => {
16288                // Drill uses SQL-standard quote doubling ('') for quotes,
16289                // but backslash escaping for special characters
16290                self.write("'");
16291                for c in s.chars() {
16292                    match c {
16293                        '\'' => self.write("''"),
16294                        '\\' => self.write("\\\\"),
16295                        '\n' => self.write("\\n"),
16296                        '\r' => self.write("\\r"),
16297                        '\t' => self.write("\\t"),
16298                        '\0' => self.write("\\0"),
16299                        _ => self.output.push(c),
16300                    }
16301                }
16302                self.write("'");
16303            }
16304            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
16305                self.write("'");
16306                for c in s.chars() {
16307                    match c {
16308                        // MySQL uses SQL standard quote doubling
16309                        '\'' => self.write("''"),
16310                        '\\' => self.write("\\\\"),
16311                        '\n' => self.write("\\n"),
16312                        '\r' => self.write("\\r"),
16313                        '\t' => self.write("\\t"),
16314                        // sqlglot writes a literal NUL for this case
16315                        '\0' => self.output.push('\0'),
16316                        _ => self.output.push(c),
16317                    }
16318                }
16319                self.write("'");
16320            }
16321            // BigQuery: Uses backslash escaping
16322            Some(DialectType::BigQuery) => {
16323                self.write("'");
16324                for c in s.chars() {
16325                    match c {
16326                        '\'' => self.write("\\'"),
16327                        '\\' => self.write("\\\\"),
16328                        '\n' => self.write("\\n"),
16329                        '\r' => self.write("\\r"),
16330                        '\t' => self.write("\\t"),
16331                        '\0' => self.write("\\0"),
16332                        '\x07' => self.write("\\a"),
16333                        '\x08' => self.write("\\b"),
16334                        '\x0C' => self.write("\\f"),
16335                        '\x0B' => self.write("\\v"),
16336                        _ => self.output.push(c),
16337                    }
16338                }
16339                self.write("'");
16340            }
16341            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
16342            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
16343            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
16344            Some(DialectType::Athena) => {
16345                if self.athena_hive_context {
16346                    // Hive-style: backslash escaping
16347                    self.write("'");
16348                    for c in s.chars() {
16349                        match c {
16350                            '\'' => self.write("\\'"),
16351                            '\\' => self.write("\\\\"),
16352                            '\n' => self.write("\\n"),
16353                            '\r' => self.write("\\r"),
16354                            '\t' => self.write("\\t"),
16355                            '\0' => self.write("\\0"),
16356                            _ => self.output.push(c),
16357                        }
16358                    }
16359                    self.write("'");
16360                } else {
16361                    // Trino-style: SQL-standard escaping, preserve backslashes
16362                    self.write("'");
16363                    for c in s.chars() {
16364                        match c {
16365                            '\'' => self.write("''"),
16366                            // Preserve backslashes literally (no re-escaping)
16367                            _ => self.output.push(c),
16368                        }
16369                    }
16370                    self.write("'");
16371                }
16372            }
16373            // Snowflake uses backslash escaping (STRING_ESCAPES = ["\\", "'"]).
16374            Some(DialectType::Snowflake) => {
16375                self.write("'");
16376                for c in s.chars() {
16377                    match c {
16378                        '\'' => self.write("\\'"),
16379                        '\\' => self.write("\\\\"),
16380                        '\0' => self.write("\\x00"),
16381                        '\x08' => self.write("\\b"),
16382                        '\x0C' => self.write("\\f"),
16383                        '\n' => self.write("\\n"),
16384                        '\r' => self.write("\\r"),
16385                        '\t' => self.write("\\t"),
16386                        _ => self.output.push(c),
16387                    }
16388                }
16389                self.write("'");
16390            }
16391            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
16392            Some(DialectType::PostgreSQL) => {
16393                self.write("'");
16394                for c in s.chars() {
16395                    match c {
16396                        '\'' => self.write("''"),
16397                        _ => self.output.push(c),
16398                    }
16399                }
16400                self.write("'");
16401            }
16402            // Redshift: Uses backslash escaping for single quotes
16403            Some(DialectType::Redshift) => {
16404                self.write("'");
16405                for c in s.chars() {
16406                    match c {
16407                        '\'' => self.write("\\'"),
16408                        _ => self.output.push(c),
16409                    }
16410                }
16411                self.write("'");
16412            }
16413            // Oracle: Uses standard double single-quote escaping
16414            Some(DialectType::Oracle) => {
16415                self.write("'");
16416                for ch in s.chars() {
16417                    if ch == '\'' {
16418                        self.output.push_str("''");
16419                    } else {
16420                        self.output.push(ch);
16421                    }
16422                }
16423                self.write("'");
16424            }
16425            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
16426            // backslash escaping for backslashes and special characters
16427            Some(DialectType::ClickHouse) => {
16428                self.write("'");
16429                for c in s.chars() {
16430                    match c {
16431                        '\'' => self.write("''"),
16432                        '\\' => self.write("\\\\"),
16433                        '\n' => self.write("\\n"),
16434                        '\r' => self.write("\\r"),
16435                        '\t' => self.write("\\t"),
16436                        '\0' => self.write("\\0"),
16437                        '\x07' => self.write("\\a"),
16438                        '\x08' => self.write("\\b"),
16439                        '\x0C' => self.write("\\f"),
16440                        '\x0B' => self.write("\\v"),
16441                        // Non-printable characters: emit as \xNN hex escapes
16442                        c if c.is_control() || (c as u32) < 0x20 => {
16443                            let byte = c as u32;
16444                            if byte < 256 {
16445                                self.write(&format!("\\x{:02X}", byte));
16446                            } else {
16447                                self.output.push(c);
16448                            }
16449                        }
16450                        _ => self.output.push(c),
16451                    }
16452                }
16453                self.write("'");
16454            }
16455            // Default: SQL standard double single quotes (works for most dialects)
16456            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
16457            _ => {
16458                self.write("'");
16459                for ch in s.chars() {
16460                    if ch == '\'' {
16461                        self.output.push_str("''");
16462                    } else {
16463                        self.output.push(ch);
16464                    }
16465                }
16466                self.write("'");
16467            }
16468        }
16469        Ok(())
16470    }
16471
16472    /// Write a byte string with proper escaping for BigQuery-style byte literals
16473    /// Escapes characters as \xNN hex escapes where needed
16474    fn write_escaped_byte_string(&mut self, s: &str) {
16475        for c in s.chars() {
16476            match c {
16477                // Escape single quotes
16478                '\'' => self.write("\\'"),
16479                // Escape backslashes
16480                '\\' => self.write("\\\\"),
16481                // Keep all printable characters (including non-ASCII) as-is
16482                _ if !c.is_control() => self.output.push(c),
16483                // Escape control characters as hex
16484                _ => {
16485                    let byte = c as u32;
16486                    if byte < 256 {
16487                        self.write(&format!("\\x{:02x}", byte));
16488                    } else {
16489                        // For unicode characters, write each UTF-8 byte
16490                        for b in c.to_string().as_bytes() {
16491                            self.write(&format!("\\x{:02x}", b));
16492                        }
16493                    }
16494                }
16495            }
16496        }
16497    }
16498
16499    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
16500        use crate::dialects::DialectType;
16501
16502        // Different dialects have different boolean literal formats
16503        match self.config.dialect {
16504            // SQL Server typically uses 1/0 for boolean literals in many contexts
16505            // However, TRUE/FALSE also works in modern versions
16506            Some(DialectType::TSQL) => {
16507                self.write(if b.value { "1" } else { "0" });
16508            }
16509            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
16510            Some(DialectType::Oracle) => {
16511                self.write(if b.value { "1" } else { "0" });
16512            }
16513            // MySQL accepts TRUE/FALSE as aliases for 1/0
16514            Some(DialectType::MySQL) => {
16515                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16516            }
16517            // Most other dialects support TRUE/FALSE
16518            _ => {
16519                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16520            }
16521        }
16522        Ok(())
16523    }
16524
16525    /// Generate an identifier that's used as an alias name
16526    /// This quotes reserved keywords in addition to already-quoted identifiers
16527    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
16528        let name = &id.name;
16529        let quote_style = &self.config.identifier_quote_style;
16530
16531        // For aliases, quote if:
16532        // 1. The identifier was explicitly quoted in the source
16533        // 2. The identifier is a reserved keyword for the current dialect
16534        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
16535
16536        // Normalize identifier if configured
16537        let output_name = if self.config.normalize_identifiers && !id.quoted {
16538            name.to_ascii_lowercase()
16539        } else {
16540            name.to_string()
16541        };
16542
16543        if needs_quoting {
16544            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
16545                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16546                && quote_style.start == '"'
16547                && output_name.contains('"')
16548            {
16549                &IdentifierQuoteStyle::BACKTICK
16550            } else {
16551                quote_style
16552            };
16553            // Escape any quote characters within the identifier
16554            let escaped_name = if quote_style.start == quote_style.end {
16555                output_name.replace(
16556                    quote_style.end,
16557                    &format!("{}{}", quote_style.end, quote_style.end),
16558                )
16559            } else {
16560                output_name.replace(
16561                    quote_style.end,
16562                    &format!("{}{}", quote_style.end, quote_style.end),
16563                )
16564            };
16565            self.write(&format!(
16566                "{}{}{}",
16567                quote_style.start, escaped_name, quote_style.end
16568            ));
16569        } else {
16570            self.write(&output_name);
16571        }
16572
16573        // Output trailing comments
16574        for comment in &id.trailing_comments {
16575            self.write(" ");
16576            self.write_formatted_comment(comment);
16577        }
16578        Ok(())
16579    }
16580
16581    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
16582        use crate::dialects::DialectType;
16583
16584        let name = &id.name;
16585
16586        // For Athena, use backticks in Hive context, double quotes in Trino context
16587        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
16588            && self.athena_hive_context
16589        {
16590            &IdentifierQuoteStyle::BACKTICK
16591        } else {
16592            &self.config.identifier_quote_style
16593        };
16594
16595        // Quote if:
16596        // 1. The identifier was explicitly quoted in the source
16597        // 2. The identifier is a reserved keyword for the current dialect
16598        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
16599        // This matches Python sqlglot's identifier_sql behavior
16600        // Also quote identifiers starting with digits if the target dialect doesn't support them
16601        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
16602        let needs_digit_quoting = starts_with_digit
16603            && !self.config.identifiers_can_start_with_digit
16604            && self.config.dialect.is_some();
16605        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
16606            && name.len() > 2
16607            && (name.starts_with("0x") || name.starts_with("0X"))
16608            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
16609        let clickhouse_unsafe_identifier =
16610            matches!(self.config.dialect, Some(DialectType::ClickHouse))
16611                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16612                && !name.starts_with('{')
16613                && !name.contains('(')
16614                && !name.contains(')')
16615                && name != "?"
16616                && name
16617                    .chars()
16618                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
16619        let needs_quoting = id.quoted
16620            || self.is_reserved_keyword(name)
16621            || self.config.always_quote_identifiers
16622            || needs_digit_quoting
16623            || mysql_invalid_hex_identifier
16624            || clickhouse_unsafe_identifier;
16625
16626        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
16627        // When quoted, we need to output `name`(16) not `name(16)`
16628        let (base_name, suffix) = if needs_quoting {
16629            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
16630            if let Some(paren_pos) = name.find('(') {
16631                let base = &name[..paren_pos];
16632                let rest = &name[paren_pos..];
16633                // Verify it looks like (digits) or (digits) ASC/DESC
16634                if rest.starts_with('(')
16635                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
16636                {
16637                    // Check if content between parens is all digits
16638                    let close_paren = rest.find(')').unwrap_or(rest.len());
16639                    let inside = &rest[1..close_paren];
16640                    if inside.chars().all(|c| c.is_ascii_digit()) {
16641                        (base.to_string(), rest.to_string())
16642                    } else {
16643                        (name.to_string(), String::new())
16644                    }
16645                } else {
16646                    (name.to_string(), String::new())
16647                }
16648            } else if name.ends_with(" ASC") {
16649                let base = &name[..name.len() - 4];
16650                (base.to_string(), " ASC".to_string())
16651            } else if name.ends_with(" DESC") {
16652                let base = &name[..name.len() - 5];
16653                (base.to_string(), " DESC".to_string())
16654            } else {
16655                (name.to_string(), String::new())
16656            }
16657        } else {
16658            (name.to_string(), String::new())
16659        };
16660
16661        // Normalize identifier if configured, with special handling for Exasol
16662        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
16663        // should be uppercased when not already quoted (to match Python sqlglot behavior)
16664        let output_name = if self.config.normalize_identifiers && !id.quoted {
16665            base_name.to_ascii_lowercase()
16666        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
16667            && !id.quoted
16668            && self.is_reserved_keyword(name)
16669        {
16670            // Exasol: uppercase reserved keywords when quoting them
16671            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
16672            base_name.to_ascii_uppercase()
16673        } else {
16674            base_name
16675        };
16676
16677        if needs_quoting {
16678            // Escape any quote characters within the identifier
16679            let escaped_name = if quote_style.start == quote_style.end {
16680                // Same start/end char (e.g., " or `) - double the quote char
16681                output_name.replace(
16682                    quote_style.end,
16683                    &format!("{}{}", quote_style.end, quote_style.end),
16684                )
16685            } else {
16686                // Different start/end (e.g., [ and ]) - escape only the end char
16687                output_name.replace(
16688                    quote_style.end,
16689                    &format!("{}{}", quote_style.end, quote_style.end),
16690                )
16691            };
16692            self.write(&format!(
16693                "{}{}{}{}",
16694                quote_style.start, escaped_name, quote_style.end, suffix
16695            ));
16696        } else {
16697            self.write(&output_name);
16698        }
16699
16700        // Output trailing comments
16701        for comment in &id.trailing_comments {
16702            self.write(" ");
16703            self.write_formatted_comment(comment);
16704        }
16705        Ok(())
16706    }
16707
16708    fn generate_column(&mut self, col: &Column) -> Result<()> {
16709        use crate::dialects::DialectType;
16710
16711        if let Some(table) = &col.table {
16712            // Exasol special case: LOCAL as column table prefix should NOT be quoted
16713            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
16714            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
16715            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
16716                && !table.quoted
16717                && table.name.eq_ignore_ascii_case("LOCAL");
16718
16719            if is_exasol_local_prefix {
16720                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
16721                self.write("LOCAL");
16722            } else {
16723                self.generate_identifier(table)?;
16724            }
16725            self.write(".");
16726        }
16727        self.generate_identifier(&col.name)?;
16728        // Oracle-style join marker (+)
16729        // Only output if dialect supports it (Oracle, Exasol)
16730        if col.join_mark && self.config.supports_column_join_marks {
16731            self.write(" (+)");
16732        }
16733        // Output trailing comments
16734        for comment in &col.trailing_comments {
16735            self.write_space();
16736            self.write_formatted_comment(comment);
16737        }
16738        Ok(())
16739    }
16740
16741    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
16742        self.write_keyword("PREPARE");
16743        self.write_space();
16744        self.generate_identifier(&prepare.name)?;
16745
16746        if !prepare.parameter_types.is_empty() {
16747            self.write(" (");
16748            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
16749                if i > 0 {
16750                    self.write(", ");
16751                }
16752                self.generate_data_type(data_type)?;
16753            }
16754            self.write(")");
16755        }
16756
16757        self.write_space();
16758        self.write_keyword("AS");
16759        self.write_space();
16760        self.generate_expression(&prepare.statement)
16761    }
16762
16763    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
16764    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
16765    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
16766        use crate::dialects::DialectType;
16767        use crate::expressions::PseudocolumnType;
16768
16769        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
16770        if pc.kind == PseudocolumnType::Sysdate
16771            && !matches!(
16772                self.config.dialect,
16773                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
16774            )
16775        {
16776            self.write_keyword("CURRENT_TIMESTAMP");
16777            // Add () for dialects that expect it
16778            if matches!(
16779                self.config.dialect,
16780                Some(DialectType::MySQL)
16781                    | Some(DialectType::ClickHouse)
16782                    | Some(DialectType::Spark)
16783                    | Some(DialectType::Databricks)
16784                    | Some(DialectType::Hive)
16785            ) {
16786                self.write("()");
16787            }
16788        } else {
16789            self.write(pc.kind.as_str());
16790        }
16791        Ok(())
16792    }
16793
16794    /// Generate CONNECT BY clause (Oracle hierarchical queries)
16795    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
16796        use crate::dialects::DialectType;
16797
16798        // Generate native CONNECT BY for Oracle and Snowflake
16799        // For other dialects, add a comment noting manual conversion needed
16800        let supports_connect_by = matches!(
16801            self.config.dialect,
16802            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
16803        );
16804
16805        if !supports_connect_by && self.config.dialect.is_some() {
16806            // Add comment for unsupported dialects
16807            if self.config.pretty {
16808                self.write_newline();
16809            } else {
16810                self.write_space();
16811            }
16812            self.write_unsupported_comment(
16813                "CONNECT BY requires manual conversion to recursive CTE",
16814            )?;
16815        }
16816
16817        // Generate START WITH if present (before CONNECT BY)
16818        if let Some(start) = &connect.start {
16819            if self.config.pretty {
16820                self.write_newline();
16821            } else {
16822                self.write_space();
16823            }
16824            self.write_keyword("START WITH");
16825            self.write_space();
16826            self.generate_expression(start)?;
16827        }
16828
16829        // Generate CONNECT BY
16830        if self.config.pretty {
16831            self.write_newline();
16832        } else {
16833            self.write_space();
16834        }
16835        self.write_keyword("CONNECT BY");
16836        if connect.nocycle {
16837            self.write_space();
16838            self.write_keyword("NOCYCLE");
16839        }
16840        self.write_space();
16841        self.generate_expression(&connect.connect)?;
16842
16843        Ok(())
16844    }
16845
16846    /// Generate Connect expression (for Expression::Connect variant)
16847    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16848        self.generate_connect(connect)
16849    }
16850
16851    /// Generate PRIOR expression
16852    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16853        self.write_keyword("PRIOR");
16854        self.write_space();
16855        self.generate_expression(&prior.this)?;
16856        Ok(())
16857    }
16858
16859    /// Generate CONNECT_BY_ROOT function
16860    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16861    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16862        self.write_keyword("CONNECT_BY_ROOT");
16863        self.write_space();
16864        self.generate_expression(&cbr.this)?;
16865        Ok(())
16866    }
16867
16868    /// Generate MATCH_RECOGNIZE clause
16869    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16870        use crate::dialects::DialectType;
16871
16872        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16873        let supports_match_recognize = matches!(
16874            self.config.dialect,
16875            Some(DialectType::Oracle)
16876                | Some(DialectType::Snowflake)
16877                | Some(DialectType::Presto)
16878                | Some(DialectType::Trino)
16879        );
16880
16881        // Generate the source table first
16882        if let Some(source) = &mr.this {
16883            self.generate_expression(source)?;
16884        }
16885
16886        if !supports_match_recognize {
16887            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16888            return Ok(());
16889        }
16890
16891        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16892        if self.config.pretty {
16893            self.write_newline();
16894        } else {
16895            self.write_space();
16896        }
16897
16898        self.write_keyword("MATCH_RECOGNIZE");
16899        self.write(" (");
16900
16901        if self.config.pretty {
16902            self.indent_level += 1;
16903        }
16904
16905        let mut needs_separator = false;
16906
16907        // PARTITION BY
16908        if let Some(partition_by) = &mr.partition_by {
16909            if !partition_by.is_empty() {
16910                if self.config.pretty {
16911                    self.write_newline();
16912                    self.write_indent();
16913                }
16914                self.write_keyword("PARTITION BY");
16915                self.write_space();
16916                for (i, expr) in partition_by.iter().enumerate() {
16917                    if i > 0 {
16918                        self.write(", ");
16919                    }
16920                    self.generate_expression(expr)?;
16921                }
16922                needs_separator = true;
16923            }
16924        }
16925
16926        // ORDER BY
16927        if let Some(order_by) = &mr.order_by {
16928            if !order_by.is_empty() {
16929                if needs_separator {
16930                    if self.config.pretty {
16931                        self.write_newline();
16932                        self.write_indent();
16933                    } else {
16934                        self.write_space();
16935                    }
16936                } else if self.config.pretty {
16937                    self.write_newline();
16938                    self.write_indent();
16939                }
16940                self.write_keyword("ORDER BY");
16941                // In pretty mode, put each ORDER BY column on a new indented line
16942                if self.config.pretty {
16943                    self.indent_level += 1;
16944                    for (i, ordered) in order_by.iter().enumerate() {
16945                        if i > 0 {
16946                            self.write(",");
16947                        }
16948                        self.write_newline();
16949                        self.write_indent();
16950                        self.generate_ordered(ordered)?;
16951                    }
16952                    self.indent_level -= 1;
16953                } else {
16954                    self.write_space();
16955                    for (i, ordered) in order_by.iter().enumerate() {
16956                        if i > 0 {
16957                            self.write(", ");
16958                        }
16959                        self.generate_ordered(ordered)?;
16960                    }
16961                }
16962                needs_separator = true;
16963            }
16964        }
16965
16966        // MEASURES
16967        if let Some(measures) = &mr.measures {
16968            if !measures.is_empty() {
16969                if needs_separator {
16970                    if self.config.pretty {
16971                        self.write_newline();
16972                        self.write_indent();
16973                    } else {
16974                        self.write_space();
16975                    }
16976                } else if self.config.pretty {
16977                    self.write_newline();
16978                    self.write_indent();
16979                }
16980                self.write_keyword("MEASURES");
16981                // In pretty mode, put each MEASURE on a new indented line
16982                if self.config.pretty {
16983                    self.indent_level += 1;
16984                    for (i, measure) in measures.iter().enumerate() {
16985                        if i > 0 {
16986                            self.write(",");
16987                        }
16988                        self.write_newline();
16989                        self.write_indent();
16990                        // Handle RUNNING/FINAL prefix
16991                        if let Some(semantics) = &measure.window_frame {
16992                            match semantics {
16993                                MatchRecognizeSemantics::Running => {
16994                                    self.write_keyword("RUNNING");
16995                                    self.write_space();
16996                                }
16997                                MatchRecognizeSemantics::Final => {
16998                                    self.write_keyword("FINAL");
16999                                    self.write_space();
17000                                }
17001                            }
17002                        }
17003                        self.generate_expression(&measure.this)?;
17004                    }
17005                    self.indent_level -= 1;
17006                } else {
17007                    self.write_space();
17008                    for (i, measure) in measures.iter().enumerate() {
17009                        if i > 0 {
17010                            self.write(", ");
17011                        }
17012                        // Handle RUNNING/FINAL prefix
17013                        if let Some(semantics) = &measure.window_frame {
17014                            match semantics {
17015                                MatchRecognizeSemantics::Running => {
17016                                    self.write_keyword("RUNNING");
17017                                    self.write_space();
17018                                }
17019                                MatchRecognizeSemantics::Final => {
17020                                    self.write_keyword("FINAL");
17021                                    self.write_space();
17022                                }
17023                            }
17024                        }
17025                        self.generate_expression(&measure.this)?;
17026                    }
17027                }
17028                needs_separator = true;
17029            }
17030        }
17031
17032        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
17033        if let Some(rows) = &mr.rows {
17034            if needs_separator {
17035                if self.config.pretty {
17036                    self.write_newline();
17037                    self.write_indent();
17038                } else {
17039                    self.write_space();
17040                }
17041            } else if self.config.pretty {
17042                self.write_newline();
17043                self.write_indent();
17044            }
17045            match rows {
17046                MatchRecognizeRows::OneRowPerMatch => {
17047                    self.write_keyword("ONE ROW PER MATCH");
17048                }
17049                MatchRecognizeRows::AllRowsPerMatch => {
17050                    self.write_keyword("ALL ROWS PER MATCH");
17051                }
17052                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
17053                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
17054                }
17055                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
17056                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
17057                }
17058                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
17059                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
17060                }
17061            }
17062            needs_separator = true;
17063        }
17064
17065        // AFTER MATCH SKIP
17066        if let Some(after) = &mr.after {
17067            if needs_separator {
17068                if self.config.pretty {
17069                    self.write_newline();
17070                    self.write_indent();
17071                } else {
17072                    self.write_space();
17073                }
17074            } else if self.config.pretty {
17075                self.write_newline();
17076                self.write_indent();
17077            }
17078            match after {
17079                MatchRecognizeAfter::PastLastRow => {
17080                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
17081                }
17082                MatchRecognizeAfter::ToNextRow => {
17083                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
17084                }
17085                MatchRecognizeAfter::ToFirst(ident) => {
17086                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
17087                    self.write_space();
17088                    self.generate_identifier(ident)?;
17089                }
17090                MatchRecognizeAfter::ToLast(ident) => {
17091                    self.write_keyword("AFTER MATCH SKIP TO LAST");
17092                    self.write_space();
17093                    self.generate_identifier(ident)?;
17094                }
17095            }
17096            needs_separator = true;
17097        }
17098
17099        // PATTERN
17100        if let Some(pattern) = &mr.pattern {
17101            if needs_separator {
17102                if self.config.pretty {
17103                    self.write_newline();
17104                    self.write_indent();
17105                } else {
17106                    self.write_space();
17107                }
17108            } else if self.config.pretty {
17109                self.write_newline();
17110                self.write_indent();
17111            }
17112            self.write_keyword("PATTERN");
17113            self.write_space();
17114            self.write("(");
17115            self.write(pattern);
17116            self.write(")");
17117            needs_separator = true;
17118        }
17119
17120        // DEFINE
17121        if let Some(define) = &mr.define {
17122            if !define.is_empty() {
17123                if needs_separator {
17124                    if self.config.pretty {
17125                        self.write_newline();
17126                        self.write_indent();
17127                    } else {
17128                        self.write_space();
17129                    }
17130                } else if self.config.pretty {
17131                    self.write_newline();
17132                    self.write_indent();
17133                }
17134                self.write_keyword("DEFINE");
17135                // In pretty mode, put each DEFINE on a new indented line
17136                if self.config.pretty {
17137                    self.indent_level += 1;
17138                    for (i, (name, expr)) in define.iter().enumerate() {
17139                        if i > 0 {
17140                            self.write(",");
17141                        }
17142                        self.write_newline();
17143                        self.write_indent();
17144                        self.generate_identifier(name)?;
17145                        self.write(" AS ");
17146                        self.generate_expression(expr)?;
17147                    }
17148                    self.indent_level -= 1;
17149                } else {
17150                    self.write_space();
17151                    for (i, (name, expr)) in define.iter().enumerate() {
17152                        if i > 0 {
17153                            self.write(", ");
17154                        }
17155                        self.generate_identifier(name)?;
17156                        self.write(" AS ");
17157                        self.generate_expression(expr)?;
17158                    }
17159                }
17160            }
17161        }
17162
17163        if self.config.pretty {
17164            self.indent_level -= 1;
17165            self.write_newline();
17166        }
17167        self.write(")");
17168
17169        // Alias - only include AS if it was explicitly present in the input
17170        if let Some(alias) = &mr.alias {
17171            self.write(" ");
17172            if mr.alias_explicit_as {
17173                self.write_keyword("AS");
17174                self.write(" ");
17175            }
17176            self.generate_identifier(alias)?;
17177        }
17178
17179        Ok(())
17180    }
17181
17182    /// Generate a query hint /*+ ... */
17183    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
17184        use crate::dialects::DialectType;
17185
17186        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
17187        let supports_hints = matches!(
17188            self.config.dialect,
17189            None |  // No dialect = preserve everything
17190            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
17191            Some(DialectType::Spark) | Some(DialectType::Hive) |
17192            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
17193        );
17194
17195        if !supports_hints || hint.expressions.is_empty() {
17196            return Ok(());
17197        }
17198
17199        // First, expand raw hint text into individual hint strings
17200        // This handles the case where the parser stored multiple hints as a single raw string
17201        let mut hint_strings: Vec<String> = Vec::new();
17202        for expr in &hint.expressions {
17203            match expr {
17204                HintExpression::Raw(text) => {
17205                    // Parse raw hint text into individual hint function calls
17206                    let parsed = self.parse_raw_hint_text(text);
17207                    hint_strings.extend(parsed);
17208                }
17209                _ => {
17210                    hint_strings.push(self.hint_expression_to_string(expr)?);
17211                }
17212            }
17213        }
17214
17215        // In pretty mode with multiple hints, always use multiline format
17216        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
17217        // always joins with newlines in pretty mode
17218        let use_multiline = self.config.pretty && hint_strings.len() > 1;
17219
17220        if use_multiline {
17221            // Pretty print with each hint on its own line
17222            self.write(" /*+ ");
17223            for (i, hint_str) in hint_strings.iter().enumerate() {
17224                if i > 0 {
17225                    self.write_newline();
17226                    self.write("  "); // 2-space indent within hint block
17227                }
17228                self.write(hint_str);
17229            }
17230            self.write(" */");
17231        } else {
17232            // Single line format
17233            self.write(" /*+ ");
17234            let sep = match self.config.dialect {
17235                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
17236                _ => " ",
17237            };
17238            for (i, hint_str) in hint_strings.iter().enumerate() {
17239                if i > 0 {
17240                    self.write(sep);
17241                }
17242                self.write(hint_str);
17243            }
17244            self.write(" */");
17245        }
17246
17247        Ok(())
17248    }
17249
17250    /// Parse raw hint text into individual hint function calls
17251    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
17252    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
17253    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
17254        let mut results = Vec::new();
17255        let mut chars = text.chars().peekable();
17256        let mut current = String::new();
17257        let mut paren_depth = 0;
17258        let mut has_unparseable_content = false;
17259        let mut position_after_last_function = 0;
17260        let mut char_position = 0;
17261
17262        while let Some(c) = chars.next() {
17263            char_position += c.len_utf8();
17264            match c {
17265                '(' => {
17266                    paren_depth += 1;
17267                    current.push(c);
17268                }
17269                ')' => {
17270                    paren_depth -= 1;
17271                    current.push(c);
17272                    // When we close the outer parenthesis, we've completed a hint function
17273                    if paren_depth == 0 {
17274                        let trimmed = current.trim().to_string();
17275                        if !trimmed.is_empty() {
17276                            // Format this hint for pretty printing if needed
17277                            let formatted = self.format_hint_function(&trimmed);
17278                            results.push(formatted);
17279                        }
17280                        current.clear();
17281                        position_after_last_function = char_position;
17282                    }
17283                }
17284                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
17285                    // Space/comma/whitespace outside parentheses - skip
17286                }
17287                _ if paren_depth == 0 => {
17288                    // Character outside parentheses - accumulate for potential hint name
17289                    current.push(c);
17290                }
17291                _ => {
17292                    current.push(c);
17293                }
17294            }
17295        }
17296
17297        // Check if there's remaining text after the last function call
17298        let remaining_text = text[position_after_last_function..].trim();
17299        if !remaining_text.is_empty() {
17300            // Check if it looks like valid hint function names
17301            // Valid hint identifiers typically are uppercase alphanumeric with underscores
17302            // If we see multiple words without parens, it's likely unparseable
17303            let words: Vec<&str> = remaining_text.split_whitespace().collect();
17304            let looks_like_hint_functions = words.iter().all(|word| {
17305                // A valid hint name followed by opening paren, or a standalone uppercase identifier
17306                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
17307            });
17308
17309            if !looks_like_hint_functions && words.len() > 1 {
17310                has_unparseable_content = true;
17311            }
17312        }
17313
17314        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
17315        if has_unparseable_content {
17316            return vec![text.trim().to_string()];
17317        }
17318
17319        // If we couldn't parse anything, return the original text as a single hint
17320        if results.is_empty() {
17321            results.push(text.trim().to_string());
17322        }
17323
17324        results
17325    }
17326
17327    /// Format a hint function for pretty printing
17328    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
17329    fn format_hint_function(&self, hint: &str) -> String {
17330        if !self.config.pretty {
17331            return hint.to_string();
17332        }
17333
17334        // Try to parse NAME(args) pattern
17335        if let Some(paren_pos) = hint.find('(') {
17336            if hint.ends_with(')') {
17337                let name = &hint[..paren_pos];
17338                let args_str = &hint[paren_pos + 1..hint.len() - 1];
17339
17340                // Parse arguments (space-separated for Oracle hints)
17341                let args: Vec<&str> = args_str.split_whitespace().collect();
17342
17343                // Calculate total width of arguments
17344                let total_args_width: usize =
17345                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
17346
17347                // If too wide, format on multiple lines
17348                if total_args_width > self.config.max_text_width && !args.is_empty() {
17349                    let mut result = format!("{}(\n", name);
17350                    for arg in &args {
17351                        result.push_str("    "); // 4-space indent for args
17352                        result.push_str(arg);
17353                        result.push('\n');
17354                    }
17355                    result.push_str("  )"); // 2-space indent for closing paren
17356                    return result;
17357                }
17358            }
17359        }
17360
17361        hint.to_string()
17362    }
17363
17364    /// Convert a hint expression to a string, handling multiline formatting for long arguments
17365    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
17366        match expr {
17367            HintExpression::Function { name, args } => {
17368                // Generate each argument to a string
17369                let arg_strings: Vec<String> = args
17370                    .iter()
17371                    .map(|arg| {
17372                        let mut gen = Generator::with_arc_config(self.config.clone());
17373                        gen.generate_expression(arg)?;
17374                        Ok(gen.output)
17375                    })
17376                    .collect::<Result<Vec<_>>>()?;
17377
17378                // Oracle hints use space-separated arguments, not comma-separated
17379                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
17380                    + arg_strings.len().saturating_sub(1); // spaces between args
17381
17382                // Check if function args need multiline formatting
17383                // Use too_wide check for argument formatting
17384                let args_multiline =
17385                    self.config.pretty && total_args_width > self.config.max_text_width;
17386
17387                if args_multiline && !arg_strings.is_empty() {
17388                    // Multiline format for long argument lists
17389                    let mut result = format!("{}(\n", name);
17390                    for arg_str in &arg_strings {
17391                        result.push_str("    "); // 4-space indent for args
17392                        result.push_str(arg_str);
17393                        result.push('\n');
17394                    }
17395                    result.push_str("  )"); // 2-space indent for closing paren
17396                    Ok(result)
17397                } else {
17398                    // Single line format with space-separated args (Oracle style)
17399                    let args_str = arg_strings.join(" ");
17400                    Ok(format!("{}({})", name, args_str))
17401                }
17402            }
17403            HintExpression::Identifier(name) => Ok(name.clone()),
17404            HintExpression::Raw(text) => {
17405                // For pretty printing, try to format the raw text
17406                if self.config.pretty {
17407                    Ok(self.format_hint_function(text))
17408                } else {
17409                    Ok(text.clone())
17410                }
17411            }
17412        }
17413    }
17414
17415    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
17416        // PostgreSQL ONLY modifier: prevents scanning child tables
17417        if table.only {
17418            self.write_keyword("ONLY");
17419            self.write_space();
17420        }
17421
17422        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
17423        if let Some(ref identifier_func) = table.identifier_func {
17424            self.generate_expression(identifier_func)?;
17425            // If table name parts are present, emit .catalog.schema.name after the function
17426            if !table.name.name.is_empty() {
17427                if let Some(catalog) = &table.catalog {
17428                    self.write(".");
17429                    self.generate_identifier(catalog)?;
17430                }
17431                if let Some(schema) = &table.schema {
17432                    self.write(".");
17433                    self.generate_identifier(schema)?;
17434                }
17435                self.write(".");
17436                self.generate_identifier(&table.name)?;
17437            }
17438        } else {
17439            if let Some(catalog) = &table.catalog {
17440                self.generate_identifier(catalog)?;
17441                self.write(".");
17442            }
17443            if let Some(schema) = &table.schema {
17444                self.generate_identifier(schema)?;
17445                self.write(".");
17446            }
17447            self.generate_identifier(&table.name)?;
17448        }
17449
17450        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
17451        if let Some(changes) = &table.changes {
17452            self.write(" ");
17453            self.generate_changes(changes)?;
17454        }
17455
17456        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
17457        if !table.partitions.is_empty() {
17458            self.write_space();
17459            self.write_keyword("PARTITION");
17460            self.write("(");
17461            for (i, partition) in table.partitions.iter().enumerate() {
17462                if i > 0 {
17463                    self.write(", ");
17464                }
17465                self.generate_identifier(partition)?;
17466            }
17467            self.write(")");
17468        }
17469
17470        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
17471        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
17472        if table.changes.is_none() {
17473            if let Some(when) = &table.when {
17474                self.write_space();
17475                self.generate_historical_data(when)?;
17476            }
17477        }
17478
17479        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
17480        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
17481        if !system_time_post_alias {
17482            if let Some(ref system_time) = table.system_time {
17483                self.write_space();
17484                self.write(system_time);
17485            }
17486        }
17487
17488        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
17489        if let Some(ref version) = table.version {
17490            self.write_space();
17491            self.generate_version(version)?;
17492        }
17493
17494        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
17495        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
17496        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
17497        let alias_post_tablesample = self.config.alias_post_tablesample;
17498
17499        if alias_post_tablesample {
17500            // TABLESAMPLE before alias (Oracle, Hive, Spark)
17501            self.generate_table_sample_clause(table)?;
17502        }
17503
17504        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
17505        // For SQLite, INDEXED BY hints come after the alias, so skip here
17506        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
17507            && table.hints.iter().any(|h| {
17508                if let Expression::Identifier(id) = h {
17509                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
17510                } else {
17511                    false
17512                }
17513            });
17514        if !table.hints.is_empty() && !is_sqlite_hint {
17515            for hint in &table.hints {
17516                self.write_space();
17517                self.generate_expression(hint)?;
17518            }
17519        }
17520
17521        if let Some(alias) = &table.alias {
17522            self.write_space();
17523            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
17524            // Generic mode and most dialects always use AS for table aliases
17525            let always_use_as = self.config.dialect.is_none()
17526                || matches!(
17527                    self.config.dialect,
17528                    Some(DialectType::Generic)
17529                        | Some(DialectType::PostgreSQL)
17530                        | Some(DialectType::Redshift)
17531                        | Some(DialectType::Snowflake)
17532                        | Some(DialectType::BigQuery)
17533                        | Some(DialectType::DuckDB)
17534                        | Some(DialectType::Presto)
17535                        | Some(DialectType::Trino)
17536                        | Some(DialectType::TSQL)
17537                        | Some(DialectType::Fabric)
17538                        | Some(DialectType::MySQL)
17539                        | Some(DialectType::Spark)
17540                        | Some(DialectType::Hive)
17541                        | Some(DialectType::SQLite)
17542                        | Some(DialectType::Drill)
17543                );
17544            let is_stage_ref = table.name.name.starts_with('@');
17545            // Oracle never uses AS for table aliases
17546            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17547            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
17548                self.write_keyword("AS");
17549                self.write_space();
17550            }
17551            self.generate_identifier(alias)?;
17552
17553            // Output column aliases if present: AS t(c1, c2)
17554            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
17555            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
17556                self.write("(");
17557                for (i, col_alias) in table.column_aliases.iter().enumerate() {
17558                    if i > 0 {
17559                        self.write(", ");
17560                    }
17561                    self.generate_identifier(col_alias)?;
17562                }
17563                self.write(")");
17564            }
17565        }
17566
17567        // BigQuery: FOR SYSTEM_TIME AS OF after alias
17568        if system_time_post_alias {
17569            if let Some(ref system_time) = table.system_time {
17570                self.write_space();
17571                self.write(system_time);
17572            }
17573        }
17574
17575        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
17576        if !alias_post_tablesample {
17577            self.generate_table_sample_clause(table)?;
17578        }
17579
17580        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
17581        if is_sqlite_hint {
17582            for hint in &table.hints {
17583                self.write_space();
17584                self.generate_expression(hint)?;
17585            }
17586        }
17587
17588        // ClickHouse FINAL modifier
17589        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17590            self.write_space();
17591            self.write_keyword("FINAL");
17592        }
17593
17594        // Output trailing comments
17595        for comment in &table.trailing_comments {
17596            self.write_space();
17597            self.write_formatted_comment(comment);
17598        }
17599        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
17600        // output here - they are output by the FROM/PIVOT generator after the full expression
17601
17602        Ok(())
17603    }
17604
17605    /// Helper to output TABLESAMPLE clause for a table reference
17606    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
17607        if let Some(ref ts) = table.table_sample {
17608            self.write_space();
17609            if ts.is_using_sample {
17610                self.write_keyword("USING SAMPLE");
17611            } else {
17612                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
17613                self.write_keyword(self.config.tablesample_keywords);
17614            }
17615            self.generate_sample_body(ts)?;
17616            // Seed for table-level sample - use dialect's configured keyword
17617            if let Some(ref seed) = ts.seed {
17618                self.write_space();
17619                self.write_keyword(self.config.tablesample_seed_keyword);
17620                self.write(" (");
17621                self.generate_expression(seed)?;
17622                self.write(")");
17623            }
17624        }
17625        Ok(())
17626    }
17627
17628    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
17629        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
17630        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
17631
17632        if sr.quoted {
17633            self.write("'");
17634        }
17635
17636        self.write(&sr.name);
17637        if let Some(path) = &sr.path {
17638            self.write(path);
17639        }
17640
17641        if sr.quoted {
17642            self.write("'");
17643        }
17644
17645        // Output FILE_FORMAT and PATTERN if present
17646        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
17647        if has_options {
17648            self.write(" (");
17649            let mut first = true;
17650
17651            if let Some(file_format) = &sr.file_format {
17652                if !first {
17653                    self.write(", ");
17654                }
17655                self.write_keyword("FILE_FORMAT");
17656                self.write(" => ");
17657                self.generate_expression(file_format)?;
17658                first = false;
17659            }
17660
17661            if let Some(pattern) = &sr.pattern {
17662                if !first {
17663                    self.write(", ");
17664                }
17665                self.write_keyword("PATTERN");
17666                self.write(" => '");
17667                self.write(pattern);
17668                self.write("'");
17669            }
17670
17671            self.write(")");
17672        }
17673        Ok(())
17674    }
17675
17676    fn generate_star(&mut self, star: &Star) -> Result<()> {
17677        use crate::dialects::DialectType;
17678
17679        if let Some(table) = &star.table {
17680            self.generate_identifier(table)?;
17681            self.write(".");
17682        }
17683        self.write("*");
17684
17685        // Generate EXCLUDE/EXCEPT clause based on dialect
17686        if let Some(except) = &star.except {
17687            if !except.is_empty() {
17688                self.write_space();
17689                // Use dialect-appropriate keyword
17690                match self.config.dialect {
17691                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
17692                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
17693                        self.write_keyword("EXCLUDE")
17694                    }
17695                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
17696                }
17697                self.write(" (");
17698                for (i, col) in except.iter().enumerate() {
17699                    if i > 0 {
17700                        self.write(", ");
17701                    }
17702                    self.generate_identifier(col)?;
17703                }
17704                self.write(")");
17705            }
17706        }
17707
17708        // Generate REPLACE clause
17709        if let Some(replace) = &star.replace {
17710            if !replace.is_empty() {
17711                self.write_space();
17712                self.write_keyword("REPLACE");
17713                self.write(" (");
17714                for (i, alias) in replace.iter().enumerate() {
17715                    if i > 0 {
17716                        self.write(", ");
17717                    }
17718                    self.generate_expression(&alias.this)?;
17719                    self.write_space();
17720                    self.write_keyword("AS");
17721                    self.write_space();
17722                    self.generate_identifier(&alias.alias)?;
17723                }
17724                self.write(")");
17725            }
17726        }
17727
17728        // Generate RENAME clause (Snowflake specific)
17729        if let Some(rename) = &star.rename {
17730            if !rename.is_empty() {
17731                self.write_space();
17732                self.write_keyword("RENAME");
17733                self.write(" (");
17734                for (i, (old_name, new_name)) in rename.iter().enumerate() {
17735                    if i > 0 {
17736                        self.write(", ");
17737                    }
17738                    self.generate_identifier(old_name)?;
17739                    self.write_space();
17740                    self.write_keyword("AS");
17741                    self.write_space();
17742                    self.generate_identifier(new_name)?;
17743                }
17744                self.write(")");
17745            }
17746        }
17747
17748        // Output trailing comments
17749        for comment in &star.trailing_comments {
17750            self.write_space();
17751            self.write_formatted_comment(comment);
17752        }
17753
17754        Ok(())
17755    }
17756
17757    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
17758    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
17759        self.write("{");
17760        match expr {
17761            Expression::Star(star) => {
17762                // Generate the star (table.* or just * with optional EXCLUDE)
17763                self.generate_star(star)?;
17764            }
17765            Expression::ILike(ilike) => {
17766                // {* ILIKE 'pattern'} syntax
17767                self.generate_expression(&ilike.left)?;
17768                self.write_space();
17769                self.write_keyword("ILIKE");
17770                self.write_space();
17771                self.generate_expression(&ilike.right)?;
17772            }
17773            _ => {
17774                self.generate_expression(expr)?;
17775            }
17776        }
17777        self.write("}");
17778        Ok(())
17779    }
17780
17781    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
17782        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
17783        // to avoid duplication (comments are captured as both Column.trailing_comments
17784        // and Alias.pre_alias_comments during parsing)
17785        match &alias.this {
17786            Expression::Column(col) => {
17787                // Generate column without trailing comments - they're in pre_alias_comments
17788                if let Some(table) = &col.table {
17789                    self.generate_identifier(table)?;
17790                    self.write(".");
17791                }
17792                self.generate_identifier(&col.name)?;
17793            }
17794            _ => {
17795                self.generate_expression(&alias.this)?;
17796            }
17797        }
17798
17799        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
17800        // moves pre-alias comments to after the alias. When there are also trailing_comments,
17801        // keep pre-alias comments in their original position (between expression and AS).
17802        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
17803            for comment in &alias.pre_alias_comments {
17804                self.write_space();
17805                self.write_formatted_comment(comment);
17806            }
17807        }
17808
17809        use crate::dialects::DialectType;
17810
17811        // Determine if we should skip AS keyword for table-valued function aliases
17812        // Oracle and some other dialects don't use AS for table aliases
17813        // Note: We specifically use TableFromRows here, NOT Function, because Function
17814        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
17815        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
17816        let is_table_source = matches!(
17817            &alias.this,
17818            Expression::JSONTable(_)
17819                | Expression::XMLTable(_)
17820                | Expression::TableFromRows(_)
17821                | Expression::Unnest(_)
17822                | Expression::MatchRecognize(_)
17823                | Expression::Select(_)
17824                | Expression::Subquery(_)
17825                | Expression::Paren(_)
17826                | Expression::JoinedTable(_)
17827        );
17828        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17829        let skip_as = is_table_source && dialect_skips_table_alias_as;
17830
17831        self.write_space();
17832        if !skip_as {
17833            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17834                if let Some(ref alias_keyword) = alias.alias_keyword {
17835                    self.write(alias_keyword);
17836                } else {
17837                    self.write_keyword("AS");
17838                }
17839            } else {
17840                self.write_keyword("AS");
17841            }
17842            self.write_space();
17843        }
17844
17845        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17846        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17847
17848        // Check if we have column aliases only (no table alias name)
17849        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17850            // Generate AS (col1, col2, ...)
17851            self.write("(");
17852            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17853                if i > 0 {
17854                    self.write(", ");
17855                }
17856                self.generate_alias_identifier(col_alias)?;
17857            }
17858            self.write(")");
17859        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17860            // Generate AS alias(col1, col2, ...)
17861            self.generate_alias_identifier(&alias.alias)?;
17862            self.write("(");
17863            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17864                if i > 0 {
17865                    self.write(", ");
17866                }
17867                self.generate_alias_identifier(col_alias)?;
17868            }
17869            self.write(")");
17870        } else {
17871            // Simple alias (or BigQuery without column aliases)
17872            self.generate_alias_identifier(&alias.alias)?;
17873        }
17874
17875        // Output trailing comments (comments after the alias)
17876        for comment in &alias.trailing_comments {
17877            self.write_space();
17878            self.write_formatted_comment(comment);
17879        }
17880
17881        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17882        // moves pre-alias comments to after the alias. When there are trailing_comments,
17883        // the pre-alias comments were already lost (consumed as column trailing comments
17884        // that were then used as pre_alias_comments). We always emit them after alias.
17885        if alias.trailing_comments.is_empty() {
17886            for comment in &alias.pre_alias_comments {
17887                self.write_space();
17888                self.write_formatted_comment(comment);
17889            }
17890        }
17891
17892        Ok(())
17893    }
17894
17895    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17896        use crate::dialects::DialectType;
17897
17898        // SingleStore uses :> syntax
17899        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17900            self.generate_expression(&cast.this)?;
17901            self.write(" :> ");
17902            self.generate_data_type(&cast.to)?;
17903            return Ok(());
17904        }
17905
17906        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17907        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17908            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17909                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17910            if is_unknown_type {
17911                if let Some(format) = &cast.format {
17912                    self.write_keyword("CAST");
17913                    self.write("(");
17914                    self.generate_expression(&cast.this)?;
17915                    self.write_space();
17916                    self.write_keyword("AS");
17917                    self.write_space();
17918                    self.write_keyword("FORMAT");
17919                    self.write_space();
17920                    self.generate_expression(format)?;
17921                    self.write(")");
17922                    return Ok(());
17923                }
17924            }
17925        }
17926
17927        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17928        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17929        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17930            if let Some(format) = &cast.format {
17931                // Check if target type is DATE or TIMESTAMP
17932                let is_date = matches!(
17933                    cast.to,
17934                    DataType::Date
17935                        | DataType::Oracle {
17936                            oracle_type: OracleDataType::Date
17937                        }
17938                );
17939                let is_timestamp = matches!(
17940                    cast.to,
17941                    DataType::Timestamp { .. }
17942                        | DataType::Oracle {
17943                            oracle_type: OracleDataType::Timestamp { .. }
17944                        }
17945                );
17946
17947                if is_date || is_timestamp {
17948                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17949                    self.write_keyword(func_name);
17950                    self.write("(");
17951                    self.generate_expression(&cast.this)?;
17952                    self.write(", ");
17953
17954                    // Normalize format string for Oracle (HH -> HH12)
17955                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17956                    if let Expression::Literal(lit) = format.as_ref() {
17957                        if let Literal::String(fmt_str) = lit.as_ref() {
17958                            let normalized = self.normalize_oracle_format(fmt_str);
17959                            self.write("'");
17960                            self.write(&normalized);
17961                            self.write("'");
17962                        }
17963                    } else {
17964                        self.generate_expression(format)?;
17965                    }
17966
17967                    self.write(")");
17968                    return Ok(());
17969                }
17970            }
17971        }
17972
17973        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
17974        // This preserves sqlglot's typed inline array literal output.
17975        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17976            if let Expression::Array(arr) = &cast.this {
17977                self.generate_data_type(&cast.to)?;
17978                // Output just the bracket content [values] without the ARRAY prefix
17979                self.write("[");
17980                for (i, expr) in arr.expressions.iter().enumerate() {
17981                    if i > 0 {
17982                        self.write(", ");
17983                    }
17984                    self.generate_expression(expr)?;
17985                }
17986                self.write("]");
17987                return Ok(());
17988            }
17989            if matches!(&cast.this, Expression::ArrayFunc(_)) {
17990                self.generate_data_type(&cast.to)?;
17991                self.generate_expression(&cast.this)?;
17992                return Ok(());
17993            }
17994        }
17995
17996        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
17997        // convert the inner Struct to ROW(values...) format
17998        if matches!(
17999            self.config.dialect,
18000            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
18001        ) {
18002            if let Expression::Struct(ref s) = cast.this {
18003                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
18004                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
18005                    self.write_keyword("CAST");
18006                    self.write("(");
18007                    self.generate_struct_as_row(s)?;
18008                    self.write_space();
18009                    self.write_keyword("AS");
18010                    self.write_space();
18011                    self.generate_data_type(&cast.to)?;
18012                    self.write(")");
18013                    return Ok(());
18014                }
18015            }
18016        }
18017
18018        // Determine if we should use :: syntax based on dialect
18019        // PostgreSQL prefers :: for identity, most others prefer CAST()
18020        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
18021
18022        if use_double_colon {
18023            // PostgreSQL :: syntax: expr::type
18024            self.generate_expression(&cast.this)?;
18025            self.write("::");
18026            self.generate_data_type(&cast.to)?;
18027        } else {
18028            // Standard CAST() syntax
18029            self.write_keyword("CAST");
18030            self.write("(");
18031            self.generate_expression(&cast.this)?;
18032            self.write_space();
18033            self.write_keyword("AS");
18034            self.write_space();
18035            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
18036            // This matches Python sqlglot's CAST_MAPPING behavior
18037            if matches!(
18038                self.config.dialect,
18039                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
18040            ) {
18041                match &cast.to {
18042                    DataType::Custom { ref name } => {
18043                        if name.eq_ignore_ascii_case("LONGTEXT")
18044                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
18045                            || name.eq_ignore_ascii_case("TINYTEXT")
18046                            || name.eq_ignore_ascii_case("LONGBLOB")
18047                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
18048                            || name.eq_ignore_ascii_case("TINYBLOB")
18049                        {
18050                            self.write_keyword("CHAR");
18051                        } else {
18052                            self.generate_data_type(&cast.to)?;
18053                        }
18054                    }
18055                    DataType::VarChar { length, .. } => {
18056                        // MySQL CAST: VARCHAR -> CHAR
18057                        self.write_keyword("CHAR");
18058                        if let Some(n) = length {
18059                            self.write(&format!("({})", n));
18060                        }
18061                    }
18062                    DataType::Text => {
18063                        // MySQL CAST: TEXT -> CHAR
18064                        self.write_keyword("CHAR");
18065                    }
18066                    DataType::Timestamp {
18067                        precision,
18068                        timezone: false,
18069                    } => {
18070                        // MySQL CAST: TIMESTAMP -> DATETIME
18071                        self.write_keyword("DATETIME");
18072                        if let Some(p) = precision {
18073                            self.write(&format!("({})", p));
18074                        }
18075                    }
18076                    _ => {
18077                        self.generate_data_type(&cast.to)?;
18078                    }
18079                }
18080            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
18081                // Snowflake CAST: STRING -> VARCHAR
18082                match &cast.to {
18083                    DataType::String { length } => {
18084                        self.write_keyword("VARCHAR");
18085                        if let Some(n) = length {
18086                            self.write(&format!("({})", n));
18087                        }
18088                    }
18089                    _ => {
18090                        self.generate_data_type(&cast.to)?;
18091                    }
18092                }
18093            } else if matches!(self.config.dialect, Some(DialectType::Oracle)) {
18094                // Oracle canonicalizes binary floating-point CAST targets even though
18095                // column definitions retain the BINARY_FLOAT/BINARY_DOUBLE spellings.
18096                match &cast.to {
18097                    DataType::Oracle {
18098                        oracle_type: OracleDataType::BinaryFloat,
18099                    } => self.write_keyword("FLOAT"),
18100                    DataType::Oracle {
18101                        oracle_type: OracleDataType::BinaryDouble,
18102                    } => self.write_keyword("DOUBLE PRECISION"),
18103                    _ => self.generate_data_type(&cast.to)?,
18104                }
18105            } else {
18106                self.generate_data_type(&cast.to)?;
18107            }
18108
18109            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
18110            if let Some(default) = &cast.default {
18111                self.write_space();
18112                self.write_keyword("DEFAULT");
18113                self.write_space();
18114                self.generate_expression(default)?;
18115                self.write_space();
18116                self.write_keyword("ON");
18117                self.write_space();
18118                self.write_keyword("CONVERSION");
18119                self.write_space();
18120                self.write_keyword("ERROR");
18121            }
18122
18123            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
18124            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
18125            if let Some(format) = &cast.format {
18126                // Check if Oracle dialect - use comma syntax
18127                if matches!(
18128                    self.config.dialect,
18129                    Some(crate::dialects::DialectType::Oracle)
18130                ) {
18131                    self.write(", ");
18132                } else {
18133                    self.write_space();
18134                    self.write_keyword("FORMAT");
18135                    self.write_space();
18136                }
18137                self.generate_expression(format)?;
18138            }
18139
18140            self.write(")");
18141            // Output trailing comments
18142            for comment in &cast.trailing_comments {
18143                self.write_space();
18144                self.write_formatted_comment(comment);
18145            }
18146        }
18147        Ok(())
18148    }
18149
18150    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
18151    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
18152    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
18153        self.write_keyword("ROW");
18154        self.write("(");
18155        for (i, (_, expr)) in s.fields.iter().enumerate() {
18156            if i > 0 {
18157                self.write(", ");
18158            }
18159            // Recursively convert inner Struct to ROW format
18160            if let Expression::Struct(ref inner_s) = expr {
18161                self.generate_struct_as_row(inner_s)?;
18162            } else {
18163                self.generate_expression(expr)?;
18164            }
18165        }
18166        self.write(")");
18167        Ok(())
18168    }
18169
18170    /// Normalize Oracle date/time format strings
18171    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
18172    fn normalize_oracle_format(&self, format: &str) -> String {
18173        // Replace standalone HH with HH12 (but not HH12 or HH24)
18174        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
18175        let mut result = String::new();
18176        let chars: Vec<char> = format.chars().collect();
18177        let mut i = 0;
18178
18179        while i < chars.len() {
18180            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
18181                // Check what follows HH
18182                if i + 2 < chars.len() {
18183                    let next = chars[i + 2];
18184                    if next == '1' || next == '2' {
18185                        // This is HH12 or HH24, keep as is
18186                        result.push('H');
18187                        result.push('H');
18188                        i += 2;
18189                        continue;
18190                    }
18191                }
18192                // Standalone HH -> HH12
18193                result.push_str("HH12");
18194                i += 2;
18195            } else {
18196                result.push(chars[i]);
18197                i += 1;
18198            }
18199        }
18200
18201        result
18202    }
18203
18204    /// Check if the current dialect prefers :: cast syntax
18205    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
18206    fn dialect_prefers_double_colon(&self) -> bool {
18207        matches!(self.config.dialect, Some(DialectType::ClickHouse))
18208    }
18209
18210    /// Generate MOD function - uses % operator for dialects that prefer or require it.
18211    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18212        use crate::dialects::DialectType;
18213
18214        // Several dialects prefer or require x % y instead of MOD(x, y).
18215        let use_percent_operator = matches!(
18216            self.config.dialect,
18217            Some(DialectType::Snowflake)
18218                | Some(DialectType::MySQL)
18219                | Some(DialectType::Presto)
18220                | Some(DialectType::Trino)
18221                | Some(DialectType::PostgreSQL)
18222                | Some(DialectType::DuckDB)
18223                | Some(DialectType::Hive)
18224                | Some(DialectType::Spark)
18225                | Some(DialectType::Databricks)
18226                | Some(DialectType::Athena)
18227                | Some(DialectType::TSQL)
18228                | Some(DialectType::Fabric)
18229        );
18230
18231        if use_percent_operator {
18232            // MOD(a, b) treats both arguments as grouped expressions. When
18233            // lowering to an infix operator, keep binary arguments grouped.
18234            let needs_paren = |e: &Expression| {
18235                matches!(
18236                    e,
18237                    Expression::Add(_)
18238                        | Expression::Sub(_)
18239                        | Expression::Mul(_)
18240                        | Expression::Div(_)
18241                        | Expression::Mod(_)
18242                        | Expression::ModFunc(_)
18243                )
18244            };
18245            if needs_paren(&f.this) {
18246                self.write("(");
18247                self.generate_expression(&f.this)?;
18248                self.write(")");
18249            } else {
18250                self.generate_expression(&f.this)?;
18251            }
18252            self.write(" % ");
18253            if needs_paren(&f.expression) {
18254                self.write("(");
18255                self.generate_expression(&f.expression)?;
18256                self.write(")");
18257            } else {
18258                self.generate_expression(&f.expression)?;
18259            }
18260            Ok(())
18261        } else {
18262            self.generate_binary_func("MOD", &f.this, &f.expression)
18263        }
18264    }
18265
18266    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
18267    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18268        use crate::dialects::DialectType;
18269
18270        // Snowflake normalizes IFNULL to COALESCE
18271        let func_name = match self.config.dialect {
18272            Some(DialectType::Snowflake) => "COALESCE",
18273            _ => "IFNULL",
18274        };
18275
18276        self.generate_binary_func(func_name, &f.this, &f.expression)
18277    }
18278
18279    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
18280    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18281        // Use original function name if preserved (for identity tests)
18282        if let Some(ref original_name) = f.original_name {
18283            return self.generate_binary_func(original_name, &f.this, &f.expression);
18284        }
18285
18286        // Otherwise, use dialect-specific function names
18287        use crate::dialects::DialectType;
18288        let func_name = match self.config.dialect {
18289            Some(DialectType::Snowflake)
18290            | Some(DialectType::ClickHouse)
18291            | Some(DialectType::PostgreSQL)
18292            | Some(DialectType::Presto)
18293            | Some(DialectType::Trino)
18294            | Some(DialectType::Athena)
18295            | Some(DialectType::DuckDB)
18296            | Some(DialectType::BigQuery)
18297            | Some(DialectType::Spark)
18298            | Some(DialectType::Databricks)
18299            | Some(DialectType::Hive) => "COALESCE",
18300            Some(DialectType::MySQL)
18301            | Some(DialectType::Doris)
18302            | Some(DialectType::StarRocks)
18303            | Some(DialectType::SingleStore)
18304            | Some(DialectType::TiDB) => "IFNULL",
18305            _ => "NVL",
18306        };
18307
18308        self.generate_binary_func(func_name, &f.this, &f.expression)
18309    }
18310
18311    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
18312    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
18313        use crate::dialects::DialectType;
18314
18315        // Snowflake normalizes STDDEV_SAMP to STDDEV
18316        let func_name = match self.config.dialect {
18317            Some(DialectType::Snowflake) => "STDDEV",
18318            _ => "STDDEV_SAMP",
18319        };
18320
18321        self.generate_agg_func(func_name, f)
18322    }
18323
18324    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
18325        self.generate_expression(&coll.this)?;
18326        self.write_space();
18327        self.write_keyword("COLLATE");
18328        self.write_space();
18329        if coll.quoted {
18330            // Single-quoted string: COLLATE 'de_DE'
18331            self.write("'");
18332            self.write(&coll.collation);
18333            self.write("'");
18334        } else if coll.double_quoted {
18335            // Double-quoted identifier: COLLATE "de_DE"
18336            self.write("\"");
18337            self.write(&coll.collation);
18338            self.write("\"");
18339        } else {
18340            // Unquoted identifier: COLLATE de_DE
18341            self.write(&coll.collation);
18342        }
18343        Ok(())
18344    }
18345
18346    fn generate_case(&mut self, case: &Case) -> Result<()> {
18347        // In pretty mode, decide whether to expand based on total text width
18348        let multiline_case = if self.config.pretty {
18349            // Build the flat representation to check width
18350            let mut statements: Vec<String> = Vec::new();
18351            let operand_str = if let Some(operand) = &case.operand {
18352                let s = self.generate_to_string(operand)?;
18353                statements.push(format!("CASE {}", s));
18354                s
18355            } else {
18356                statements.push("CASE".to_string());
18357                String::new()
18358            };
18359            let _ = operand_str;
18360            for (condition, result) in &case.whens {
18361                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
18362                statements.push(format!("THEN {}", self.generate_to_string(result)?));
18363            }
18364            if let Some(else_) = &case.else_ {
18365                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
18366            }
18367            statements.push("END".to_string());
18368            self.too_wide(&statements)
18369        } else {
18370            false
18371        };
18372
18373        self.write_keyword("CASE");
18374        if let Some(operand) = &case.operand {
18375            self.write_space();
18376            self.generate_expression(operand)?;
18377        }
18378        if multiline_case {
18379            self.indent_level += 1;
18380        }
18381        for (condition, result) in &case.whens {
18382            if multiline_case {
18383                self.write_newline();
18384                self.write_indent();
18385            } else {
18386                self.write_space();
18387            }
18388            self.write_keyword("WHEN");
18389            self.write_space();
18390            self.generate_expression(condition)?;
18391            if multiline_case {
18392                self.write_newline();
18393                self.write_indent();
18394            } else {
18395                self.write_space();
18396            }
18397            self.write_keyword("THEN");
18398            self.write_space();
18399            self.generate_expression(result)?;
18400        }
18401        if let Some(else_) = &case.else_ {
18402            if multiline_case {
18403                self.write_newline();
18404                self.write_indent();
18405            } else {
18406                self.write_space();
18407            }
18408            self.write_keyword("ELSE");
18409            self.write_space();
18410            self.generate_expression(else_)?;
18411        }
18412        if multiline_case {
18413            self.indent_level -= 1;
18414            self.write_newline();
18415            self.write_indent();
18416        } else {
18417            self.write_space();
18418        }
18419        self.write_keyword("END");
18420        // Emit any comments that were attached to the CASE keyword
18421        for comment in &case.comments {
18422            self.write(" ");
18423            self.write_formatted_comment(comment);
18424        }
18425        Ok(())
18426    }
18427
18428    fn generate_function(&mut self, func: &Function) -> Result<()> {
18429        // Normalize function name based on dialect settings
18430        let normalized_name = if func.name.eq_ignore_ascii_case("GROUPING")
18431            && func.args.len() > 1
18432            && matches!(
18433                self.config.dialect,
18434                Some(DialectType::TSQL | DialectType::Fabric)
18435            ) {
18436            Cow::Borrowed("GROUPING_ID")
18437        } else if matches!(self.config.dialect, Some(DialectType::Snowflake))
18438            && func.name.to_ascii_uppercase().starts_with("IDENTIFIER(")
18439        {
18440            Cow::Borrowed(func.name.as_str())
18441        } else {
18442            self.normalize_func_name(&func.name)
18443        };
18444
18445        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
18446        if matches!(self.config.dialect, Some(DialectType::DuckDB))
18447            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
18448        {
18449            self.write("LIST_FILTER(");
18450            self.write("[");
18451            for (i, arg) in func.args.iter().enumerate() {
18452                if i > 0 {
18453                    self.write(", ");
18454                }
18455                self.generate_expression(arg)?;
18456            }
18457            self.write("], _u -> NOT _u IS NULL)");
18458            return Ok(());
18459        }
18460
18461        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
18462        // rather than bracket-array syntax.
18463        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18464            && func.name.eq_ignore_ascii_case("TO_VARIANT")
18465            && func.args.len() == 1
18466        {
18467            let array_expressions = match &func.args[0] {
18468                Expression::ArrayFunc(arr) => Some(&arr.expressions),
18469                Expression::Array(arr) => Some(&arr.expressions),
18470                _ => None,
18471            };
18472            if let Some(expressions) = array_expressions {
18473                self.write_keyword("TO_VARIANT");
18474                self.write("(");
18475                self.write_keyword("ARRAY_CONSTRUCT");
18476                self.write("(");
18477                for (i, arg) in expressions.iter().enumerate() {
18478                    if i > 0 {
18479                        self.write(", ");
18480                    }
18481                    self.generate_expression(arg)?;
18482                }
18483                self.write(")");
18484                self.write(")");
18485                return Ok(());
18486            }
18487        }
18488
18489        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
18490        if func.name.eq_ignore_ascii_case("STRUCT")
18491            && !matches!(
18492                self.config.dialect,
18493                Some(DialectType::BigQuery)
18494                    | Some(DialectType::Spark)
18495                    | Some(DialectType::Databricks)
18496                    | Some(DialectType::Hive)
18497                    | None
18498            )
18499        {
18500            return self.generate_struct_function_cross_dialect(func);
18501        }
18502
18503        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
18504        // This is an internal marker function for ::? JSON path syntax
18505        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
18506            self.generate_expression(&func.args[0])?;
18507            self.write("::?");
18508            // Extract the key from the string literal
18509            if let Expression::Literal(lit) = &func.args[1] {
18510                if let crate::expressions::Literal::String(key) = lit.as_ref() {
18511                    self.write(key);
18512                }
18513            } else {
18514                self.generate_expression(&func.args[1])?;
18515            }
18516            return Ok(());
18517        }
18518
18519        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
18520        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
18521            self.generate_expression(&func.args[0])?;
18522            self.write(" # ");
18523            self.generate_expression(&func.args[1])?;
18524            return Ok(());
18525        }
18526
18527        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
18528        if matches!(
18529            self.config.dialect,
18530            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
18531        ) && func.name.eq_ignore_ascii_case("TRY")
18532            && func.args.len() == 1
18533        {
18534            self.generate_expression(&func.args[0])?;
18535            return Ok(());
18536        }
18537
18538        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
18539        if self.config.dialect == Some(DialectType::ClickHouse)
18540            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
18541            && func.args.len() == 1
18542        {
18543            self.write("dateTrunc('DAY', ");
18544            self.generate_expression(&func.args[0])?;
18545            self.write(")");
18546            return Ok(());
18547        }
18548
18549        // ClickHouse uses dateTrunc casing.
18550        if self.config.dialect == Some(DialectType::ClickHouse)
18551            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18552            && func.args.len() == 2
18553        {
18554            self.write("dateTrunc(");
18555            self.generate_expression(&func.args[0])?;
18556            self.write(", ");
18557            self.generate_expression(&func.args[1])?;
18558            self.write(")");
18559            return Ok(());
18560        }
18561
18562        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
18563        if matches!(
18564            self.config.dialect,
18565            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
18566        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
18567        {
18568            self.write_keyword("SUBSTR");
18569            self.write("(");
18570            for (i, arg) in func.args.iter().enumerate() {
18571                if i > 0 {
18572                    self.write(", ");
18573                }
18574                self.generate_expression(arg)?;
18575            }
18576            self.write(")");
18577            return Ok(());
18578        }
18579
18580        if self.config.dialect == Some(DialectType::Snowflake)
18581            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
18582            && func.args.len() == 1
18583        {
18584            self.write_keyword("ARRAY_DISTINCT");
18585            self.write("(");
18586            self.write_keyword("ARRAY_COMPACT");
18587            self.write("(");
18588            self.generate_expression(&func.args[0])?;
18589            self.write("))");
18590            return Ok(());
18591        }
18592
18593        if self.config.dialect == Some(DialectType::Snowflake)
18594            && func.name.eq_ignore_ascii_case("LIST")
18595            && func.args.len() == 1
18596            && !matches!(func.args.first(), Some(Expression::Select(_)))
18597        {
18598            self.write_keyword("ARRAY_AGG");
18599            self.write("(");
18600            self.generate_expression(&func.args[0])?;
18601            self.write(")");
18602            return Ok(());
18603        }
18604
18605        // Redshift: CONCAT(a, b, ...) -> a || b || ...
18606        if self.config.dialect == Some(DialectType::Redshift)
18607            && func.name.eq_ignore_ascii_case("CONCAT")
18608            && func.args.len() >= 2
18609        {
18610            for (i, arg) in func.args.iter().enumerate() {
18611                if i > 0 {
18612                    self.write(" || ");
18613                }
18614                self.generate_expression(arg)?;
18615            }
18616            return Ok(());
18617        }
18618
18619        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
18620        if self.config.dialect == Some(DialectType::Redshift)
18621            && func.name.eq_ignore_ascii_case("CONCAT_WS")
18622            && func.args.len() >= 2
18623        {
18624            let sep = &func.args[0];
18625            for (i, arg) in func.args.iter().skip(1).enumerate() {
18626                if i > 0 {
18627                    self.write(" || ");
18628                    self.generate_expression(sep)?;
18629                    self.write(" || ");
18630                }
18631                self.generate_expression(arg)?;
18632            }
18633            return Ok(());
18634        }
18635
18636        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
18637        // Unit should be unquoted uppercase identifier
18638        if self.config.dialect == Some(DialectType::Redshift)
18639            && (func.name.eq_ignore_ascii_case("DATEDIFF")
18640                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
18641            && func.args.len() == 3
18642        {
18643            self.write_keyword("DATEDIFF");
18644            self.write("(");
18645            // First arg is unit - normalize to unquoted uppercase
18646            self.write_redshift_date_part(&func.args[0]);
18647            self.write(", ");
18648            self.generate_expression(&func.args[1])?;
18649            self.write(", ");
18650            self.generate_expression(&func.args[2])?;
18651            self.write(")");
18652            return Ok(());
18653        }
18654
18655        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
18656        // Unit should be unquoted uppercase identifier
18657        if self.config.dialect == Some(DialectType::Redshift)
18658            && (func.name.eq_ignore_ascii_case("DATEADD")
18659                || func.name.eq_ignore_ascii_case("DATE_ADD"))
18660            && func.args.len() == 3
18661        {
18662            self.write_keyword("DATEADD");
18663            self.write("(");
18664            // First arg is unit - normalize to unquoted uppercase
18665            self.write_redshift_date_part(&func.args[0]);
18666            self.write(", ");
18667            self.generate_expression(&func.args[1])?;
18668            self.write(", ");
18669            self.generate_expression(&func.args[2])?;
18670            self.write(")");
18671            return Ok(());
18672        }
18673
18674        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
18675        if func.name.eq_ignore_ascii_case("UUID_STRING")
18676            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
18677        {
18678            if matches!(
18679                self.config.dialect,
18680                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
18681            ) {
18682                self.write_keyword("CAST");
18683                self.write("(");
18684                self.write_keyword("UUID");
18685                self.write("() ");
18686                self.write_keyword("AS");
18687                self.write(" ");
18688                self.write_keyword("STRING");
18689                self.write(")");
18690                return Ok(());
18691            }
18692
18693            if matches!(
18694                self.config.dialect,
18695                Some(DialectType::Presto | DialectType::Trino)
18696            ) {
18697                self.write_keyword("CAST");
18698                self.write("(");
18699                self.write_keyword("UUID");
18700                self.write("() ");
18701                self.write_keyword("AS");
18702                self.write(" ");
18703                self.write_keyword("VARCHAR");
18704                self.write(")");
18705                return Ok(());
18706            }
18707
18708            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
18709                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(");
18710                self.generate_expression(&func.args[0])?;
18711                self.write(", '-', '')) || ENCODE(");
18712                self.generate_expression(&func.args[1])?;
18713                self.write(")), 1, 32) AS h))");
18714                return Ok(());
18715            }
18716
18717            let func_name = match self.config.dialect {
18718                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
18719                Some(DialectType::BigQuery) => "GENERATE_UUID",
18720                _ => "UUID",
18721            };
18722            self.write_keyword(func_name);
18723            self.write("()");
18724            return Ok(());
18725        }
18726
18727        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
18728        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
18729        // Positional args are mapped to named parameters.
18730        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18731            && func.name.eq_ignore_ascii_case("GENERATOR")
18732        {
18733            let has_positional_args =
18734                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
18735            if has_positional_args {
18736                let param_names = ["ROWCOUNT", "TIMELIMIT"];
18737                self.write_keyword("GENERATOR");
18738                self.write("(");
18739                for (i, arg) in func.args.iter().enumerate() {
18740                    if i > 0 {
18741                        self.write(", ");
18742                    }
18743                    if i < param_names.len() {
18744                        self.write_keyword(param_names[i]);
18745                        self.write(" => ");
18746                        self.generate_expression(arg)?;
18747                    } else {
18748                        self.generate_expression(arg)?;
18749                    }
18750                }
18751                self.write(")");
18752                return Ok(());
18753            }
18754        }
18755
18756        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
18757        // Unit should be quoted uppercase string
18758        if self.config.dialect == Some(DialectType::Redshift)
18759            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18760            && func.args.len() == 2
18761        {
18762            self.write_keyword("DATE_TRUNC");
18763            self.write("(");
18764            // First arg is unit - normalize to quoted uppercase
18765            self.write_redshift_date_part_quoted(&func.args[0]);
18766            self.write(", ");
18767            self.generate_expression(&func.args[1])?;
18768            self.write(")");
18769            return Ok(());
18770        }
18771
18772        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
18773        if matches!(
18774            self.config.dialect,
18775            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18776        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18777            || func.name.eq_ignore_ascii_case("DATEPART"))
18778            && func.args.len() == 2
18779        {
18780            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18781                let date_part = self.classify_tsql_date_part_name(&part);
18782                self.generate_tsql_date_part(date_part, &func.args[1])?;
18783            } else {
18784                self.write_keyword("DATEPART");
18785                self.write("(");
18786                self.generate_expression(&func.args[0])?;
18787                self.write(", ");
18788                self.generate_expression(&func.args[1])?;
18789                self.write(")");
18790            }
18791            return Ok(());
18792        }
18793
18794        // TSQL/Fabric: DATETRUNC(datepart, value) requires an unquoted datepart keyword.
18795        if matches!(
18796            self.config.dialect,
18797            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18798        ) && (func.name.eq_ignore_ascii_case("DATETRUNC")
18799            || func.name.eq_ignore_ascii_case("DATE_TRUNC"))
18800            && func.args.len() == 2
18801        {
18802            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18803                let date_part = self.classify_tsql_date_trunc_name(&part);
18804                self.generate_tsql_date_trunc(date_part, &func.args[1])?;
18805            } else {
18806                self.write_keyword("DATETRUNC");
18807                self.write("(");
18808                self.generate_expression(&func.args[0])?;
18809                self.write(", ");
18810                self.generate_expression(&func.args[1])?;
18811                self.write(")");
18812            }
18813            return Ok(());
18814        }
18815
18816        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
18817        if matches!(
18818            self.config.dialect,
18819            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
18820        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18821            || func.name.eq_ignore_ascii_case("DATEPART"))
18822            && func.args.len() == 2
18823        {
18824            self.write_keyword("EXTRACT");
18825            self.write("(");
18826            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
18827            match &func.args[0] {
18828                Expression::Literal(lit)
18829                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18830                {
18831                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
18832                        unreachable!()
18833                    };
18834                    self.write(&s.to_ascii_lowercase());
18835                }
18836                _ => self.generate_expression(&func.args[0])?,
18837            }
18838            self.write_space();
18839            self.write_keyword("FROM");
18840            self.write_space();
18841            self.generate_expression(&func.args[1])?;
18842            self.write(")");
18843            return Ok(());
18844        }
18845
18846        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
18847        if self.config.dialect == Some(DialectType::PostgreSQL)
18848            && matches!(
18849                func.name.to_ascii_uppercase().as_str(),
18850                "DATE_ADD" | "DATE_SUB"
18851            )
18852            && func.args.len() == 2
18853            && matches!(func.args[1], Expression::Interval(_))
18854        {
18855            self.generate_expression(&func.args[0])?;
18856            self.write_space();
18857            if func.name.eq_ignore_ascii_case("DATE_SUB") {
18858                self.write("-");
18859            } else {
18860                self.write("+");
18861            }
18862            self.write_space();
18863            self.generate_expression(&func.args[1])?;
18864            return Ok(());
18865        }
18866
18867        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
18868        // Also DATE literals in Dremio should be CAST(...AS DATE)
18869        if self.config.dialect == Some(DialectType::Dremio)
18870            && (func.name.eq_ignore_ascii_case("DATE_PART")
18871                || func.name.eq_ignore_ascii_case("DATEPART"))
18872            && func.args.len() == 2
18873        {
18874            self.write_keyword("EXTRACT");
18875            self.write("(");
18876            self.generate_expression(&func.args[0])?;
18877            self.write_space();
18878            self.write_keyword("FROM");
18879            self.write_space();
18880            // For Dremio, DATE literals should become CAST('value' AS DATE)
18881            self.generate_dremio_date_expression(&func.args[1])?;
18882            self.write(")");
18883            return Ok(());
18884        }
18885
18886        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18887        if self.config.dialect == Some(DialectType::Dremio)
18888            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18889            && func.args.is_empty()
18890        {
18891            self.write_keyword("CURRENT_DATE_UTC");
18892            return Ok(());
18893        }
18894
18895        // Dremio: DATETYPE(year, month, day) transformation
18896        // - If all args are integer literals: DATE('YYYY-MM-DD')
18897        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18898        if self.config.dialect == Some(DialectType::Dremio)
18899            && func.name.eq_ignore_ascii_case("DATETYPE")
18900            && func.args.len() == 3
18901        {
18902            // Helper function to extract integer from number literal
18903            fn get_int_literal(expr: &Expression) -> Option<i64> {
18904                if let Expression::Literal(lit) = expr {
18905                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18906                        s.parse::<i64>().ok()
18907                    } else {
18908                        None
18909                    }
18910                } else {
18911                    None
18912                }
18913            }
18914
18915            // Check if all arguments are integer literals
18916            if let (Some(year), Some(month), Some(day)) = (
18917                get_int_literal(&func.args[0]),
18918                get_int_literal(&func.args[1]),
18919                get_int_literal(&func.args[2]),
18920            ) {
18921                // All are integer literals: DATE('YYYY-MM-DD')
18922                self.write_keyword("DATE");
18923                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18924                return Ok(());
18925            }
18926
18927            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18928            self.write_keyword("CAST");
18929            self.write("(");
18930            self.write_keyword("CONCAT");
18931            self.write("(");
18932            self.generate_expression(&func.args[0])?;
18933            self.write(", '-', ");
18934            self.generate_expression(&func.args[1])?;
18935            self.write(", '-', ");
18936            self.generate_expression(&func.args[2])?;
18937            self.write(")");
18938            self.write_space();
18939            self.write_keyword("AS");
18940            self.write_space();
18941            self.write_keyword("DATE");
18942            self.write(")");
18943            return Ok(());
18944        }
18945
18946        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18947        // when it's not an integer literal
18948        let is_presto_like = matches!(
18949            self.config.dialect,
18950            Some(DialectType::Presto) | Some(DialectType::Trino)
18951        );
18952        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18953            self.write_keyword("DATE_ADD");
18954            self.write("(");
18955            // First arg: unit (pass through as-is, e.g., 'DAY')
18956            self.generate_expression(&func.args[0])?;
18957            self.write(", ");
18958            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18959            let interval = &func.args[1];
18960            let needs_cast = !self.returns_integer_type(interval);
18961            if needs_cast {
18962                self.write_keyword("CAST");
18963                self.write("(");
18964            }
18965            self.generate_expression(interval)?;
18966            if needs_cast {
18967                self.write_space();
18968                self.write_keyword("AS");
18969                self.write_space();
18970                self.write_keyword("BIGINT");
18971                self.write(")");
18972            }
18973            self.write(", ");
18974            // Third arg: date
18975            self.generate_expression(&func.args[2])?;
18976            self.write(")");
18977            return Ok(());
18978        }
18979
18980        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
18981        let use_brackets = func.use_bracket_syntax;
18982
18983        // Special case: functions WITH ORDINALITY need special output order
18984        // Input: FUNC(args) WITH ORDINALITY
18985        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
18986        // Output must be: FUNC(args) WITH ORDINALITY
18987        let has_ordinality = func.name.len() >= 16
18988            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
18989        let output_name = if has_ordinality {
18990            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
18991            self.normalize_func_name(base_name)
18992        } else {
18993            normalized_name.clone()
18994        };
18995
18996        // For qualified names (schema.function or object.method), preserve original case
18997        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
18998        let quote_source_clickhouse_function =
18999            matches!(self.config.dialect, Some(DialectType::ClickHouse))
19000                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
19001                && func.quoted;
19002
19003        if quote_source_clickhouse_function {
19004            self.generate_identifier(&Identifier {
19005                name: func.name.clone(),
19006                quoted: true,
19007                trailing_comments: Vec::new(),
19008                span: None,
19009            })?;
19010        } else if func.name.contains('.') && !has_ordinality {
19011            // Don't normalize qualified functions - preserve original case
19012            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
19013            if func.quoted {
19014                self.write("`");
19015                self.write(&func.name);
19016                self.write("`");
19017            } else {
19018                self.write(&func.name);
19019            }
19020        } else {
19021            self.write(&output_name);
19022        }
19023
19024        // If no_parens is true and there are no args, output just the function name
19025        // Unless the target dialect requires parens for this function
19026        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
19027            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
19028                || func.name.eq_ignore_ascii_case("SESSION_USER")
19029                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
19030            {
19031                matches!(
19032                    self.config.dialect,
19033                    Some(DialectType::Snowflake)
19034                        | Some(DialectType::Spark)
19035                        | Some(DialectType::Databricks)
19036                        | Some(DialectType::Hive)
19037                )
19038            } else {
19039                false
19040            };
19041            !needs_parens
19042        };
19043        if force_parens {
19044            // Output trailing comments
19045            for comment in &func.trailing_comments {
19046                self.write_space();
19047                self.write_formatted_comment(comment);
19048            }
19049            return Ok(());
19050        }
19051
19052        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
19053        if func.name.eq_ignore_ascii_case("CUBE")
19054            || func.name.eq_ignore_ascii_case("ROLLUP")
19055            || func.name.eq_ignore_ascii_case("GROUPING SETS")
19056        {
19057            self.write(" (");
19058        } else if use_brackets {
19059            self.write("[");
19060        } else {
19061            self.write("(");
19062        }
19063        if func.distinct {
19064            self.write_keyword("DISTINCT");
19065            self.write_space();
19066        }
19067
19068        // Check if arguments should be split onto multiple lines (pretty + too wide)
19069        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
19070            && (func.name.eq_ignore_ascii_case("TABLE")
19071                || func.name.eq_ignore_ascii_case("FLATTEN"));
19072        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
19073        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
19074            || func.name.eq_ignore_ascii_case("CUBE")
19075            || func.name.eq_ignore_ascii_case("ROLLUP");
19076        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
19077            if is_grouping_func {
19078                true
19079            } else {
19080                // Pre-render arguments to check total width
19081                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
19082                for arg in &func.args {
19083                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
19084                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
19085                    temp_gen.generate_expression(arg)?;
19086                    expr_strings.push(temp_gen.output);
19087                }
19088                self.too_wide(&expr_strings)
19089            }
19090        } else {
19091            false
19092        };
19093
19094        if should_split {
19095            // Split onto multiple lines
19096            self.write_newline();
19097            self.indent_level += 1;
19098            for (i, arg) in func.args.iter().enumerate() {
19099                self.write_indent();
19100                self.generate_expression(arg)?;
19101                if i + 1 < func.args.len() {
19102                    self.write(",");
19103                }
19104                self.write_newline();
19105            }
19106            self.indent_level -= 1;
19107            self.write_indent();
19108        } else {
19109            // All on one line
19110            for (i, arg) in func.args.iter().enumerate() {
19111                if i > 0 {
19112                    self.write(", ");
19113                }
19114                self.generate_expression(arg)?;
19115            }
19116        }
19117
19118        if use_brackets {
19119            self.write("]");
19120        } else {
19121            self.write(")");
19122        }
19123        // Append WITH ORDINALITY after closing paren for table-valued functions
19124        if has_ordinality {
19125            self.write_space();
19126            self.write_keyword("WITH ORDINALITY");
19127        }
19128        // Output trailing comments
19129        for comment in &func.trailing_comments {
19130            self.write_space();
19131            self.write_formatted_comment(comment);
19132        }
19133        Ok(())
19134    }
19135
19136    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
19137        self.generate_expression(&fe.this)?;
19138        self.write_keyword(" EMITS ");
19139        self.generate_expression(&fe.emits)?;
19140        Ok(())
19141    }
19142
19143    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
19144        // Normalize function name based on dialect settings
19145        let mut normalized_name = self.normalize_func_name(&func.name);
19146
19147        // Dialect-specific name mappings for aggregate functions
19148        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
19149            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
19150            match self.config.dialect {
19151                Some(DialectType::ClickHouse) => {
19152                    normalized_name = if is_max {
19153                        Cow::Borrowed("argMax")
19154                    } else {
19155                        Cow::Borrowed("argMin")
19156                    };
19157                }
19158                Some(DialectType::DuckDB) => {
19159                    normalized_name = if is_max {
19160                        Cow::Borrowed("ARG_MAX")
19161                    } else {
19162                        Cow::Borrowed("ARG_MIN")
19163                    };
19164                }
19165                _ => {}
19166            }
19167        }
19168        self.write(normalized_name.as_ref());
19169        self.write("(");
19170        if func.distinct {
19171            self.write_keyword("DISTINCT");
19172            self.write_space();
19173        }
19174
19175        // Check if we need to transform multi-arg COUNT DISTINCT
19176        // When dialect doesn't support multi_arg_distinct, transform:
19177        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19178        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
19179        let needs_multi_arg_transform =
19180            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
19181
19182        if needs_multi_arg_transform {
19183            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19184            self.write_keyword("CASE");
19185            for arg in &func.args {
19186                self.write_space();
19187                self.write_keyword("WHEN");
19188                self.write_space();
19189                self.generate_expression(arg)?;
19190                self.write_space();
19191                self.write_keyword("IS NULL THEN NULL");
19192            }
19193            self.write_space();
19194            self.write_keyword("ELSE");
19195            self.write(" (");
19196            for (i, arg) in func.args.iter().enumerate() {
19197                if i > 0 {
19198                    self.write(", ");
19199                }
19200                self.generate_expression(arg)?;
19201            }
19202            self.write(")");
19203            self.write_space();
19204            self.write_keyword("END");
19205        } else {
19206            for (i, arg) in func.args.iter().enumerate() {
19207                if i > 0 {
19208                    self.write(", ");
19209                }
19210                self.generate_expression(arg)?;
19211            }
19212        }
19213
19214        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
19215        let clickhouse_ignore_nulls_outside =
19216            matches!(self.config.dialect, Some(DialectType::ClickHouse));
19217        if self.config.ignore_nulls_in_func
19218            && !matches!(
19219                self.config.dialect,
19220                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
19221            )
19222        {
19223            if let Some(ignore) = func.ignore_nulls {
19224                self.write_space();
19225                if ignore {
19226                    self.write_keyword("IGNORE NULLS");
19227                } else {
19228                    self.write_keyword("RESPECT NULLS");
19229                }
19230            }
19231        }
19232
19233        // ORDER BY inside aggregate
19234        if !func.order_by.is_empty() {
19235            self.write_space();
19236            self.write_keyword("ORDER BY");
19237            self.write_space();
19238            for (i, ord) in func.order_by.iter().enumerate() {
19239                if i > 0 {
19240                    self.write(", ");
19241                }
19242                self.generate_ordered(ord)?;
19243            }
19244        }
19245
19246        // LIMIT inside aggregate
19247        if let Some(limit) = &func.limit {
19248            self.write_space();
19249            self.write_keyword("LIMIT");
19250            self.write_space();
19251            // Check if this is a Tuple representing LIMIT offset, count
19252            if let Expression::Tuple(t) = limit.as_ref() {
19253                if t.expressions.len() == 2 {
19254                    self.generate_expression(&t.expressions[0])?;
19255                    self.write(", ");
19256                    self.generate_expression(&t.expressions[1])?;
19257                } else {
19258                    self.generate_expression(limit)?;
19259                }
19260            } else {
19261                self.generate_expression(limit)?;
19262            }
19263        }
19264
19265        self.write(")");
19266
19267        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
19268        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
19269            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19270        {
19271            if let Some(ignore) = func.ignore_nulls {
19272                self.write_space();
19273                if ignore {
19274                    self.write_keyword("IGNORE NULLS");
19275                } else {
19276                    self.write_keyword("RESPECT NULLS");
19277                }
19278            }
19279        }
19280
19281        if let Some(filter) = &func.filter {
19282            self.write_space();
19283            self.write_keyword("FILTER");
19284            self.write("(");
19285            self.write_keyword("WHERE");
19286            self.write_space();
19287            self.generate_expression(filter)?;
19288            self.write(")");
19289        }
19290
19291        Ok(())
19292    }
19293
19294    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
19295        self.generate_expression(&wf.this)?;
19296
19297        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
19298        if let Some(keep) = &wf.keep {
19299            self.write_space();
19300            self.write_keyword("KEEP");
19301            self.write(" (");
19302            self.write_keyword("DENSE_RANK");
19303            self.write_space();
19304            if keep.first {
19305                self.write_keyword("FIRST");
19306            } else {
19307                self.write_keyword("LAST");
19308            }
19309            self.write_space();
19310            self.write_keyword("ORDER BY");
19311            self.write_space();
19312            for (i, ord) in keep.order_by.iter().enumerate() {
19313                if i > 0 {
19314                    self.write(", ");
19315                }
19316                self.generate_ordered(ord)?;
19317            }
19318            self.write(")");
19319        }
19320
19321        // Check if there's any OVER clause content
19322        let has_over = !wf.over.partition_by.is_empty()
19323            || !wf.over.order_by.is_empty()
19324            || wf.over.frame.is_some()
19325            || wf.over.window_name.is_some();
19326
19327        // Only output OVER if there's actual window specification (not just KEEP alone)
19328        if has_over {
19329            self.write_space();
19330            self.write_keyword("OVER");
19331
19332            // Check if this is just a bare named window reference (no parens needed)
19333            let has_specs = !wf.over.partition_by.is_empty()
19334                || !wf.over.order_by.is_empty()
19335                || wf.over.frame.is_some();
19336
19337            if wf.over.window_name.is_some() && !has_specs {
19338                // OVER window_name (without parentheses)
19339                self.write_space();
19340                self.write(&wf.over.window_name.as_ref().unwrap().name);
19341            } else {
19342                // OVER (...) or OVER (window_name ...)
19343                self.write(" (");
19344                self.generate_over(&wf.over)?;
19345                self.write(")");
19346            }
19347        } else if wf.keep.is_none() {
19348            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
19349            self.write_space();
19350            self.write_keyword("OVER");
19351            self.write(" ()");
19352        }
19353
19354        Ok(())
19355    }
19356
19357    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
19358    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
19359        self.generate_expression(&wg.this)?;
19360        self.write_space();
19361        self.write_keyword("WITHIN GROUP");
19362        self.write(" (");
19363        self.write_keyword("ORDER BY");
19364        self.write_space();
19365        for (i, ord) in wg.order_by.iter().enumerate() {
19366            if i > 0 {
19367                self.write(", ");
19368            }
19369            self.generate_ordered(ord)?;
19370        }
19371        self.write(")");
19372        Ok(())
19373    }
19374
19375    /// Generate the contents of an OVER clause (without parentheses)
19376    fn generate_over(&mut self, over: &Over) -> Result<()> {
19377        let mut has_content = false;
19378
19379        // Named window reference
19380        if let Some(name) = &over.window_name {
19381            self.write(&name.name);
19382            has_content = true;
19383        }
19384
19385        // PARTITION BY
19386        if !over.partition_by.is_empty() {
19387            if has_content {
19388                self.write_space();
19389            }
19390            self.write_keyword("PARTITION BY");
19391            self.write_space();
19392            for (i, expr) in over.partition_by.iter().enumerate() {
19393                if i > 0 {
19394                    self.write(", ");
19395                }
19396                self.generate_expression(expr)?;
19397            }
19398            has_content = true;
19399        }
19400
19401        // ORDER BY
19402        if !over.order_by.is_empty() {
19403            if has_content {
19404                self.write_space();
19405            }
19406            self.write_keyword("ORDER BY");
19407            self.write_space();
19408            for (i, ordered) in over.order_by.iter().enumerate() {
19409                if i > 0 {
19410                    self.write(", ");
19411                }
19412                self.generate_ordered(ordered)?;
19413            }
19414            has_content = true;
19415        }
19416
19417        // Window frame
19418        if let Some(frame) = &over.frame {
19419            if has_content {
19420                self.write_space();
19421            }
19422            self.generate_window_frame(frame)?;
19423        }
19424
19425        Ok(())
19426    }
19427
19428    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
19429        // Exasol uses lowercase for frame kind (rows/range/groups)
19430        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19431
19432        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
19433        if !lowercase_frame {
19434            if let Some(kind_text) = &frame.kind_text {
19435                self.write(kind_text);
19436            } else {
19437                match frame.kind {
19438                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
19439                    WindowFrameKind::Range => self.write_keyword("RANGE"),
19440                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
19441                }
19442            }
19443        } else {
19444            match frame.kind {
19445                WindowFrameKind::Rows => self.write("rows"),
19446                WindowFrameKind::Range => self.write("range"),
19447                WindowFrameKind::Groups => self.write("groups"),
19448            }
19449        }
19450
19451        // Use BETWEEN format only when there's an explicit end bound,
19452        // or when normalize_window_frame_between is enabled and the start is a directional bound
19453        self.write_space();
19454        let should_normalize = self.config.normalize_window_frame_between
19455            && frame.end.is_none()
19456            && matches!(
19457                frame.start,
19458                WindowFrameBound::Preceding(_)
19459                    | WindowFrameBound::Following(_)
19460                    | WindowFrameBound::UnboundedPreceding
19461                    | WindowFrameBound::UnboundedFollowing
19462            );
19463
19464        if let Some(end) = &frame.end {
19465            // BETWEEN format: RANGE BETWEEN start AND end
19466            self.write_keyword("BETWEEN");
19467            self.write_space();
19468            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19469            self.write_space();
19470            self.write_keyword("AND");
19471            self.write_space();
19472            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
19473        } else if should_normalize {
19474            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
19475            self.write_keyword("BETWEEN");
19476            self.write_space();
19477            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19478            self.write_space();
19479            self.write_keyword("AND");
19480            self.write_space();
19481            self.write_keyword("CURRENT ROW");
19482        } else {
19483            // Single bound format: RANGE CURRENT ROW
19484            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19485        }
19486
19487        // EXCLUDE clause
19488        if let Some(exclude) = &frame.exclude {
19489            self.write_space();
19490            self.write_keyword("EXCLUDE");
19491            self.write_space();
19492            match exclude {
19493                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
19494                WindowFrameExclude::Group => self.write_keyword("GROUP"),
19495                WindowFrameExclude::Ties => self.write_keyword("TIES"),
19496                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
19497            }
19498        }
19499
19500        Ok(())
19501    }
19502
19503    fn generate_window_frame_bound(
19504        &mut self,
19505        bound: &WindowFrameBound,
19506        side_text: Option<&str>,
19507    ) -> Result<()> {
19508        // Exasol uses lowercase for preceding/following
19509        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19510
19511        match bound {
19512            WindowFrameBound::CurrentRow => {
19513                self.write_keyword("CURRENT ROW");
19514            }
19515            WindowFrameBound::UnboundedPreceding => {
19516                self.write_keyword("UNBOUNDED");
19517                self.write_space();
19518                if lowercase_frame {
19519                    self.write("preceding");
19520                } else if let Some(text) = side_text {
19521                    self.write(text);
19522                } else {
19523                    self.write_keyword("PRECEDING");
19524                }
19525            }
19526            WindowFrameBound::UnboundedFollowing => {
19527                self.write_keyword("UNBOUNDED");
19528                self.write_space();
19529                if lowercase_frame {
19530                    self.write("following");
19531                } else if let Some(text) = side_text {
19532                    self.write(text);
19533                } else {
19534                    self.write_keyword("FOLLOWING");
19535                }
19536            }
19537            WindowFrameBound::Preceding(expr) => {
19538                self.generate_expression(expr)?;
19539                self.write_space();
19540                if lowercase_frame {
19541                    self.write("preceding");
19542                } else if let Some(text) = side_text {
19543                    self.write(text);
19544                } else {
19545                    self.write_keyword("PRECEDING");
19546                }
19547            }
19548            WindowFrameBound::Following(expr) => {
19549                self.generate_expression(expr)?;
19550                self.write_space();
19551                if lowercase_frame {
19552                    self.write("following");
19553                } else if let Some(text) = side_text {
19554                    self.write(text);
19555                } else {
19556                    self.write_keyword("FOLLOWING");
19557                }
19558            }
19559            WindowFrameBound::BarePreceding => {
19560                if lowercase_frame {
19561                    self.write("preceding");
19562                } else if let Some(text) = side_text {
19563                    self.write(text);
19564                } else {
19565                    self.write_keyword("PRECEDING");
19566                }
19567            }
19568            WindowFrameBound::BareFollowing => {
19569                if lowercase_frame {
19570                    self.write("following");
19571                } else if let Some(text) = side_text {
19572                    self.write(text);
19573                } else {
19574                    self.write_keyword("FOLLOWING");
19575                }
19576            }
19577            WindowFrameBound::Value(expr) => {
19578                // Bare numeric bound without PRECEDING/FOLLOWING
19579                self.generate_expression(expr)?;
19580            }
19581        }
19582        Ok(())
19583    }
19584
19585    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
19586        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
19587        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
19588        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
19589            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
19590            && !matches!(&interval.this, Some(Expression::Literal(_)));
19591
19592        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
19593        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
19594        if self.config.single_string_interval {
19595            if let (
19596                Some(Expression::Literal(lit)),
19597                Some(IntervalUnitSpec::Simple {
19598                    ref unit,
19599                    ref use_plural,
19600                }),
19601            ) = (&interval.this, &interval.unit)
19602            {
19603                if let Literal::String(ref val) = lit.as_ref() {
19604                    self.write_keyword("INTERVAL");
19605                    self.write_space();
19606                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19607                    let unit_str = self.interval_unit_str(unit, effective_plural);
19608                    self.write("'");
19609                    self.write(val);
19610                    self.write(" ");
19611                    self.write(&unit_str);
19612                    self.write("'");
19613                    return Ok(());
19614                }
19615            }
19616        }
19617
19618        if !skip_interval_keyword {
19619            self.write_keyword("INTERVAL");
19620        }
19621
19622        // Generate value if present
19623        if let Some(ref value) = interval.this {
19624            if !skip_interval_keyword {
19625                self.write_space();
19626            }
19627            // If the value is a complex expression (not a literal/column/function call)
19628            // and there's a unit, wrap it in parentheses
19629            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
19630            let needs_parens = interval.unit.is_some()
19631                && matches!(
19632                    value,
19633                    Expression::Add(_)
19634                        | Expression::Sub(_)
19635                        | Expression::Mul(_)
19636                        | Expression::Div(_)
19637                        | Expression::Mod(_)
19638                        | Expression::BitwiseAnd(_)
19639                        | Expression::BitwiseOr(_)
19640                        | Expression::BitwiseXor(_)
19641                );
19642            if needs_parens {
19643                self.write("(");
19644            }
19645            self.generate_expression(value)?;
19646            if needs_parens {
19647                self.write(")");
19648            }
19649        }
19650
19651        // Generate unit if present
19652        if let Some(ref unit_spec) = interval.unit {
19653            self.write_space();
19654            self.write_interval_unit_spec(unit_spec)?;
19655        }
19656
19657        Ok(())
19658    }
19659
19660    /// Return the string representation of an interval unit
19661    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
19662        match (unit, use_plural) {
19663            (IntervalUnit::Year, false) => "YEAR",
19664            (IntervalUnit::Year, true) => "YEARS",
19665            (IntervalUnit::Quarter, false) => "QUARTER",
19666            (IntervalUnit::Quarter, true) => "QUARTERS",
19667            (IntervalUnit::Month, false) => "MONTH",
19668            (IntervalUnit::Month, true) => "MONTHS",
19669            (IntervalUnit::Week, false) => "WEEK",
19670            (IntervalUnit::Week, true) => "WEEKS",
19671            (IntervalUnit::Day, false) => "DAY",
19672            (IntervalUnit::Day, true) => "DAYS",
19673            (IntervalUnit::Hour, false) => "HOUR",
19674            (IntervalUnit::Hour, true) => "HOURS",
19675            (IntervalUnit::Minute, false) => "MINUTE",
19676            (IntervalUnit::Minute, true) => "MINUTES",
19677            (IntervalUnit::Second, false) => "SECOND",
19678            (IntervalUnit::Second, true) => "SECONDS",
19679            (IntervalUnit::Millisecond, false) => "MILLISECOND",
19680            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
19681            (IntervalUnit::Microsecond, false) => "MICROSECOND",
19682            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
19683            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
19684            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
19685        }
19686    }
19687
19688    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
19689        match unit_spec {
19690            IntervalUnitSpec::Simple { unit, use_plural } => {
19691                // If dialect doesn't allow plural forms, force singular
19692                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19693                self.write_simple_interval_unit(unit, effective_plural);
19694            }
19695            IntervalUnitSpec::Span(span) => {
19696                self.write_simple_interval_unit(&span.this, false);
19697                self.write_space();
19698                self.write_keyword("TO");
19699                self.write_space();
19700                self.write_simple_interval_unit(&span.expression, false);
19701            }
19702            IntervalUnitSpec::ExprSpan(span) => {
19703                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
19704                self.generate_expression(&span.this)?;
19705                self.write_space();
19706                self.write_keyword("TO");
19707                self.write_space();
19708                self.generate_expression(&span.expression)?;
19709            }
19710            IntervalUnitSpec::Expr(expr) => {
19711                self.generate_expression(expr)?;
19712            }
19713        }
19714        Ok(())
19715    }
19716
19717    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
19718        // Output interval unit, respecting plural preference
19719        match (unit, use_plural) {
19720            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
19721            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
19722            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
19723            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
19724            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
19725            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
19726            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
19727            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
19728            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
19729            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
19730            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
19731            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
19732            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
19733            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
19734            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
19735            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
19736            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
19737            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
19738            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
19739            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
19740            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
19741            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
19742        }
19743    }
19744
19745    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
19746    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
19747    fn write_redshift_date_part(&mut self, expr: &Expression) {
19748        let part_str = self.extract_date_part_string(expr);
19749        if let Some(part) = part_str {
19750            let normalized = self.normalize_date_part(&part);
19751            self.write_keyword(&normalized);
19752        } else {
19753            // If we can't extract a date part string, fall back to generating the expression
19754            let _ = self.generate_expression(expr);
19755        }
19756    }
19757
19758    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
19759    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
19760    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
19761        let part_str = self.extract_date_part_string(expr);
19762        if let Some(part) = part_str {
19763            let normalized = self.normalize_date_part(&part);
19764            self.write("'");
19765            self.write(&normalized);
19766            self.write("'");
19767        } else {
19768            // If we can't extract a date part string, fall back to generating the expression
19769            let _ = self.generate_expression(expr);
19770        }
19771    }
19772
19773    /// Extract date part string from expression (handles string literals and identifiers)
19774    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
19775        match expr {
19776            Expression::Literal(lit)
19777                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
19778            {
19779                let crate::expressions::Literal::String(s) = lit.as_ref() else {
19780                    unreachable!()
19781                };
19782                Some(s.clone())
19783            }
19784            Expression::Identifier(id) => Some(id.name.clone()),
19785            Expression::Var(v) => Some(v.this.clone()),
19786            Expression::Column(col) if col.table.is_none() => {
19787                // Simple column reference without table prefix, treat as identifier
19788                Some(col.name.name.clone())
19789            }
19790            Expression::Cast(cast)
19791                if cast.format.is_none()
19792                    && cast.default.is_none()
19793                    && Self::is_string_data_type(&cast.to)
19794                    && matches!(
19795                        &cast.this,
19796                        Expression::Literal(lit)
19797                            if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
19798                    ) =>
19799            {
19800                self.extract_date_part_string(&cast.this)
19801            }
19802            _ => None,
19803        }
19804    }
19805
19806    fn classify_tsql_datetime_field(&self, field: &DateTimeField) -> TsqlDatePart {
19807        match field {
19808            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19809            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19810            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19811            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19812            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19813            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19814            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19815            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19816            DateTimeField::DayOfWeek => TsqlDatePart::Native("WEEKDAY".to_string()),
19817            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19818            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19819            DateTimeField::WeekWithModifier(modifier) => {
19820                TsqlDatePart::Unsupported(format!("WEEK({modifier})"))
19821            }
19822            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19823            DateTimeField::Epoch => TsqlDatePart::Epoch,
19824            DateTimeField::TimezoneMinute => TsqlDatePart::Native("TZOFFSET".to_string()),
19825            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19826            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19827            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19828            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19829            DateTimeField::Custom(name) => self.classify_tsql_date_part_name(name),
19830        }
19831    }
19832
19833    fn classify_tsql_date_part_name(&self, part: &str) -> TsqlDatePart {
19834        let trimmed = part.trim();
19835        let upper = trimmed.to_ascii_uppercase();
19836        match upper.as_str() {
19837            "YEAR" => TsqlDatePart::Native(trimmed.to_string()),
19838            "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19839                TsqlDatePart::Native("YEAR".to_string())
19840            }
19841            "QUARTER" => TsqlDatePart::Native(trimmed.to_string()),
19842            "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => TsqlDatePart::Native("QUARTER".to_string()),
19843            "MONTH" => TsqlDatePart::Native(trimmed.to_string()),
19844            "MM" | "M" | "MON" | "MONS" | "MONTHS" => TsqlDatePart::Native("MONTH".to_string()),
19845            "DAYOFYEAR" => TsqlDatePart::Native(trimmed.to_string()),
19846            "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19847            "DAY" => TsqlDatePart::Native(trimmed.to_string()),
19848            "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19849            "WEEK" | "W" | "WK" | "WW" | "WEEKOFYEAR" | "WOY" | "WY" | "WEEKS" => {
19850                if upper == "WEEK" {
19851                    TsqlDatePart::Native(trimmed.to_string())
19852                } else {
19853                    TsqlDatePart::Native("WEEK".to_string())
19854                }
19855            }
19856            "WEEKDAY" => TsqlDatePart::Native(trimmed.to_string()),
19857            "DAYOFWEEK" | "DOW" | "DW" => TsqlDatePart::Native("WEEKDAY".to_string()),
19858            "ISODOW" | "ISO_DOW" | "DOW_ISO" | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY"
19859            | "DAYOFWEEKISO" | "DAYOFWEEK_ISO" | "WEEKDAY_ISO" => TsqlDatePart::IsoDayOfWeek,
19860            "HOUR" => TsqlDatePart::Native(trimmed.to_string()),
19861            "H" | "HH" | "HR" | "HOURS" | "HRS" => TsqlDatePart::Native("HOUR".to_string()),
19862            "MINUTE" => TsqlDatePart::Native(trimmed.to_string()),
19863            "MI" | "MIN" | "MINUTES" | "MINS" | "N" => TsqlDatePart::Native("MINUTE".to_string()),
19864            "SECOND" => TsqlDatePart::Native(trimmed.to_string()),
19865            "S" | "SEC" | "SECONDS" | "SECS" | "SS" => TsqlDatePart::Native("SECOND".to_string()),
19866            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19867            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19868                if upper == "MILLISECOND" {
19869                    TsqlDatePart::Native(trimmed.to_string())
19870                } else {
19871                    TsqlDatePart::Native("MILLISECOND".to_string())
19872                }
19873            }
19874            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19875            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19876                if upper == "MICROSECOND" {
19877                    TsqlDatePart::Native(trimmed.to_string())
19878                } else {
19879                    TsqlDatePart::Native("MICROSECOND".to_string())
19880                }
19881            }
19882            "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS" | "NANOSECS" => {
19883                if upper == "NANOSECOND" {
19884                    TsqlDatePart::Native(trimmed.to_string())
19885                } else {
19886                    TsqlDatePart::Native("NANOSECOND".to_string())
19887                }
19888            }
19889            "TZOFFSET" => TsqlDatePart::Native(trimmed.to_string()),
19890            "TZ" | "TZM" | "TIMEZONE_MINUTE" => TsqlDatePart::Native("TZOFFSET".to_string()),
19891            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19892            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => {
19893                if upper == "ISO_WEEK" {
19894                    TsqlDatePart::Native(trimmed.to_string())
19895                } else {
19896                    TsqlDatePart::Native("ISO_WEEK".to_string())
19897                }
19898            }
19899            "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" => TsqlDatePart::Epoch,
19900            "DECADE" | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19901            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "TIMEZONE" | "TIMEZONE_HOUR" | "TZH"
19902            | "DATE" | "TIME" => TsqlDatePart::Unsupported(upper),
19903            _ => TsqlDatePart::Unsupported(upper),
19904        }
19905    }
19906
19907    fn classify_tsql_date_trunc_field(&self, field: &DateTimeField) -> TsqlDatePart {
19908        match field {
19909            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19910            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19911            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19912            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19913            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19914            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19915            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19916            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19917            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19918            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19919            DateTimeField::WeekWithModifier(modifier) => {
19920                match modifier.to_ascii_uppercase().as_str() {
19921                    "MONDAY" | "ISO" | "ISO_WEEK" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19922                    "SUNDAY" => TsqlDatePart::Native("WEEK".to_string()),
19923                    _ => TsqlDatePart::Unsupported(format!("WEEK({modifier})")),
19924                }
19925            }
19926            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19927            DateTimeField::DayOfWeek => TsqlDatePart::Unsupported("WEEKDAY".to_string()),
19928            DateTimeField::Epoch => TsqlDatePart::Unsupported("EPOCH".to_string()),
19929            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19930            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19931            DateTimeField::TimezoneMinute => {
19932                TsqlDatePart::Unsupported("TIMEZONE_MINUTE".to_string())
19933            }
19934            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19935            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19936            DateTimeField::Custom(name) => self.classify_tsql_date_trunc_name(name),
19937        }
19938    }
19939
19940    fn classify_tsql_date_trunc_name(&self, part: &str) -> TsqlDatePart {
19941        let upper = part.trim().to_ascii_uppercase();
19942        match upper.as_str() {
19943            "YEAR" | "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19944                TsqlDatePart::Native("YEAR".to_string())
19945            }
19946            "QUARTER" | "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => {
19947                TsqlDatePart::Native("QUARTER".to_string())
19948            }
19949            "MONTH" | "MM" | "M" | "MON" | "MONS" | "MONTHS" => {
19950                TsqlDatePart::Native("MONTH".to_string())
19951            }
19952            "DAYOFYEAR" | "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19953            "DAY" | "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19954            "WEEK" | "W" | "WK" | "WW" | "WEEKS" | "WEEKOFYEAR" | "WOY" | "WY" => {
19955                TsqlDatePart::Native("WEEK".to_string())
19956            }
19957            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19958            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19959            "HOUR" | "H" | "HH" | "HR" | "HOURS" | "HRS" => {
19960                TsqlDatePart::Native("HOUR".to_string())
19961            }
19962            "MINUTE" | "MI" | "MIN" | "MINUTES" | "MINS" | "N" => {
19963                TsqlDatePart::Native("MINUTE".to_string())
19964            }
19965            "SECOND" | "S" | "SEC" | "SECONDS" | "SECS" | "SS" => {
19966                TsqlDatePart::Native("SECOND".to_string())
19967            }
19968            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19969            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19970                TsqlDatePart::Native("MILLISECOND".to_string())
19971            }
19972            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19973            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19974                TsqlDatePart::Native("MICROSECOND".to_string())
19975            }
19976            "WEEKDAY" | "DAYOFWEEK" | "DOW" | "DW" | "ISODOW" | "ISO_DOW" | "DOW_ISO"
19977            | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY" | "DAYOFWEEKISO" | "DAYOFWEEK_ISO"
19978            | "WEEKDAY_ISO" | "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS"
19979            | "NANOSECS" | "TZOFFSET" | "TZ" | "TZM" | "TIMEZONE" | "TIMEZONE_HOUR"
19980            | "TIMEZONE_MINUTE" | "TZH" | "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" | "DECADE"
19981            | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19982            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "DATE" | "TIME" => {
19983                TsqlDatePart::Unsupported(upper)
19984            }
19985            _ => TsqlDatePart::Unsupported(upper),
19986        }
19987    }
19988
19989    fn generate_tsql_date_part(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
19990        match part {
19991            TsqlDatePart::Native(name) => self.generate_tsql_native_datepart(&name, expr),
19992            TsqlDatePart::Epoch => self.generate_tsql_epoch_seconds(expr),
19993            TsqlDatePart::IsoDayOfWeek => self.generate_tsql_iso_day_of_week(expr),
19994            TsqlDatePart::Unsupported(name) => {
19995                self.unsupported(format!("DATEPART {name} is not supported by T-SQL/Fabric"))?;
19996                self.write_keyword("DATEPART");
19997                self.write("(");
19998                self.write_keyword(&name);
19999                self.write(", ");
20000                self.generate_expression(expr)?;
20001                self.write(")");
20002                Ok(())
20003            }
20004        }
20005    }
20006
20007    fn generate_tsql_native_datepart(&mut self, name: &str, expr: &Expression) -> Result<()> {
20008        self.write_keyword("DATEPART");
20009        self.write("(");
20010        self.write_keyword(name);
20011        self.write(", ");
20012        self.generate_expression(expr)?;
20013        self.write(")");
20014        Ok(())
20015    }
20016
20017    fn generate_tsql_epoch_seconds(&mut self, expr: &Expression) -> Result<()> {
20018        self.write_keyword("DATEDIFF");
20019        self.write("(SECOND, ");
20020        self.write_keyword("CAST");
20021        self.write("('1970-01-01' AS ");
20022        self.write_keyword("DATETIME2");
20023        self.write("), ");
20024        self.generate_expression(expr)?;
20025        self.write(")");
20026        Ok(())
20027    }
20028
20029    fn generate_tsql_iso_day_of_week(&mut self, expr: &Expression) -> Result<()> {
20030        self.write("(((DATEPART(WEEKDAY, ");
20031        self.generate_expression(expr)?;
20032        self.write(") + @@DATEFIRST - 2) % 7) + 1)");
20033        Ok(())
20034    }
20035
20036    fn generate_tsql_date_trunc(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
20037        match part {
20038            TsqlDatePart::Native(name) => {
20039                self.write_keyword("DATETRUNC");
20040                self.write("(");
20041                self.write_keyword(&name);
20042                self.write(", ");
20043                self.generate_expression(expr)?;
20044                self.write(")");
20045                Ok(())
20046            }
20047            TsqlDatePart::Epoch => {
20048                self.generate_tsql_unsupported_date_trunc("EPOCH".to_string(), expr)
20049            }
20050            TsqlDatePart::IsoDayOfWeek => {
20051                self.generate_tsql_unsupported_date_trunc("ISODOW".to_string(), expr)
20052            }
20053            TsqlDatePart::Unsupported(name) => {
20054                self.generate_tsql_unsupported_date_trunc(name, expr)
20055            }
20056        }
20057    }
20058
20059    fn generate_tsql_unsupported_date_trunc(
20060        &mut self,
20061        name: String,
20062        expr: &Expression,
20063    ) -> Result<()> {
20064        self.unsupported(format!("DATETRUNC {name} is not supported by T-SQL/Fabric"))?;
20065        self.write_keyword("DATETRUNC");
20066        self.write("(");
20067        self.write_keyword(&name);
20068        self.write(", ");
20069        self.generate_expression(expr)?;
20070        self.write(")");
20071        Ok(())
20072    }
20073
20074    /// Normalize date part to uppercase singular form
20075    /// days -> DAY, months -> MONTH, etc.
20076    fn normalize_date_part(&self, part: &str) -> String {
20077        let mut buf = [0u8; 64];
20078        let lower: &str = if part.len() <= 64 {
20079            for (i, b) in part.bytes().enumerate() {
20080                buf[i] = b.to_ascii_lowercase();
20081            }
20082            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
20083        } else {
20084            return part.to_ascii_uppercase();
20085        };
20086        match lower {
20087            "day" | "days" | "d" => "DAY".to_string(),
20088            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
20089            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
20090            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
20091            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
20092            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
20093            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
20094            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
20095            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
20096            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
20097            _ => part.to_ascii_uppercase(),
20098        }
20099    }
20100
20101    fn write_datetime_field(&mut self, field: &DateTimeField) {
20102        match field {
20103            DateTimeField::Year => self.write_keyword("YEAR"),
20104            DateTimeField::Month => self.write_keyword("MONTH"),
20105            DateTimeField::Day => self.write_keyword("DAY"),
20106            DateTimeField::Hour => self.write_keyword("HOUR"),
20107            DateTimeField::Minute => self.write_keyword("MINUTE"),
20108            DateTimeField::Second => self.write_keyword("SECOND"),
20109            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
20110            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
20111            DateTimeField::DayOfWeek => {
20112                let name = match self.config.dialect {
20113                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
20114                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
20115                    _ => "DOW",
20116                };
20117                self.write_keyword(name);
20118            }
20119            DateTimeField::DayOfYear => {
20120                let name = match self.config.dialect {
20121                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
20122                    _ => "DOY",
20123                };
20124                self.write_keyword(name);
20125            }
20126            DateTimeField::Week => self.write_keyword("WEEK"),
20127            DateTimeField::WeekWithModifier(modifier) => {
20128                self.write_keyword("WEEK");
20129                self.write("(");
20130                self.write(modifier);
20131                self.write(")");
20132            }
20133            DateTimeField::Quarter => self.write_keyword("QUARTER"),
20134            DateTimeField::Epoch => self.write_keyword("EPOCH"),
20135            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
20136            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
20137            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
20138            DateTimeField::Date => self.write_keyword("DATE"),
20139            DateTimeField::Time => self.write_keyword("TIME"),
20140            DateTimeField::Custom(name) => self.write(name),
20141        }
20142    }
20143
20144    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
20145    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
20146        match field {
20147            DateTimeField::Year => self.write("year"),
20148            DateTimeField::Month => self.write("month"),
20149            DateTimeField::Day => self.write("day"),
20150            DateTimeField::Hour => self.write("hour"),
20151            DateTimeField::Minute => self.write("minute"),
20152            DateTimeField::Second => self.write("second"),
20153            DateTimeField::Millisecond => self.write("millisecond"),
20154            DateTimeField::Microsecond => self.write("microsecond"),
20155            DateTimeField::DayOfWeek => self.write("dow"),
20156            DateTimeField::DayOfYear => self.write("doy"),
20157            DateTimeField::Week => self.write("week"),
20158            DateTimeField::WeekWithModifier(modifier) => {
20159                self.write("week(");
20160                self.write(modifier);
20161                self.write(")");
20162            }
20163            DateTimeField::Quarter => self.write("quarter"),
20164            DateTimeField::Epoch => self.write("epoch"),
20165            DateTimeField::Timezone => self.write("timezone"),
20166            DateTimeField::TimezoneHour => self.write("timezone_hour"),
20167            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
20168            DateTimeField::Date => self.write("date"),
20169            DateTimeField::Time => self.write("time"),
20170            DateTimeField::Custom(name) => self.write(name),
20171        }
20172    }
20173
20174    // Helper function generators
20175
20176    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
20177        self.write_keyword(name);
20178        self.write("(");
20179        self.generate_expression(arg)?;
20180        self.write(")");
20181        Ok(())
20182    }
20183
20184    /// Generate a unary function, using the original name if available for round-trip preservation
20185    fn generate_unary_func(
20186        &mut self,
20187        default_name: &str,
20188        f: &crate::expressions::UnaryFunc,
20189    ) -> Result<()> {
20190        let name = f.original_name.as_deref().unwrap_or(default_name);
20191        self.write_keyword(name);
20192        self.write("(");
20193        self.generate_expression(&f.this)?;
20194        self.write(")");
20195        Ok(())
20196    }
20197
20198    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
20199    fn generate_sqrt_cbrt(
20200        &mut self,
20201        f: &crate::expressions::UnaryFunc,
20202        func_name: &str,
20203        _op: &str,
20204    ) -> Result<()> {
20205        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
20206        // Always use function syntax for consistency
20207        self.write_keyword(func_name);
20208        self.write("(");
20209        self.generate_expression(&f.this)?;
20210        self.write(")");
20211        Ok(())
20212    }
20213
20214    fn generate_binary_func(
20215        &mut self,
20216        name: &str,
20217        arg1: &Expression,
20218        arg2: &Expression,
20219    ) -> Result<()> {
20220        self.write_keyword(name);
20221        self.write("(");
20222        self.generate_expression(arg1)?;
20223        self.write(", ");
20224        self.generate_expression(arg2)?;
20225        self.write(")");
20226        Ok(())
20227    }
20228
20229    /// Generate CHAR/CHR function with optional USING charset
20230    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
20231    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
20232    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
20233        // Use stored name if available, otherwise default to CHAR
20234        let func_name = f.name.as_deref().unwrap_or("CHAR");
20235        self.write_keyword(func_name);
20236        self.write("(");
20237        for (i, arg) in f.args.iter().enumerate() {
20238            if i > 0 {
20239                self.write(", ");
20240            }
20241            self.generate_expression(arg)?;
20242        }
20243        if let Some(ref charset) = f.charset {
20244            self.write(" ");
20245            self.write_keyword("USING");
20246            self.write(" ");
20247            self.write(charset);
20248        }
20249        self.write(")");
20250        Ok(())
20251    }
20252
20253    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
20254        use crate::dialects::DialectType;
20255
20256        match self.config.dialect {
20257            Some(DialectType::Teradata) => {
20258                // Teradata uses ** operator for exponentiation
20259                self.generate_expression(&f.this)?;
20260                self.write(" ** ");
20261                self.generate_expression(&f.expression)?;
20262                Ok(())
20263            }
20264            _ => {
20265                // Other dialects use POWER function
20266                self.generate_binary_func("POWER", &f.this, &f.expression)
20267            }
20268        }
20269    }
20270
20271    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
20272        self.write_func_name(name);
20273        self.write("(");
20274        for (i, arg) in args.iter().enumerate() {
20275            if i > 0 {
20276                self.write(", ");
20277            }
20278            self.generate_expression(arg)?;
20279        }
20280        self.write(")");
20281        Ok(())
20282    }
20283
20284    // String function generators
20285
20286    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
20287        self.write_keyword("CONCAT_WS");
20288        self.write("(");
20289        self.generate_expression(&f.separator)?;
20290        for expr in &f.expressions {
20291            self.write(", ");
20292            self.generate_expression(expr)?;
20293        }
20294        self.write(")");
20295        Ok(())
20296    }
20297
20298    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20299        if let Expression::Concat(op) = expr {
20300            Self::collect_concat_operands(&op.left, out);
20301            Self::collect_concat_operands(&op.right, out);
20302        } else {
20303            out.push(expr);
20304        }
20305    }
20306
20307    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
20308        let mut operands = Vec::new();
20309        Self::collect_concat_operands(&op.left, &mut operands);
20310        Self::collect_concat_operands(&op.right, &mut operands);
20311
20312        self.write_keyword("CONCAT");
20313        self.write("(");
20314        for (i, operand) in operands.iter().enumerate() {
20315            if i > 0 {
20316                self.write(", ");
20317            }
20318            self.generate_expression(operand)?;
20319        }
20320        self.write(")");
20321        Ok(())
20322    }
20323
20324    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20325        if let Expression::DPipe(dpipe) = expr {
20326            Self::collect_dpipe_operands(&dpipe.this, out);
20327            Self::collect_dpipe_operands(&dpipe.expression, out);
20328        } else {
20329            out.push(expr);
20330        }
20331    }
20332
20333    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
20334        let mut operands = Vec::new();
20335        Self::collect_dpipe_operands(&e.this, &mut operands);
20336        Self::collect_dpipe_operands(&e.expression, &mut operands);
20337
20338        self.write_keyword("CONCAT");
20339        self.write("(");
20340        for (i, operand) in operands.iter().enumerate() {
20341            if i > 0 {
20342                self.write(", ");
20343            }
20344            self.generate_expression(operand)?;
20345        }
20346        self.write(")");
20347        Ok(())
20348    }
20349
20350    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
20351        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
20352        let use_substr = matches!(
20353            self.config.dialect,
20354            Some(
20355                DialectType::Oracle
20356                    | DialectType::Presto
20357                    | DialectType::Trino
20358                    | DialectType::Athena
20359            )
20360        );
20361        if use_substr {
20362            self.write_keyword("SUBSTR");
20363        } else {
20364            self.write_keyword("SUBSTRING");
20365        }
20366        self.write("(");
20367        self.generate_expression(&f.this)?;
20368        // PostgreSQL always uses FROM/FOR syntax
20369        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
20370        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
20371        let use_comma_syntax = matches!(
20372            self.config.dialect,
20373            Some(DialectType::Spark)
20374                | Some(DialectType::Hive)
20375                | Some(DialectType::Databricks)
20376                | Some(DialectType::TSQL)
20377                | Some(DialectType::Fabric)
20378        );
20379        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
20380            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
20381            self.write_space();
20382            self.write_keyword("FROM");
20383            self.write_space();
20384            self.generate_expression(&f.start)?;
20385            if let Some(length) = &f.length {
20386                self.write_space();
20387                self.write_keyword("FOR");
20388                self.write_space();
20389                self.generate_expression(length)?;
20390            }
20391        } else {
20392            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
20393            self.write(", ");
20394            self.generate_expression(&f.start)?;
20395            if let Some(length) = &f.length {
20396                self.write(", ");
20397                self.generate_expression(length)?;
20398            }
20399        }
20400        self.write(")");
20401        Ok(())
20402    }
20403
20404    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
20405        self.write_keyword("OVERLAY");
20406        self.write("(");
20407        self.generate_expression(&f.this)?;
20408        self.write_space();
20409        self.write_keyword("PLACING");
20410        self.write_space();
20411        self.generate_expression(&f.replacement)?;
20412        self.write_space();
20413        self.write_keyword("FROM");
20414        self.write_space();
20415        self.generate_expression(&f.from)?;
20416        if let Some(length) = &f.length {
20417            self.write_space();
20418            self.write_keyword("FOR");
20419            self.write_space();
20420            self.generate_expression(length)?;
20421        }
20422        self.write(")");
20423        Ok(())
20424    }
20425
20426    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
20427        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
20428        // when no characters are specified (PostgreSQL style)
20429        if f.position_explicit && f.characters.is_none() {
20430            match f.position {
20431                TrimPosition::Leading => {
20432                    self.write_keyword("LTRIM");
20433                    self.write("(");
20434                    self.generate_expression(&f.this)?;
20435                    self.write(")");
20436                    return Ok(());
20437                }
20438                TrimPosition::Trailing => {
20439                    self.write_keyword("RTRIM");
20440                    self.write("(");
20441                    self.generate_expression(&f.this)?;
20442                    self.write(")");
20443                    return Ok(());
20444                }
20445                TrimPosition::Both => {
20446                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
20447                    // Fall through to standard TRIM handling
20448                }
20449            }
20450        }
20451
20452        self.write_keyword("TRIM");
20453        self.write("(");
20454        // When BOTH is specified without trim characters, simplify to just TRIM(str)
20455        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
20456        let force_standard = f.characters.is_some()
20457            && !f.sql_standard_syntax
20458            && matches!(
20459                self.config.dialect,
20460                Some(DialectType::Hive)
20461                    | Some(DialectType::Spark)
20462                    | Some(DialectType::Databricks)
20463                    | Some(DialectType::ClickHouse)
20464            );
20465        let use_standard = (f.sql_standard_syntax || force_standard)
20466            && !(f.position_explicit
20467                && f.characters.is_none()
20468                && matches!(f.position, TrimPosition::Both));
20469        if use_standard {
20470            // SQL standard syntax: TRIM(BOTH chars FROM str)
20471            // Only output position if it was explicitly specified
20472            if f.position_explicit {
20473                match f.position {
20474                    TrimPosition::Both => self.write_keyword("BOTH"),
20475                    TrimPosition::Leading => self.write_keyword("LEADING"),
20476                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
20477                }
20478                self.write_space();
20479            }
20480            if let Some(chars) = &f.characters {
20481                self.generate_expression(chars)?;
20482                self.write_space();
20483            }
20484            self.write_keyword("FROM");
20485            self.write_space();
20486            self.generate_expression(&f.this)?;
20487        } else {
20488            // Simple function syntax: TRIM(str) or TRIM(str, chars)
20489            self.generate_expression(&f.this)?;
20490            if let Some(chars) = &f.characters {
20491                self.write(", ");
20492                self.generate_expression(chars)?;
20493            }
20494        }
20495        self.write(")");
20496        Ok(())
20497    }
20498
20499    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
20500        self.write_keyword("REPLACE");
20501        self.write("(");
20502        self.generate_expression(&f.this)?;
20503        self.write(", ");
20504        self.generate_expression(&f.old)?;
20505        self.write(", ");
20506        self.generate_expression(&f.new)?;
20507        self.write(")");
20508        Ok(())
20509    }
20510
20511    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
20512        self.write_keyword(name);
20513        self.write("(");
20514        self.generate_expression(&f.this)?;
20515        self.write(", ");
20516        self.generate_expression(&f.length)?;
20517        self.write(")");
20518        Ok(())
20519    }
20520
20521    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
20522        self.write_keyword("REPEAT");
20523        self.write("(");
20524        self.generate_expression(&f.this)?;
20525        self.write(", ");
20526        self.generate_expression(&f.times)?;
20527        self.write(")");
20528        Ok(())
20529    }
20530
20531    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
20532        self.write_keyword(name);
20533        self.write("(");
20534        self.generate_expression(&f.this)?;
20535        self.write(", ");
20536        self.generate_expression(&f.length)?;
20537        if let Some(fill) = &f.fill {
20538            self.write(", ");
20539            self.generate_expression(fill)?;
20540        }
20541        self.write(")");
20542        Ok(())
20543    }
20544
20545    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
20546        self.write_keyword("SPLIT");
20547        self.write("(");
20548        self.generate_expression(&f.this)?;
20549        self.write(", ");
20550        self.generate_expression(&f.delimiter)?;
20551        self.write(")");
20552        Ok(())
20553    }
20554
20555    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
20556        use crate::dialects::DialectType;
20557        // PostgreSQL uses ~ operator for regex matching
20558        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
20559            self.generate_expression(&f.this)?;
20560            self.write(" ~ ");
20561            self.generate_expression(&f.pattern)?;
20562        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && f.flags.is_none()
20563        {
20564            // ClickHouse has no REGEXP_LIKE; the regex-match operator is match(haystack, pattern).
20565            self.write("match(");
20566            self.generate_expression(&f.this)?;
20567            self.write(", ");
20568            self.generate_expression(&f.pattern)?;
20569            self.write(")");
20570        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
20571            // Exasol uses REGEXP_LIKE as infix binary operator
20572            self.generate_expression(&f.this)?;
20573            self.write_keyword(" REGEXP_LIKE ");
20574            self.generate_expression(&f.pattern)?;
20575        } else if matches!(
20576            self.config.dialect,
20577            Some(DialectType::SingleStore)
20578                | Some(DialectType::Spark)
20579                | Some(DialectType::Hive)
20580                | Some(DialectType::Databricks)
20581        ) && f.flags.is_none()
20582        {
20583            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
20584            self.generate_expression(&f.this)?;
20585            self.write_keyword(" RLIKE ");
20586            self.generate_expression(&f.pattern)?;
20587        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
20588            // StarRocks uses REGEXP function syntax
20589            self.write_keyword("REGEXP");
20590            self.write("(");
20591            self.generate_expression(&f.this)?;
20592            self.write(", ");
20593            self.generate_expression(&f.pattern)?;
20594            if let Some(flags) = &f.flags {
20595                self.write(", ");
20596                self.generate_expression(flags)?;
20597            }
20598            self.write(")");
20599        } else {
20600            self.write_keyword("REGEXP_LIKE");
20601            self.write("(");
20602            self.generate_expression(&f.this)?;
20603            self.write(", ");
20604            self.generate_expression(&f.pattern)?;
20605            if let Some(flags) = &f.flags {
20606                self.write(", ");
20607                self.generate_expression(flags)?;
20608            }
20609            self.write(")");
20610        }
20611        Ok(())
20612    }
20613
20614    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
20615        self.write_keyword("REGEXP_REPLACE");
20616        self.write("(");
20617        self.generate_expression(&f.this)?;
20618        self.write(", ");
20619        self.generate_expression(&f.pattern)?;
20620        self.write(", ");
20621        self.generate_expression(&f.replacement)?;
20622        if let Some(flags) = &f.flags {
20623            self.write(", ");
20624            self.generate_expression(flags)?;
20625        }
20626        self.write(")");
20627        Ok(())
20628    }
20629
20630    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
20631        self.write_keyword("REGEXP_EXTRACT");
20632        self.write("(");
20633        self.generate_expression(&f.this)?;
20634        self.write(", ");
20635        self.generate_expression(&f.pattern)?;
20636        if let Some(group) = &f.group {
20637            self.write(", ");
20638            self.generate_expression(group)?;
20639        }
20640        self.write(")");
20641        Ok(())
20642    }
20643
20644    // Math function generators
20645
20646    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
20647        self.write_keyword("ROUND");
20648        self.write("(");
20649        self.generate_expression(&f.this)?;
20650        if let Some(decimals) = &f.decimals {
20651            self.write(", ");
20652            self.generate_expression(decimals)?;
20653        }
20654        self.write(")");
20655        Ok(())
20656    }
20657
20658    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
20659        self.write_keyword("FLOOR");
20660        self.write("(");
20661        self.generate_expression(&f.this)?;
20662        // Handle Druid-style FLOOR(time TO unit) syntax
20663        if let Some(to) = &f.to {
20664            self.write(" ");
20665            self.write_keyword("TO");
20666            self.write(" ");
20667            self.generate_expression(to)?;
20668        } else if let Some(scale) = &f.scale {
20669            self.write(", ");
20670            self.generate_expression(scale)?;
20671        }
20672        self.write(")");
20673        Ok(())
20674    }
20675
20676    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
20677        self.write_keyword("CEIL");
20678        self.write("(");
20679        self.generate_expression(&f.this)?;
20680        // Handle Druid-style CEIL(time TO unit) syntax
20681        if let Some(to) = &f.to {
20682            self.write(" ");
20683            self.write_keyword("TO");
20684            self.write(" ");
20685            self.generate_expression(to)?;
20686        } else if let Some(decimals) = &f.decimals {
20687            self.write(", ");
20688            self.generate_expression(decimals)?;
20689        }
20690        self.write(")");
20691        Ok(())
20692    }
20693
20694    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
20695        use crate::expressions::Literal;
20696
20697        if let Some(base) = &f.base {
20698            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
20699            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
20700            if self.is_log_base_none() {
20701                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
20702                {
20703                    self.write_func_name("LOG2");
20704                    self.write("(");
20705                    self.generate_expression(&f.this)?;
20706                    self.write(")");
20707                    return Ok(());
20708                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
20709                {
20710                    self.write_func_name("LOG10");
20711                    self.write("(");
20712                    self.generate_expression(&f.this)?;
20713                    self.write(")");
20714                    return Ok(());
20715                }
20716                // Other bases: fall through to LOG(base, value) — best effort
20717            }
20718
20719            self.write_func_name("LOG");
20720            self.write("(");
20721            if self.is_log_value_first() {
20722                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
20723                self.generate_expression(&f.this)?;
20724                self.write(", ");
20725                self.generate_expression(base)?;
20726            } else {
20727                // Default (PostgreSQL, etc.): LOG(base, value)
20728                self.generate_expression(base)?;
20729                self.write(", ");
20730                self.generate_expression(&f.this)?;
20731            }
20732            self.write(")");
20733        } else {
20734            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
20735            self.write_func_name("LOG");
20736            self.write("(");
20737            self.generate_expression(&f.this)?;
20738            self.write(")");
20739        }
20740        Ok(())
20741    }
20742
20743    /// Whether the target dialect uses LOG(value, base) order (value first).
20744    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
20745    fn is_log_value_first(&self) -> bool {
20746        use crate::dialects::DialectType;
20747        matches!(
20748            self.config.dialect,
20749            Some(DialectType::BigQuery)
20750                | Some(DialectType::TSQL)
20751                | Some(DialectType::Tableau)
20752                | Some(DialectType::Fabric)
20753        )
20754    }
20755
20756    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
20757    /// Presto, Trino, ClickHouse, Athena.
20758    fn is_log_base_none(&self) -> bool {
20759        use crate::dialects::DialectType;
20760        matches!(
20761            self.config.dialect,
20762            Some(DialectType::Presto)
20763                | Some(DialectType::Trino)
20764                | Some(DialectType::ClickHouse)
20765                | Some(DialectType::Athena)
20766        )
20767    }
20768
20769    // Date/time function generators
20770
20771    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
20772        self.write_keyword("CURRENT_TIME");
20773        if let Some(precision) = f.precision {
20774            self.write(&format!("({})", precision));
20775        } else if matches!(
20776            self.config.dialect,
20777            Some(crate::dialects::DialectType::MySQL)
20778                | Some(crate::dialects::DialectType::SingleStore)
20779                | Some(crate::dialects::DialectType::TiDB)
20780        ) {
20781            self.write("()");
20782        }
20783        Ok(())
20784    }
20785
20786    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
20787        use crate::dialects::DialectType;
20788
20789        // Oracle/Redshift SYSDATE handling
20790        if f.sysdate {
20791            match self.config.dialect {
20792                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
20793                    self.write_keyword("SYSDATE");
20794                    return Ok(());
20795                }
20796                Some(DialectType::Snowflake) => {
20797                    // Snowflake uses SYSDATE() function
20798                    self.write_keyword("SYSDATE");
20799                    self.write("()");
20800                    return Ok(());
20801                }
20802                _ => {
20803                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
20804                }
20805            }
20806        }
20807
20808        self.write_keyword("CURRENT_TIMESTAMP");
20809        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
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                | Some(crate::dialects::DialectType::Spark)
20818                | Some(crate::dialects::DialectType::Hive)
20819                | Some(crate::dialects::DialectType::Databricks)
20820                | Some(crate::dialects::DialectType::ClickHouse)
20821                | Some(crate::dialects::DialectType::BigQuery)
20822                | Some(crate::dialects::DialectType::Snowflake)
20823                | Some(crate::dialects::DialectType::Exasol)
20824        ) {
20825            self.write("()");
20826        }
20827        Ok(())
20828    }
20829
20830    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
20831        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
20832        if self.config.dialect == Some(DialectType::Exasol) {
20833            self.write_keyword("CONVERT_TZ");
20834            self.write("(");
20835            self.generate_expression(&f.this)?;
20836            self.write(", 'UTC', ");
20837            self.generate_expression(&f.zone)?;
20838            self.write(")");
20839            return Ok(());
20840        }
20841
20842        self.generate_expression(&f.this)?;
20843        self.write_space();
20844        self.write_keyword("AT TIME ZONE");
20845        self.write_space();
20846        self.generate_expression(&f.zone)?;
20847        Ok(())
20848    }
20849
20850    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
20851        use crate::dialects::DialectType;
20852
20853        // Presto/Trino use DATE_ADD('unit', interval, date) format
20854        // with the interval cast to BIGINT when needed
20855        let is_presto_like = matches!(
20856            self.config.dialect,
20857            Some(DialectType::Presto) | Some(DialectType::Trino)
20858        );
20859
20860        if is_presto_like {
20861            self.write_keyword(name);
20862            self.write("(");
20863            // Unit as string literal
20864            self.write("'");
20865            self.write_simple_interval_unit(&f.unit, false);
20866            self.write("'");
20867            self.write(", ");
20868            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
20869            let needs_cast = !self.returns_integer_type(&f.interval);
20870            if needs_cast {
20871                self.write_keyword("CAST");
20872                self.write("(");
20873            }
20874            self.generate_expression(&f.interval)?;
20875            if needs_cast {
20876                self.write_space();
20877                self.write_keyword("AS");
20878                self.write_space();
20879                self.write_keyword("BIGINT");
20880                self.write(")");
20881            }
20882            self.write(", ");
20883            self.generate_expression(&f.this)?;
20884            self.write(")");
20885        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20886            self.generate_expression(&f.this)?;
20887            self.write_space();
20888            if name.eq_ignore_ascii_case("DATE_SUB") {
20889                self.write("-");
20890            } else {
20891                self.write("+");
20892            }
20893            self.write_space();
20894            self.write_keyword("INTERVAL");
20895            self.write_space();
20896            self.write("'");
20897            let mut interval_gen = Generator::with_arc_config(self.config.clone());
20898            let interval_sql = interval_gen.generate(&f.interval)?;
20899            self.write(&interval_sql);
20900            self.write(" ");
20901            self.write_simple_interval_unit(&f.unit, false);
20902            self.write("'");
20903        } else {
20904            self.write_keyword(name);
20905            self.write("(");
20906            self.generate_expression(&f.this)?;
20907            self.write(", ");
20908            self.write_keyword("INTERVAL");
20909            self.write_space();
20910            self.generate_expression(&f.interval)?;
20911            self.write_space();
20912            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
20913            self.write(")");
20914        }
20915        Ok(())
20916    }
20917
20918    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
20919    /// This is a heuristic to avoid full type inference
20920    fn returns_integer_type(&self, expr: &Expression) -> bool {
20921        use crate::expressions::{DataType, Literal};
20922        match expr {
20923            // Integer literals (no decimal point)
20924            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
20925                let Literal::Number(n) = lit.as_ref() else {
20926                    unreachable!()
20927                };
20928                !n.contains('.')
20929            }
20930
20931            // FLOOR(x) returns integer if x is integer
20932            Expression::Floor(f) => self.returns_integer_type(&f.this),
20933
20934            // ROUND(x) returns integer if x is integer
20935            Expression::Round(f) => {
20936                // Only if no decimals arg or it's returning an integer
20937                f.decimals.is_none() && self.returns_integer_type(&f.this)
20938            }
20939
20940            // SIGN returns integer if input is integer
20941            Expression::Sign(f) => self.returns_integer_type(&f.this),
20942
20943            // ABS returns the same type as input
20944            Expression::Abs(f) => self.returns_integer_type(&f.this),
20945
20946            // Arithmetic operations on integers return integers
20947            Expression::Mul(op) => {
20948                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20949            }
20950            Expression::Add(op) => {
20951                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20952            }
20953            Expression::Sub(op) => {
20954                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20955            }
20956            Expression::Mod(op) => self.returns_integer_type(&op.left),
20957
20958            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
20959            Expression::Cast(c) => matches!(
20960                &c.to,
20961                DataType::BigInt { .. }
20962                    | DataType::Int { .. }
20963                    | DataType::SmallInt { .. }
20964                    | DataType::TinyInt { .. }
20965            ),
20966
20967            // Negation: -x returns integer if x is integer
20968            Expression::Neg(op) => self.returns_integer_type(&op.this),
20969
20970            // Parenthesized expression
20971            Expression::Paren(p) => self.returns_integer_type(&p.this),
20972
20973            // Column references and most expressions are assumed to need casting
20974            // since we don't have full type information
20975            _ => false,
20976        }
20977    }
20978
20979    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
20980        self.write_keyword("DATEDIFF");
20981        self.write("(");
20982        if let Some(unit) = &f.unit {
20983            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
20984            self.write(", ");
20985        }
20986        if self.config.dialect == Some(DialectType::Snowflake) {
20987            self.generate_expression(&f.expression)?;
20988            self.write(", ");
20989            self.generate_expression(&f.this)?;
20990        } else {
20991            self.generate_expression(&f.this)?;
20992            self.write(", ");
20993            self.generate_expression(&f.expression)?;
20994        }
20995        self.write(")");
20996        Ok(())
20997    }
20998
20999    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
21000        if matches!(
21001            self.config.dialect,
21002            Some(DialectType::TSQL) | Some(DialectType::Fabric)
21003        ) {
21004            let date_part = self.classify_tsql_date_trunc_field(&f.unit);
21005            self.generate_tsql_date_trunc(date_part, &f.this)?;
21006            return Ok(());
21007        }
21008        if self.config.dialect == Some(DialectType::ClickHouse) {
21009            self.write("dateTrunc");
21010        } else {
21011            self.write_keyword("DATE_TRUNC");
21012        }
21013        self.write("('");
21014        self.write_datetime_field(&f.unit);
21015        self.write("', ");
21016        self.generate_expression(&f.this)?;
21017        self.write(")");
21018        Ok(())
21019    }
21020
21021    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
21022        use crate::dialects::DialectType;
21023        use crate::expressions::DateTimeField;
21024
21025        self.write_keyword("LAST_DAY");
21026        self.write("(");
21027        self.generate_expression(&f.this)?;
21028        if let Some(unit) = &f.unit {
21029            self.write(", ");
21030            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
21031            // WEEK(SUNDAY) -> WEEK
21032            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21033                if let DateTimeField::WeekWithModifier(_) = unit {
21034                    self.write_keyword("WEEK");
21035                } else {
21036                    self.write_datetime_field(unit);
21037                }
21038            } else {
21039                self.write_datetime_field(unit);
21040            }
21041        }
21042        self.write(")");
21043        Ok(())
21044    }
21045
21046    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
21047        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
21048        if matches!(
21049            self.config.dialect,
21050            Some(DialectType::TSQL) | Some(DialectType::Fabric)
21051        ) {
21052            let date_part = self.classify_tsql_datetime_field(&f.field);
21053            self.generate_tsql_date_part(date_part, &f.this)?;
21054            return Ok(());
21055        }
21056        self.write_keyword("EXTRACT");
21057        self.write("(");
21058        // Hive/Spark use lowercase datetime fields in EXTRACT
21059        if matches!(
21060            self.config.dialect,
21061            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
21062        ) {
21063            self.write_datetime_field_lower(&f.field);
21064        } else {
21065            self.write_datetime_field(&f.field);
21066        }
21067        self.write_space();
21068        self.write_keyword("FROM");
21069        self.write_space();
21070        self.generate_expression(&f.this)?;
21071        self.write(")");
21072        Ok(())
21073    }
21074
21075    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
21076        self.write_keyword("TO_DATE");
21077        self.write("(");
21078        self.generate_expression(&f.this)?;
21079        if let Some(format) = &f.format {
21080            self.write(", ");
21081            self.generate_expression(format)?;
21082        }
21083        self.write(")");
21084        Ok(())
21085    }
21086
21087    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
21088        self.write_keyword("TO_TIMESTAMP");
21089        self.write("(");
21090        self.generate_expression(&f.this)?;
21091        if let Some(format) = &f.format {
21092            self.write(", ");
21093            self.generate_expression(format)?;
21094        }
21095        self.write(")");
21096        Ok(())
21097    }
21098
21099    // Control flow function generators
21100
21101    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
21102        use crate::dialects::DialectType;
21103
21104        // Generic mode: normalize IF to CASE WHEN
21105        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
21106            self.write_keyword("CASE WHEN");
21107            self.write_space();
21108            self.generate_expression(&f.condition)?;
21109            self.write_space();
21110            self.write_keyword("THEN");
21111            self.write_space();
21112            self.generate_expression(&f.true_value)?;
21113            if let Some(false_val) = &f.false_value {
21114                self.write_space();
21115                self.write_keyword("ELSE");
21116                self.write_space();
21117                self.generate_expression(false_val)?;
21118            }
21119            self.write_space();
21120            self.write_keyword("END");
21121            return Ok(());
21122        }
21123
21124        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
21125        if self.config.dialect == Some(DialectType::Exasol) {
21126            self.write_keyword("IF");
21127            self.write_space();
21128            self.generate_expression(&f.condition)?;
21129            self.write_space();
21130            self.write_keyword("THEN");
21131            self.write_space();
21132            self.generate_expression(&f.true_value)?;
21133            if let Some(false_val) = &f.false_value {
21134                self.write_space();
21135                self.write_keyword("ELSE");
21136                self.write_space();
21137                self.generate_expression(false_val)?;
21138            }
21139            self.write_space();
21140            self.write_keyword("ENDIF");
21141            return Ok(());
21142        }
21143
21144        // Choose function name based on target dialect
21145        let func_name = match self.config.dialect {
21146            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
21147            Some(DialectType::Snowflake) => "IFF",
21148            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
21149            Some(DialectType::Drill) => "`IF`",
21150            _ => "IF",
21151        };
21152        self.write(func_name);
21153        self.write("(");
21154        self.generate_expression(&f.condition)?;
21155        self.write(", ");
21156        self.generate_expression(&f.true_value)?;
21157        if let Some(false_val) = &f.false_value {
21158            self.write(", ");
21159            self.generate_expression(false_val)?;
21160        }
21161        self.write(")");
21162        Ok(())
21163    }
21164
21165    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
21166        self.write_keyword("NVL2");
21167        self.write("(");
21168        self.generate_expression(&f.this)?;
21169        self.write(", ");
21170        self.generate_expression(&f.true_value)?;
21171        self.write(", ");
21172        self.generate_expression(&f.false_value)?;
21173        self.write(")");
21174        Ok(())
21175    }
21176
21177    // Typed aggregate function generators
21178
21179    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
21180        // Use normalize_functions for COUNT to respect ClickHouse case preservation
21181        let count_name = match self.config.normalize_functions {
21182            NormalizeFunctions::Upper => "COUNT".to_string(),
21183            NormalizeFunctions::Lower => "count".to_string(),
21184            NormalizeFunctions::None => f
21185                .original_name
21186                .clone()
21187                .unwrap_or_else(|| "COUNT".to_string()),
21188        };
21189        self.write(&count_name);
21190        self.write("(");
21191        if f.distinct {
21192            self.write_keyword("DISTINCT");
21193            self.write_space();
21194        }
21195        if f.star {
21196            self.write("*");
21197        } else if let Some(ref expr) = f.this {
21198            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
21199            if let Expression::Tuple(tuple) = expr {
21200                // Check if we need to transform multi-arg COUNT DISTINCT
21201                // When dialect doesn't support multi_arg_distinct, transform:
21202                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
21203                let needs_transform =
21204                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
21205
21206                if needs_transform {
21207                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
21208                    self.write_keyword("CASE");
21209                    for e in &tuple.expressions {
21210                        self.write_space();
21211                        self.write_keyword("WHEN");
21212                        self.write_space();
21213                        self.generate_expression(e)?;
21214                        self.write_space();
21215                        self.write_keyword("IS NULL THEN NULL");
21216                    }
21217                    self.write_space();
21218                    self.write_keyword("ELSE");
21219                    self.write(" (");
21220                    for (i, e) in tuple.expressions.iter().enumerate() {
21221                        if i > 0 {
21222                            self.write(", ");
21223                        }
21224                        self.generate_expression(e)?;
21225                    }
21226                    self.write(")");
21227                    self.write_space();
21228                    self.write_keyword("END");
21229                } else {
21230                    for (i, e) in tuple.expressions.iter().enumerate() {
21231                        if i > 0 {
21232                            self.write(", ");
21233                        }
21234                        self.generate_expression(e)?;
21235                    }
21236                }
21237            } else {
21238                self.generate_expression(expr)?;
21239            }
21240        }
21241        let clickhouse_ignore_nulls_outside =
21242            matches!(self.config.dialect, Some(DialectType::ClickHouse));
21243        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
21244            self.write_space();
21245            if ignore {
21246                self.write_keyword("IGNORE NULLS");
21247            } else {
21248                self.write_keyword("RESPECT NULLS");
21249            }
21250        }
21251        self.write(")");
21252        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
21253            self.write_space();
21254            if ignore {
21255                self.write_keyword("IGNORE NULLS");
21256            } else {
21257                self.write_keyword("RESPECT NULLS");
21258            }
21259        }
21260        if let Some(ref filter) = f.filter {
21261            self.write_space();
21262            self.write_keyword("FILTER");
21263            self.write("(");
21264            self.write_keyword("WHERE");
21265            self.write_space();
21266            self.generate_expression(filter)?;
21267            self.write(")");
21268        }
21269        Ok(())
21270    }
21271
21272    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21273        // Apply function name normalization based on config
21274        let func_name: Cow<'_, str> = match self.config.normalize_functions {
21275            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21276            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21277            NormalizeFunctions::None => {
21278                // Use the original function name from parsing if available,
21279                // otherwise fall back to lowercase of the hardcoded constant
21280                if let Some(ref original) = f.name {
21281                    Cow::Owned(original.clone())
21282                } else {
21283                    Cow::Owned(name.to_ascii_lowercase())
21284                }
21285            }
21286        };
21287        self.write(func_name.as_ref());
21288        self.write("(");
21289        if f.distinct {
21290            self.write_keyword("DISTINCT");
21291            self.write_space();
21292        }
21293        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
21294        // Other aggregates may legitimately receive NULL as an explicit argument.
21295        let is_zero_arg_mode =
21296            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
21297        if !is_zero_arg_mode {
21298            self.generate_expression(&f.this)?;
21299        }
21300        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
21301        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21302        if self.config.ignore_nulls_in_func
21303            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21304        {
21305            match f.ignore_nulls {
21306                Some(true) => {
21307                    self.write_space();
21308                    self.write_keyword("IGNORE NULLS");
21309                }
21310                Some(false) => {
21311                    self.write_space();
21312                    self.write_keyword("RESPECT NULLS");
21313                }
21314                None => {}
21315            }
21316        }
21317        // Generate HAVING MAX/MIN if present (BigQuery syntax)
21318        // e.g., ANY_VALUE(fruit HAVING MAX sold)
21319        if let Some((ref expr, is_max)) = f.having_max {
21320            self.write_space();
21321            self.write_keyword("HAVING");
21322            self.write_space();
21323            if is_max {
21324                self.write_keyword("MAX");
21325            } else {
21326                self.write_keyword("MIN");
21327            }
21328            self.write_space();
21329            self.generate_expression(expr)?;
21330        }
21331        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
21332        if !f.order_by.is_empty() {
21333            self.write_space();
21334            self.write_keyword("ORDER BY");
21335            self.write_space();
21336            for (i, ord) in f.order_by.iter().enumerate() {
21337                if i > 0 {
21338                    self.write(", ");
21339                }
21340                self.generate_ordered(ord)?;
21341            }
21342        }
21343        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
21344        if let Some(ref limit) = f.limit {
21345            self.write_space();
21346            self.write_keyword("LIMIT");
21347            self.write_space();
21348            // Check if this is a Tuple representing LIMIT offset, count
21349            if let Expression::Tuple(t) = limit.as_ref() {
21350                if t.expressions.len() == 2 {
21351                    self.generate_expression(&t.expressions[0])?;
21352                    self.write(", ");
21353                    self.generate_expression(&t.expressions[1])?;
21354                } else {
21355                    self.generate_expression(limit)?;
21356                }
21357            } else {
21358                self.generate_expression(limit)?;
21359            }
21360        }
21361        self.write(")");
21362        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
21363        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21364        if !self.config.ignore_nulls_in_func
21365            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21366        {
21367            match f.ignore_nulls {
21368                Some(true) => {
21369                    self.write_space();
21370                    self.write_keyword("IGNORE NULLS");
21371                }
21372                Some(false) => {
21373                    self.write_space();
21374                    self.write_keyword("RESPECT NULLS");
21375                }
21376                None => {}
21377            }
21378        }
21379        if let Some(ref filter) = f.filter {
21380            self.write_space();
21381            self.write_keyword("FILTER");
21382            self.write("(");
21383            self.write_keyword("WHERE");
21384            self.write_space();
21385            self.generate_expression(filter)?;
21386            self.write(")");
21387        }
21388        Ok(())
21389    }
21390
21391    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
21392    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
21393    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21394        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
21395        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21396            // Create a modified copy without ignore_nulls, add TRUE as part of the output
21397            let func_name: Cow<'_, str> = match self.config.normalize_functions {
21398                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21399                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21400                NormalizeFunctions::None => {
21401                    if let Some(ref original) = f.name {
21402                        Cow::Owned(original.clone())
21403                    } else {
21404                        Cow::Owned(name.to_ascii_lowercase())
21405                    }
21406                }
21407            };
21408            self.write(func_name.as_ref());
21409            self.write("(");
21410            if f.distinct {
21411                self.write_keyword("DISTINCT");
21412                self.write_space();
21413            }
21414            if !matches!(f.this, Expression::Null(_)) {
21415                self.generate_expression(&f.this)?;
21416            }
21417            self.write(", ");
21418            self.write_keyword("TRUE");
21419            self.write(")");
21420            return Ok(());
21421        }
21422        self.generate_agg_func(name, f)
21423    }
21424
21425    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
21426        self.write_keyword("GROUP_CONCAT");
21427        self.write("(");
21428        if f.distinct {
21429            self.write_keyword("DISTINCT");
21430            self.write_space();
21431        }
21432        self.generate_expression(&f.this)?;
21433        if let Some(ref order_by) = f.order_by {
21434            self.write_space();
21435            self.write_keyword("ORDER BY");
21436            self.write_space();
21437            for (i, ord) in order_by.iter().enumerate() {
21438                if i > 0 {
21439                    self.write(", ");
21440                }
21441                self.generate_ordered(ord)?;
21442            }
21443        }
21444        if let Some(ref sep) = f.separator {
21445            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
21446            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
21447            if matches!(
21448                self.config.dialect,
21449                Some(crate::dialects::DialectType::SQLite)
21450            ) {
21451                self.write(", ");
21452                self.generate_expression(sep)?;
21453            } else {
21454                self.write_space();
21455                self.write_keyword("SEPARATOR");
21456                self.write_space();
21457                self.generate_expression(sep)?;
21458            }
21459        }
21460        if let Some(ref limit) = f.limit {
21461            self.write_space();
21462            self.write_keyword("LIMIT");
21463            self.write_space();
21464            self.generate_expression(limit)?;
21465        }
21466        self.write(")");
21467        if let Some(ref filter) = f.filter {
21468            self.write_space();
21469            self.write_keyword("FILTER");
21470            self.write("(");
21471            self.write_keyword("WHERE");
21472            self.write_space();
21473            self.generate_expression(filter)?;
21474            self.write(")");
21475        }
21476        Ok(())
21477    }
21478
21479    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
21480        let uses_within_group_order = matches!(
21481            self.config.dialect,
21482            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21483        );
21484        self.write_keyword("STRING_AGG");
21485        self.write("(");
21486        if f.distinct {
21487            self.write_keyword("DISTINCT");
21488            self.write_space();
21489        }
21490        self.generate_expression(&f.this)?;
21491        if let Some(ref separator) = f.separator {
21492            self.write(", ");
21493            self.generate_string_agg_separator(separator)?;
21494        }
21495        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
21496        if !uses_within_group_order {
21497            if let Some(ref order_by) = f.order_by {
21498                self.write_space();
21499                self.write_keyword("ORDER BY");
21500                self.write_space();
21501                for (i, ord) in order_by.iter().enumerate() {
21502                    if i > 0 {
21503                        self.write(", ");
21504                    }
21505                    self.generate_ordered(ord)?;
21506                }
21507            }
21508        }
21509        if let Some(ref limit) = f.limit {
21510            self.write_space();
21511            self.write_keyword("LIMIT");
21512            self.write_space();
21513            self.generate_expression(limit)?;
21514        }
21515        self.write(")");
21516        if uses_within_group_order {
21517            if let Some(ref order_by) = f.order_by {
21518                self.write_space();
21519                self.write_keyword("WITHIN GROUP");
21520                self.write(" (");
21521                self.write_keyword("ORDER BY");
21522                self.write_space();
21523                for (i, ord) in order_by.iter().enumerate() {
21524                    if i > 0 {
21525                        self.write(", ");
21526                    }
21527                    self.generate_ordered(ord)?;
21528                }
21529                self.write(")");
21530            }
21531        }
21532        if let Some(ref filter) = f.filter {
21533            self.write_space();
21534            self.write_keyword("FILTER");
21535            self.write("(");
21536            self.write_keyword("WHERE");
21537            self.write_space();
21538            self.generate_expression(filter)?;
21539            self.write(")");
21540        }
21541        Ok(())
21542    }
21543
21544    fn generate_string_agg_separator(&mut self, separator: &Expression) -> Result<()> {
21545        if matches!(
21546            self.config.dialect,
21547            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21548        ) {
21549            if let Some(inner) = Self::tsql_string_agg_literal_separator_cast_inner(separator) {
21550                return self.generate_expression(inner);
21551            }
21552        }
21553
21554        self.generate_expression(separator)
21555    }
21556
21557    fn tsql_string_agg_literal_separator_cast_inner(separator: &Expression) -> Option<&Expression> {
21558        let Expression::Cast(cast) = separator else {
21559            return None;
21560        };
21561
21562        if cast.format.is_some() || cast.default.is_some() {
21563            return None;
21564        }
21565
21566        if !Self::is_string_data_type(&cast.to) {
21567            return None;
21568        }
21569
21570        match &cast.this {
21571            Expression::Literal(literal) if literal.is_string() => Some(&cast.this),
21572            _ => None,
21573        }
21574    }
21575
21576    fn is_string_data_type(data_type: &DataType) -> bool {
21577        matches!(
21578            data_type,
21579            DataType::Char { .. }
21580                | DataType::VarChar { .. }
21581                | DataType::String { .. }
21582                | DataType::Text
21583                | DataType::TextWithLength { .. }
21584        ) || matches!(
21585            data_type,
21586            DataType::Custom { name, .. }
21587                if name.eq_ignore_ascii_case("VARCHAR")
21588                    || name.eq_ignore_ascii_case("NVARCHAR")
21589                    || name.eq_ignore_ascii_case("VARCHAR(MAX)")
21590                    || name.eq_ignore_ascii_case("NVARCHAR(MAX)")
21591        )
21592    }
21593
21594    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
21595        use crate::dialects::DialectType;
21596        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
21597        self.write_keyword("LISTAGG");
21598        self.write("(");
21599        if f.distinct {
21600            self.write_keyword("DISTINCT");
21601            self.write_space();
21602        }
21603        self.generate_expression(&f.this)?;
21604        if let Some(ref sep) = f.separator {
21605            self.write(", ");
21606            self.generate_expression(sep)?;
21607        } else if matches!(
21608            self.config.dialect,
21609            Some(DialectType::Trino) | Some(DialectType::Presto)
21610        ) {
21611            // Trino/Presto require explicit separator; default to ','
21612            self.write(", ','");
21613        }
21614        if let Some(ref overflow) = f.on_overflow {
21615            self.write_space();
21616            self.write_keyword("ON OVERFLOW");
21617            self.write_space();
21618            match overflow {
21619                ListAggOverflow::Error => self.write_keyword("ERROR"),
21620                ListAggOverflow::Truncate { filler, with_count } => {
21621                    self.write_keyword("TRUNCATE");
21622                    if let Some(ref fill) = filler {
21623                        self.write_space();
21624                        self.generate_expression(fill)?;
21625                    }
21626                    if *with_count {
21627                        self.write_space();
21628                        self.write_keyword("WITH COUNT");
21629                    } else {
21630                        self.write_space();
21631                        self.write_keyword("WITHOUT COUNT");
21632                    }
21633                }
21634            }
21635        }
21636        if order_inside_args {
21637            if let Some(ref order_by) = f.order_by {
21638                self.write_space();
21639                self.write_keyword("ORDER BY");
21640                self.write_space();
21641                for (i, ord) in order_by.iter().enumerate() {
21642                    if i > 0 {
21643                        self.write(", ");
21644                    }
21645                    self.generate_ordered(ord)?;
21646                }
21647            }
21648        }
21649        self.write(")");
21650        if !order_inside_args {
21651            if let Some(ref order_by) = f.order_by {
21652                self.write_space();
21653                self.write_keyword("WITHIN GROUP");
21654                self.write(" (");
21655                self.write_keyword("ORDER BY");
21656                self.write_space();
21657                for (i, ord) in order_by.iter().enumerate() {
21658                    if i > 0 {
21659                        self.write(", ");
21660                    }
21661                    self.generate_ordered(ord)?;
21662                }
21663                self.write(")");
21664            }
21665        }
21666        if let Some(ref filter) = f.filter {
21667            self.write_space();
21668            self.write_keyword("FILTER");
21669            self.write("(");
21670            self.write_keyword("WHERE");
21671            self.write_space();
21672            self.generate_expression(filter)?;
21673            self.write(")");
21674        }
21675        Ok(())
21676    }
21677
21678    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
21679        self.write_keyword("SUM_IF");
21680        self.write("(");
21681        self.generate_expression(&f.this)?;
21682        self.write(", ");
21683        self.generate_expression(&f.condition)?;
21684        self.write(")");
21685        if let Some(ref filter) = f.filter {
21686            self.write_space();
21687            self.write_keyword("FILTER");
21688            self.write("(");
21689            self.write_keyword("WHERE");
21690            self.write_space();
21691            self.generate_expression(filter)?;
21692            self.write(")");
21693        }
21694        Ok(())
21695    }
21696
21697    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
21698        self.write_keyword("APPROX_PERCENTILE");
21699        self.write("(");
21700        self.generate_expression(&f.this)?;
21701        self.write(", ");
21702        self.generate_expression(&f.percentile)?;
21703        if let Some(ref acc) = f.accuracy {
21704            self.write(", ");
21705            self.generate_expression(acc)?;
21706        }
21707        self.write(")");
21708        if let Some(ref filter) = f.filter {
21709            self.write_space();
21710            self.write_keyword("FILTER");
21711            self.write("(");
21712            self.write_keyword("WHERE");
21713            self.write_space();
21714            self.generate_expression(filter)?;
21715            self.write(")");
21716        }
21717        Ok(())
21718    }
21719
21720    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
21721        self.write_keyword(name);
21722        self.write("(");
21723        self.generate_expression(&f.percentile)?;
21724        self.write(")");
21725        if let Some(ref order_by) = f.order_by {
21726            self.write_space();
21727            self.write_keyword("WITHIN GROUP");
21728            self.write(" (");
21729            self.write_keyword("ORDER BY");
21730            self.write_space();
21731            self.generate_expression(&f.this)?;
21732            for ord in order_by.iter() {
21733                if ord.desc {
21734                    self.write_space();
21735                    self.write_keyword("DESC");
21736                }
21737            }
21738            self.write(")");
21739        }
21740        if let Some(ref filter) = f.filter {
21741            self.write_space();
21742            self.write_keyword("FILTER");
21743            self.write("(");
21744            self.write_keyword("WHERE");
21745            self.write_space();
21746            self.generate_expression(filter)?;
21747            self.write(")");
21748        }
21749        Ok(())
21750    }
21751
21752    // Window function generators
21753
21754    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
21755        self.write_keyword("NTILE");
21756        self.write("(");
21757        if let Some(num_buckets) = &f.num_buckets {
21758            self.generate_expression(num_buckets)?;
21759        }
21760        if let Some(order_by) = &f.order_by {
21761            self.write_keyword(" ORDER BY ");
21762            for (i, ob) in order_by.iter().enumerate() {
21763                if i > 0 {
21764                    self.write(", ");
21765                }
21766                self.generate_ordered(ob)?;
21767            }
21768        }
21769        self.write(")");
21770        Ok(())
21771    }
21772
21773    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
21774        self.write_keyword(name);
21775        self.write("(");
21776        self.generate_expression(&f.this)?;
21777        if let Some(ref offset) = f.offset {
21778            self.write(", ");
21779            self.generate_expression(offset)?;
21780            if let Some(ref default) = f.default {
21781                self.write(", ");
21782                self.generate_expression(default)?;
21783            }
21784        }
21785        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
21786        if self.config.ignore_nulls_in_func {
21787            match f.ignore_nulls {
21788                Some(true) => {
21789                    self.write_space();
21790                    self.write_keyword("IGNORE NULLS");
21791                }
21792                Some(false) => {
21793                    self.write_space();
21794                    self.write_keyword("RESPECT NULLS");
21795                }
21796                None => {}
21797            }
21798        }
21799        self.write(")");
21800        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21801        if !self.config.ignore_nulls_in_func {
21802            match f.ignore_nulls {
21803                Some(true) => {
21804                    self.write_space();
21805                    self.write_keyword("IGNORE NULLS");
21806                }
21807                Some(false) => {
21808                    self.write_space();
21809                    self.write_keyword("RESPECT NULLS");
21810                }
21811                None => {}
21812            }
21813        }
21814        Ok(())
21815    }
21816
21817    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
21818        self.write_keyword(name);
21819        self.write("(");
21820        self.generate_expression(&f.this)?;
21821        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
21822        if !f.order_by.is_empty() {
21823            self.write_space();
21824            self.write_keyword("ORDER BY");
21825            self.write_space();
21826            for (i, ordered) in f.order_by.iter().enumerate() {
21827                if i > 0 {
21828                    self.write(", ");
21829                }
21830                self.generate_ordered(ordered)?;
21831            }
21832        }
21833        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21834        if self.config.ignore_nulls_in_func {
21835            match f.ignore_nulls {
21836                Some(true) => {
21837                    self.write_space();
21838                    self.write_keyword("IGNORE NULLS");
21839                }
21840                Some(false) => {
21841                    self.write_space();
21842                    self.write_keyword("RESPECT NULLS");
21843                }
21844                None => {}
21845            }
21846        }
21847        self.write(")");
21848        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21849        if !self.config.ignore_nulls_in_func {
21850            match f.ignore_nulls {
21851                Some(true) => {
21852                    self.write_space();
21853                    self.write_keyword("IGNORE NULLS");
21854                }
21855                Some(false) => {
21856                    self.write_space();
21857                    self.write_keyword("RESPECT NULLS");
21858                }
21859                None => {}
21860            }
21861        }
21862        Ok(())
21863    }
21864
21865    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
21866    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
21867    fn generate_value_func_with_ignore_nulls_bool(
21868        &mut self,
21869        name: &str,
21870        f: &ValueFunc,
21871    ) -> Result<()> {
21872        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21873            self.write_keyword(name);
21874            self.write("(");
21875            self.generate_expression(&f.this)?;
21876            self.write(", ");
21877            self.write_keyword("TRUE");
21878            self.write(")");
21879            return Ok(());
21880        }
21881        self.generate_value_func(name, f)
21882    }
21883
21884    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
21885        self.write_keyword("NTH_VALUE");
21886        self.write("(");
21887        self.generate_expression(&f.this)?;
21888        self.write(", ");
21889        self.generate_expression(&f.offset)?;
21890        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21891        if self.config.ignore_nulls_in_func {
21892            match f.ignore_nulls {
21893                Some(true) => {
21894                    self.write_space();
21895                    self.write_keyword("IGNORE NULLS");
21896                }
21897                Some(false) => {
21898                    self.write_space();
21899                    self.write_keyword("RESPECT NULLS");
21900                }
21901                None => {}
21902            }
21903        }
21904        self.write(")");
21905        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
21906        if matches!(
21907            self.config.dialect,
21908            Some(crate::dialects::DialectType::Snowflake)
21909        ) {
21910            match f.from_first {
21911                Some(true) => {
21912                    self.write_space();
21913                    self.write_keyword("FROM FIRST");
21914                }
21915                Some(false) => {
21916                    self.write_space();
21917                    self.write_keyword("FROM LAST");
21918                }
21919                None => {}
21920            }
21921        }
21922        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21923        if !self.config.ignore_nulls_in_func {
21924            match f.ignore_nulls {
21925                Some(true) => {
21926                    self.write_space();
21927                    self.write_keyword("IGNORE NULLS");
21928                }
21929                Some(false) => {
21930                    self.write_space();
21931                    self.write_keyword("RESPECT NULLS");
21932                }
21933                None => {}
21934            }
21935        }
21936        Ok(())
21937    }
21938
21939    // Additional string function generators
21940
21941    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
21942        // Standard syntax: POSITION(substr IN str)
21943        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
21944        if matches!(
21945            self.config.dialect,
21946            Some(crate::dialects::DialectType::ClickHouse)
21947        ) {
21948            self.write_keyword("POSITION");
21949            self.write("(");
21950            self.generate_expression(&f.string)?;
21951            self.write(", ");
21952            self.generate_expression(&f.substring)?;
21953            if let Some(ref start) = f.start {
21954                self.write(", ");
21955                self.generate_expression(start)?;
21956            }
21957            self.write(")");
21958            return Ok(());
21959        }
21960
21961        self.write_keyword("POSITION");
21962        self.write("(");
21963        self.generate_expression(&f.substring)?;
21964        self.write_space();
21965        self.write_keyword("IN");
21966        self.write_space();
21967        self.generate_expression(&f.string)?;
21968        if let Some(ref start) = f.start {
21969            self.write(", ");
21970            self.generate_expression(start)?;
21971        }
21972        self.write(")");
21973        Ok(())
21974    }
21975
21976    // Additional math function generators
21977
21978    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
21979        // Teradata RANDOM(lower, upper)
21980        if f.lower.is_some() || f.upper.is_some() {
21981            self.write_keyword("RANDOM");
21982            self.write("(");
21983            if let Some(ref lower) = f.lower {
21984                self.generate_expression(lower)?;
21985            }
21986            if let Some(ref upper) = f.upper {
21987                self.write(", ");
21988                self.generate_expression(upper)?;
21989            }
21990            self.write(")");
21991            return Ok(());
21992        }
21993        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
21994        let func_name = match self.config.dialect {
21995            Some(crate::dialects::DialectType::Snowflake)
21996            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
21997            _ => "RAND",
21998        };
21999        self.write_keyword(func_name);
22000        self.write("(");
22001        // DuckDB doesn't support seeded RANDOM, so skip the seed
22002        if !matches!(
22003            self.config.dialect,
22004            Some(crate::dialects::DialectType::DuckDB)
22005        ) {
22006            if let Some(ref seed) = f.seed {
22007                self.generate_expression(seed)?;
22008            }
22009        }
22010        self.write(")");
22011        Ok(())
22012    }
22013
22014    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
22015        self.write_keyword("TRUNCATE");
22016        self.write("(");
22017        self.generate_expression(&f.this)?;
22018        if let Some(ref decimals) = f.decimals {
22019            self.write(", ");
22020            self.generate_expression(decimals)?;
22021        }
22022        self.write(")");
22023        Ok(())
22024    }
22025
22026    // Control flow generators
22027
22028    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
22029        self.write_keyword("DECODE");
22030        self.write("(");
22031        self.generate_expression(&f.this)?;
22032        for (search, result) in &f.search_results {
22033            self.write(", ");
22034            self.generate_expression(search)?;
22035            self.write(", ");
22036            self.generate_expression(result)?;
22037        }
22038        if let Some(ref default) = f.default {
22039            self.write(", ");
22040            self.generate_expression(default)?;
22041        }
22042        self.write(")");
22043        Ok(())
22044    }
22045
22046    // Date/time function generators
22047
22048    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
22049        self.write_keyword(name);
22050        self.write("(");
22051        self.generate_expression(&f.this)?;
22052        self.write(", ");
22053        self.generate_expression(&f.format)?;
22054        self.write(")");
22055        Ok(())
22056    }
22057
22058    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
22059        self.write_keyword("FROM_UNIXTIME");
22060        self.write("(");
22061        self.generate_expression(&f.this)?;
22062        if let Some(ref format) = f.format {
22063            self.write(", ");
22064            self.generate_expression(format)?;
22065        }
22066        self.write(")");
22067        Ok(())
22068    }
22069
22070    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
22071        self.write_keyword("UNIX_TIMESTAMP");
22072        self.write("(");
22073        if let Some(ref expr) = f.this {
22074            self.generate_expression(expr)?;
22075            if let Some(ref format) = f.format {
22076                self.write(", ");
22077                self.generate_expression(format)?;
22078            }
22079        } else if matches!(
22080            self.config.dialect,
22081            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
22082        ) {
22083            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
22084            self.write_keyword("CURRENT_TIMESTAMP");
22085            self.write("()");
22086        }
22087        self.write(")");
22088        Ok(())
22089    }
22090
22091    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
22092        self.write_keyword("MAKE_DATE");
22093        self.write("(");
22094        self.generate_expression(&f.year)?;
22095        self.write(", ");
22096        self.generate_expression(&f.month)?;
22097        self.write(", ");
22098        self.generate_expression(&f.day)?;
22099        self.write(")");
22100        Ok(())
22101    }
22102
22103    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
22104        self.write_keyword("MAKE_TIMESTAMP");
22105        self.write("(");
22106        self.generate_expression(&f.year)?;
22107        self.write(", ");
22108        self.generate_expression(&f.month)?;
22109        self.write(", ");
22110        self.generate_expression(&f.day)?;
22111        self.write(", ");
22112        self.generate_expression(&f.hour)?;
22113        self.write(", ");
22114        self.generate_expression(&f.minute)?;
22115        self.write(", ");
22116        self.generate_expression(&f.second)?;
22117        if let Some(ref tz) = f.timezone {
22118            self.write(", ");
22119            self.generate_expression(tz)?;
22120        }
22121        self.write(")");
22122        Ok(())
22123    }
22124
22125    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
22126    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
22127        match expr {
22128            Expression::Struct(s) => {
22129                if s.fields.iter().all(|(name, _)| name.is_some()) {
22130                    Some(
22131                        s.fields
22132                            .iter()
22133                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
22134                            .collect(),
22135                    )
22136                } else {
22137                    None
22138                }
22139            }
22140            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22141                // Check if all args are Alias (named fields)
22142                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
22143                    Some(
22144                        f.args
22145                            .iter()
22146                            .filter_map(|a| {
22147                                if let Expression::Alias(alias) = a {
22148                                    Some(alias.alias.name.clone())
22149                                } else {
22150                                    None
22151                                }
22152                            })
22153                            .collect(),
22154                    )
22155                } else {
22156                    None
22157                }
22158            }
22159            _ => None,
22160        }
22161    }
22162
22163    /// Check if a struct expression has any unnamed fields
22164    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
22165        match expr {
22166            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
22167            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22168                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
22169            }
22170            _ => false,
22171        }
22172    }
22173
22174    /// Get the field count of a struct expression
22175    fn struct_field_count(expr: &Expression) -> usize {
22176        match expr {
22177            Expression::Struct(s) => s.fields.len(),
22178            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
22179            _ => 0,
22180        }
22181    }
22182
22183    /// Apply field names to an unnamed struct expression, producing a new expression with names
22184    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
22185        match expr {
22186            Expression::Struct(s) => {
22187                let mut new_fields = Vec::with_capacity(s.fields.len());
22188                for (i, (name, value)) in s.fields.iter().enumerate() {
22189                    if name.is_none() && i < field_names.len() {
22190                        new_fields.push((Some(field_names[i].clone()), value.clone()));
22191                    } else {
22192                        new_fields.push((name.clone(), value.clone()));
22193                    }
22194                }
22195                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
22196            }
22197            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22198                let mut new_args = Vec::with_capacity(f.args.len());
22199                for (i, arg) in f.args.iter().enumerate() {
22200                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
22201                        // Wrap the value in an Alias with the inherited name
22202                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
22203                            this: arg.clone(),
22204                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
22205                            column_aliases: Vec::new(),
22206                            alias_explicit_as: false,
22207                            alias_keyword: None,
22208                            pre_alias_comments: Vec::new(),
22209                            trailing_comments: Vec::new(),
22210                            inferred_type: None,
22211                        })));
22212                    } else {
22213                        new_args.push(arg.clone());
22214                    }
22215                }
22216                Expression::Function(Box::new(crate::expressions::Function {
22217                    name: f.name.clone(),
22218                    args: new_args,
22219                    distinct: f.distinct,
22220                    trailing_comments: f.trailing_comments.clone(),
22221                    use_bracket_syntax: f.use_bracket_syntax,
22222                    no_parens: f.no_parens,
22223                    quoted: f.quoted,
22224                    span: None,
22225                    inferred_type: None,
22226                }))
22227            }
22228            _ => expr.clone(),
22229        }
22230    }
22231
22232    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
22233    /// This implements BigQuery's implicit field name inheritance for struct arrays.
22234    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
22235    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
22236        let first = match expressions.first() {
22237            Some(e) => e,
22238            None => return expressions.to_vec(),
22239        };
22240
22241        let field_names = match Self::extract_struct_field_names(first) {
22242            Some(names) if !names.is_empty() => names,
22243            _ => return expressions.to_vec(),
22244        };
22245
22246        let mut result = Vec::with_capacity(expressions.len());
22247        for (idx, expr) in expressions.iter().enumerate() {
22248            if idx == 0 {
22249                result.push(expr.clone());
22250                continue;
22251            }
22252            // Check if this is a struct with unnamed fields that needs name propagation
22253            if Self::struct_field_count(expr) == field_names.len()
22254                && Self::struct_has_unnamed_fields(expr)
22255            {
22256                result.push(Self::apply_struct_field_names(expr, &field_names));
22257            } else {
22258                result.push(expr.clone());
22259            }
22260        }
22261        result
22262    }
22263
22264    // Array function generators
22265
22266    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
22267        // Apply struct name inheritance for target dialects that need it
22268        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
22269        let needs_inheritance = matches!(
22270            self.config.dialect,
22271            Some(DialectType::DuckDB)
22272                | Some(DialectType::Spark)
22273                | Some(DialectType::Databricks)
22274                | Some(DialectType::Hive)
22275                | Some(DialectType::Snowflake)
22276                | Some(DialectType::Presto)
22277                | Some(DialectType::Trino)
22278        );
22279        let propagated: Vec<Expression>;
22280        let expressions = if needs_inheritance && f.expressions.len() > 1 {
22281            propagated = Self::inherit_struct_field_names(&f.expressions);
22282            &propagated
22283        } else {
22284            &f.expressions
22285        };
22286
22287        // Check if elements should be split onto multiple lines (pretty + too wide)
22288        let should_split = if self.config.pretty && !expressions.is_empty() {
22289            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
22290            for expr in expressions {
22291                let mut temp_gen = Generator::with_arc_config(self.config.clone());
22292                Arc::make_mut(&mut temp_gen.config).pretty = false;
22293                temp_gen.generate_expression(expr)?;
22294                expr_strings.push(temp_gen.output);
22295            }
22296            self.too_wide(&expr_strings)
22297        } else {
22298            false
22299        };
22300
22301        if f.bracket_notation {
22302            // For Spark/Databricks, use ARRAY(...) with parens
22303            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
22304            // For others (DuckDB, Snowflake), use bare [...]
22305            let (open, close) = match self.config.dialect {
22306                None
22307                | Some(DialectType::Generic)
22308                | Some(DialectType::Spark)
22309                | Some(DialectType::Databricks)
22310                | Some(DialectType::Hive) => {
22311                    self.write_keyword("ARRAY");
22312                    ("(", ")")
22313                }
22314                Some(DialectType::Presto)
22315                | Some(DialectType::Trino)
22316                | Some(DialectType::PostgreSQL)
22317                | Some(DialectType::Redshift)
22318                | Some(DialectType::Materialize)
22319                | Some(DialectType::RisingWave)
22320                | Some(DialectType::CockroachDB) => {
22321                    self.write_keyword("ARRAY");
22322                    ("[", "]")
22323                }
22324                _ => ("[", "]"),
22325            };
22326            self.write(open);
22327            if should_split {
22328                self.write_newline();
22329                self.indent_level += 1;
22330                for (i, expr) in expressions.iter().enumerate() {
22331                    self.write_indent();
22332                    self.generate_expression(expr)?;
22333                    if i + 1 < expressions.len() {
22334                        self.write(",");
22335                    }
22336                    self.write_newline();
22337                }
22338                self.indent_level -= 1;
22339                self.write_indent();
22340            } else {
22341                for (i, expr) in expressions.iter().enumerate() {
22342                    if i > 0 {
22343                        self.write(", ");
22344                    }
22345                    self.generate_expression(expr)?;
22346                }
22347            }
22348            self.write(close);
22349        } else {
22350            // Use LIST keyword if that was the original syntax (DuckDB)
22351            if f.use_list_keyword {
22352                self.write_keyword("LIST");
22353            } else {
22354                self.write_keyword("ARRAY");
22355            }
22356            // For Spark/Hive, always use ARRAY(...) with parens
22357            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
22358            let has_subquery = expressions
22359                .iter()
22360                .any(|e| matches!(e, Expression::Select(_)));
22361            let (open, close) = if matches!(
22362                self.config.dialect,
22363                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
22364            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
22365                && has_subquery)
22366            {
22367                ("(", ")")
22368            } else {
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        }
22395        Ok(())
22396    }
22397
22398    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
22399        self.write_keyword("ARRAY_SORT");
22400        self.write("(");
22401        self.generate_expression(&f.this)?;
22402        if let Some(ref comp) = f.comparator {
22403            self.write(", ");
22404            self.generate_expression(comp)?;
22405        }
22406        self.write(")");
22407        Ok(())
22408    }
22409
22410    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
22411        self.write_keyword(name);
22412        self.write("(");
22413        self.generate_expression(&f.this)?;
22414        self.write(", ");
22415        self.generate_expression(&f.separator)?;
22416        if let Some(ref null_rep) = f.null_replacement {
22417            self.write(", ");
22418            self.generate_expression(null_rep)?;
22419        }
22420        self.write(")");
22421        Ok(())
22422    }
22423
22424    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
22425        self.write_keyword("UNNEST");
22426        self.write("(");
22427        self.generate_expression(&f.this)?;
22428        for extra in &f.expressions {
22429            self.write(", ");
22430            self.generate_expression(extra)?;
22431        }
22432        self.write(")");
22433        if f.with_ordinality {
22434            self.write_space();
22435            if self.config.unnest_with_ordinality {
22436                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
22437                self.write_keyword("WITH ORDINALITY");
22438            } else if f.offset_alias.is_some() {
22439                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
22440                // Alias (if any) comes BEFORE WITH OFFSET
22441                if let Some(ref alias) = f.alias {
22442                    self.write_keyword("AS");
22443                    self.write_space();
22444                    self.generate_identifier(alias)?;
22445                    self.write_space();
22446                }
22447                self.write_keyword("WITH OFFSET");
22448                if let Some(ref offset_alias) = f.offset_alias {
22449                    self.write_space();
22450                    self.write_keyword("AS");
22451                    self.write_space();
22452                    self.generate_identifier(offset_alias)?;
22453                }
22454            } else {
22455                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
22456                self.write_keyword("WITH OFFSET");
22457                if f.alias.is_none() {
22458                    self.write(" AS offset");
22459                }
22460            }
22461        }
22462        if let Some(ref alias) = f.alias {
22463            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
22464            let should_add_alias = if !f.with_ordinality {
22465                true
22466            } else if self.config.unnest_with_ordinality {
22467                // Presto/Trino: alias comes after WITH ORDINALITY
22468                true
22469            } else if f.offset_alias.is_some() {
22470                // BigQuery expansion: alias already handled above
22471                false
22472            } else {
22473                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
22474                true
22475            };
22476            if should_add_alias {
22477                self.write_space();
22478                self.write_keyword("AS");
22479                self.write_space();
22480                self.generate_identifier(alias)?;
22481            }
22482        }
22483        Ok(())
22484    }
22485
22486    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
22487        self.write_keyword("FILTER");
22488        self.write("(");
22489        self.generate_expression(&f.this)?;
22490        self.write(", ");
22491        self.generate_expression(&f.filter)?;
22492        self.write(")");
22493        Ok(())
22494    }
22495
22496    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
22497        self.write_keyword("TRANSFORM");
22498        self.write("(");
22499        self.generate_expression(&f.this)?;
22500        self.write(", ");
22501        self.generate_expression(&f.transform)?;
22502        self.write(")");
22503        Ok(())
22504    }
22505
22506    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
22507        self.write_keyword(name);
22508        self.write("(");
22509        self.generate_expression(&f.start)?;
22510        self.write(", ");
22511        self.generate_expression(&f.stop)?;
22512        if let Some(ref step) = f.step {
22513            self.write(", ");
22514            self.generate_expression(step)?;
22515        }
22516        self.write(")");
22517        Ok(())
22518    }
22519
22520    // Struct function generators
22521
22522    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
22523        self.write_keyword("STRUCT");
22524        self.write("(");
22525        for (i, (name, expr)) in f.fields.iter().enumerate() {
22526            if i > 0 {
22527                self.write(", ");
22528            }
22529            if let Some(ref id) = name {
22530                self.generate_identifier(id)?;
22531                self.write(" ");
22532                self.write_keyword("AS");
22533                self.write(" ");
22534            }
22535            self.generate_expression(expr)?;
22536        }
22537        self.write(")");
22538        Ok(())
22539    }
22540
22541    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
22542    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
22543        // Extract named/unnamed fields from function args
22544        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
22545        let mut names: Vec<Option<String>> = Vec::new();
22546        let mut values: Vec<&Expression> = Vec::new();
22547        let mut all_named = true;
22548
22549        for arg in &func.args {
22550            match arg {
22551                Expression::Alias(a) => {
22552                    names.push(Some(a.alias.name.clone()));
22553                    values.push(&a.this);
22554                }
22555                _ => {
22556                    names.push(None);
22557                    values.push(arg);
22558                    all_named = false;
22559                }
22560            }
22561        }
22562
22563        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22564            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
22565            self.write("{");
22566            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22567                if i > 0 {
22568                    self.write(", ");
22569                }
22570                if let Some(n) = name {
22571                    self.write("'");
22572                    self.write(n);
22573                    self.write("'");
22574                } else {
22575                    self.write("'_");
22576                    self.write(&i.to_string());
22577                    self.write("'");
22578                }
22579                self.write(": ");
22580                self.generate_expression(value)?;
22581            }
22582            self.write("}");
22583            return Ok(());
22584        }
22585
22586        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22587            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
22588            self.write_keyword("OBJECT_CONSTRUCT");
22589            self.write("(");
22590            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22591                if i > 0 {
22592                    self.write(", ");
22593                }
22594                if let Some(n) = name {
22595                    self.write("'");
22596                    self.write(n);
22597                    self.write("'");
22598                } else {
22599                    self.write("'_");
22600                    self.write(&i.to_string());
22601                    self.write("'");
22602                }
22603                self.write(", ");
22604                self.generate_expression(value)?;
22605            }
22606            self.write(")");
22607            return Ok(());
22608        }
22609
22610        if matches!(
22611            self.config.dialect,
22612            Some(DialectType::Presto) | Some(DialectType::Trino)
22613        ) {
22614            if all_named && !names.is_empty() {
22615                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
22616                // Need to infer types from values
22617                self.write_keyword("CAST");
22618                self.write("(");
22619                self.write_keyword("ROW");
22620                self.write("(");
22621                for (i, value) in values.iter().enumerate() {
22622                    if i > 0 {
22623                        self.write(", ");
22624                    }
22625                    self.generate_expression(value)?;
22626                }
22627                self.write(")");
22628                self.write(" ");
22629                self.write_keyword("AS");
22630                self.write(" ");
22631                self.write_keyword("ROW");
22632                self.write("(");
22633                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22634                    if i > 0 {
22635                        self.write(", ");
22636                    }
22637                    if let Some(n) = name {
22638                        self.write(n);
22639                    }
22640                    self.write(" ");
22641                    let type_str = Self::infer_sql_type_for_presto(value);
22642                    self.write_keyword(&type_str);
22643                }
22644                self.write(")");
22645                self.write(")");
22646            } else {
22647                // Unnamed: ROW(values...)
22648                self.write_keyword("ROW");
22649                self.write("(");
22650                for (i, value) in values.iter().enumerate() {
22651                    if i > 0 {
22652                        self.write(", ");
22653                    }
22654                    self.generate_expression(value)?;
22655                }
22656                self.write(")");
22657            }
22658            return Ok(());
22659        }
22660
22661        // Default: ROW(values...) for other dialects
22662        self.write_keyword("ROW");
22663        self.write("(");
22664        for (i, value) in values.iter().enumerate() {
22665            if i > 0 {
22666                self.write(", ");
22667            }
22668            self.generate_expression(value)?;
22669        }
22670        self.write(")");
22671        Ok(())
22672    }
22673
22674    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
22675    fn infer_sql_type_for_presto(expr: &Expression) -> String {
22676        match expr {
22677            Expression::Literal(lit)
22678                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
22679            {
22680                "VARCHAR".to_string()
22681            }
22682            Expression::Literal(lit)
22683                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
22684            {
22685                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
22686                    unreachable!()
22687                };
22688                if n.contains('.') {
22689                    "DOUBLE".to_string()
22690                } else {
22691                    "INTEGER".to_string()
22692                }
22693            }
22694            Expression::Boolean(_) => "BOOLEAN".to_string(),
22695            Expression::Literal(lit)
22696                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
22697            {
22698                "DATE".to_string()
22699            }
22700            Expression::Literal(lit)
22701                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
22702            {
22703                "TIMESTAMP".to_string()
22704            }
22705            Expression::Literal(lit)
22706                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
22707            {
22708                "TIMESTAMP".to_string()
22709            }
22710            Expression::Array(_) | Expression::ArrayFunc(_) => {
22711                // Try to infer element type from first element
22712                "ARRAY(VARCHAR)".to_string()
22713            }
22714            // For nested structs - generate a nested ROW type by inspecting fields
22715            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
22716            Expression::Function(f) => {
22717                if f.name.eq_ignore_ascii_case("STRUCT") {
22718                    "ROW".to_string()
22719                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
22720                    "DATE".to_string()
22721                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
22722                    || f.name.eq_ignore_ascii_case("NOW")
22723                {
22724                    "TIMESTAMP".to_string()
22725                } else {
22726                    "VARCHAR".to_string()
22727                }
22728            }
22729            _ => "VARCHAR".to_string(),
22730        }
22731    }
22732
22733    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
22734        // DuckDB uses STRUCT_EXTRACT function syntax
22735        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22736            self.write_keyword("STRUCT_EXTRACT");
22737            self.write("(");
22738            self.generate_expression(&f.this)?;
22739            self.write(", ");
22740            // Output field name as string literal
22741            self.write("'");
22742            self.write(&f.field.name);
22743            self.write("'");
22744            self.write(")");
22745            return Ok(());
22746        }
22747        self.generate_expression(&f.this)?;
22748        self.write(".");
22749        self.generate_identifier(&f.field)
22750    }
22751
22752    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
22753        if matches!(
22754            self.config.dialect,
22755            Some(DialectType::Spark | DialectType::Databricks)
22756        ) {
22757            self.write_keyword("STRUCT");
22758            self.write("(");
22759            for (i, (name, value)) in f.pairs.iter().enumerate() {
22760                if i > 0 {
22761                    self.write(", ");
22762                }
22763                self.generate_expression(value)?;
22764                self.write(" ");
22765                self.write_keyword("AS");
22766                self.write(" ");
22767                if let Expression::Literal(lit) = name {
22768                    if let Literal::String(field_name) = lit.as_ref() {
22769                        self.generate_identifier(&Identifier::new(field_name))?;
22770                    } else {
22771                        self.generate_expression(name)?;
22772                    }
22773                } else {
22774                    self.generate_expression(name)?;
22775                }
22776            }
22777            self.write(")");
22778            return Ok(());
22779        }
22780
22781        self.write_keyword("NAMED_STRUCT");
22782        self.write("(");
22783        for (i, (name, value)) in f.pairs.iter().enumerate() {
22784            if i > 0 {
22785                self.write(", ");
22786            }
22787            self.generate_expression(name)?;
22788            self.write(", ");
22789            self.generate_expression(value)?;
22790        }
22791        self.write(")");
22792        Ok(())
22793    }
22794
22795    // Map function generators
22796
22797    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
22798        if f.curly_brace_syntax {
22799            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
22800            if f.with_map_keyword {
22801                self.write_keyword("MAP");
22802                self.write(" ");
22803            }
22804            self.write("{");
22805            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
22806                if i > 0 {
22807                    self.write(", ");
22808                }
22809                self.generate_expression(key)?;
22810                self.write(": ");
22811                self.generate_expression(val)?;
22812            }
22813            self.write("}");
22814        } else {
22815            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
22816            self.write_keyword("MAP");
22817            self.write("(");
22818            self.write_keyword("ARRAY");
22819            self.write("[");
22820            for (i, key) in f.keys.iter().enumerate() {
22821                if i > 0 {
22822                    self.write(", ");
22823                }
22824                self.generate_expression(key)?;
22825            }
22826            self.write("], ");
22827            self.write_keyword("ARRAY");
22828            self.write("[");
22829            for (i, val) in f.values.iter().enumerate() {
22830                if i > 0 {
22831                    self.write(", ");
22832                }
22833                self.generate_expression(val)?;
22834            }
22835            self.write("])");
22836        }
22837        Ok(())
22838    }
22839
22840    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
22841        self.write_keyword(name);
22842        self.write("(");
22843        self.generate_expression(&f.this)?;
22844        self.write(", ");
22845        self.generate_expression(&f.transform)?;
22846        self.write(")");
22847        Ok(())
22848    }
22849
22850    // JSON function generators
22851
22852    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
22853        use crate::dialects::DialectType;
22854
22855        // Check if we should use arrow syntax (-> or ->>)
22856        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
22857
22858        if use_arrow {
22859            // Output arrow syntax: expr -> path or expr ->> path
22860            self.generate_expression(&f.this)?;
22861            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
22862                self.write(" ->> ");
22863            } else {
22864                self.write(" -> ");
22865            }
22866            self.generate_expression(&f.path)?;
22867            return Ok(());
22868        }
22869
22870        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
22871        if f.hash_arrow_syntax
22872            && matches!(
22873                self.config.dialect,
22874                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22875            )
22876        {
22877            self.generate_expression(&f.this)?;
22878            self.write(" #>> ");
22879            self.generate_expression(&f.path)?;
22880            return Ok(());
22881        }
22882
22883        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
22884        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
22885        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22886            match name {
22887                "JSON_EXTRACT_SCALAR"
22888                | "JSON_EXTRACT_PATH_TEXT"
22889                | "JSON_EXTRACT"
22890                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
22891                _ => name,
22892            }
22893        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
22894            match name {
22895                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
22896                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
22897                _ => name,
22898            }
22899        } else {
22900            name
22901        };
22902
22903        self.write_keyword(func_name);
22904        self.write("(");
22905        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
22906        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22907            if let Expression::Cast(ref cast) = f.this {
22908                if matches!(cast.to, crate::expressions::DataType::Json) {
22909                    self.generate_expression(&cast.this)?;
22910                } else {
22911                    self.generate_expression(&f.this)?;
22912                }
22913            } else {
22914                self.generate_expression(&f.this)?;
22915            }
22916        } else {
22917            self.generate_expression(&f.this)?;
22918        }
22919        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
22920        // decompose JSON path into separate string arguments
22921        if matches!(
22922            self.config.dialect,
22923            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22924        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
22925        {
22926            if let Expression::Literal(ref lit) = f.path {
22927                if let Literal::String(ref s) = lit.as_ref() {
22928                    let parts = Self::decompose_json_path(s);
22929                    for part in &parts {
22930                        self.write(", '");
22931                        self.write(part);
22932                        self.write("'");
22933                    }
22934                }
22935            } else {
22936                self.write(", ");
22937                self.generate_expression(&f.path)?;
22938            }
22939        } else {
22940            self.write(", ");
22941            self.generate_expression(&f.path)?;
22942        }
22943
22944        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
22945        // These go BEFORE the closing parenthesis
22946        if let Some(ref wrapper) = f.wrapper_option {
22947            self.write_space();
22948            self.write_keyword(wrapper);
22949        }
22950        if let Some(ref quotes) = f.quotes_option {
22951            self.write_space();
22952            self.write_keyword(quotes);
22953            if f.on_scalar_string {
22954                self.write_space();
22955                self.write_keyword("ON SCALAR STRING");
22956            }
22957        }
22958        if let Some(ref on_err) = f.on_error {
22959            self.write_space();
22960            self.write_keyword(on_err);
22961        }
22962        if let Some(ref ret_type) = f.returning {
22963            self.write_space();
22964            self.write_keyword("RETURNING");
22965            self.write_space();
22966            self.generate_data_type(ret_type)?;
22967        }
22968
22969        self.write(")");
22970        Ok(())
22971    }
22972
22973    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
22974    fn dialect_supports_json_arrow(&self) -> bool {
22975        use crate::dialects::DialectType;
22976        match self.config.dialect {
22977            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
22978            Some(DialectType::PostgreSQL) => true,
22979            Some(DialectType::MySQL) => true,
22980            Some(DialectType::DuckDB) => true,
22981            Some(DialectType::CockroachDB) => true,
22982            Some(DialectType::StarRocks) => true,
22983            Some(DialectType::SQLite) => true,
22984            // Other dialects use function syntax
22985            _ => false,
22986        }
22987    }
22988
22989    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
22990        use crate::dialects::DialectType;
22991
22992        // PostgreSQL uses #> operator for JSONB path extraction
22993        if matches!(
22994            self.config.dialect,
22995            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22996        ) && name == "JSON_EXTRACT_PATH"
22997        {
22998            self.generate_expression(&f.this)?;
22999            self.write(" #> ");
23000            if f.paths.len() == 1 {
23001                self.generate_expression(&f.paths[0])?;
23002            } else {
23003                // Multiple paths: ARRAY[path1, path2, ...]
23004                self.write_keyword("ARRAY");
23005                self.write("[");
23006                for (i, path) in f.paths.iter().enumerate() {
23007                    if i > 0 {
23008                        self.write(", ");
23009                    }
23010                    self.generate_expression(path)?;
23011                }
23012                self.write("]");
23013            }
23014            return Ok(());
23015        }
23016
23017        self.write_keyword(name);
23018        self.write("(");
23019        self.generate_expression(&f.this)?;
23020        for path in &f.paths {
23021            self.write(", ");
23022            self.generate_expression(path)?;
23023        }
23024        self.write(")");
23025        Ok(())
23026    }
23027
23028    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
23029        use crate::dialects::DialectType;
23030
23031        self.write_keyword("JSON_OBJECT");
23032        self.write("(");
23033        if f.star {
23034            self.write("*");
23035        } else {
23036            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
23037            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
23038            // Also respect the json_key_value_pair_sep config
23039            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
23040                || matches!(
23041                    self.config.dialect,
23042                    Some(DialectType::BigQuery)
23043                        | Some(DialectType::MySQL)
23044                        | Some(DialectType::SQLite)
23045                );
23046
23047            for (i, (key, value)) in f.pairs.iter().enumerate() {
23048                if i > 0 {
23049                    self.write(", ");
23050                }
23051                self.generate_expression(key)?;
23052                if use_comma_syntax {
23053                    self.write(", ");
23054                } else {
23055                    self.write(": ");
23056                }
23057                self.generate_expression(value)?;
23058            }
23059        }
23060        if let Some(null_handling) = f.null_handling {
23061            self.write_space();
23062            match null_handling {
23063                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23064                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23065            }
23066        }
23067        if f.with_unique_keys {
23068            self.write_space();
23069            self.write_keyword("WITH UNIQUE KEYS");
23070        }
23071        if let Some(ref ret_type) = f.returning_type {
23072            self.write_space();
23073            self.write_keyword("RETURNING");
23074            self.write_space();
23075            self.generate_data_type(ret_type)?;
23076            if f.format_json {
23077                self.write_space();
23078                self.write_keyword("FORMAT JSON");
23079            }
23080            if let Some(ref enc) = f.encoding {
23081                self.write_space();
23082                self.write_keyword("ENCODING");
23083                self.write_space();
23084                self.write(enc);
23085            }
23086        }
23087        self.write(")");
23088        Ok(())
23089    }
23090
23091    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
23092        self.write_keyword(name);
23093        self.write("(");
23094        self.generate_expression(&f.this)?;
23095        for (path, value) in &f.path_values {
23096            self.write(", ");
23097            self.generate_expression(path)?;
23098            self.write(", ");
23099            self.generate_expression(value)?;
23100        }
23101        self.write(")");
23102        Ok(())
23103    }
23104
23105    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
23106        self.write_keyword("JSON_ARRAYAGG");
23107        self.write("(");
23108        self.generate_expression(&f.this)?;
23109        if let Some(ref order_by) = f.order_by {
23110            self.write_space();
23111            self.write_keyword("ORDER BY");
23112            self.write_space();
23113            for (i, ord) in order_by.iter().enumerate() {
23114                if i > 0 {
23115                    self.write(", ");
23116                }
23117                self.generate_ordered(ord)?;
23118            }
23119        }
23120        if let Some(null_handling) = f.null_handling {
23121            self.write_space();
23122            match null_handling {
23123                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23124                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23125            }
23126        }
23127        self.write(")");
23128        if let Some(ref filter) = f.filter {
23129            self.write_space();
23130            self.write_keyword("FILTER");
23131            self.write("(");
23132            self.write_keyword("WHERE");
23133            self.write_space();
23134            self.generate_expression(filter)?;
23135            self.write(")");
23136        }
23137        Ok(())
23138    }
23139
23140    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
23141        self.write_keyword("JSON_OBJECTAGG");
23142        self.write("(");
23143        self.generate_expression(&f.key)?;
23144        self.write(": ");
23145        self.generate_expression(&f.value)?;
23146        if let Some(null_handling) = f.null_handling {
23147            self.write_space();
23148            match null_handling {
23149                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23150                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23151            }
23152        }
23153        self.write(")");
23154        if let Some(ref filter) = f.filter {
23155            self.write_space();
23156            self.write_keyword("FILTER");
23157            self.write("(");
23158            self.write_keyword("WHERE");
23159            self.write_space();
23160            self.generate_expression(filter)?;
23161            self.write(")");
23162        }
23163        Ok(())
23164    }
23165
23166    // Type casting/conversion generators
23167
23168    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
23169        use crate::dialects::DialectType;
23170
23171        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
23172        if self.config.dialect == Some(DialectType::Redshift) {
23173            self.write_keyword("CAST");
23174            self.write("(");
23175            self.generate_expression(&f.this)?;
23176            self.write_space();
23177            self.write_keyword("AS");
23178            self.write_space();
23179            self.generate_data_type(&f.to)?;
23180            self.write(")");
23181            return Ok(());
23182        }
23183
23184        self.write_keyword("CONVERT");
23185        self.write("(");
23186        self.generate_data_type(&f.to)?;
23187        self.write(", ");
23188        self.generate_expression(&f.this)?;
23189        if let Some(ref style) = f.style {
23190            self.write(", ");
23191            self.generate_expression(style)?;
23192        }
23193        self.write(")");
23194        Ok(())
23195    }
23196
23197    // Additional expression generators
23198
23199    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
23200        if f.colon {
23201            // DuckDB syntax: LAMBDA x : expr
23202            self.write_keyword("LAMBDA");
23203            self.write_space();
23204            for (i, param) in f.parameters.iter().enumerate() {
23205                if i > 0 {
23206                    self.write(", ");
23207                }
23208                self.generate_identifier(param)?;
23209            }
23210            self.write(" : ");
23211        } else {
23212            // Standard syntax: x -> expr or (x, y) -> expr
23213            if f.parameters.len() == 1 {
23214                self.generate_identifier(&f.parameters[0])?;
23215            } else {
23216                self.write("(");
23217                for (i, param) in f.parameters.iter().enumerate() {
23218                    if i > 0 {
23219                        self.write(", ");
23220                    }
23221                    self.generate_identifier(param)?;
23222                }
23223                self.write(")");
23224            }
23225            self.write(" -> ");
23226        }
23227        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23228            if let Expression::Lambda(inner) = &f.body {
23229                self.generate_lambda_with_parenthesized_single_param(inner)?;
23230                return Ok(());
23231            }
23232        }
23233
23234        self.generate_expression(&f.body)
23235    }
23236
23237    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
23238        if f.colon {
23239            return self.generate_lambda(f);
23240        }
23241
23242        self.write("(");
23243        for (i, param) in f.parameters.iter().enumerate() {
23244            if i > 0 {
23245                self.write(", ");
23246            }
23247            self.generate_identifier(param)?;
23248        }
23249        self.write(") -> ");
23250        self.generate_expression(&f.body)
23251    }
23252
23253    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
23254        self.generate_identifier(&f.name)?;
23255        match f.separator {
23256            NamedArgSeparator::DArrow => self.write(" => "),
23257            NamedArgSeparator::ColonEq => self.write(" := "),
23258            NamedArgSeparator::Eq => self.write(" = "),
23259        }
23260        self.generate_expression(&f.value)
23261    }
23262
23263    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
23264        self.write_keyword(&f.prefix);
23265        self.write(" ");
23266        self.generate_expression(&f.this)
23267    }
23268
23269    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
23270        match f.style {
23271            ParameterStyle::Question => self.write("?"),
23272            ParameterStyle::Dollar => {
23273                if let Some(idx) = f.index {
23274                    if matches!(
23275                        self.config.dialect,
23276                        Some(DialectType::TSQL) | Some(DialectType::Fabric)
23277                    ) {
23278                        self.write("@P");
23279                        self.write(&idx.to_string());
23280                    } else {
23281                        self.write("$");
23282                        self.write(&idx.to_string());
23283                    }
23284                } else {
23285                    self.write("$");
23286                    if let Some(ref name) = f.name {
23287                        // Session variable like $x or $query_id
23288                        self.write(name);
23289                    }
23290                }
23291            }
23292            ParameterStyle::DollarBrace => {
23293                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
23294                self.write("${");
23295                if let Some(ref name) = f.name {
23296                    self.write(name);
23297                }
23298                if let Some(ref expr) = f.expression {
23299                    self.write(":");
23300                    self.write(expr);
23301                }
23302                self.write("}");
23303            }
23304            ParameterStyle::Colon => {
23305                self.write(":");
23306                if let Some(idx) = f.index {
23307                    self.write(&idx.to_string());
23308                } else if let Some(ref name) = f.name {
23309                    self.write(name);
23310                }
23311            }
23312            ParameterStyle::At => {
23313                self.write("@");
23314                if let Some(ref name) = f.name {
23315                    if f.string_quoted {
23316                        self.write("'");
23317                        self.write(name);
23318                        self.write("'");
23319                    } else if f.quoted {
23320                        self.write("\"");
23321                        self.write(name);
23322                        self.write("\"");
23323                    } else {
23324                        self.write(name);
23325                    }
23326                }
23327            }
23328            ParameterStyle::DoubleAt => {
23329                self.write("@@");
23330                if let Some(ref name) = f.name {
23331                    self.write(name);
23332                }
23333            }
23334            ParameterStyle::DoubleDollar => {
23335                self.write("$$");
23336                if let Some(ref name) = f.name {
23337                    self.write(name);
23338                }
23339            }
23340            ParameterStyle::Percent => {
23341                if let Some(ref name) = f.name {
23342                    // %(name)s format
23343                    self.write("%(");
23344                    self.write(name);
23345                    self.write(")s");
23346                } else {
23347                    // %s format
23348                    self.write("%s");
23349                }
23350            }
23351            ParameterStyle::Brace => {
23352                // Spark/Databricks widget template variable: {name}
23353                // ClickHouse query parameter may include kind: {name: Type}
23354                self.write("{");
23355                if let Some(ref name) = f.name {
23356                    self.write(name);
23357                }
23358                if let Some(ref expr) = f.expression {
23359                    self.write(": ");
23360                    self.write(expr);
23361                }
23362                self.write("}");
23363            }
23364        }
23365        Ok(())
23366    }
23367
23368    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
23369        self.write("?");
23370        if let Some(idx) = f.index {
23371            self.write(&idx.to_string());
23372        }
23373        Ok(())
23374    }
23375
23376    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
23377        if f.is_block {
23378            self.write("/*");
23379            self.write(&f.text);
23380            self.write("*/");
23381        } else {
23382            self.write("--");
23383            self.write(&f.text);
23384        }
23385        Ok(())
23386    }
23387
23388    // Additional predicate generators
23389
23390    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
23391        self.generate_expression(&f.this)?;
23392        if f.not {
23393            self.write_space();
23394            self.write_keyword("NOT");
23395        }
23396        self.write_space();
23397        self.write_keyword("SIMILAR TO");
23398        self.write_space();
23399        self.generate_expression(&f.pattern)?;
23400        if let Some(ref escape) = f.escape {
23401            self.write_space();
23402            self.write_keyword("ESCAPE");
23403            self.write_space();
23404            self.generate_expression(escape)?;
23405        }
23406        Ok(())
23407    }
23408
23409    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
23410        self.generate_expression(&f.this)?;
23411        self.write_space();
23412        // Output comparison operator if present
23413        if let Some(op) = &f.op {
23414            match op {
23415                QuantifiedOp::Eq => self.write("="),
23416                QuantifiedOp::Neq => self.write("<>"),
23417                QuantifiedOp::Lt => self.write("<"),
23418                QuantifiedOp::Lte => self.write("<="),
23419                QuantifiedOp::Gt => self.write(">"),
23420                QuantifiedOp::Gte => self.write(">="),
23421            }
23422            self.write_space();
23423        }
23424        self.write_keyword(name);
23425
23426        // If the child is a Subquery, it provides its own parens — output with space
23427        if matches!(&f.subquery, Expression::Subquery(_)) {
23428            self.write_space();
23429            self.generate_expression(&f.subquery)?;
23430        } else {
23431            let is_statement = matches!(
23432                &f.subquery,
23433                Expression::Select(_)
23434                    | Expression::Union(_)
23435                    | Expression::Intersect(_)
23436                    | Expression::Except(_)
23437            );
23438            if is_statement
23439                && !self.config.quantified_no_paren_space
23440                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
23441            {
23442                self.write_space();
23443            }
23444            self.write("(");
23445
23446            if self.config.pretty && is_statement {
23447                self.write_newline();
23448                self.indent_level += 1;
23449                self.write_indent();
23450            }
23451            self.generate_expression(&f.subquery)?;
23452            if self.config.pretty && is_statement {
23453                self.write_newline();
23454                self.indent_level -= 1;
23455                self.write_indent();
23456            }
23457            self.write(")");
23458        }
23459        Ok(())
23460    }
23461
23462    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
23463        // Check if this is a simple binary form (this OVERLAPS expression)
23464        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
23465            self.generate_expression(this)?;
23466            self.write_space();
23467            self.write_keyword("OVERLAPS");
23468            self.write_space();
23469            self.generate_expression(expr)?;
23470        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
23471            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
23472        {
23473            // Full ANSI form: (a, b) OVERLAPS (c, d)
23474            self.write("(");
23475            self.generate_expression(ls)?;
23476            self.write(", ");
23477            self.generate_expression(le)?;
23478            self.write(")");
23479            self.write_space();
23480            self.write_keyword("OVERLAPS");
23481            self.write_space();
23482            self.write("(");
23483            self.generate_expression(rs)?;
23484            self.write(", ");
23485            self.generate_expression(re)?;
23486            self.write(")");
23487        }
23488        Ok(())
23489    }
23490
23491    // Type conversion generators
23492
23493    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
23494        use crate::dialects::DialectType;
23495
23496        // SingleStore uses !:> syntax for try cast
23497        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23498            self.generate_expression(&cast.this)?;
23499            self.write(" !:> ");
23500            self.generate_data_type(&cast.to)?;
23501            return Ok(());
23502        }
23503
23504        // Teradata uses TRYCAST (no underscore)
23505        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
23506            self.write_keyword("TRYCAST");
23507            self.write("(");
23508            self.generate_expression(&cast.this)?;
23509            self.write_space();
23510            self.write_keyword("AS");
23511            self.write_space();
23512            self.generate_data_type(&cast.to)?;
23513            self.write(")");
23514            return Ok(());
23515        }
23516
23517        // Dialects without TRY_CAST: generate as regular CAST
23518        let keyword = if matches!(
23519            self.config.dialect,
23520            Some(DialectType::Hive)
23521                | Some(DialectType::MySQL)
23522                | Some(DialectType::SQLite)
23523                | Some(DialectType::Oracle)
23524                | Some(DialectType::ClickHouse)
23525                | Some(DialectType::Redshift)
23526                | Some(DialectType::PostgreSQL)
23527                | Some(DialectType::StarRocks)
23528                | Some(DialectType::Doris)
23529        ) {
23530            "CAST"
23531        } else {
23532            "TRY_CAST"
23533        };
23534
23535        self.write_keyword(keyword);
23536        self.write("(");
23537        self.generate_expression(&cast.this)?;
23538        self.write_space();
23539        self.write_keyword("AS");
23540        self.write_space();
23541        self.generate_data_type(&cast.to)?;
23542
23543        // Output FORMAT clause if present
23544        if let Some(format) = &cast.format {
23545            self.write_space();
23546            self.write_keyword("FORMAT");
23547            self.write_space();
23548            self.generate_expression(format)?;
23549        }
23550
23551        self.write(")");
23552        Ok(())
23553    }
23554
23555    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
23556        self.write_keyword("SAFE_CAST");
23557        self.write("(");
23558        self.generate_expression(&cast.this)?;
23559        self.write_space();
23560        self.write_keyword("AS");
23561        self.write_space();
23562        self.generate_data_type(&cast.to)?;
23563
23564        // Output FORMAT clause if present
23565        if let Some(format) = &cast.format {
23566            self.write_space();
23567            self.write_keyword("FORMAT");
23568            self.write_space();
23569            self.generate_expression(format)?;
23570        }
23571
23572        self.write(")");
23573        Ok(())
23574    }
23575
23576    // Array/struct/map access generators
23577
23578    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
23579        // Wrap the base expression in parentheses when it uses arrow syntax (->)
23580        // which has lower precedence than bracket subscript ([]).
23581        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
23582        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
23583        if needs_parens {
23584            self.write("(");
23585        }
23586        self.generate_expression(&s.this)?;
23587        if needs_parens {
23588            self.write(")");
23589        }
23590        self.write("[");
23591        self.generate_expression(&s.index)?;
23592        self.write("]");
23593        Ok(())
23594    }
23595
23596    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
23597        self.generate_expression(&d.this)?;
23598        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
23599        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
23600        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
23601            && matches!(
23602                &d.this,
23603                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
23604            );
23605        if use_colon {
23606            self.write(":");
23607        } else {
23608            self.write(".");
23609        }
23610        self.generate_identifier(&d.field)
23611    }
23612
23613    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
23614        self.generate_expression(&m.this)?;
23615        self.write(".");
23616        // Method names after a dot should not be quoted based on reserved keywords
23617        // Only quote if explicitly marked as quoted in the AST
23618        if m.method.quoted {
23619            let q = self.config.identifier_quote;
23620            self.write(&format!("{}{}{}", q, m.method.name, q));
23621        } else {
23622            self.write(&m.method.name);
23623        }
23624        self.write("(");
23625        for (i, arg) in m.args.iter().enumerate() {
23626            if i > 0 {
23627                self.write(", ");
23628            }
23629            self.generate_expression(arg)?;
23630        }
23631        self.write(")");
23632        Ok(())
23633    }
23634
23635    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
23636        // Check if we need to wrap the inner expression in parentheses
23637        // JSON arrow expressions have lower precedence than array subscript
23638        let needs_parens = matches!(
23639            &s.this,
23640            Expression::JsonExtract(f) if f.arrow_syntax
23641        ) || matches!(
23642            &s.this,
23643            Expression::JsonExtractScalar(f) if f.arrow_syntax
23644        );
23645
23646        if needs_parens {
23647            self.write("(");
23648        }
23649        self.generate_expression(&s.this)?;
23650        if needs_parens {
23651            self.write(")");
23652        }
23653        self.write("[");
23654        if let Some(start) = &s.start {
23655            self.generate_expression(start)?;
23656        }
23657        self.write(":");
23658        if let Some(end) = &s.end {
23659            self.generate_expression(end)?;
23660        }
23661        self.write("]");
23662        Ok(())
23663    }
23664
23665    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23666        // Generate left expression, but skip trailing comments if they're already in left_comments
23667        // to avoid duplication (comments are captured as both expr.trailing_comments
23668        // and BinaryOp.left_comments during parsing)
23669        match &op.left {
23670            Expression::Column(col) => {
23671                // Generate column with trailing comments but skip them if they're
23672                // already captured in BinaryOp.left_comments to avoid duplication
23673                if let Some(table) = &col.table {
23674                    self.generate_identifier(table)?;
23675                    self.write(".");
23676                }
23677                self.generate_identifier(&col.name)?;
23678                // Oracle-style join marker (+)
23679                if col.join_mark && self.config.supports_column_join_marks {
23680                    self.write(" (+)");
23681                }
23682                // Output column trailing comments if they're not already in left_comments
23683                if op.left_comments.is_empty() {
23684                    for comment in &col.trailing_comments {
23685                        self.write_space();
23686                        self.write_formatted_comment(comment);
23687                    }
23688                }
23689            }
23690            Expression::Add(inner_op)
23691            | Expression::Sub(inner_op)
23692            | Expression::Mul(inner_op)
23693            | Expression::Div(inner_op)
23694            | Expression::Concat(inner_op) => {
23695                // Generate binary op without its trailing comments
23696                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23697                    Expression::Add(_) => "+",
23698                    Expression::Sub(_) => "-",
23699                    Expression::Mul(_) => "*",
23700                    Expression::Div(_) => "/",
23701                    Expression::Concat(_) => "||",
23702                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23703                })?;
23704            }
23705            _ => {
23706                self.generate_expression(&op.left)?;
23707            }
23708        }
23709        // Output comments after left operand
23710        for comment in &op.left_comments {
23711            self.write_space();
23712            self.write_formatted_comment(comment);
23713        }
23714        if self.config.pretty
23715            && matches!(self.config.dialect, Some(DialectType::Snowflake))
23716            && (operator == "AND" || operator == "OR")
23717        {
23718            self.write_newline();
23719            self.write_indent();
23720            self.write_keyword(operator);
23721        } else {
23722            self.write_space();
23723            if operator.chars().all(|c| c.is_alphabetic()) {
23724                self.write_keyword(operator);
23725            } else {
23726                self.write(operator);
23727            }
23728        }
23729        // Output comments after operator (before right operand)
23730        for comment in &op.operator_comments {
23731            self.write_space();
23732            self.write_formatted_comment(comment);
23733        }
23734        self.write_space();
23735        self.generate_expression(&op.right)?;
23736        // Output trailing comments after right operand
23737        for comment in &op.trailing_comments {
23738            self.write_space();
23739            self.write_formatted_comment(comment);
23740        }
23741        Ok(())
23742    }
23743
23744    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
23745        let keyword = connector.keyword();
23746        let Some(terms) = self.flatten_connector_terms(op, connector) else {
23747            return self.generate_binary_op(op, keyword);
23748        };
23749
23750        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
23751            let should_wrap = matches!(connector, ConnectorOperator::Or)
23752                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
23753                && matches!(
23754                    generator.config.source_dialect,
23755                    Some(DialectType::ClickHouse)
23756                )
23757                && matches!(term, Expression::And(_));
23758            if should_wrap {
23759                generator.write("(");
23760                generator.generate_expression(term)?;
23761                generator.write(")");
23762            } else {
23763                generator.generate_expression(term)?;
23764            }
23765            Ok(())
23766        };
23767
23768        wrap_clickhouse_or_term(self, terms[0])?;
23769        for term in terms.iter().skip(1) {
23770            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
23771                self.write_newline();
23772                self.write_indent();
23773                self.write_keyword(keyword);
23774            } else {
23775                self.write_space();
23776                self.write_keyword(keyword);
23777            }
23778            self.write_space();
23779            wrap_clickhouse_or_term(self, term)?;
23780        }
23781
23782        Ok(())
23783    }
23784
23785    fn flatten_connector_terms<'a>(
23786        &self,
23787        root: &'a BinaryOp,
23788        connector: ConnectorOperator,
23789    ) -> Option<Vec<&'a Expression>> {
23790        if !root.left_comments.is_empty()
23791            || !root.operator_comments.is_empty()
23792            || !root.trailing_comments.is_empty()
23793        {
23794            return None;
23795        }
23796
23797        let mut terms = Vec::new();
23798        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
23799
23800        while let Some(expr) = stack.pop() {
23801            match (connector, expr) {
23802                (ConnectorOperator::And, Expression::And(inner))
23803                    if inner.left_comments.is_empty()
23804                        && inner.operator_comments.is_empty()
23805                        && inner.trailing_comments.is_empty() =>
23806                {
23807                    stack.push(&inner.right);
23808                    stack.push(&inner.left);
23809                }
23810                (ConnectorOperator::Or, Expression::Or(inner))
23811                    if inner.left_comments.is_empty()
23812                        && inner.operator_comments.is_empty()
23813                        && inner.trailing_comments.is_empty() =>
23814                {
23815                    stack.push(&inner.right);
23816                    stack.push(&inner.left);
23817                }
23818                _ => terms.push(expr),
23819            }
23820        }
23821
23822        if terms.len() > 1 {
23823            Some(terms)
23824        } else {
23825            None
23826        }
23827    }
23828
23829    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
23830        let mut depth = 0usize;
23831        let mut quote: Option<char> = None;
23832        let mut chars = sql.chars().peekable();
23833
23834        while let Some(ch) = chars.next() {
23835            if let Some(quote_char) = quote {
23836                if ch == '\\' {
23837                    chars.next();
23838                } else if ch == quote_char {
23839                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
23840                        chars.next();
23841                    } else {
23842                        quote = None;
23843                    }
23844                }
23845                continue;
23846            }
23847
23848            match ch {
23849                '\'' | '"' | '`' => quote = Some(ch),
23850                '(' => depth += 1,
23851                ')' => depth = depth.saturating_sub(1),
23852                _ => {}
23853            }
23854        }
23855
23856        depth
23857    }
23858
23859    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
23860    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23861        self.generate_like_op_inner(op, operator, false)
23862    }
23863
23864    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23865        self.generate_like_op_inner(op, operator, true)
23866    }
23867
23868    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
23869        if negated
23870            && matches!(
23871                self.config.dialect,
23872                Some(DialectType::ClickHouse)
23873                    | Some(DialectType::DataFusion)
23874                    | Some(DialectType::TSQL)
23875                    | Some(DialectType::Fabric)
23876            )
23877        {
23878            self.write_keyword("NOT");
23879            self.write_space();
23880            return self.generate_like_op_inner(op, operator, false);
23881        }
23882
23883        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23884            if let Expression::Star(star) = &op.left {
23885                if star
23886                    .except
23887                    .as_ref()
23888                    .is_some_and(|except| !except.is_empty())
23889                {
23890                    if let Some(table) = &star.table {
23891                        self.generate_identifier(table)?;
23892                        self.write(".");
23893                    }
23894                    self.write("*");
23895                    self.write_space();
23896                    self.write_keyword(operator);
23897                    if let Some(quantifier) = &op.quantifier {
23898                        self.write_space();
23899                        self.write_keyword(quantifier);
23900                        self.write_space();
23901                    } else {
23902                        self.write_space();
23903                    }
23904                    self.generate_expression(&op.right)?;
23905                    if let Some(escape) = &op.escape {
23906                        self.write_space();
23907                        self.write_keyword("ESCAPE");
23908                        self.write_space();
23909                        self.generate_expression(escape)?;
23910                    }
23911                    if let Some(except) = &star.except {
23912                        self.write_space();
23913                        self.write_keyword("EXCEPT");
23914                        self.write(" (");
23915                        for (i, col) in except.iter().enumerate() {
23916                            if i > 0 {
23917                                self.write(", ");
23918                            }
23919                            self.generate_identifier(col)?;
23920                        }
23921                        self.write(")");
23922                    }
23923                    return Ok(());
23924                }
23925            }
23926        }
23927
23928        self.generate_expression(&op.left)?;
23929        self.write_space();
23930        if negated {
23931            self.write_keyword("NOT");
23932            self.write_space();
23933        }
23934        // Drill backtick-quotes ILIKE
23935        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
23936            self.write("`ILIKE`");
23937        } else {
23938            self.write_keyword(operator);
23939        }
23940        if let Some(quantifier) = &op.quantifier {
23941            self.write_space();
23942            self.write_keyword(quantifier);
23943            // Match Python sqlglot behavior:
23944            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
23945            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
23946            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
23947            let is_any =
23948                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
23949            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
23950                self.write_space();
23951            }
23952        } else {
23953            self.write_space();
23954        }
23955        self.generate_expression(&op.right)?;
23956        if let Some(escape) = &op.escape {
23957            self.write_space();
23958            self.write_keyword("ESCAPE");
23959            self.write_space();
23960            self.generate_expression(escape)?;
23961        }
23962        Ok(())
23963    }
23964
23965    /// Generate null-safe equality
23966    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
23967    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
23968        use crate::dialects::DialectType;
23969        self.generate_expression(&op.left)?;
23970        self.write_space();
23971        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
23972            self.write("<=>");
23973        } else {
23974            self.write_keyword("IS NOT DISTINCT FROM");
23975        }
23976        self.write_space();
23977        self.generate_expression(&op.right)?;
23978        Ok(())
23979    }
23980
23981    /// Generate IS DISTINCT FROM (null-safe inequality)
23982    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
23983        self.generate_expression(&op.left)?;
23984        self.write_space();
23985        self.write_keyword("IS DISTINCT FROM");
23986        self.write_space();
23987        self.generate_expression(&op.right)?;
23988        Ok(())
23989    }
23990
23991    /// Generate binary op without trailing comments (used when nested inside another binary op)
23992    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23993        // Generate left expression, but skip trailing comments
23994        match &op.left {
23995            Expression::Column(col) => {
23996                if let Some(table) = &col.table {
23997                    self.generate_identifier(table)?;
23998                    self.write(".");
23999                }
24000                self.generate_identifier(&col.name)?;
24001                // Oracle-style join marker (+)
24002                if col.join_mark && self.config.supports_column_join_marks {
24003                    self.write(" (+)");
24004                }
24005            }
24006            Expression::Add(inner_op)
24007            | Expression::Sub(inner_op)
24008            | Expression::Mul(inner_op)
24009            | Expression::Div(inner_op)
24010            | Expression::Concat(inner_op) => {
24011                self.generate_binary_op_no_trailing(inner_op, match &op.left {
24012                    Expression::Add(_) => "+",
24013                    Expression::Sub(_) => "-",
24014                    Expression::Mul(_) => "*",
24015                    Expression::Div(_) => "/",
24016                    Expression::Concat(_) => "||",
24017                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
24018                })?;
24019            }
24020            _ => {
24021                self.generate_expression(&op.left)?;
24022            }
24023        }
24024        // Output left_comments
24025        for comment in &op.left_comments {
24026            self.write_space();
24027            self.write_formatted_comment(comment);
24028        }
24029        self.write_space();
24030        if operator.chars().all(|c| c.is_alphabetic()) {
24031            self.write_keyword(operator);
24032        } else {
24033            self.write(operator);
24034        }
24035        // Output operator_comments
24036        for comment in &op.operator_comments {
24037            self.write_space();
24038            self.write_formatted_comment(comment);
24039        }
24040        self.write_space();
24041        // Generate right expression, but skip trailing comments if it's a Column
24042        // (the parent's left_comments will output them)
24043        match &op.right {
24044            Expression::Column(col) => {
24045                if let Some(table) = &col.table {
24046                    self.generate_identifier(table)?;
24047                    self.write(".");
24048                }
24049                self.generate_identifier(&col.name)?;
24050                // Oracle-style join marker (+)
24051                if col.join_mark && self.config.supports_column_join_marks {
24052                    self.write(" (+)");
24053                }
24054            }
24055            _ => {
24056                self.generate_expression(&op.right)?;
24057            }
24058        }
24059        // Skip trailing_comments - parent will handle them via its left_comments
24060        Ok(())
24061    }
24062
24063    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
24064        if operator.chars().all(|c| c.is_alphabetic()) {
24065            self.write_keyword(operator);
24066            self.write_space();
24067        } else {
24068            self.write(operator);
24069            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
24070            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
24071                self.write_space();
24072            }
24073        }
24074        self.generate_expression(&op.this)
24075    }
24076
24077    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
24078        // Generic mode supports two styles for negated IN:
24079        // - Prefix: NOT a IN (...)
24080        // - Infix:  a NOT IN (...)
24081        let is_generic =
24082            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24083        let use_prefix_not =
24084            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
24085        if use_prefix_not {
24086            self.write_keyword("NOT");
24087            self.write_space();
24088        }
24089        self.generate_expression(&in_expr.this)?;
24090        if in_expr.global {
24091            self.write_space();
24092            self.write_keyword("GLOBAL");
24093        }
24094        if in_expr.not && !use_prefix_not {
24095            self.write_space();
24096            self.write_keyword("NOT");
24097        }
24098        self.write_space();
24099        self.write_keyword("IN");
24100
24101        // BigQuery: IN UNNEST(expr)
24102        if let Some(unnest_expr) = &in_expr.unnest {
24103            self.write_space();
24104            self.write_keyword("UNNEST");
24105            self.write("(");
24106            self.generate_expression(unnest_expr)?;
24107            self.write(")");
24108            return Ok(());
24109        }
24110
24111        if let Some(query) = &in_expr.query {
24112            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
24113            // vs a subquery (col IN (SELECT ...))
24114            let is_bare = in_expr.expressions.is_empty()
24115                && !matches!(
24116                    query,
24117                    Expression::Select(_)
24118                        | Expression::Union(_)
24119                        | Expression::Intersect(_)
24120                        | Expression::Except(_)
24121                        | Expression::Subquery(_)
24122                );
24123            if is_bare {
24124                // Bare identifier: no parentheses
24125                self.write_space();
24126                self.generate_expression(query)?;
24127            } else {
24128                // Subquery: with parentheses
24129                self.write(" (");
24130                let is_statement = matches!(
24131                    query,
24132                    Expression::Select(_)
24133                        | Expression::Union(_)
24134                        | Expression::Intersect(_)
24135                        | Expression::Except(_)
24136                        | Expression::Subquery(_)
24137                );
24138                if self.config.pretty && is_statement {
24139                    self.write_newline();
24140                    self.indent_level += 1;
24141                    self.write_indent();
24142                }
24143                self.generate_expression(query)?;
24144                if self.config.pretty && is_statement {
24145                    self.write_newline();
24146                    self.indent_level -= 1;
24147                    self.write_indent();
24148                }
24149                self.write(")");
24150            }
24151        } else {
24152            // DuckDB: IN without parentheses for single expression that is NOT a literal
24153            // (array/list membership like 'red' IN tbl.flags)
24154            // ClickHouse: IN without parentheses for single non-array expressions
24155            let is_duckdb = matches!(
24156                self.config.dialect,
24157                Some(crate::dialects::DialectType::DuckDB)
24158            );
24159            let is_clickhouse = matches!(
24160                self.config.dialect,
24161                Some(crate::dialects::DialectType::ClickHouse)
24162            );
24163            let single_expr = in_expr.expressions.len() == 1;
24164            if is_clickhouse && single_expr {
24165                if let Expression::Array(arr) = &in_expr.expressions[0] {
24166                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
24167                    self.write(" (");
24168                    for (i, expr) in arr.expressions.iter().enumerate() {
24169                        if i > 0 {
24170                            self.write(", ");
24171                        }
24172                        self.generate_expression(expr)?;
24173                    }
24174                    self.write(")");
24175                } else if in_expr.is_field {
24176                    self.write_space();
24177                    self.generate_expression(&in_expr.expressions[0])?;
24178                } else {
24179                    self.write(" (");
24180                    self.generate_expression(&in_expr.expressions[0])?;
24181                    self.write(")");
24182                }
24183            } else {
24184                let is_bare_ref = single_expr
24185                    && matches!(
24186                        &in_expr.expressions[0],
24187                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
24188                    );
24189                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
24190                    // Bare field reference (no parens in source): IN identifier
24191                    // Also DuckDB: IN without parentheses for array/list membership
24192                    self.write_space();
24193                    self.generate_expression(&in_expr.expressions[0])?;
24194                } else {
24195                    // Standard IN (list)
24196                    self.write(" (");
24197                    for (i, expr) in in_expr.expressions.iter().enumerate() {
24198                        if i > 0 {
24199                            self.write(", ");
24200                        }
24201                        self.generate_expression(expr)?;
24202                    }
24203                    self.write(")");
24204                }
24205            }
24206        }
24207
24208        Ok(())
24209    }
24210
24211    fn generate_between(&mut self, between: &Between) -> Result<()> {
24212        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
24213        let use_prefix_not = between.not
24214            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
24215        if use_prefix_not {
24216            self.write_keyword("NOT");
24217            self.write_space();
24218        }
24219        self.generate_expression(&between.this)?;
24220        if between.not && !use_prefix_not {
24221            self.write_space();
24222            self.write_keyword("NOT");
24223        }
24224        self.write_space();
24225        self.write_keyword("BETWEEN");
24226        // Emit SYMMETRIC/ASYMMETRIC if present
24227        if let Some(sym) = between.symmetric {
24228            if sym {
24229                self.write(" SYMMETRIC");
24230            } else {
24231                self.write(" ASYMMETRIC");
24232            }
24233        }
24234        self.write_space();
24235        self.generate_expression(&between.low)?;
24236        self.write_space();
24237        self.write_keyword("AND");
24238        self.write_space();
24239        self.generate_expression(&between.high)
24240    }
24241
24242    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
24243        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
24244        let use_prefix_not = is_null.not
24245            && (self.config.dialect.is_none()
24246                || self.config.dialect == Some(DialectType::Generic)
24247                || is_null.postfix_form);
24248        if use_prefix_not {
24249            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
24250            self.write_keyword("NOT");
24251            self.write_space();
24252            self.generate_expression(&is_null.this)?;
24253            self.write_space();
24254            self.write_keyword("IS");
24255            self.write_space();
24256            self.write_keyword("NULL");
24257        } else {
24258            self.generate_expression(&is_null.this)?;
24259            self.write_space();
24260            self.write_keyword("IS");
24261            if is_null.not {
24262                self.write_space();
24263                self.write_keyword("NOT");
24264            }
24265            self.write_space();
24266            self.write_keyword("NULL");
24267        }
24268        Ok(())
24269    }
24270
24271    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
24272        self.generate_expression(&is_true.this)?;
24273        self.write_space();
24274        self.write_keyword("IS");
24275        if is_true.not {
24276            self.write_space();
24277            self.write_keyword("NOT");
24278        }
24279        self.write_space();
24280        self.write_keyword("TRUE");
24281        Ok(())
24282    }
24283
24284    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
24285        self.generate_expression(&is_false.this)?;
24286        self.write_space();
24287        self.write_keyword("IS");
24288        if is_false.not {
24289            self.write_space();
24290            self.write_keyword("NOT");
24291        }
24292        self.write_space();
24293        self.write_keyword("FALSE");
24294        Ok(())
24295    }
24296
24297    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
24298        self.generate_expression(&is_json.this)?;
24299        self.write_space();
24300        self.write_keyword("IS");
24301        if is_json.negated {
24302            self.write_space();
24303            self.write_keyword("NOT");
24304        }
24305        self.write_space();
24306        self.write_keyword("JSON");
24307
24308        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
24309        if let Some(ref json_type) = is_json.json_type {
24310            self.write_space();
24311            self.write_keyword(json_type);
24312        }
24313
24314        // Output key uniqueness constraint if specified
24315        match &is_json.unique_keys {
24316            Some(JsonUniqueKeys::With) => {
24317                self.write_space();
24318                self.write_keyword("WITH UNIQUE KEYS");
24319            }
24320            Some(JsonUniqueKeys::Without) => {
24321                self.write_space();
24322                self.write_keyword("WITHOUT UNIQUE KEYS");
24323            }
24324            Some(JsonUniqueKeys::Shorthand) => {
24325                self.write_space();
24326                self.write_keyword("UNIQUE KEYS");
24327            }
24328            None => {}
24329        }
24330
24331        Ok(())
24332    }
24333
24334    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
24335        self.generate_expression(&is_expr.left)?;
24336        self.write_space();
24337        self.write_keyword("IS");
24338        self.write_space();
24339        self.generate_expression(&is_expr.right)
24340    }
24341
24342    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
24343        if exists.not {
24344            self.write_keyword("NOT");
24345            self.write_space();
24346        }
24347        self.write_keyword("EXISTS");
24348        self.write("(");
24349        let is_statement = matches!(
24350            &exists.this,
24351            Expression::Select(_)
24352                | Expression::Union(_)
24353                | Expression::Intersect(_)
24354                | Expression::Except(_)
24355        );
24356        if self.config.pretty && is_statement {
24357            self.write_newline();
24358            self.indent_level += 1;
24359            self.write_indent();
24360            self.generate_expression(&exists.this)?;
24361            self.write_newline();
24362            self.indent_level -= 1;
24363            self.write_indent();
24364            self.write(")");
24365        } else {
24366            self.generate_expression(&exists.this)?;
24367            self.write(")");
24368        }
24369        Ok(())
24370    }
24371
24372    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
24373        self.generate_expression(&op.left)?;
24374        self.write_space();
24375        self.write_keyword("MEMBER OF");
24376        self.write("(");
24377        self.generate_expression(&op.right)?;
24378        self.write(")");
24379        Ok(())
24380    }
24381
24382    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
24383        if subquery.lateral {
24384            self.write_keyword("LATERAL");
24385            self.write_space();
24386        }
24387
24388        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
24389        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
24390        // to carry the LIMIT modifier without adding more parens
24391        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
24392            matches!(
24393                &p.this,
24394                Expression::Select(_)
24395                    | Expression::Union(_)
24396                    | Expression::Intersect(_)
24397                    | Expression::Except(_)
24398                    | Expression::Subquery(_)
24399            )
24400        } else {
24401            false
24402        };
24403
24404        // Check if inner expression is a statement for pretty formatting
24405        let is_statement = matches!(
24406            &subquery.this,
24407            Expression::Select(_)
24408                | Expression::Union(_)
24409                | Expression::Intersect(_)
24410                | Expression::Except(_)
24411                | Expression::Merge(_)
24412        );
24413
24414        if !skip_outer_parens {
24415            self.write("(");
24416            if self.config.pretty && is_statement {
24417                self.write_newline();
24418                self.indent_level += 1;
24419                self.write_indent();
24420            }
24421        }
24422        self.generate_expression(&subquery.this)?;
24423
24424        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
24425        if subquery.modifiers_inside {
24426            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
24427            if let Some(order_by) = &subquery.order_by {
24428                self.write_space();
24429                self.write_keyword("ORDER BY");
24430                self.write_space();
24431                for (i, ord) in order_by.expressions.iter().enumerate() {
24432                    if i > 0 {
24433                        self.write(", ");
24434                    }
24435                    self.generate_ordered(ord)?;
24436                }
24437            }
24438
24439            if let Some(limit) = &subquery.limit {
24440                self.write_space();
24441                self.write_keyword("LIMIT");
24442                self.write_space();
24443                self.generate_expression(&limit.this)?;
24444                if limit.percent {
24445                    self.write_space();
24446                    self.write_keyword("PERCENT");
24447                }
24448            }
24449
24450            if let Some(offset) = &subquery.offset {
24451                self.write_space();
24452                self.write_keyword("OFFSET");
24453                self.write_space();
24454                self.generate_expression(&offset.this)?;
24455            }
24456        }
24457
24458        if !skip_outer_parens {
24459            if self.config.pretty && is_statement {
24460                self.write_newline();
24461                self.indent_level -= 1;
24462                self.write_indent();
24463            }
24464            self.write(")");
24465        }
24466
24467        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
24468        if !subquery.modifiers_inside {
24469            if let Some(order_by) = &subquery.order_by {
24470                self.write_space();
24471                self.write_keyword("ORDER BY");
24472                self.write_space();
24473                for (i, ord) in order_by.expressions.iter().enumerate() {
24474                    if i > 0 {
24475                        self.write(", ");
24476                    }
24477                    self.generate_ordered(ord)?;
24478                }
24479            }
24480
24481            if let Some(limit) = &subquery.limit {
24482                self.write_space();
24483                self.write_keyword("LIMIT");
24484                self.write_space();
24485                self.generate_expression(&limit.this)?;
24486                if limit.percent {
24487                    self.write_space();
24488                    self.write_keyword("PERCENT");
24489                }
24490            }
24491
24492            if let Some(offset) = &subquery.offset {
24493                self.write_space();
24494                self.write_keyword("OFFSET");
24495                self.write_space();
24496                self.generate_expression(&offset.this)?;
24497            }
24498
24499            // Generate DISTRIBUTE BY (Hive/Spark)
24500            if let Some(distribute_by) = &subquery.distribute_by {
24501                self.write_space();
24502                self.write_keyword("DISTRIBUTE BY");
24503                self.write_space();
24504                for (i, expr) in distribute_by.expressions.iter().enumerate() {
24505                    if i > 0 {
24506                        self.write(", ");
24507                    }
24508                    self.generate_expression(expr)?;
24509                }
24510            }
24511
24512            // Generate SORT BY (Hive/Spark)
24513            if let Some(sort_by) = &subquery.sort_by {
24514                self.write_space();
24515                self.write_keyword("SORT BY");
24516                self.write_space();
24517                for (i, ord) in sort_by.expressions.iter().enumerate() {
24518                    if i > 0 {
24519                        self.write(", ");
24520                    }
24521                    self.generate_ordered(ord)?;
24522                }
24523            }
24524
24525            // Generate CLUSTER BY (Hive/Spark)
24526            if let Some(cluster_by) = &subquery.cluster_by {
24527                self.write_space();
24528                self.write_keyword("CLUSTER BY");
24529                self.write_space();
24530                for (i, ord) in cluster_by.expressions.iter().enumerate() {
24531                    if i > 0 {
24532                        self.write(", ");
24533                    }
24534                    self.generate_ordered(ord)?;
24535                }
24536            }
24537        }
24538
24539        if let Some(alias) = &subquery.alias {
24540            self.write_space();
24541            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
24542                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
24543                    && !subquery.alias_explicit_as);
24544            if !skip_as {
24545                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24546                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
24547                } else {
24548                    self.write_keyword("AS");
24549                }
24550                self.write_space();
24551            }
24552            self.generate_identifier(alias)?;
24553            if !subquery.column_aliases.is_empty() {
24554                self.write("(");
24555                for (i, col) in subquery.column_aliases.iter().enumerate() {
24556                    if i > 0 {
24557                        self.write(", ");
24558                    }
24559                    self.generate_identifier(col)?;
24560                }
24561                self.write(")");
24562            }
24563        }
24564        // Output trailing comments
24565        for comment in &subquery.trailing_comments {
24566            self.write(" ");
24567            self.write_formatted_comment(comment);
24568        }
24569        Ok(())
24570    }
24571
24572    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
24573        // Generate WITH clause if present
24574        if let Some(ref with) = pivot.with {
24575            self.generate_with(with)?;
24576            self.write_space();
24577        }
24578
24579        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
24580
24581        // Check for Redshift UNPIVOT in FROM clause:
24582        // UNPIVOT expr [AS val AT attr]
24583        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
24584        let is_redshift_unpivot = pivot.unpivot
24585            && pivot.expressions.is_empty()
24586            && pivot.fields.is_empty()
24587            && pivot.using.is_empty()
24588            && pivot.into.is_none()
24589            && !matches!(&pivot.this, Expression::Null(_));
24590
24591        if is_redshift_unpivot {
24592            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
24593            self.write_keyword("UNPIVOT");
24594            self.write_space();
24595            self.generate_expression(&pivot.this)?;
24596            // Alias - for Redshift it can be "val AT attr" format
24597            if let Some(alias) = &pivot.alias {
24598                self.write_space();
24599                self.write_keyword("AS");
24600                self.write_space();
24601                // The alias might contain " AT " for the attr part
24602                self.write(&alias.name);
24603            }
24604            return Ok(());
24605        }
24606
24607        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
24608        let is_simplified = !pivot.using.is_empty()
24609            || pivot.into.is_some()
24610            || (pivot.fields.is_empty()
24611                && !pivot.expressions.is_empty()
24612                && !matches!(&pivot.this, Expression::Null(_)));
24613
24614        if is_simplified {
24615            // DuckDB simplified syntax:
24616            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
24617            //   UNPIVOT table ON cols INTO NAME col VALUE col
24618            self.write_keyword(direction);
24619            self.write_space();
24620            self.generate_expression(&pivot.this)?;
24621
24622            if !pivot.expressions.is_empty() {
24623                self.write_space();
24624                self.write_keyword("ON");
24625                self.write_space();
24626                for (i, expr) in pivot.expressions.iter().enumerate() {
24627                    if i > 0 {
24628                        self.write(", ");
24629                    }
24630                    self.generate_expression(expr)?;
24631                }
24632            }
24633
24634            // INTO (for UNPIVOT)
24635            if let Some(into) = &pivot.into {
24636                self.write_space();
24637                self.write_keyword("INTO");
24638                self.write_space();
24639                self.generate_expression(into)?;
24640            }
24641
24642            // USING (for PIVOT)
24643            if !pivot.using.is_empty() {
24644                self.write_space();
24645                self.write_keyword("USING");
24646                self.write_space();
24647                for (i, expr) in pivot.using.iter().enumerate() {
24648                    if i > 0 {
24649                        self.write(", ");
24650                    }
24651                    self.generate_expression(expr)?;
24652                }
24653            }
24654
24655            // GROUP BY
24656            if let Some(group) = &pivot.group {
24657                self.write_space();
24658                self.generate_expression(group)?;
24659            }
24660        } else {
24661            // Standard syntax:
24662            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
24663            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
24664            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
24665            if !matches!(&pivot.this, Expression::Null(_)) {
24666                self.generate_expression(&pivot.this)?;
24667                self.write_space();
24668            }
24669            self.write_keyword(direction);
24670            self.write("(");
24671
24672            // Aggregation expressions
24673            for (i, expr) in pivot.expressions.iter().enumerate() {
24674                if i > 0 {
24675                    self.write(", ");
24676                }
24677                self.generate_expression(expr)?;
24678            }
24679
24680            // FOR...IN fields
24681            if !pivot.fields.is_empty() {
24682                if !pivot.expressions.is_empty() {
24683                    self.write_space();
24684                }
24685                self.write_keyword("FOR");
24686                self.write_space();
24687                for (i, field) in pivot.fields.iter().enumerate() {
24688                    if i > 0 {
24689                        self.write_space();
24690                    }
24691                    // field is an In expression: column IN (values)
24692                    self.generate_expression(field)?;
24693                }
24694            }
24695
24696            // DEFAULT ON NULL
24697            if let Some(default_val) = &pivot.default_on_null {
24698                self.write_space();
24699                self.write_keyword("DEFAULT ON NULL");
24700                self.write(" (");
24701                self.generate_expression(default_val)?;
24702                self.write(")");
24703            }
24704
24705            // GROUP BY inside PIVOT parens
24706            if let Some(group) = &pivot.group {
24707                self.write_space();
24708                self.generate_expression(group)?;
24709            }
24710
24711            self.write(")");
24712        }
24713
24714        // Alias
24715        if let Some(alias) = &pivot.alias {
24716            self.write_space();
24717            self.write_keyword("AS");
24718            self.write_space();
24719            self.generate_identifier(alias)?;
24720            self.generate_alias_column_list(&pivot.alias_columns)?;
24721        }
24722
24723        Ok(())
24724    }
24725
24726    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
24727        self.generate_expression(&unpivot.this)?;
24728        self.write_space();
24729        self.write_keyword("UNPIVOT");
24730        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
24731        if let Some(include) = unpivot.include_nulls {
24732            self.write_space();
24733            if include {
24734                self.write_keyword("INCLUDE NULLS");
24735            } else {
24736                self.write_keyword("EXCLUDE NULLS");
24737            }
24738            self.write_space();
24739        }
24740        self.write("(");
24741        if unpivot.value_column_parenthesized {
24742            self.write("(");
24743        }
24744        self.generate_identifier(&unpivot.value_column)?;
24745        // Output additional value columns if present
24746        for extra_col in &unpivot.extra_value_columns {
24747            self.write(", ");
24748            self.generate_identifier(extra_col)?;
24749        }
24750        if unpivot.value_column_parenthesized {
24751            self.write(")");
24752        }
24753        self.write_space();
24754        self.write_keyword("FOR");
24755        self.write_space();
24756        self.generate_identifier(&unpivot.name_column)?;
24757        self.write_space();
24758        self.write_keyword("IN");
24759        self.write(" (");
24760        for (i, col) in unpivot.columns.iter().enumerate() {
24761            if i > 0 {
24762                self.write(", ");
24763            }
24764            self.generate_expression(col)?;
24765        }
24766        self.write("))");
24767        if let Some(alias) = &unpivot.alias {
24768            self.write_space();
24769            self.write_keyword("AS");
24770            self.write_space();
24771            self.generate_identifier(alias)?;
24772            self.generate_alias_column_list(&unpivot.alias_columns)?;
24773        }
24774        Ok(())
24775    }
24776
24777    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
24778        if columns.is_empty() {
24779            return Ok(());
24780        }
24781
24782        self.write("(");
24783        for (i, column) in columns.iter().enumerate() {
24784            if i > 0 {
24785                self.write(", ");
24786            }
24787            self.generate_identifier(column)?;
24788        }
24789        self.write(")");
24790        Ok(())
24791    }
24792
24793    fn generate_values(&mut self, values: &Values) -> Result<()> {
24794        self.write_keyword("VALUES");
24795        for (i, row) in values.expressions.iter().enumerate() {
24796            if i > 0 {
24797                self.write(",");
24798            }
24799            self.write(" (");
24800            for (j, expr) in row.expressions.iter().enumerate() {
24801                if j > 0 {
24802                    self.write(", ");
24803                }
24804                self.generate_expression(expr)?;
24805            }
24806            self.write(")");
24807        }
24808        if let Some(alias) = &values.alias {
24809            self.write_space();
24810            self.write_keyword("AS");
24811            self.write_space();
24812            self.generate_identifier(alias)?;
24813            if !values.column_aliases.is_empty() {
24814                self.write("(");
24815                for (i, col) in values.column_aliases.iter().enumerate() {
24816                    if i > 0 {
24817                        self.write(", ");
24818                    }
24819                    self.generate_identifier(col)?;
24820                }
24821                self.write(")");
24822            }
24823        }
24824        Ok(())
24825    }
24826
24827    fn generate_array(&mut self, arr: &Array) -> Result<()> {
24828        // Apply struct name inheritance for target dialects that need it
24829        let needs_inheritance = matches!(
24830            self.config.dialect,
24831            Some(DialectType::DuckDB)
24832                | Some(DialectType::Spark)
24833                | Some(DialectType::Databricks)
24834                | Some(DialectType::Hive)
24835                | Some(DialectType::Snowflake)
24836                | Some(DialectType::Presto)
24837                | Some(DialectType::Trino)
24838        );
24839        let propagated: Vec<Expression>;
24840        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
24841            propagated = Self::inherit_struct_field_names(&arr.expressions);
24842            &propagated
24843        } else {
24844            &arr.expressions
24845        };
24846
24847        // Generic mode: ARRAY(1, 2, 3) with parentheses
24848        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
24849        let use_parens =
24850            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24851        if !self.config.array_bracket_only {
24852            self.write_keyword("ARRAY");
24853        }
24854        if use_parens {
24855            self.write("(");
24856        } else {
24857            self.write("[");
24858        }
24859        for (i, expr) in expressions.iter().enumerate() {
24860            if i > 0 {
24861                self.write(", ");
24862            }
24863            self.generate_expression(expr)?;
24864        }
24865        if use_parens {
24866            self.write(")");
24867        } else {
24868            self.write("]");
24869        }
24870        Ok(())
24871    }
24872
24873    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
24874        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
24875        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
24876        if tuple.expressions.len() == 2 {
24877            if let Expression::TableAlias(_) = &tuple.expressions[1] {
24878                // First element is the function/expression, second is the TableAlias
24879                self.generate_expression(&tuple.expressions[0])?;
24880                self.write_space();
24881                self.write_keyword("AS");
24882                self.write_space();
24883                self.generate_expression(&tuple.expressions[1])?;
24884                return Ok(());
24885            }
24886        }
24887
24888        // In pretty mode, format long tuples with each element on a new line
24889        // Only expand if total width exceeds threshold
24890        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
24891            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
24892            for expr in &tuple.expressions {
24893                expr_strings.push(self.generate_to_string(expr)?);
24894            }
24895            self.too_wide(&expr_strings)
24896        } else {
24897            false
24898        };
24899
24900        if expand_tuple {
24901            self.write("(");
24902            self.write_newline();
24903            self.indent_level += 1;
24904            for (i, expr) in tuple.expressions.iter().enumerate() {
24905                if i > 0 {
24906                    self.write(",");
24907                    self.write_newline();
24908                }
24909                self.write_indent();
24910                self.generate_expression(expr)?;
24911            }
24912            self.indent_level -= 1;
24913            self.write_newline();
24914            self.write_indent();
24915            self.write(")");
24916        } else {
24917            self.write("(");
24918            for (i, expr) in tuple.expressions.iter().enumerate() {
24919                if i > 0 {
24920                    self.write(", ");
24921                }
24922                self.generate_expression(expr)?;
24923            }
24924            self.write(")");
24925        }
24926        Ok(())
24927    }
24928
24929    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
24930        self.generate_expression(&pipe.this)?;
24931        self.write(" |> ");
24932        self.generate_expression(&pipe.expression)?;
24933        Ok(())
24934    }
24935
24936    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
24937        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
24938            && !self.config.null_ordering_supported
24939            && matches!(
24940                self.config.dialect,
24941                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24942            );
24943        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
24944        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
24945            let target_default_nulls_first = !ordered.desc;
24946
24947            unsupported_tsql_null_ordering
24948                && nulls_first != target_default_nulls_first
24949                && !random_ordering
24950        } else {
24951            false
24952        };
24953
24954        if emulate_tsql_null_ordering {
24955            if Self::is_integer_ordering_literal(&ordered.this) {
24956                let nulls_order = if ordered.nulls_first == Some(true) {
24957                    "NULLS FIRST"
24958                } else {
24959                    "NULLS LAST"
24960                };
24961                self.unsupported(format!(
24962                    "'{nulls_order}' translation not supported with positional ordering"
24963                ))?;
24964            } else {
24965                self.write_keyword("CASE WHEN");
24966                self.write_space();
24967                self.generate_expression(&ordered.this)?;
24968                self.write_space();
24969                self.write_keyword("IS NULL THEN 1 ELSE 0 END");
24970                if ordered.nulls_first == Some(true) {
24971                    self.write_space();
24972                    self.write_keyword("DESC");
24973                }
24974                self.write(", ");
24975            }
24976        }
24977
24978        self.generate_expression(&ordered.this)?;
24979        if ordered.desc {
24980            self.write_space();
24981            self.write_keyword("DESC");
24982        } else if ordered.explicit_asc {
24983            self.write_space();
24984            self.write_keyword("ASC");
24985        }
24986        if let Some(nulls_first) = ordered.nulls_first {
24987            if !unsupported_tsql_null_ordering
24988                && (self.config.null_ordering_supported
24989                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
24990            {
24991                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
24992                // for the dialect. Different dialects have different NULL ordering defaults:
24993                //
24994                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
24995                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
24996                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
24997                //
24998                // nulls_are_small (Spark, Hive, BigQuery, most others):
24999                //   - ASC: NULLS FIRST is default
25000                //   - DESC: NULLS LAST is default
25001                //
25002                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
25003                //   - NULLS LAST is always the default regardless of sort direction
25004                let is_asc = !ordered.desc;
25005                let is_nulls_are_large = matches!(
25006                    self.config.dialect,
25007                    Some(DialectType::Oracle)
25008                        | Some(DialectType::PostgreSQL)
25009                        | Some(DialectType::Redshift)
25010                        | Some(DialectType::Snowflake)
25011                );
25012                let is_nulls_are_last = matches!(
25013                    self.config.dialect,
25014                    Some(DialectType::Dremio)
25015                        | Some(DialectType::DuckDB)
25016                        | Some(DialectType::Presto)
25017                        | Some(DialectType::Trino)
25018                        | Some(DialectType::Athena)
25019                        | Some(DialectType::ClickHouse)
25020                        | Some(DialectType::Drill)
25021                        | Some(DialectType::Exasol)
25022                );
25023
25024                // Check if the NULLS ordering matches the default for this dialect
25025                let is_default_nulls = if is_nulls_are_large {
25026                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
25027                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
25028                } else if is_nulls_are_last {
25029                    // For nulls_are_last: NULLS LAST is always default
25030                    !nulls_first
25031                } else {
25032                    false
25033                };
25034
25035                if !is_default_nulls {
25036                    self.write_space();
25037                    self.write_keyword("NULLS");
25038                    self.write_space();
25039                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
25040                }
25041            }
25042        }
25043        // WITH FILL clause (ClickHouse)
25044        if let Some(ref with_fill) = ordered.with_fill {
25045            self.write_space();
25046            self.generate_with_fill(with_fill)?;
25047        }
25048        Ok(())
25049    }
25050
25051    fn is_integer_ordering_literal(expr: &Expression) -> bool {
25052        matches!(
25053            expr,
25054            Expression::Literal(lit)
25055                if matches!(lit.as_ref(), Literal::Number(n) if n.parse::<u64>().is_ok())
25056        )
25057    }
25058
25059    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
25060    fn write_clickhouse_type(&mut self, type_str: &str) {
25061        if self.clickhouse_nullable_depth < 0 {
25062            // Map key context: don't wrap in Nullable
25063            self.write(type_str);
25064        } else {
25065            self.write(&format!("Nullable({})", type_str));
25066        }
25067    }
25068
25069    fn write_type_with_length(&mut self, name: &str, length: Option<u32>) {
25070        self.write_keyword(name);
25071        if let Some(length) = length {
25072            self.write(&format!("({length})"));
25073        }
25074    }
25075
25076    fn write_oracle_number(&mut self, precision: Option<u32>, scale: Option<i32>) -> Result<()> {
25077        use crate::dialects::DialectType;
25078
25079        if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25080            self.write_keyword("NUMBER");
25081            if precision.is_some() || scale.is_some() {
25082                self.write("(");
25083                if let Some(precision) = precision {
25084                    self.write(&precision.to_string());
25085                } else {
25086                    self.write("*");
25087                }
25088                if let Some(scale) = scale {
25089                    self.write(&format!(", {scale}"));
25090                }
25091                self.write(")");
25092            }
25093            return Ok(());
25094        }
25095
25096        let target = self.config.dialect;
25097        match (precision, scale) {
25098            (None, None) => match target {
25099                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25100                    self.write_keyword("NUMERIC");
25101                }
25102                Some(DialectType::MySQL)
25103                | Some(DialectType::SingleStore)
25104                | Some(DialectType::TiDB) => {
25105                    self.unsupported(
25106                        "Oracle NUMBER without precision or scale has no exact MySQL mapping",
25107                    )?;
25108                    self.write_keyword("DECIMAL(65, 30)");
25109                }
25110                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25111                    self.unsupported(
25112                        "Oracle NUMBER without precision or scale has no exact T-SQL mapping",
25113                    )?;
25114                    self.write_keyword("DECIMAL(38, 10)");
25115                }
25116                _ => self.write_keyword("DECIMAL"),
25117            },
25118            (Some(precision), None) | (Some(precision), Some(0)) => {
25119                if precision <= 9 {
25120                    self.write_keyword("INT");
25121                } else if precision <= 18 {
25122                    self.write_keyword("BIGINT");
25123                } else {
25124                    self.write(&format!("DECIMAL({precision}, 0)"));
25125                }
25126            }
25127            (Some(precision), Some(scale)) if scale < 0 => match target {
25128                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25129                    self.write(&format!("NUMERIC({precision}, {scale})"));
25130                }
25131                Some(DialectType::MySQL)
25132                | Some(DialectType::SingleStore)
25133                | Some(DialectType::TiDB) => {
25134                    self.unsupported(format!(
25135                        "Oracle NUMBER({precision}, {scale}) negative-scale rounding cannot be enforced by MySQL"
25136                    ))?;
25137                    let width = precision.saturating_add(scale.unsigned_abs()).min(65);
25138                    self.write(&format!("DECIMAL({width}, 0)"));
25139                }
25140                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25141                    self.unsupported(format!(
25142                        "Oracle NUMBER({precision}, {scale}) negative-scale rounding cannot be enforced by T-SQL"
25143                    ))?;
25144                    let width = precision.saturating_add(scale.unsigned_abs()).min(38);
25145                    self.write(&format!("DECIMAL({width}, 0)"));
25146                }
25147                _ => {
25148                    self.unsupported(format!(
25149                        "Oracle NUMBER({precision}, {scale}) negative-scale rounding may not be supported"
25150                    ))?;
25151                    let width = precision.saturating_add(scale.unsigned_abs());
25152                    self.write(&format!("DECIMAL({width}, 0)"));
25153                }
25154            },
25155            (Some(precision), Some(scale)) => {
25156                let mut target_precision = precision;
25157                let mut target_scale = u32::try_from(scale).unwrap_or_default();
25158                let limits = match target {
25159                    Some(DialectType::MySQL)
25160                    | Some(DialectType::SingleStore)
25161                    | Some(DialectType::TiDB) => Some((65, 30, "MySQL")),
25162                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => Some((38, 38, "T-SQL")),
25163                    _ => None,
25164                };
25165                if let Some((max_precision, max_scale, dialect_name)) = limits {
25166                    let requested_precision = target_precision;
25167                    let requested_scale = target_scale;
25168                    target_scale = target_scale.min(max_scale);
25169                    target_precision = target_precision.max(target_scale).min(max_precision);
25170                    if target_precision != requested_precision || target_scale != requested_scale {
25171                        self.unsupported(format!(
25172                            "Oracle NUMBER({precision}, {scale}) exceeds {dialect_name} DECIMAL limits"
25173                        ))?;
25174                    }
25175                }
25176                self.write(&format!("DECIMAL({target_precision}, {target_scale})"));
25177            }
25178            (None, Some(scale)) => {
25179                self.unsupported(format!(
25180                    "Oracle NUMBER(*, {scale}) has no exact target mapping"
25181                ))?;
25182                self.write_keyword("DECIMAL");
25183            }
25184        }
25185        Ok(())
25186    }
25187
25188    fn write_oracle_character(
25189        &mut self,
25190        kind: OracleCharacterKind,
25191        length: Option<u32>,
25192        semantics: Option<OracleCharacterLengthSemantics>,
25193    ) -> Result<()> {
25194        use crate::dialects::DialectType;
25195
25196        if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25197            let name = match kind {
25198                OracleCharacterKind::Char => "CHAR",
25199                OracleCharacterKind::VarChar => "VARCHAR2",
25200                OracleCharacterKind::NChar => "NCHAR",
25201                OracleCharacterKind::NVarChar => "NVARCHAR2",
25202            };
25203            self.write_keyword(name);
25204            if let Some(length) = length {
25205                self.write(&format!("({length}"));
25206                if let Some(semantics) = semantics {
25207                    self.write(match semantics {
25208                        OracleCharacterLengthSemantics::Byte => " BYTE",
25209                        OracleCharacterLengthSemantics::Char => " CHAR",
25210                    });
25211                }
25212                self.write(")");
25213            }
25214            return Ok(());
25215        }
25216
25217        let mut target_kind = kind;
25218        match self.config.dialect {
25219            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25220                if semantics == Some(OracleCharacterLengthSemantics::Char)
25221                    && matches!(
25222                        kind,
25223                        OracleCharacterKind::Char | OracleCharacterKind::VarChar
25224                    ) =>
25225            {
25226                self.unsupported(
25227                    "Oracle CHAR length semantics require a national-character approximation in T-SQL",
25228                )?;
25229                target_kind = match kind {
25230                    OracleCharacterKind::Char => OracleCharacterKind::NChar,
25231                    OracleCharacterKind::VarChar => OracleCharacterKind::NVarChar,
25232                    _ => kind,
25233                };
25234            }
25235            Some(DialectType::PostgreSQL) | Some(DialectType::Materialize)
25236                if matches!(
25237                    kind,
25238                    OracleCharacterKind::NChar | OracleCharacterKind::NVarChar
25239                ) =>
25240            {
25241                target_kind = match kind {
25242                    OracleCharacterKind::NChar => OracleCharacterKind::Char,
25243                    OracleCharacterKind::NVarChar => OracleCharacterKind::VarChar,
25244                    _ => kind,
25245                };
25246            }
25247            _ => {}
25248        }
25249
25250        if semantics == Some(OracleCharacterLengthSemantics::Byte)
25251            && matches!(
25252                self.config.dialect,
25253                Some(DialectType::MySQL)
25254                    | Some(DialectType::SingleStore)
25255                    | Some(DialectType::TiDB)
25256                    | Some(DialectType::PostgreSQL)
25257                    | Some(DialectType::Materialize)
25258            )
25259        {
25260            self.unsupported(
25261                "Oracle BYTE character length semantics cannot be enforced by the target type",
25262            )?;
25263        }
25264
25265        let name = match target_kind {
25266            OracleCharacterKind::Char => "CHAR",
25267            OracleCharacterKind::VarChar => "VARCHAR",
25268            OracleCharacterKind::NChar => "NCHAR",
25269            OracleCharacterKind::NVarChar => "NVARCHAR",
25270        };
25271        let length = if matches!(
25272            target_kind,
25273            OracleCharacterKind::Char | OracleCharacterKind::NChar
25274        ) {
25275            Some(length.unwrap_or(1))
25276        } else {
25277            length
25278        };
25279        self.write_type_with_length(name, length);
25280        Ok(())
25281    }
25282
25283    fn oracle_timestamp_precision(&mut self, precision: Option<u32>, maximum: u32) -> Result<u32> {
25284        let requested = precision.unwrap_or(6);
25285        if requested > maximum {
25286            self.unsupported(format!(
25287                "Oracle timestamp precision {requested} exceeds the target maximum of {maximum}"
25288            ))?;
25289        }
25290        Ok(requested.min(maximum))
25291    }
25292
25293    fn write_oracle_data_type(&mut self, data_type: &OracleDataType) -> Result<()> {
25294        use crate::dialects::DialectType;
25295
25296        match data_type {
25297            OracleDataType::Number { precision, scale } => {
25298                self.write_oracle_number(*precision, *scale)?;
25299            }
25300            OracleDataType::BinaryFloat => match self.config.dialect {
25301                Some(DialectType::Oracle) | None => self.write_keyword("BINARY_FLOAT"),
25302                Some(DialectType::TSQL)
25303                | Some(DialectType::Fabric)
25304                | Some(DialectType::PostgreSQL)
25305                | Some(DialectType::Materialize) => self.write_keyword("REAL"),
25306                _ => self.write_keyword("FLOAT"),
25307            },
25308            OracleDataType::BinaryDouble => match self.config.dialect {
25309                Some(DialectType::Oracle) | None => self.write_keyword("BINARY_DOUBLE"),
25310                Some(DialectType::TSQL) | Some(DialectType::Fabric) => self.write_keyword("FLOAT"),
25311                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25312                    self.write_keyword("DOUBLE PRECISION")
25313                }
25314                _ => self.write_keyword("DOUBLE"),
25315            },
25316            OracleDataType::Float { precision } => {
25317                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25318                    self.write_keyword("FLOAT");
25319                    if let Some(precision) = precision {
25320                        self.write(&format!("({precision})"));
25321                    }
25322                } else {
25323                    let precision = precision.unwrap_or(126);
25324                    match self.config.dialect {
25325                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25326                            if precision > 53 {
25327                                self.unsupported(format!(
25328                                    "Oracle FLOAT({precision}) exceeds T-SQL FLOAT precision"
25329                                ))?;
25330                            }
25331                            self.write(&format!("FLOAT({})", precision.min(53)));
25332                        }
25333                        Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25334                            self.write_keyword(if precision <= 24 {
25335                                "REAL"
25336                            } else {
25337                                "DOUBLE PRECISION"
25338                            });
25339                        }
25340                        Some(DialectType::MySQL)
25341                        | Some(DialectType::SingleStore)
25342                        | Some(DialectType::TiDB) => {
25343                            self.write_keyword(if precision <= 24 { "FLOAT" } else { "DOUBLE" });
25344                        }
25345                        _ => self.write_keyword("DOUBLE PRECISION"),
25346                    }
25347                }
25348            }
25349            OracleDataType::Character {
25350                kind,
25351                length,
25352                semantics,
25353            } => self.write_oracle_character(*kind, *length, *semantics)?,
25354            OracleDataType::Date => match self.config.dialect {
25355                Some(DialectType::Oracle) | None => self.write_keyword("DATE"),
25356                Some(DialectType::MySQL)
25357                | Some(DialectType::SingleStore)
25358                | Some(DialectType::TiDB) => self.write_keyword("DATETIME"),
25359                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25360                    self.write_keyword("DATETIME2(0)")
25361                }
25362                _ => self.write_keyword("TIMESTAMP(0)"),
25363            },
25364            OracleDataType::Timestamp {
25365                precision,
25366                timezone,
25367            } => {
25368                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25369                    self.write_keyword("TIMESTAMP");
25370                    if let Some(precision) = precision {
25371                        self.write(&format!("({precision})"));
25372                    }
25373                    match timezone {
25374                        OracleTimestampTimeZone::None => {}
25375                        OracleTimestampTimeZone::WithTimeZone => {
25376                            self.write_keyword(" WITH TIME ZONE")
25377                        }
25378                        OracleTimestampTimeZone::WithLocalTimeZone => {
25379                            self.write_keyword(" WITH LOCAL TIME ZONE")
25380                        }
25381                    }
25382                } else {
25383                    match self.config.dialect {
25384                        Some(DialectType::MySQL)
25385                        | Some(DialectType::SingleStore)
25386                        | Some(DialectType::TiDB) => {
25387                            let precision = self.oracle_timestamp_precision(*precision, 6)?;
25388                            match timezone {
25389                                OracleTimestampTimeZone::None => {
25390                                    self.write(&format!("DATETIME({precision})"));
25391                                }
25392                                OracleTimestampTimeZone::WithTimeZone => {
25393                                    self.unsupported(
25394                                        "Oracle TIMESTAMP WITH TIME ZONE loses its time zone in MySQL",
25395                                    )?;
25396                                    self.write(&format!("DATETIME({precision})"));
25397                                }
25398                                OracleTimestampTimeZone::WithLocalTimeZone => {
25399                                    self.unsupported(
25400                                        "Oracle TIMESTAMP WITH LOCAL TIME ZONE has only an approximate MySQL mapping",
25401                                    )?;
25402                                    self.write(&format!("TIMESTAMP({precision})"));
25403                                }
25404                            }
25405                        }
25406                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25407                            let precision = self.oracle_timestamp_precision(*precision, 7)?;
25408                            match timezone {
25409                                OracleTimestampTimeZone::None => {
25410                                    self.write(&format!("DATETIME2({precision})"));
25411                                }
25412                                OracleTimestampTimeZone::WithTimeZone => {
25413                                    self.write(&format!("DATETIMEOFFSET({precision})"));
25414                                }
25415                                OracleTimestampTimeZone::WithLocalTimeZone => {
25416                                    self.unsupported(
25417                                        "Oracle TIMESTAMP WITH LOCAL TIME ZONE loses session time-zone semantics in T-SQL",
25418                                    )?;
25419                                    self.write(&format!("DATETIME2({precision})"));
25420                                }
25421                            }
25422                        }
25423                        Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25424                            let precision = self.oracle_timestamp_precision(*precision, 6)?;
25425                            match timezone {
25426                                OracleTimestampTimeZone::None => {
25427                                    self.write(&format!("TIMESTAMP({precision})"));
25428                                }
25429                                OracleTimestampTimeZone::WithTimeZone => {
25430                                    self.write(&format!("TIMESTAMPTZ({precision})"));
25431                                }
25432                                OracleTimestampTimeZone::WithLocalTimeZone => {
25433                                    self.unsupported(
25434                                        "Oracle TIMESTAMP WITH LOCAL TIME ZONE has only an approximate PostgreSQL mapping",
25435                                    )?;
25436                                    self.write(&format!("TIMESTAMPTZ({precision})"));
25437                                }
25438                            }
25439                        }
25440                        _ => {
25441                            let precision = precision.unwrap_or(6);
25442                            let name = if matches!(timezone, OracleTimestampTimeZone::None) {
25443                                "TIMESTAMP"
25444                            } else {
25445                                "TIMESTAMPTZ"
25446                            };
25447                            self.write(&format!("{name}({precision})"));
25448                        }
25449                    }
25450                }
25451            }
25452            OracleDataType::IntervalYearToMonth { year_precision } => {
25453                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25454                    self.write_keyword("INTERVAL YEAR");
25455                    if let Some(precision) = year_precision {
25456                        self.write(&format!("({precision})"));
25457                    }
25458                    self.write_keyword(" TO MONTH");
25459                } else if matches!(
25460                    self.config.dialect,
25461                    Some(DialectType::MySQL)
25462                        | Some(DialectType::SingleStore)
25463                        | Some(DialectType::TiDB)
25464                        | Some(DialectType::TSQL)
25465                        | Some(DialectType::Fabric)
25466                ) {
25467                    self.unsupported(
25468                        "Oracle INTERVAL YEAR TO MONTH has no native target column type",
25469                    )?;
25470                    self.write_keyword("VARCHAR(30)");
25471                } else {
25472                    if year_precision.is_some() {
25473                        self.unsupported(
25474                            "Oracle INTERVAL YEAR leading precision cannot be preserved",
25475                        )?;
25476                    }
25477                    self.write_keyword("INTERVAL YEAR TO MONTH");
25478                }
25479            }
25480            OracleDataType::IntervalDayToSecond {
25481                day_precision,
25482                fractional_seconds_precision,
25483            } => {
25484                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25485                    self.write_keyword("INTERVAL DAY");
25486                    if let Some(precision) = day_precision {
25487                        self.write(&format!("({precision})"));
25488                    }
25489                    self.write_keyword(" TO SECOND");
25490                    if let Some(precision) = fractional_seconds_precision {
25491                        self.write(&format!("({precision})"));
25492                    }
25493                } else if matches!(
25494                    self.config.dialect,
25495                    Some(DialectType::MySQL)
25496                        | Some(DialectType::SingleStore)
25497                        | Some(DialectType::TiDB)
25498                        | Some(DialectType::TSQL)
25499                        | Some(DialectType::Fabric)
25500                ) {
25501                    self.unsupported(
25502                        "Oracle INTERVAL DAY TO SECOND has no native target column type",
25503                    )?;
25504                    self.write_keyword("VARCHAR(30)");
25505                } else {
25506                    if day_precision.is_some() {
25507                        self.unsupported(
25508                            "Oracle INTERVAL DAY leading precision cannot be preserved",
25509                        )?;
25510                    }
25511                    self.write_keyword("INTERVAL");
25512                    if let Some(precision) = fractional_seconds_precision {
25513                        self.write(&format!("({precision})"));
25514                    }
25515                    self.write_keyword(" DAY TO SECOND");
25516                }
25517            }
25518            OracleDataType::Clob { national } => match self.config.dialect {
25519                Some(DialectType::Oracle) | None => {
25520                    self.write_keyword(if *national { "NCLOB" } else { "CLOB" })
25521                }
25522                Some(DialectType::MySQL)
25523                | Some(DialectType::SingleStore)
25524                | Some(DialectType::TiDB) => self.write_keyword("LONGTEXT"),
25525                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25526                    self.write_keyword(if *national {
25527                        "NVARCHAR(MAX)"
25528                    } else {
25529                        "VARCHAR(MAX)"
25530                    });
25531                }
25532                _ => self.write_keyword("TEXT"),
25533            },
25534            OracleDataType::Blob => match self.config.dialect {
25535                Some(DialectType::Oracle) | None => self.write_keyword("BLOB"),
25536                Some(DialectType::MySQL)
25537                | Some(DialectType::SingleStore)
25538                | Some(DialectType::TiDB) => self.write_keyword("LONGBLOB"),
25539                Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25540                    self.write_keyword("VARBINARY(MAX)")
25541                }
25542                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25543                    self.write_keyword("BYTEA")
25544                }
25545                _ => self.write_keyword("BLOB"),
25546            },
25547            OracleDataType::Raw { length } => match self.config.dialect {
25548                Some(DialectType::Oracle) | None => self.write_type_with_length("RAW", *length),
25549                Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25550                    if length.is_some() {
25551                        self.unsupported("Oracle RAW length constraint is lost in PostgreSQL")?;
25552                    }
25553                    self.write_keyword("BYTEA");
25554                }
25555                _ => self.write_type_with_length("VARBINARY", *length),
25556            },
25557            OracleDataType::Long { raw } => {
25558                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25559                    self.write_keyword(if *raw { "LONG RAW" } else { "LONG" });
25560                } else if *raw {
25561                    match self.config.dialect {
25562                        Some(DialectType::MySQL)
25563                        | Some(DialectType::SingleStore)
25564                        | Some(DialectType::TiDB) => self.write_keyword("LONGBLOB"),
25565                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25566                            self.write_keyword("VARBINARY(MAX)")
25567                        }
25568                        Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25569                            self.write_keyword("BYTEA")
25570                        }
25571                        _ => self.write_keyword("BLOB"),
25572                    }
25573                } else {
25574                    match self.config.dialect {
25575                        Some(DialectType::MySQL)
25576                        | Some(DialectType::SingleStore)
25577                        | Some(DialectType::TiDB) => self.write_keyword("LONGTEXT"),
25578                        Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25579                            self.write_keyword("VARCHAR(MAX)")
25580                        }
25581                        _ => self.write_keyword("TEXT"),
25582                    }
25583                }
25584            }
25585            OracleDataType::RowId => {
25586                if matches!(self.config.dialect, Some(DialectType::Oracle) | None) {
25587                    self.write_keyword("ROWID");
25588                } else {
25589                    self.unsupported(
25590                        "Oracle ROWID physical row identity is reduced to its text representation",
25591                    )?;
25592                    self.write_keyword("CHAR(18)");
25593                }
25594            }
25595        }
25596        Ok(())
25597    }
25598
25599    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
25600        use crate::dialects::DialectType;
25601
25602        match dt {
25603            DataType::Boolean => {
25604                // Dialect-specific boolean type mappings
25605                match self.config.dialect {
25606                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
25607                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
25608                    Some(DialectType::Oracle) => {
25609                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
25610                        self.write_keyword("NUMBER(1)")
25611                    }
25612                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
25613                    _ => self.write_keyword("BOOLEAN"),
25614                }
25615            }
25616            DataType::TinyInt { length } => {
25617                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
25618                // Dremio maps TINYINT to INT
25619                // ClickHouse maps TINYINT to Int8
25620                match self.config.dialect {
25621                    Some(DialectType::PostgreSQL)
25622                    | Some(DialectType::Redshift)
25623                    | Some(DialectType::Oracle)
25624                    | Some(DialectType::Exasol) => {
25625                        self.write_keyword("SMALLINT");
25626                    }
25627                    Some(DialectType::Teradata) => {
25628                        // Teradata uses BYTEINT for smallest integer
25629                        self.write_keyword("BYTEINT");
25630                    }
25631                    Some(DialectType::Dremio) => {
25632                        // Dremio maps TINYINT to INT
25633                        self.write_keyword("INT");
25634                    }
25635                    Some(DialectType::ClickHouse) => {
25636                        self.write_clickhouse_type("Int8");
25637                    }
25638                    _ => {
25639                        self.write_keyword("TINYINT");
25640                    }
25641                }
25642                if let Some(n) = length {
25643                    if !matches!(
25644                        self.config.dialect,
25645                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
25646                    ) {
25647                        self.write(&format!("({})", n));
25648                    }
25649                }
25650            }
25651            DataType::SmallInt { length } => {
25652                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
25653                match self.config.dialect {
25654                    Some(DialectType::Dremio) => {
25655                        self.write_keyword("INT");
25656                    }
25657                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
25658                        self.write_keyword("INTEGER");
25659                    }
25660                    Some(DialectType::BigQuery) => {
25661                        self.write_keyword("INT64");
25662                    }
25663                    Some(DialectType::ClickHouse) => {
25664                        self.write_clickhouse_type("Int16");
25665                    }
25666                    _ => {
25667                        self.write_keyword("SMALLINT");
25668                        if let Some(n) = length {
25669                            self.write(&format!("({})", n));
25670                        }
25671                    }
25672                }
25673            }
25674            DataType::Int {
25675                length,
25676                integer_spelling: _,
25677            } => {
25678                // BigQuery uses INT64 for INT
25679                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
25680                    self.write_keyword("INT64");
25681                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25682                    self.write_clickhouse_type("Int32");
25683                } else {
25684                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
25685                    let use_integer = match self.config.dialect {
25686                        Some(DialectType::TSQL)
25687                        | Some(DialectType::Fabric)
25688                        | Some(DialectType::Presto)
25689                        | Some(DialectType::Trino)
25690                        | Some(DialectType::SQLite)
25691                        | Some(DialectType::Redshift) => true,
25692                        _ => false,
25693                    };
25694                    if use_integer {
25695                        self.write_keyword("INTEGER");
25696                    } else {
25697                        self.write_keyword("INT");
25698                    }
25699                    if let Some(n) = length {
25700                        self.write(&format!("({})", n));
25701                    }
25702                }
25703            }
25704            DataType::BigInt { length } => {
25705                // Dialect-specific bigint type mappings
25706                match self.config.dialect {
25707                    Some(DialectType::Oracle) => {
25708                        // Oracle doesn't have BIGINT, uses INT
25709                        self.write_keyword("INT");
25710                    }
25711                    Some(DialectType::ClickHouse) => {
25712                        self.write_clickhouse_type("Int64");
25713                    }
25714                    _ => {
25715                        self.write_keyword("BIGINT");
25716                        if let Some(n) = length {
25717                            self.write(&format!("({})", n));
25718                        }
25719                    }
25720                }
25721            }
25722            DataType::Float {
25723                precision,
25724                scale,
25725                real_spelling,
25726            } => {
25727                // Dialect-specific float type mappings
25728                // If real_spelling is true, preserve REAL; otherwise use dialect default
25729                // Spark/Hive don't support REAL, always use FLOAT
25730                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25731                    self.write_clickhouse_type("Float32");
25732                } else if *real_spelling
25733                    && !matches!(
25734                        self.config.dialect,
25735                        Some(DialectType::Spark)
25736                            | Some(DialectType::Databricks)
25737                            | Some(DialectType::Hive)
25738                            | Some(DialectType::Snowflake)
25739                            | Some(DialectType::MySQL)
25740                            | Some(DialectType::BigQuery)
25741                    )
25742                {
25743                    self.write_keyword("REAL")
25744                } else {
25745                    match self.config.dialect {
25746                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
25747                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25748                        _ => self.write_keyword("FLOAT"),
25749                    }
25750                }
25751                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
25752                // Spark/Hive don't support FLOAT(precision)
25753                if !matches!(
25754                    self.config.dialect,
25755                    Some(DialectType::Spark)
25756                        | Some(DialectType::Databricks)
25757                        | Some(DialectType::Hive)
25758                        | Some(DialectType::Presto)
25759                        | Some(DialectType::Trino)
25760                ) {
25761                    if let Some(p) = precision {
25762                        self.write(&format!("({}", p));
25763                        if let Some(s) = scale {
25764                            self.write(&format!(", {})", s));
25765                        } else {
25766                            self.write(")");
25767                        }
25768                    }
25769                }
25770            }
25771            DataType::Double { precision, scale } => {
25772                // Dialect-specific double type mappings
25773                match self.config.dialect {
25774                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25775                        self.write_keyword("FLOAT")
25776                    } // SQL Server/Fabric FLOAT is double
25777                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
25778                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
25779                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25780                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
25781                    Some(DialectType::PostgreSQL)
25782                    | Some(DialectType::Redshift)
25783                    | Some(DialectType::Teradata)
25784                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
25785                    _ => self.write_keyword("DOUBLE"),
25786                }
25787                // MySQL supports DOUBLE(precision, scale)
25788                if let Some(p) = precision {
25789                    self.write(&format!("({}", p));
25790                    if let Some(s) = scale {
25791                        self.write(&format!(", {})", s));
25792                    } else {
25793                        self.write(")");
25794                    }
25795                }
25796            }
25797            DataType::Decimal { precision, scale } => {
25798                // Dialect-specific decimal type mappings
25799                match self.config.dialect {
25800                    Some(DialectType::ClickHouse) => {
25801                        self.write("Decimal");
25802                        if let Some(p) = precision {
25803                            self.write(&format!("({}", p));
25804                            if let Some(s) = scale {
25805                                self.write(&format!(", {}", s));
25806                            }
25807                            self.write(")");
25808                        }
25809                    }
25810                    Some(DialectType::Oracle) => {
25811                        // Oracle uses NUMBER instead of DECIMAL
25812                        self.write_keyword("NUMBER");
25813                        if let Some(p) = precision {
25814                            self.write(&format!("({}", p));
25815                            if let Some(s) = scale {
25816                                self.write(&format!(", {}", s));
25817                            }
25818                            self.write(")");
25819                        }
25820                    }
25821                    Some(DialectType::BigQuery) => {
25822                        // BigQuery uses NUMERIC instead of DECIMAL
25823                        self.write_keyword("NUMERIC");
25824                        if let Some(p) = precision {
25825                            self.write(&format!("({}", p));
25826                            if let Some(s) = scale {
25827                                self.write(&format!(", {}", s));
25828                            }
25829                            self.write(")");
25830                        }
25831                    }
25832                    _ => {
25833                        self.write_keyword("DECIMAL");
25834                        if let Some(p) = precision {
25835                            self.write(&format!("({}", p));
25836                            if let Some(s) = scale {
25837                                self.write(&format!(", {}", s));
25838                            }
25839                            self.write(")");
25840                        }
25841                    }
25842                }
25843            }
25844            DataType::Oracle { oracle_type } => {
25845                self.write_oracle_data_type(oracle_type)?;
25846            }
25847            DataType::Char { length } => {
25848                // Dialect-specific char type mappings
25849                match self.config.dialect {
25850                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25851                        // DuckDB/SQLite maps CHAR to TEXT
25852                        self.write_keyword("TEXT");
25853                    }
25854                    Some(DialectType::Hive)
25855                    | Some(DialectType::Spark)
25856                    | Some(DialectType::Databricks) => {
25857                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
25858                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
25859                        if length.is_some()
25860                            && !matches!(self.config.dialect, Some(DialectType::Hive))
25861                        {
25862                            self.write_keyword("CHAR");
25863                            if let Some(n) = length {
25864                                self.write(&format!("({})", n));
25865                            }
25866                        } else {
25867                            self.write_keyword("STRING");
25868                        }
25869                    }
25870                    Some(DialectType::Dremio) => {
25871                        // Dremio maps CHAR to VARCHAR
25872                        self.write_keyword("VARCHAR");
25873                        if let Some(n) = length {
25874                            self.write(&format!("({})", n));
25875                        }
25876                    }
25877                    _ => {
25878                        self.write_keyword("CHAR");
25879                        if let Some(n) = length {
25880                            self.write(&format!("({})", n));
25881                        }
25882                    }
25883                }
25884            }
25885            DataType::VarChar {
25886                length,
25887                parenthesized_length,
25888            } => {
25889                // Dialect-specific varchar type mappings
25890                match self.config.dialect {
25891                    Some(DialectType::Oracle) => {
25892                        self.write_keyword("VARCHAR2");
25893                        if let Some(n) = length {
25894                            self.write(&format!("({})", n));
25895                        }
25896                    }
25897                    Some(DialectType::DuckDB) => {
25898                        // DuckDB maps VARCHAR to TEXT, preserving length
25899                        self.write_keyword("TEXT");
25900                        if let Some(n) = length {
25901                            self.write(&format!("({})", n));
25902                        }
25903                    }
25904                    Some(DialectType::SQLite) => {
25905                        // SQLite maps VARCHAR to TEXT, preserving length
25906                        self.write_keyword("TEXT");
25907                        if let Some(n) = length {
25908                            self.write(&format!("({})", n));
25909                        }
25910                    }
25911                    Some(DialectType::MySQL) if length.is_none() => {
25912                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
25913                        self.write_keyword("TEXT");
25914                    }
25915                    Some(DialectType::Hive)
25916                    | Some(DialectType::Spark)
25917                    | Some(DialectType::Databricks)
25918                        if length.is_none() =>
25919                    {
25920                        // Hive/Spark/Databricks: VARCHAR without length → STRING
25921                        self.write_keyword("STRING");
25922                    }
25923                    _ => {
25924                        self.write_keyword("VARCHAR");
25925                        if let Some(n) = length {
25926                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
25927                            if *parenthesized_length {
25928                                self.write(&format!("(({}))", n));
25929                            } else {
25930                                self.write(&format!("({})", n));
25931                            }
25932                        }
25933                    }
25934                }
25935            }
25936            DataType::Text => {
25937                // Dialect-specific text type mappings
25938                match self.config.dialect {
25939                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
25940                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25941                        self.write_keyword("VARCHAR(MAX)")
25942                    }
25943                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25944                    Some(DialectType::Snowflake)
25945                    | Some(DialectType::Dremio)
25946                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
25947                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
25948                    Some(DialectType::Presto)
25949                    | Some(DialectType::Trino)
25950                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25951                    Some(DialectType::Spark)
25952                    | Some(DialectType::Databricks)
25953                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25954                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
25955                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
25956                        self.write_keyword("STRING")
25957                    }
25958                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
25959                    _ => self.write_keyword("TEXT"),
25960                }
25961            }
25962            DataType::TextWithLength { length } => {
25963                // TEXT(n) - dialect-specific type with length
25964                match self.config.dialect {
25965                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
25966                    Some(DialectType::Hive)
25967                    | Some(DialectType::Spark)
25968                    | Some(DialectType::Databricks) => {
25969                        self.write(&format!("VARCHAR({})", length));
25970                    }
25971                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
25972                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
25973                    Some(DialectType::Snowflake)
25974                    | Some(DialectType::Presto)
25975                    | Some(DialectType::Trino)
25976                    | Some(DialectType::Athena)
25977                    | Some(DialectType::Drill)
25978                    | Some(DialectType::Dremio) => {
25979                        self.write(&format!("VARCHAR({})", length));
25980                    }
25981                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25982                        self.write(&format!("VARCHAR({})", length))
25983                    }
25984                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
25985                        self.write(&format!("STRING({})", length))
25986                    }
25987                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
25988                    _ => self.write(&format!("TEXT({})", length)),
25989                }
25990            }
25991            DataType::String { length } => {
25992                // STRING type with optional length (BigQuery STRING(n))
25993                match self.config.dialect {
25994                    Some(DialectType::ClickHouse) => {
25995                        // ClickHouse uses String with specific casing
25996                        self.write("String");
25997                        if let Some(n) = length {
25998                            self.write(&format!("({})", n));
25999                        }
26000                    }
26001                    Some(DialectType::BigQuery)
26002                    | Some(DialectType::Hive)
26003                    | Some(DialectType::Spark)
26004                    | Some(DialectType::Databricks)
26005                    | Some(DialectType::StarRocks)
26006                    | Some(DialectType::Doris) => {
26007                        self.write_keyword("STRING");
26008                        if let Some(n) = length {
26009                            self.write(&format!("({})", n));
26010                        }
26011                    }
26012                    Some(DialectType::PostgreSQL) => {
26013                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
26014                        if let Some(n) = length {
26015                            self.write_keyword("VARCHAR");
26016                            self.write(&format!("({})", n));
26017                        } else {
26018                            self.write_keyword("TEXT");
26019                        }
26020                    }
26021                    Some(DialectType::Redshift) => {
26022                        // Redshift: STRING -> VARCHAR(MAX)
26023                        if let Some(n) = length {
26024                            self.write_keyword("VARCHAR");
26025                            self.write(&format!("({})", n));
26026                        } else {
26027                            self.write_keyword("VARCHAR(MAX)");
26028                        }
26029                    }
26030                    Some(DialectType::MySQL) => {
26031                        // MySQL doesn't have STRING - use VARCHAR or TEXT
26032                        if let Some(n) = length {
26033                            self.write_keyword("VARCHAR");
26034                            self.write(&format!("({})", n));
26035                        } else {
26036                            self.write_keyword("TEXT");
26037                        }
26038                    }
26039                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
26040                        // TSQL: STRING -> VARCHAR(MAX)
26041                        if let Some(n) = length {
26042                            self.write_keyword("VARCHAR");
26043                            self.write(&format!("({})", n));
26044                        } else {
26045                            self.write_keyword("VARCHAR(MAX)");
26046                        }
26047                    }
26048                    Some(DialectType::Oracle) => {
26049                        // Oracle: STRING -> CLOB
26050                        self.write_keyword("CLOB");
26051                    }
26052                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
26053                        // DuckDB/Materialize uses TEXT for string types
26054                        self.write_keyword("TEXT");
26055                        if let Some(n) = length {
26056                            self.write(&format!("({})", n));
26057                        }
26058                    }
26059                    Some(DialectType::Presto)
26060                    | Some(DialectType::Trino)
26061                    | Some(DialectType::Drill)
26062                    | Some(DialectType::Dremio) => {
26063                        // Presto/Trino/Drill use VARCHAR for string types
26064                        self.write_keyword("VARCHAR");
26065                        if let Some(n) = length {
26066                            self.write(&format!("({})", n));
26067                        }
26068                    }
26069                    Some(DialectType::Snowflake) => {
26070                        // Snowflake: STRING stays as STRING (identity/DDL)
26071                        // CAST context STRING -> VARCHAR is handled in generate_cast
26072                        self.write_keyword("STRING");
26073                        if let Some(n) = length {
26074                            self.write(&format!("({})", n));
26075                        }
26076                    }
26077                    _ => {
26078                        // Default: output STRING with optional length
26079                        self.write_keyword("STRING");
26080                        if let Some(n) = length {
26081                            self.write(&format!("({})", n));
26082                        }
26083                    }
26084                }
26085            }
26086            DataType::Binary { length } => {
26087                // Dialect-specific binary type mappings
26088                match self.config.dialect {
26089                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
26090                        self.write_keyword("BYTEA");
26091                        if let Some(n) = length {
26092                            self.write(&format!("({})", n));
26093                        }
26094                    }
26095                    Some(DialectType::Redshift) => {
26096                        self.write_keyword("VARBYTE");
26097                        if let Some(n) = length {
26098                            self.write(&format!("({})", n));
26099                        }
26100                    }
26101                    Some(DialectType::DuckDB)
26102                    | Some(DialectType::SQLite)
26103                    | Some(DialectType::Oracle) => {
26104                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
26105                        self.write_keyword("BLOB");
26106                        if let Some(n) = length {
26107                            self.write(&format!("({})", n));
26108                        }
26109                    }
26110                    Some(DialectType::Presto)
26111                    | Some(DialectType::Trino)
26112                    | Some(DialectType::Athena)
26113                    | Some(DialectType::Drill)
26114                    | Some(DialectType::Dremio) => {
26115                        // These dialects map BINARY to VARBINARY
26116                        self.write_keyword("VARBINARY");
26117                        if let Some(n) = length {
26118                            self.write(&format!("({})", n));
26119                        }
26120                    }
26121                    Some(DialectType::ClickHouse) => {
26122                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
26123                        if self.clickhouse_nullable_depth < 0 {
26124                            self.write("BINARY");
26125                        } else {
26126                            self.write("Nullable(BINARY");
26127                        }
26128                        if let Some(n) = length {
26129                            self.write(&format!("({})", n));
26130                        }
26131                        if self.clickhouse_nullable_depth >= 0 {
26132                            self.write(")");
26133                        }
26134                    }
26135                    _ => {
26136                        self.write_keyword("BINARY");
26137                        if let Some(n) = length {
26138                            self.write(&format!("({})", n));
26139                        }
26140                    }
26141                }
26142            }
26143            DataType::VarBinary { length } => {
26144                // Dialect-specific varbinary type mappings
26145                match self.config.dialect {
26146                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
26147                        self.write_keyword("BYTEA");
26148                        if let Some(n) = length {
26149                            self.write(&format!("({})", n));
26150                        }
26151                    }
26152                    Some(DialectType::Redshift) => {
26153                        self.write_keyword("VARBYTE");
26154                        if let Some(n) = length {
26155                            self.write(&format!("({})", n));
26156                        }
26157                    }
26158                    Some(DialectType::DuckDB)
26159                    | Some(DialectType::SQLite)
26160                    | Some(DialectType::Oracle) => {
26161                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
26162                        self.write_keyword("BLOB");
26163                        if let Some(n) = length {
26164                            self.write(&format!("({})", n));
26165                        }
26166                    }
26167                    Some(DialectType::Exasol) => {
26168                        // Exasol maps VARBINARY to VARCHAR
26169                        self.write_keyword("VARCHAR");
26170                    }
26171                    Some(DialectType::Spark)
26172                    | Some(DialectType::Hive)
26173                    | Some(DialectType::Databricks) => {
26174                        // Spark/Hive use BINARY instead of VARBINARY
26175                        self.write_keyword("BINARY");
26176                        if let Some(n) = length {
26177                            self.write(&format!("({})", n));
26178                        }
26179                    }
26180                    Some(DialectType::ClickHouse) => {
26181                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
26182                        self.write_clickhouse_type("String");
26183                    }
26184                    _ => {
26185                        self.write_keyword("VARBINARY");
26186                        if let Some(n) = length {
26187                            self.write(&format!("({})", n));
26188                        }
26189                    }
26190                }
26191            }
26192            DataType::Blob => {
26193                // Dialect-specific blob type mappings
26194                match self.config.dialect {
26195                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
26196                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
26197                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
26198                        self.write_keyword("VARBINARY")
26199                    }
26200                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
26201                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
26202                    Some(DialectType::Presto)
26203                    | Some(DialectType::Trino)
26204                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
26205                    Some(DialectType::DuckDB) => {
26206                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
26207                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
26208                        self.write_keyword("VARBINARY");
26209                    }
26210                    Some(DialectType::Spark)
26211                    | Some(DialectType::Databricks)
26212                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
26213                    Some(DialectType::ClickHouse) => {
26214                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
26215                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
26216                        // This matches Python sqlglot behavior.
26217                        self.write("Nullable(String)");
26218                    }
26219                    _ => self.write_keyword("BLOB"),
26220                }
26221            }
26222            DataType::Bit { length } => {
26223                // Dialect-specific bit type mappings
26224                match self.config.dialect {
26225                    Some(DialectType::Dremio)
26226                    | Some(DialectType::Spark)
26227                    | Some(DialectType::Databricks)
26228                    | Some(DialectType::Hive)
26229                    | Some(DialectType::Snowflake)
26230                    | Some(DialectType::BigQuery)
26231                    | Some(DialectType::Presto)
26232                    | Some(DialectType::Trino)
26233                    | Some(DialectType::ClickHouse)
26234                    | Some(DialectType::Redshift) => {
26235                        // These dialects don't support BIT type, use BOOLEAN
26236                        self.write_keyword("BOOLEAN");
26237                    }
26238                    _ => {
26239                        self.write_keyword("BIT");
26240                        if let Some(n) = length {
26241                            self.write(&format!("({})", n));
26242                        }
26243                    }
26244                }
26245            }
26246            DataType::VarBit { length } => {
26247                self.write_keyword("VARBIT");
26248                if let Some(n) = length {
26249                    self.write(&format!("({})", n));
26250                }
26251            }
26252            DataType::Date => self.write_keyword("DATE"),
26253            DataType::Time {
26254                precision,
26255                timezone,
26256            } => {
26257                if *timezone {
26258                    // Dialect-specific TIME WITH TIME ZONE output
26259                    match self.config.dialect {
26260                        Some(DialectType::DuckDB) => {
26261                            // DuckDB: TIMETZ (drops precision)
26262                            self.write_keyword("TIMETZ");
26263                        }
26264                        Some(DialectType::PostgreSQL) => {
26265                            // PostgreSQL: TIMETZ or TIMETZ(p)
26266                            self.write_keyword("TIMETZ");
26267                            if let Some(p) = precision {
26268                                self.write(&format!("({})", p));
26269                            }
26270                        }
26271                        _ => {
26272                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
26273                            self.write_keyword("TIME");
26274                            if let Some(p) = precision {
26275                                self.write(&format!("({})", p));
26276                            }
26277                            self.write_keyword(" WITH TIME ZONE");
26278                        }
26279                    }
26280                } else {
26281                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
26282                    if matches!(
26283                        self.config.dialect,
26284                        Some(DialectType::Spark)
26285                            | Some(DialectType::Databricks)
26286                            | Some(DialectType::Hive)
26287                    ) {
26288                        self.write_keyword("TIMESTAMP");
26289                    } else {
26290                        self.write_keyword("TIME");
26291                        if let Some(p) = precision {
26292                            self.write(&format!("({})", p));
26293                        }
26294                    }
26295                }
26296            }
26297            DataType::Timestamp {
26298                precision,
26299                timezone,
26300            } => {
26301                // Dialect-specific timestamp type mappings
26302                match self.config.dialect {
26303                    Some(DialectType::Snowflake) if *timezone => {
26304                        self.write_keyword("TIMESTAMPTZ");
26305                        if let Some(p) = precision {
26306                            self.write(&format!("({})", p));
26307                        }
26308                    }
26309                    Some(DialectType::ClickHouse) => {
26310                        self.write("DateTime");
26311                        if let Some(p) = precision {
26312                            self.write(&format!("({})", p));
26313                        }
26314                    }
26315                    Some(DialectType::TSQL) => {
26316                        if *timezone {
26317                            self.write_keyword("DATETIMEOFFSET");
26318                        } else {
26319                            self.write_keyword("DATETIME2");
26320                        }
26321                        if let Some(p) = precision {
26322                            self.write(&format!("({})", p));
26323                        }
26324                    }
26325                    Some(DialectType::MySQL) => {
26326                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
26327                        self.write_keyword("TIMESTAMP");
26328                        if let Some(p) = precision {
26329                            self.write(&format!("({})", p));
26330                        }
26331                    }
26332                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
26333                        // Doris/StarRocks: TIMESTAMP -> DATETIME
26334                        self.write_keyword("DATETIME");
26335                        if let Some(p) = precision {
26336                            self.write(&format!("({})", p));
26337                        }
26338                    }
26339                    Some(DialectType::BigQuery) => {
26340                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
26341                        if *timezone {
26342                            self.write_keyword("TIMESTAMP");
26343                        } else {
26344                            self.write_keyword("DATETIME");
26345                        }
26346                    }
26347                    Some(DialectType::DuckDB) => {
26348                        // DuckDB: TIMESTAMPTZ shorthand
26349                        if *timezone {
26350                            self.write_keyword("TIMESTAMPTZ");
26351                        } else {
26352                            self.write_keyword("TIMESTAMP");
26353                            if let Some(p) = precision {
26354                                self.write(&format!("({})", p));
26355                            }
26356                        }
26357                    }
26358                    _ => {
26359                        if *timezone && !self.config.tz_to_with_time_zone {
26360                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
26361                            self.write_keyword("TIMESTAMPTZ");
26362                            if let Some(p) = precision {
26363                                self.write(&format!("({})", p));
26364                            }
26365                        } else {
26366                            self.write_keyword("TIMESTAMP");
26367                            if let Some(p) = precision {
26368                                self.write(&format!("({})", p));
26369                            }
26370                            if *timezone {
26371                                self.write_space();
26372                                self.write_keyword("WITH TIME ZONE");
26373                            }
26374                        }
26375                    }
26376                }
26377            }
26378            DataType::Interval { unit, to } => {
26379                self.write_keyword("INTERVAL");
26380                if let Some(u) = unit {
26381                    self.write_space();
26382                    self.write_keyword(u);
26383                }
26384                // Handle range intervals like DAY TO HOUR
26385                if let Some(t) = to {
26386                    self.write_space();
26387                    self.write_keyword("TO");
26388                    self.write_space();
26389                    self.write_keyword(t);
26390                }
26391            }
26392            DataType::Json => {
26393                // Dialect-specific JSON type mappings
26394                match self.config.dialect {
26395                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
26396                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
26397                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
26398                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
26399                    _ => self.write_keyword("JSON"),
26400                }
26401            }
26402            DataType::JsonB => {
26403                // JSONB is PostgreSQL specific, but Doris also supports it
26404                match self.config.dialect {
26405                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
26406                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
26407                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
26408                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
26409                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
26410                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
26411                }
26412            }
26413            DataType::Uuid => {
26414                // Dialect-specific UUID type mappings
26415                match self.config.dialect {
26416                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
26417                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
26418                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
26419                    Some(DialectType::BigQuery)
26420                    | Some(DialectType::Spark)
26421                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
26422                    _ => self.write_keyword("UUID"),
26423                }
26424            }
26425            DataType::Array {
26426                element_type,
26427                dimension,
26428            } => {
26429                // Dialect-specific array syntax
26430                match self.config.dialect {
26431                    Some(DialectType::PostgreSQL)
26432                    | Some(DialectType::Redshift)
26433                    | Some(DialectType::DuckDB) => {
26434                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
26435                        self.generate_data_type(element_type)?;
26436                        if let Some(dim) = dimension {
26437                            self.write(&format!("[{}]", dim));
26438                        } else {
26439                            self.write("[]");
26440                        }
26441                    }
26442                    Some(DialectType::BigQuery) => {
26443                        self.write_keyword("ARRAY<");
26444                        self.generate_data_type(element_type)?;
26445                        self.write(">");
26446                    }
26447                    Some(DialectType::Snowflake)
26448                    | Some(DialectType::Presto)
26449                    | Some(DialectType::Trino)
26450                    | Some(DialectType::ClickHouse) => {
26451                        // These dialects use Array(TYPE) parentheses syntax
26452                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26453                            self.write("Array(");
26454                        } else {
26455                            self.write_keyword("ARRAY(");
26456                        }
26457                        self.generate_data_type(element_type)?;
26458                        self.write(")");
26459                    }
26460                    Some(DialectType::TSQL)
26461                    | Some(DialectType::MySQL)
26462                    | Some(DialectType::Oracle) => {
26463                        // These dialects don't have native array types
26464                        // Fall back to JSON or use native workarounds
26465                        match self.config.dialect {
26466                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
26467                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
26468                            _ => self.write_keyword("JSON"),
26469                        }
26470                    }
26471                    _ => {
26472                        // Default: use angle bracket syntax (ARRAY<T>)
26473                        self.write_keyword("ARRAY<");
26474                        self.generate_data_type(element_type)?;
26475                        self.write(">");
26476                    }
26477                }
26478            }
26479            DataType::List { element_type } => {
26480                // Materialize: element_type LIST (postfix syntax)
26481                self.generate_data_type(element_type)?;
26482                self.write_keyword(" LIST");
26483            }
26484            DataType::Map {
26485                key_type,
26486                value_type,
26487            } => {
26488                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
26489                match self.config.dialect {
26490                    Some(DialectType::Materialize) => {
26491                        // Materialize: MAP[key_type => value_type]
26492                        self.write_keyword("MAP[");
26493                        self.generate_data_type(key_type)?;
26494                        self.write(" => ");
26495                        self.generate_data_type(value_type)?;
26496                        self.write("]");
26497                    }
26498                    Some(DialectType::Snowflake)
26499                    | Some(DialectType::RisingWave)
26500                    | Some(DialectType::DuckDB)
26501                    | Some(DialectType::Presto)
26502                    | Some(DialectType::Trino)
26503                    | Some(DialectType::Athena) => {
26504                        self.write_keyword("MAP(");
26505                        self.generate_data_type(key_type)?;
26506                        self.write(", ");
26507                        self.generate_data_type(value_type)?;
26508                        self.write(")");
26509                    }
26510                    Some(DialectType::ClickHouse) => {
26511                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
26512                        // Key types must NOT be wrapped in Nullable
26513                        self.write("Map(");
26514                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
26515                        self.generate_data_type(key_type)?;
26516                        self.clickhouse_nullable_depth = 0;
26517                        self.write(", ");
26518                        self.generate_data_type(value_type)?;
26519                        self.write(")");
26520                    }
26521                    _ => {
26522                        self.write_keyword("MAP<");
26523                        self.generate_data_type(key_type)?;
26524                        self.write(", ");
26525                        self.generate_data_type(value_type)?;
26526                        self.write(">");
26527                    }
26528                }
26529            }
26530            DataType::Vector {
26531                element_type,
26532                dimension,
26533            } => {
26534                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
26535                    // SingleStore format: VECTOR(dimension, type_alias)
26536                    self.write_keyword("VECTOR(");
26537                    if let Some(dim) = dimension {
26538                        self.write(&dim.to_string());
26539                    }
26540                    // Map type back to SingleStore alias
26541                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
26542                        DataType::TinyInt { .. } => Some("I8"),
26543                        DataType::SmallInt { .. } => Some("I16"),
26544                        DataType::Int { .. } => Some("I32"),
26545                        DataType::BigInt { .. } => Some("I64"),
26546                        DataType::Float { .. } => Some("F32"),
26547                        DataType::Double { .. } => Some("F64"),
26548                        _ => None,
26549                    });
26550                    if let Some(alias) = type_alias {
26551                        if dimension.is_some() {
26552                            self.write(", ");
26553                        }
26554                        self.write(alias);
26555                    }
26556                    self.write(")");
26557                } else {
26558                    // Snowflake format: VECTOR(type, dimension)
26559                    self.write_keyword("VECTOR(");
26560                    if let Some(ref et) = element_type {
26561                        self.generate_data_type(et)?;
26562                        if dimension.is_some() {
26563                            self.write(", ");
26564                        }
26565                    }
26566                    if let Some(dim) = dimension {
26567                        self.write(&dim.to_string());
26568                    }
26569                    self.write(")");
26570                }
26571            }
26572            DataType::Object { fields, modifier } => {
26573                self.write_keyword("OBJECT(");
26574                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
26575                    if i > 0 {
26576                        self.write(", ");
26577                    }
26578                    self.write(name);
26579                    self.write(" ");
26580                    self.generate_data_type(dt)?;
26581                    if *not_null {
26582                        self.write_keyword(" NOT NULL");
26583                    }
26584                }
26585                self.write(")");
26586                if let Some(mod_str) = modifier {
26587                    self.write(" ");
26588                    self.write_keyword(mod_str);
26589                }
26590            }
26591            DataType::Struct { fields, nested } => {
26592                // Dialect-specific struct type mappings
26593                match self.config.dialect {
26594                    Some(DialectType::Snowflake) => {
26595                        // Snowflake maps STRUCT to OBJECT
26596                        self.write_keyword("OBJECT(");
26597                        for (i, field) in fields.iter().enumerate() {
26598                            if i > 0 {
26599                                self.write(", ");
26600                            }
26601                            if !field.name.is_empty() {
26602                                self.write(&field.name);
26603                                self.write(" ");
26604                            }
26605                            self.generate_data_type(&field.data_type)?;
26606                        }
26607                        self.write(")");
26608                    }
26609                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
26610                        // Presto/Trino use ROW(name TYPE, ...) syntax
26611                        self.write_keyword("ROW(");
26612                        for (i, field) in fields.iter().enumerate() {
26613                            if i > 0 {
26614                                self.write(", ");
26615                            }
26616                            if !field.name.is_empty() {
26617                                self.write(&field.name);
26618                                self.write(" ");
26619                            }
26620                            self.generate_data_type(&field.data_type)?;
26621                        }
26622                        self.write(")");
26623                    }
26624                    Some(DialectType::DuckDB) => {
26625                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
26626                        self.write_keyword("STRUCT(");
26627                        for (i, field) in fields.iter().enumerate() {
26628                            if i > 0 {
26629                                self.write(", ");
26630                            }
26631                            if !field.name.is_empty() {
26632                                self.write(&field.name);
26633                                self.write(" ");
26634                            }
26635                            self.generate_data_type(&field.data_type)?;
26636                        }
26637                        self.write(")");
26638                    }
26639                    Some(DialectType::ClickHouse) => {
26640                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
26641                        self.write("Tuple(");
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::SingleStore) => {
26655                        // SingleStore uses RECORD(name TYPE, ...) for struct types
26656                        self.write_keyword("RECORD(");
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                    _ => {
26670                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
26671                        let force_angle_brackets = matches!(
26672                            self.config.dialect,
26673                            Some(DialectType::Hive)
26674                                | Some(DialectType::Spark)
26675                                | Some(DialectType::Databricks)
26676                        );
26677                        if *nested && !force_angle_brackets {
26678                            self.write_keyword("STRUCT(");
26679                            for (i, field) in fields.iter().enumerate() {
26680                                if i > 0 {
26681                                    self.write(", ");
26682                                }
26683                                if !field.name.is_empty() {
26684                                    self.write(&field.name);
26685                                    self.write(" ");
26686                                }
26687                                self.generate_data_type(&field.data_type)?;
26688                            }
26689                            self.write(")");
26690                        } else {
26691                            self.write_keyword("STRUCT<");
26692                            for (i, field) in fields.iter().enumerate() {
26693                                if i > 0 {
26694                                    self.write(", ");
26695                                }
26696                                if !field.name.is_empty() {
26697                                    // Named field: name TYPE (with configurable separator for Hive)
26698                                    self.write(&field.name);
26699                                    self.write(self.config.struct_field_sep);
26700                                }
26701                                // For anonymous fields, just output the type
26702                                self.generate_data_type(&field.data_type)?;
26703                                // Spark/Databricks: Output COMMENT clause if present
26704                                if let Some(comment) = &field.comment {
26705                                    self.write(" COMMENT '");
26706                                    self.write(comment);
26707                                    self.write("'");
26708                                }
26709                                // BigQuery: Output OPTIONS clause if present
26710                                if !field.options.is_empty() {
26711                                    self.write(" ");
26712                                    self.generate_options_clause(&field.options)?;
26713                                }
26714                            }
26715                            self.write(">");
26716                        }
26717                    }
26718                }
26719            }
26720            DataType::Enum {
26721                values,
26722                assignments,
26723            } => {
26724                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
26725                // ClickHouse: Enum('hello' = 1, 'world' = 2)
26726                if self.config.dialect == Some(DialectType::ClickHouse) {
26727                    self.write("Enum(");
26728                } else {
26729                    self.write_keyword("ENUM(");
26730                }
26731                for (i, val) in values.iter().enumerate() {
26732                    if i > 0 {
26733                        self.write(", ");
26734                    }
26735                    self.write("'");
26736                    self.write(val);
26737                    self.write("'");
26738                    if let Some(Some(assignment)) = assignments.get(i) {
26739                        self.write(" = ");
26740                        self.write(assignment);
26741                    }
26742                }
26743                self.write(")");
26744            }
26745            DataType::Set { values } => {
26746                // MySQL SET type: SET('a', 'b', 'c')
26747                self.write_keyword("SET(");
26748                for (i, val) in values.iter().enumerate() {
26749                    if i > 0 {
26750                        self.write(", ");
26751                    }
26752                    self.write("'");
26753                    self.write(val);
26754                    self.write("'");
26755                }
26756                self.write(")");
26757            }
26758            DataType::Union { fields } => {
26759                // DuckDB UNION type: UNION(num INT, str TEXT)
26760                self.write_keyword("UNION(");
26761                for (i, (name, dt)) in fields.iter().enumerate() {
26762                    if i > 0 {
26763                        self.write(", ");
26764                    }
26765                    if !name.is_empty() {
26766                        self.write(name);
26767                        self.write(" ");
26768                    }
26769                    self.generate_data_type(dt)?;
26770                }
26771                self.write(")");
26772            }
26773            DataType::Nullable { inner } => {
26774                // ClickHouse: Nullable(T), other dialects: just the inner type
26775                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26776                    self.write("Nullable(");
26777                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
26778                    let saved_depth = self.clickhouse_nullable_depth;
26779                    self.clickhouse_nullable_depth = -1;
26780                    self.generate_data_type(inner)?;
26781                    self.clickhouse_nullable_depth = saved_depth;
26782                    self.write(")");
26783                } else {
26784                    // Map ClickHouse-specific custom type names to standard types
26785                    match inner.as_ref() {
26786                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
26787                            self.generate_data_type(&DataType::Timestamp {
26788                                precision: None,
26789                                timezone: false,
26790                            })?;
26791                        }
26792                        _ => {
26793                            self.generate_data_type(inner)?;
26794                        }
26795                    }
26796                }
26797            }
26798            DataType::Custom { name } => {
26799                // Handle dialect-specific type transformations
26800                let name_upper = name.to_ascii_uppercase();
26801                match self.config.dialect {
26802                    Some(DialectType::ClickHouse) => {
26803                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26804                            (name_upper[..idx].to_string(), &name[idx..])
26805                        } else {
26806                            (name_upper.clone(), "")
26807                        };
26808                        let mapped = match base_upper.as_str() {
26809                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
26810                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
26811                            "DATETIME64" => "DateTime64",
26812                            "DATE32" => "Date32",
26813                            "INT" => "Int32",
26814                            "MEDIUMINT" => "Int32",
26815                            "INT8" => "Int8",
26816                            "INT16" => "Int16",
26817                            "INT32" => "Int32",
26818                            "INT64" => "Int64",
26819                            "INT128" => "Int128",
26820                            "INT256" => "Int256",
26821                            "UINT8" => "UInt8",
26822                            "UINT16" => "UInt16",
26823                            "UINT32" => "UInt32",
26824                            "UINT64" => "UInt64",
26825                            "UINT128" => "UInt128",
26826                            "UINT256" => "UInt256",
26827                            "FLOAT32" => "Float32",
26828                            "FLOAT64" => "Float64",
26829                            "DECIMAL32" => "Decimal32",
26830                            "DECIMAL64" => "Decimal64",
26831                            "DECIMAL128" => "Decimal128",
26832                            "DECIMAL256" => "Decimal256",
26833                            "ENUM" => "Enum",
26834                            "ENUM8" => "Enum8",
26835                            "ENUM16" => "Enum16",
26836                            "FIXEDSTRING" => "FixedString",
26837                            "NESTED" => "Nested",
26838                            "LOWCARDINALITY" => "LowCardinality",
26839                            "NULLABLE" => "Nullable",
26840                            "IPV4" => "IPv4",
26841                            "IPV6" => "IPv6",
26842                            "POINT" => "Point",
26843                            "RING" => "Ring",
26844                            "LINESTRING" => "LineString",
26845                            "MULTILINESTRING" => "MultiLineString",
26846                            "POLYGON" => "Polygon",
26847                            "MULTIPOLYGON" => "MultiPolygon",
26848                            "AGGREGATEFUNCTION" => "AggregateFunction",
26849                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
26850                            "DYNAMIC" => "Dynamic",
26851                            _ => "",
26852                        };
26853                        if mapped.is_empty() {
26854                            self.write(name);
26855                        } else {
26856                            self.write(mapped);
26857                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
26858                                && !suffix.is_empty()
26859                            {
26860                                let escaped_suffix = suffix
26861                                    .replace('\\', "\\\\")
26862                                    .replace('\t', "\\t")
26863                                    .replace('\n', "\\n")
26864                                    .replace('\r', "\\r");
26865                                self.write(&escaped_suffix);
26866                            } else {
26867                                self.write(suffix);
26868                            }
26869                        }
26870                    }
26871                    Some(DialectType::MySQL)
26872                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
26873                    {
26874                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
26875                        self.write_keyword("TIMESTAMP");
26876                    }
26877                    Some(DialectType::Snowflake) => {
26878                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26879                            (name_upper[..idx].to_string(), &name[idx..])
26880                        } else {
26881                            (name_upper.clone(), "")
26882                        };
26883
26884                        match base_upper.as_str() {
26885                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
26886                                self.write_keyword("TIMESTAMPNTZ");
26887                                self.write(suffix);
26888                            }
26889                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
26890                                self.write_keyword("TIMESTAMPLTZ");
26891                                self.write(suffix);
26892                            }
26893                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
26894                                self.write_keyword("TIMESTAMPTZ");
26895                                self.write(suffix);
26896                            }
26897                            _ => self.write(name),
26898                        }
26899                    }
26900                    Some(DialectType::Fabric) => {
26901                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
26902                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26903                        } else {
26904                            (name_upper.clone(), None)
26905                        };
26906
26907                        match base_upper.as_str() {
26908                            "NVARCHAR" => {
26909                                self.write_keyword("VARCHAR");
26910                                if let Some(args) = args_str {
26911                                    self.write(args);
26912                                }
26913                            }
26914                            "NCHAR" => {
26915                                self.write_keyword("CHAR");
26916                                if let Some(args) = args_str {
26917                                    self.write(args);
26918                                }
26919                            }
26920                            _ => self.write(name),
26921                        }
26922                    }
26923                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
26924                        self.write_keyword("SQL_VARIANT");
26925                    }
26926                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
26927                        self.write_keyword("DECIMAL(38, 5)");
26928                    }
26929                    Some(DialectType::Exasol) => {
26930                        // Exasol type mappings for custom types
26931                        match name_upper.as_str() {
26932                            // Binary types → VARCHAR
26933                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
26934                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
26935                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
26936                            // Integer types
26937                            "MEDIUMINT" => self.write_keyword("INT"),
26938                            // Decimal types → DECIMAL
26939                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
26940                                self.write_keyword("DECIMAL")
26941                            }
26942                            // Timestamp types
26943                            "DATETIME" => self.write_keyword("TIMESTAMP"),
26944                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
26945                            _ => self.write(name),
26946                        }
26947                    }
26948                    Some(DialectType::Dremio) => {
26949                        // Dremio type mappings for custom types
26950                        match name_upper.as_str() {
26951                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
26952                            "ARRAY" => self.write_keyword("LIST"),
26953                            "NCHAR" => self.write_keyword("VARCHAR"),
26954                            _ => self.write(name),
26955                        }
26956                    }
26957                    // Map dialect-specific custom types to standard SQL types for other dialects
26958                    _ => {
26959                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
26960                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
26961                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26962                        } else {
26963                            (name_upper.clone(), None)
26964                        };
26965
26966                        match base_upper.as_str() {
26967                            "INT64"
26968                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26969                            {
26970                                self.write_keyword("BIGINT");
26971                            }
26972                            "FLOAT64"
26973                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26974                            {
26975                                self.write_keyword("DOUBLE");
26976                            }
26977                            "BOOL"
26978                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26979                            {
26980                                self.write_keyword("BOOLEAN");
26981                            }
26982                            "BYTES"
26983                                if matches!(
26984                                    self.config.dialect,
26985                                    Some(DialectType::Spark)
26986                                        | Some(DialectType::Hive)
26987                                        | Some(DialectType::Databricks)
26988                                ) =>
26989                            {
26990                                self.write_keyword("BINARY");
26991                            }
26992                            "BYTES"
26993                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26994                            {
26995                                self.write_keyword("VARBINARY");
26996                            }
26997                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
26998                            "DATETIME2" | "SMALLDATETIME"
26999                                if !matches!(
27000                                    self.config.dialect,
27001                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27002                                ) =>
27003                            {
27004                                // PostgreSQL preserves precision, others don't
27005                                if matches!(
27006                                    self.config.dialect,
27007                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27008                                ) {
27009                                    self.write_keyword("TIMESTAMP");
27010                                    if let Some(args) = _args_str {
27011                                        self.write(args);
27012                                    }
27013                                } else {
27014                                    self.write_keyword("TIMESTAMP");
27015                                }
27016                            }
27017                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
27018                            "DATETIMEOFFSET"
27019                                if !matches!(
27020                                    self.config.dialect,
27021                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27022                                ) =>
27023                            {
27024                                if matches!(
27025                                    self.config.dialect,
27026                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27027                                ) {
27028                                    self.write_keyword("TIMESTAMPTZ");
27029                                    if let Some(args) = _args_str {
27030                                        self.write(args);
27031                                    }
27032                                } else {
27033                                    self.write_keyword("TIMESTAMPTZ");
27034                                }
27035                            }
27036                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
27037                            "UNIQUEIDENTIFIER"
27038                                if !matches!(
27039                                    self.config.dialect,
27040                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27041                                ) =>
27042                            {
27043                                match self.config.dialect {
27044                                    Some(DialectType::Spark)
27045                                    | Some(DialectType::Databricks)
27046                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
27047                                    _ => self.write_keyword("UUID"),
27048                                }
27049                            }
27050                            // TSQL BIT -> BOOLEAN for most dialects
27051                            "BIT"
27052                                if !matches!(
27053                                    self.config.dialect,
27054                                    Some(DialectType::TSQL)
27055                                        | Some(DialectType::Fabric)
27056                                        | Some(DialectType::PostgreSQL)
27057                                        | Some(DialectType::MySQL)
27058                                        | Some(DialectType::DuckDB)
27059                                ) =>
27060                            {
27061                                self.write_keyword("BOOLEAN");
27062                            }
27063                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
27064                            "NVARCHAR"
27065                                if !matches!(
27066                                    self.config.dialect,
27067                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27068                                ) =>
27069                            {
27070                                match self.config.dialect {
27071                                    Some(DialectType::Oracle) => {
27072                                        // Oracle: NVARCHAR -> NVARCHAR2
27073                                        self.write_keyword("NVARCHAR2");
27074                                        if let Some(args) = _args_str {
27075                                            self.write(args);
27076                                        }
27077                                    }
27078                                    Some(DialectType::BigQuery) => {
27079                                        // BigQuery: NVARCHAR -> STRING
27080                                        self.write_keyword("STRING");
27081                                    }
27082                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
27083                                        self.write_keyword("TEXT");
27084                                        if let Some(args) = _args_str {
27085                                            self.write(args);
27086                                        }
27087                                    }
27088                                    Some(DialectType::Hive) => {
27089                                        // Hive: NVARCHAR -> STRING
27090                                        self.write_keyword("STRING");
27091                                    }
27092                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
27093                                        if _args_str.is_some() {
27094                                            self.write_keyword("VARCHAR");
27095                                            self.write(_args_str.unwrap());
27096                                        } else {
27097                                            self.write_keyword("STRING");
27098                                        }
27099                                    }
27100                                    _ => {
27101                                        self.write_keyword("VARCHAR");
27102                                        if let Some(args) = _args_str {
27103                                            self.write(args);
27104                                        }
27105                                    }
27106                                }
27107                            }
27108                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
27109                            "NCHAR"
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 natively supports NCHAR
27118                                        self.write_keyword("NCHAR");
27119                                        if let Some(args) = _args_str {
27120                                            self.write(args);
27121                                        }
27122                                    }
27123                                    Some(DialectType::BigQuery) => {
27124                                        // BigQuery: NCHAR -> STRING
27125                                        self.write_keyword("STRING");
27126                                    }
27127                                    Some(DialectType::Hive) => {
27128                                        // Hive: NCHAR -> STRING
27129                                        self.write_keyword("STRING");
27130                                    }
27131                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
27132                                        self.write_keyword("TEXT");
27133                                        if let Some(args) = _args_str {
27134                                            self.write(args);
27135                                        }
27136                                    }
27137                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
27138                                        if _args_str.is_some() {
27139                                            self.write_keyword("CHAR");
27140                                            self.write(_args_str.unwrap());
27141                                        } else {
27142                                            self.write_keyword("STRING");
27143                                        }
27144                                    }
27145                                    _ => {
27146                                        self.write_keyword("CHAR");
27147                                        if let Some(args) = _args_str {
27148                                            self.write(args);
27149                                        }
27150                                    }
27151                                }
27152                            }
27153                            // MySQL text variant types -> map to appropriate target type
27154                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
27155                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
27156                                Some(DialectType::MySQL)
27157                                | Some(DialectType::SingleStore)
27158                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
27159                                Some(DialectType::Spark)
27160                                | Some(DialectType::Databricks)
27161                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
27162                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
27163                                Some(DialectType::Presto)
27164                                | Some(DialectType::Trino)
27165                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
27166                                Some(DialectType::Snowflake)
27167                                | Some(DialectType::Redshift)
27168                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
27169                                _ => self.write_keyword("TEXT"),
27170                            },
27171                            // MySQL blob variant types -> map to appropriate target type
27172                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
27173                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
27174                                Some(DialectType::MySQL)
27175                                | Some(DialectType::SingleStore)
27176                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
27177                                Some(DialectType::Spark)
27178                                | Some(DialectType::Databricks)
27179                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
27180                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
27181                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
27182                                Some(DialectType::Presto)
27183                                | Some(DialectType::Trino)
27184                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
27185                                Some(DialectType::Snowflake)
27186                                | Some(DialectType::Redshift)
27187                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
27188                                _ => self.write_keyword("BLOB"),
27189                            },
27190                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
27191                            "LONGVARCHAR" => match self.config.dialect {
27192                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
27193                                _ => self.write_keyword("VARCHAR"),
27194                            },
27195                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
27196                            "DATETIME" => {
27197                                match self.config.dialect {
27198                                    Some(DialectType::MySQL)
27199                                    | Some(DialectType::Doris)
27200                                    | Some(DialectType::StarRocks)
27201                                    | Some(DialectType::TSQL)
27202                                    | Some(DialectType::Fabric)
27203                                    | Some(DialectType::BigQuery)
27204                                    | Some(DialectType::SQLite)
27205                                    | Some(DialectType::Snowflake) => {
27206                                        self.write_keyword("DATETIME");
27207                                        if let Some(args) = _args_str {
27208                                            self.write(args);
27209                                        }
27210                                    }
27211                                    Some(_) => {
27212                                        // Only map to TIMESTAMP when we have a specific target dialect
27213                                        self.write_keyword("TIMESTAMP");
27214                                        if let Some(args) = _args_str {
27215                                            self.write(args);
27216                                        }
27217                                    }
27218                                    None => {
27219                                        // No dialect - preserve original
27220                                        self.write(name);
27221                                    }
27222                                }
27223                            }
27224                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
27225                            "VARCHAR2"
27226                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
27227                            {
27228                                match self.config.dialect {
27229                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
27230                                        self.write_keyword("TEXT");
27231                                    }
27232                                    Some(DialectType::Hive)
27233                                    | Some(DialectType::Spark)
27234                                    | Some(DialectType::Databricks)
27235                                    | Some(DialectType::BigQuery)
27236                                    | Some(DialectType::ClickHouse)
27237                                    | Some(DialectType::StarRocks)
27238                                    | Some(DialectType::Doris) => {
27239                                        self.write_keyword("STRING");
27240                                    }
27241                                    _ => {
27242                                        self.write_keyword("VARCHAR");
27243                                        if let Some(args) = _args_str {
27244                                            self.write(args);
27245                                        }
27246                                    }
27247                                }
27248                            }
27249                            "NVARCHAR2"
27250                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
27251                            {
27252                                match self.config.dialect {
27253                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
27254                                        self.write_keyword("TEXT");
27255                                    }
27256                                    Some(DialectType::Hive)
27257                                    | Some(DialectType::Spark)
27258                                    | Some(DialectType::Databricks)
27259                                    | Some(DialectType::BigQuery)
27260                                    | Some(DialectType::ClickHouse)
27261                                    | Some(DialectType::StarRocks)
27262                                    | Some(DialectType::Doris) => {
27263                                        self.write_keyword("STRING");
27264                                    }
27265                                    _ => {
27266                                        self.write_keyword("VARCHAR");
27267                                        if let Some(args) = _args_str {
27268                                            self.write(args);
27269                                        }
27270                                    }
27271                                }
27272                            }
27273                            _ => self.write(name),
27274                        }
27275                    }
27276                }
27277            }
27278            DataType::Geometry { subtype, srid } => {
27279                // Dialect-specific geometry type mappings
27280                match self.config.dialect {
27281                    Some(DialectType::MySQL) => {
27282                        // MySQL uses POINT SRID 4326 syntax for specific types
27283                        if let Some(sub) = subtype {
27284                            self.write_keyword(sub);
27285                            if let Some(s) = srid {
27286                                self.write(" SRID ");
27287                                self.write(&s.to_string());
27288                            }
27289                        } else {
27290                            self.write_keyword("GEOMETRY");
27291                        }
27292                    }
27293                    Some(DialectType::BigQuery) => {
27294                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
27295                        self.write_keyword("GEOGRAPHY");
27296                    }
27297                    Some(DialectType::Teradata) => {
27298                        // Teradata uses ST_GEOMETRY
27299                        self.write_keyword("ST_GEOMETRY");
27300                        if subtype.is_some() || srid.is_some() {
27301                            self.write("(");
27302                            if let Some(sub) = subtype {
27303                                self.write_keyword(sub);
27304                            }
27305                            if let Some(s) = srid {
27306                                if subtype.is_some() {
27307                                    self.write(", ");
27308                                }
27309                                self.write(&s.to_string());
27310                            }
27311                            self.write(")");
27312                        }
27313                    }
27314                    _ => {
27315                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
27316                        self.write_keyword("GEOMETRY");
27317                        if subtype.is_some() || srid.is_some() {
27318                            self.write("(");
27319                            if let Some(sub) = subtype {
27320                                self.write_keyword(sub);
27321                            }
27322                            if let Some(s) = srid {
27323                                if subtype.is_some() {
27324                                    self.write(", ");
27325                                }
27326                                self.write(&s.to_string());
27327                            }
27328                            self.write(")");
27329                        }
27330                    }
27331                }
27332            }
27333            DataType::Geography { subtype, srid } => {
27334                // Dialect-specific geography type mappings
27335                match self.config.dialect {
27336                    Some(DialectType::MySQL) => {
27337                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
27338                        if let Some(sub) = subtype {
27339                            self.write_keyword(sub);
27340                        } else {
27341                            self.write_keyword("GEOMETRY");
27342                        }
27343                        // Geography implies SRID 4326 (WGS84)
27344                        let effective_srid = srid.unwrap_or(4326);
27345                        self.write(" SRID ");
27346                        self.write(&effective_srid.to_string());
27347                    }
27348                    Some(DialectType::BigQuery) => {
27349                        // BigQuery uses simple GEOGRAPHY without parameters
27350                        self.write_keyword("GEOGRAPHY");
27351                    }
27352                    Some(DialectType::Snowflake) => {
27353                        // Snowflake uses GEOGRAPHY without parameters
27354                        self.write_keyword("GEOGRAPHY");
27355                    }
27356                    _ => {
27357                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
27358                        self.write_keyword("GEOGRAPHY");
27359                        if subtype.is_some() || srid.is_some() {
27360                            self.write("(");
27361                            if let Some(sub) = subtype {
27362                                self.write_keyword(sub);
27363                            }
27364                            if let Some(s) = srid {
27365                                if subtype.is_some() {
27366                                    self.write(", ");
27367                                }
27368                                self.write(&s.to_string());
27369                            }
27370                            self.write(")");
27371                        }
27372                    }
27373                }
27374            }
27375            DataType::CharacterSet { name } => {
27376                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
27377                self.write_keyword("CHAR CHARACTER SET ");
27378                self.write(name);
27379            }
27380            _ => self.write("UNKNOWN"),
27381        }
27382        Ok(())
27383    }
27384
27385    // === Helper methods ===
27386
27387    #[inline]
27388    fn write(&mut self, s: &str) {
27389        self.output.push_str(s);
27390    }
27391
27392    #[inline]
27393    fn write_space(&mut self) {
27394        self.output.push(' ');
27395    }
27396
27397    #[inline]
27398    fn write_keyword(&mut self, keyword: &str) {
27399        if self.config.uppercase_keywords {
27400            self.output.push_str(keyword);
27401        } else {
27402            for b in keyword.bytes() {
27403                self.output.push(b.to_ascii_lowercase() as char);
27404            }
27405        }
27406    }
27407
27408    /// Write a function name respecting the normalize_functions config setting
27409    fn write_func_name(&mut self, name: &str) {
27410        let normalized = self.normalize_func_name(name);
27411        self.output.push_str(normalized.as_ref());
27412    }
27413
27414    /// Convert strptime format string to Exasol format string
27415    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
27416    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
27417    fn convert_strptime_to_exasol_format(format: &str) -> String {
27418        let mut result = String::new();
27419        let chars: Vec<char> = format.chars().collect();
27420        let mut i = 0;
27421        while i < chars.len() {
27422            if chars[i] == '%' && i + 1 < chars.len() {
27423                let spec = chars[i + 1];
27424                let exasol_spec = match spec {
27425                    'Y' => "YYYY",
27426                    'y' => "YY",
27427                    'm' => "MM",
27428                    'd' => "DD",
27429                    'H' => "HH",
27430                    'M' => "MI",
27431                    'S' => "SS",
27432                    'a' => "DY",    // abbreviated weekday name
27433                    'A' => "DAY",   // full weekday name
27434                    'b' => "MON",   // abbreviated month name
27435                    'B' => "MONTH", // full month name
27436                    'I' => "H12",   // 12-hour format
27437                    'u' => "ID",    // ISO weekday (1-7)
27438                    'V' => "IW",    // ISO week number
27439                    'G' => "IYYY",  // ISO year
27440                    'W' => "UW",    // Week number (Monday as first day)
27441                    'U' => "UW",    // Week number (Sunday as first day)
27442                    'z' => "Z",     // timezone offset
27443                    _ => {
27444                        // Unknown specifier, keep as-is
27445                        result.push('%');
27446                        result.push(spec);
27447                        i += 2;
27448                        continue;
27449                    }
27450                };
27451                result.push_str(exasol_spec);
27452                i += 2;
27453            } else {
27454                result.push(chars[i]);
27455                i += 1;
27456            }
27457        }
27458        result
27459    }
27460
27461    /// Convert strptime format string to PostgreSQL/Redshift format string
27462    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
27463    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
27464    fn convert_strptime_to_postgres_format(format: &str) -> String {
27465        let mut result = String::new();
27466        let chars: Vec<char> = format.chars().collect();
27467        let mut i = 0;
27468        while i < chars.len() {
27469            if chars[i] == '%' && i + 1 < chars.len() {
27470                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
27471                if chars[i + 1] == '-' && i + 2 < chars.len() {
27472                    let spec = chars[i + 2];
27473                    let pg_spec = match spec {
27474                        'd' => "FMDD",
27475                        'm' => "FMMM",
27476                        'H' => "FMHH24",
27477                        'M' => "FMMI",
27478                        'S' => "FMSS",
27479                        _ => {
27480                            result.push('%');
27481                            result.push('-');
27482                            result.push(spec);
27483                            i += 3;
27484                            continue;
27485                        }
27486                    };
27487                    result.push_str(pg_spec);
27488                    i += 3;
27489                    continue;
27490                }
27491                let spec = chars[i + 1];
27492                let pg_spec = match spec {
27493                    'Y' => "YYYY",
27494                    'y' => "YY",
27495                    'm' => "MM",
27496                    'd' => "DD",
27497                    'H' => "HH24",
27498                    'I' => "HH12",
27499                    'M' => "MI",
27500                    'S' => "SS",
27501                    'f' => "US",      // microseconds
27502                    'u' => "D",       // day of week (1=Monday)
27503                    'j' => "DDD",     // day of year
27504                    'z' => "OF",      // UTC offset
27505                    'Z' => "TZ",      // timezone name
27506                    'A' => "TMDay",   // full weekday name
27507                    'a' => "TMDy",    // abbreviated weekday name
27508                    'b' => "TMMon",   // abbreviated month name
27509                    'B' => "TMMonth", // full month name
27510                    'U' => "WW",      // week number
27511                    _ => {
27512                        // Unknown specifier, keep as-is
27513                        result.push('%');
27514                        result.push(spec);
27515                        i += 2;
27516                        continue;
27517                    }
27518                };
27519                result.push_str(pg_spec);
27520                i += 2;
27521            } else {
27522                result.push(chars[i]);
27523                i += 1;
27524            }
27525        }
27526        result
27527    }
27528
27529    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
27530    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
27531        if self.config.limit_only_literals {
27532            if let Some(value) = Self::try_evaluate_constant(expr) {
27533                self.write(&value.to_string());
27534                return Ok(());
27535            }
27536        }
27537        self.generate_expression(expr)
27538    }
27539
27540    fn is_noop_limit_expr(expr: &Expression) -> bool {
27541        match expr {
27542            Expression::Null(_) => true,
27543            Expression::Identifier(identifier) => identifier.name.eq_ignore_ascii_case("ALL"),
27544            Expression::Column(column) => {
27545                column.table.is_none() && column.name.name.eq_ignore_ascii_case("ALL")
27546            }
27547            Expression::Var(var) => var.this.eq_ignore_ascii_case("ALL"),
27548            Expression::Paren(paren) => Self::is_noop_limit_expr(&paren.this),
27549            _ => false,
27550        }
27551    }
27552
27553    /// Format a comment with proper spacing.
27554    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
27555    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
27556    fn write_formatted_comment(&mut self, comment: &str) {
27557        // Normalize all comments to block comment format /* ... */
27558        // This matches Python sqlglot behavior which always outputs block comments
27559        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
27560            // Already block comment - extract inner content
27561            // Preserve internal whitespace, but ensure at least one space padding
27562            &comment[2..comment.len() - 2]
27563        } else if comment.starts_with("--") {
27564            // Line comment - extract content after --
27565            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
27566            &comment[2..]
27567        } else {
27568            // Raw content (no delimiters)
27569            comment
27570        };
27571        // Skip empty comments (e.g., bare "--" with no content)
27572        if content.trim().is_empty() {
27573            return;
27574        }
27575        // Escape nested block comment markers to prevent premature closure or unintended nesting.
27576        // This matches Python sqlglot's sanitize_comment behavior.
27577        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
27578        let content = &sanitized;
27579        // Ensure at least one space after /* and before */
27580        self.output.push_str("/*");
27581        if !content.starts_with(' ') {
27582            self.output.push(' ');
27583        }
27584        self.output.push_str(content);
27585        if !content.ends_with(' ') {
27586            self.output.push(' ');
27587        }
27588        self.output.push_str("*/");
27589    }
27590
27591    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
27592    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
27593    fn escape_block_for_single_quote(&self, block: &str) -> String {
27594        let escape_backslash = matches!(
27595            self.config.dialect,
27596            Some(crate::dialects::DialectType::Snowflake)
27597        );
27598        let mut escaped = String::with_capacity(block.len() + 4);
27599        for ch in block.chars() {
27600            if ch == '\'' {
27601                escaped.push('\\');
27602                escaped.push('\'');
27603            } else if escape_backslash && ch == '\\' {
27604                escaped.push('\\');
27605                escaped.push('\\');
27606            } else {
27607                escaped.push(ch);
27608            }
27609        }
27610        escaped
27611    }
27612
27613    fn write_newline(&mut self) {
27614        self.output.push('\n');
27615    }
27616
27617    fn write_indent(&mut self) {
27618        for _ in 0..self.indent_level {
27619            self.output.push_str(self.config.indent);
27620        }
27621    }
27622
27623    // === SQLGlot-style pretty printing helpers ===
27624
27625    /// Returns the separator string for pretty printing.
27626    /// Check if the total length of arguments exceeds max_text_width.
27627    /// Used for dynamic line breaking in expressions() formatting.
27628    fn too_wide(&self, args: &[String]) -> bool {
27629        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
27630    }
27631
27632    /// Generate an expression to a string using a temporary non-pretty generator.
27633    /// Useful for width calculations before deciding on formatting.
27634    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
27635        let config = GeneratorConfig {
27636            pretty: false,
27637            dialect: self.config.dialect,
27638            ..Default::default()
27639        };
27640        let mut gen = Generator::with_config(config);
27641        gen.generate_expression(expr)?;
27642        Ok(gen.output)
27643    }
27644
27645    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
27646    /// In pretty mode: newline + indented keyword + newline + indented condition
27647    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
27648        if self.config.pretty {
27649            self.write_newline();
27650            self.write_indent();
27651            self.write_keyword(keyword);
27652            self.write_newline();
27653            self.indent_level += 1;
27654            self.write_indent();
27655            self.generate_expression(condition)?;
27656            self.indent_level -= 1;
27657        } else {
27658            self.write_space();
27659            self.write_keyword(keyword);
27660            self.write_space();
27661            self.generate_expression(condition)?;
27662        }
27663        Ok(())
27664    }
27665
27666    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
27667    /// In pretty mode: each expression on new line with indentation
27668    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
27669        if exprs.is_empty() {
27670            return Ok(());
27671        }
27672
27673        if self.config.pretty {
27674            self.write_newline();
27675            self.write_indent();
27676            self.write_keyword(keyword);
27677            self.write_newline();
27678            self.indent_level += 1;
27679            for (i, expr) in exprs.iter().enumerate() {
27680                if i > 0 {
27681                    self.write(",");
27682                    self.write_newline();
27683                }
27684                self.write_indent();
27685                self.generate_expression(expr)?;
27686            }
27687            self.indent_level -= 1;
27688        } else {
27689            self.write_space();
27690            self.write_keyword(keyword);
27691            self.write_space();
27692            for (i, expr) in exprs.iter().enumerate() {
27693                if i > 0 {
27694                    self.write(", ");
27695                }
27696                self.generate_expression(expr)?;
27697            }
27698        }
27699        Ok(())
27700    }
27701
27702    /// Writes ORDER BY / SORT BY clause with Ordered expressions
27703    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
27704        if orderings.is_empty() {
27705            return Ok(());
27706        }
27707
27708        if self.config.pretty {
27709            self.write_newline();
27710            self.write_indent();
27711            self.write_keyword(keyword);
27712            self.write_newline();
27713            self.indent_level += 1;
27714            for (i, ordered) in orderings.iter().enumerate() {
27715                if i > 0 {
27716                    self.write(",");
27717                    self.write_newline();
27718                }
27719                self.write_indent();
27720                self.generate_ordered(ordered)?;
27721            }
27722            self.indent_level -= 1;
27723        } else {
27724            self.write_space();
27725            self.write_keyword(keyword);
27726            self.write_space();
27727            for (i, ordered) in orderings.iter().enumerate() {
27728                if i > 0 {
27729                    self.write(", ");
27730                }
27731                self.generate_ordered(ordered)?;
27732            }
27733        }
27734        Ok(())
27735    }
27736
27737    /// Writes WINDOW clause with named window definitions
27738    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
27739        if windows.is_empty() {
27740            return Ok(());
27741        }
27742
27743        if self.config.pretty {
27744            self.write_newline();
27745            self.write_indent();
27746            self.write_keyword("WINDOW");
27747            self.write_newline();
27748            self.indent_level += 1;
27749            for (i, named_window) in windows.iter().enumerate() {
27750                if i > 0 {
27751                    self.write(",");
27752                    self.write_newline();
27753                }
27754                self.write_indent();
27755                self.generate_identifier(&named_window.name)?;
27756                self.write_space();
27757                self.write_keyword("AS");
27758                self.write(" (");
27759                self.generate_over(&named_window.spec)?;
27760                self.write(")");
27761            }
27762            self.indent_level -= 1;
27763        } else {
27764            self.write_space();
27765            self.write_keyword("WINDOW");
27766            self.write_space();
27767            for (i, named_window) in windows.iter().enumerate() {
27768                if i > 0 {
27769                    self.write(", ");
27770                }
27771                self.generate_identifier(&named_window.name)?;
27772                self.write_space();
27773                self.write_keyword("AS");
27774                self.write(" (");
27775                self.generate_over(&named_window.spec)?;
27776                self.write(")");
27777            }
27778        }
27779        Ok(())
27780    }
27781
27782    // === BATCH-GENERATED STUB METHODS (481 variants) ===
27783    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
27784        // AI_AGG(this, expression)
27785        self.write_keyword("AI_AGG");
27786        self.write("(");
27787        self.generate_expression(&e.this)?;
27788        self.write(", ");
27789        self.generate_expression(&e.expression)?;
27790        self.write(")");
27791        Ok(())
27792    }
27793
27794    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
27795        // AI_CLASSIFY(input, [categories], [config])
27796        self.write_keyword("AI_CLASSIFY");
27797        self.write("(");
27798        self.generate_expression(&e.this)?;
27799        if let Some(categories) = &e.categories {
27800            self.write(", ");
27801            self.generate_expression(categories)?;
27802        }
27803        if let Some(config) = &e.config {
27804            self.write(", ");
27805            self.generate_expression(config)?;
27806        }
27807        self.write(")");
27808        Ok(())
27809    }
27810
27811    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
27812        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
27813        self.write_keyword("ADD");
27814        self.write_space();
27815        if e.exists {
27816            self.write_keyword("IF NOT EXISTS");
27817            self.write_space();
27818        }
27819        self.generate_expression(&e.this)?;
27820        if let Some(location) = &e.location {
27821            self.write_space();
27822            self.generate_expression(location)?;
27823        }
27824        Ok(())
27825    }
27826
27827    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
27828        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
27829        self.write_keyword("ALGORITHM");
27830        self.write("=");
27831        self.generate_expression(&e.this)?;
27832        Ok(())
27833    }
27834
27835    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
27836        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
27837        self.generate_expression(&e.this)?;
27838        self.write_space();
27839        self.write_keyword("AS");
27840        self.write(" (");
27841        for (i, expr) in e.expressions.iter().enumerate() {
27842            if i > 0 {
27843                self.write(", ");
27844            }
27845            self.generate_expression(expr)?;
27846        }
27847        self.write(")");
27848        Ok(())
27849    }
27850
27851    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
27852        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
27853        self.write_keyword("ALLOWED_VALUES");
27854        self.write_space();
27855        for (i, expr) in e.expressions.iter().enumerate() {
27856            if i > 0 {
27857                self.write(", ");
27858            }
27859            self.generate_expression(expr)?;
27860        }
27861        Ok(())
27862    }
27863
27864    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
27865        // Python: complex logic based on dtype, default, comment, visible, etc.
27866        self.write_keyword("ALTER COLUMN");
27867        self.write_space();
27868        self.generate_expression(&e.this)?;
27869
27870        if let Some(dtype) = &e.dtype {
27871            self.write_space();
27872            self.write_keyword("SET DATA TYPE");
27873            self.write_space();
27874            self.generate_expression(dtype)?;
27875            if let Some(collate) = &e.collate {
27876                self.write_space();
27877                self.write_keyword("COLLATE");
27878                self.write_space();
27879                self.generate_expression(collate)?;
27880            }
27881            if let Some(using) = &e.using {
27882                self.write_space();
27883                self.write_keyword("USING");
27884                self.write_space();
27885                self.generate_expression(using)?;
27886            }
27887        } else if let Some(default) = &e.default {
27888            self.write_space();
27889            self.write_keyword("SET DEFAULT");
27890            self.write_space();
27891            self.generate_expression(default)?;
27892        } else if let Some(comment) = &e.comment {
27893            self.write_space();
27894            self.write_keyword("COMMENT");
27895            self.write_space();
27896            self.generate_expression(comment)?;
27897        } else if let Some(drop) = &e.drop {
27898            self.write_space();
27899            self.write_keyword("DROP");
27900            self.write_space();
27901            self.generate_expression(drop)?;
27902        } else if let Some(visible) = &e.visible {
27903            self.write_space();
27904            self.generate_expression(visible)?;
27905        } else if let Some(rename_to) = &e.rename_to {
27906            self.write_space();
27907            self.write_keyword("RENAME TO");
27908            self.write_space();
27909            self.generate_expression(rename_to)?;
27910        } else if let Some(allow_null) = &e.allow_null {
27911            self.write_space();
27912            self.generate_expression(allow_null)?;
27913        }
27914        Ok(())
27915    }
27916
27917    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
27918        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
27919        self.write_keyword("ALTER SESSION");
27920        self.write_space();
27921        if e.unset.is_some() {
27922            self.write_keyword("UNSET");
27923        } else {
27924            self.write_keyword("SET");
27925        }
27926        self.write_space();
27927        for (i, expr) in e.expressions.iter().enumerate() {
27928            if i > 0 {
27929                self.write(", ");
27930            }
27931            self.generate_expression(expr)?;
27932        }
27933        Ok(())
27934    }
27935
27936    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
27937        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
27938        self.write_keyword("SET");
27939
27940        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
27941        if let Some(opt) = &e.option {
27942            self.write_space();
27943            self.generate_expression(opt)?;
27944        }
27945
27946        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
27947        // Check if expressions look like property assignments
27948        if !e.expressions.is_empty() {
27949            // Check if this looks like property assignments (for SET PROPERTIES)
27950            let is_properties = e
27951                .expressions
27952                .iter()
27953                .any(|expr| matches!(expr, Expression::Eq(_)));
27954            if is_properties && e.option.is_none() {
27955                self.write_space();
27956                self.write_keyword("PROPERTIES");
27957            }
27958            self.write_space();
27959            for (i, expr) in e.expressions.iter().enumerate() {
27960                if i > 0 {
27961                    self.write(", ");
27962                }
27963                self.generate_expression(expr)?;
27964            }
27965        }
27966
27967        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
27968        if let Some(file_format) = &e.file_format {
27969            self.write(" ");
27970            self.write_keyword("STAGE_FILE_FORMAT");
27971            self.write(" = (");
27972            self.generate_space_separated_properties(file_format)?;
27973            self.write(")");
27974        }
27975
27976        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
27977        if let Some(copy_options) = &e.copy_options {
27978            self.write(" ");
27979            self.write_keyword("STAGE_COPY_OPTIONS");
27980            self.write(" = (");
27981            self.generate_space_separated_properties(copy_options)?;
27982            self.write(")");
27983        }
27984
27985        // Generate TAG ...
27986        if let Some(tag) = &e.tag {
27987            self.write(" ");
27988            self.write_keyword("TAG");
27989            self.write(" ");
27990            self.generate_expression(tag)?;
27991        }
27992
27993        Ok(())
27994    }
27995
27996    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
27997    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
27998        match expr {
27999            Expression::Tuple(t) => {
28000                for (i, prop) in t.expressions.iter().enumerate() {
28001                    if i > 0 {
28002                        self.write(" ");
28003                    }
28004                    self.generate_expression(prop)?;
28005                }
28006            }
28007            _ => {
28008                self.generate_expression(expr)?;
28009            }
28010        }
28011        Ok(())
28012    }
28013
28014    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
28015        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
28016        self.write_keyword("ALTER");
28017        if e.compound.is_some() {
28018            self.write_space();
28019            self.write_keyword("COMPOUND");
28020        }
28021        self.write_space();
28022        self.write_keyword("SORTKEY");
28023        self.write_space();
28024        if let Some(this) = &e.this {
28025            self.generate_expression(this)?;
28026        } else if !e.expressions.is_empty() {
28027            self.write("(");
28028            for (i, expr) in e.expressions.iter().enumerate() {
28029                if i > 0 {
28030                    self.write(", ");
28031                }
28032                self.generate_expression(expr)?;
28033            }
28034            self.write(")");
28035        }
28036        Ok(())
28037    }
28038
28039    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
28040        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
28041        self.write_keyword("ANALYZE");
28042        if !e.options.is_empty() {
28043            self.write_space();
28044            for (i, opt) in e.options.iter().enumerate() {
28045                if i > 0 {
28046                    self.write_space();
28047                }
28048                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
28049                if let Expression::Identifier(id) = opt {
28050                    self.write_keyword(&id.name);
28051                } else {
28052                    self.generate_expression(opt)?;
28053                }
28054            }
28055        }
28056        if let Some(kind) = &e.kind {
28057            self.write_space();
28058            self.write_keyword(kind);
28059        }
28060        if let Some(this) = &e.this {
28061            self.write_space();
28062            self.generate_expression(this)?;
28063        }
28064        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
28065        if !e.columns.is_empty() {
28066            self.write("(");
28067            for (i, col) in e.columns.iter().enumerate() {
28068                if i > 0 {
28069                    self.write(", ");
28070                }
28071                self.write(col);
28072            }
28073            self.write(")");
28074        }
28075        if let Some(partition) = &e.partition {
28076            self.write_space();
28077            self.generate_expression(partition)?;
28078        }
28079        if let Some(mode) = &e.mode {
28080            self.write_space();
28081            self.generate_expression(mode)?;
28082        }
28083        if let Some(expression) = &e.expression {
28084            self.write_space();
28085            self.generate_expression(expression)?;
28086        }
28087        if !e.properties.is_empty() {
28088            self.write_space();
28089            self.write_keyword(self.config.with_properties_prefix);
28090            self.write(" (");
28091            for (i, prop) in e.properties.iter().enumerate() {
28092                if i > 0 {
28093                    self.write(", ");
28094                }
28095                self.generate_expression(prop)?;
28096            }
28097            self.write(")");
28098        }
28099        Ok(())
28100    }
28101
28102    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
28103        // Python: return f"DELETE{kind} STATISTICS"
28104        self.write_keyword("DELETE");
28105        if let Some(kind) = &e.kind {
28106            self.write_space();
28107            self.write_keyword(kind);
28108        }
28109        self.write_space();
28110        self.write_keyword("STATISTICS");
28111        Ok(())
28112    }
28113
28114    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
28115        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
28116        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
28117        if let Expression::Identifier(id) = e.this.as_ref() {
28118            self.write_keyword(&id.name);
28119        } else {
28120            self.generate_expression(&e.this)?;
28121        }
28122        self.write_space();
28123        self.write_keyword("HISTOGRAM ON");
28124        self.write_space();
28125        for (i, expr) in e.expressions.iter().enumerate() {
28126            if i > 0 {
28127                self.write(", ");
28128            }
28129            self.generate_expression(expr)?;
28130        }
28131        if let Some(expression) = &e.expression {
28132            self.write_space();
28133            self.generate_expression(expression)?;
28134        }
28135        if let Some(update_options) = &e.update_options {
28136            self.write_space();
28137            self.generate_expression(update_options)?;
28138            self.write_space();
28139            self.write_keyword("UPDATE");
28140        }
28141        Ok(())
28142    }
28143
28144    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
28145        // Python: return f"LIST CHAINED ROWS{inner_expression}"
28146        self.write_keyword("LIST CHAINED ROWS");
28147        if let Some(expression) = &e.expression {
28148            self.write_space();
28149            self.write_keyword("INTO");
28150            self.write_space();
28151            self.generate_expression(expression)?;
28152        }
28153        Ok(())
28154    }
28155
28156    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
28157        // Python: return f"SAMPLE {sample} {kind}"
28158        self.write_keyword("SAMPLE");
28159        self.write_space();
28160        if let Some(sample) = &e.sample {
28161            self.generate_expression(sample)?;
28162            self.write_space();
28163        }
28164        self.write_keyword(&e.kind);
28165        Ok(())
28166    }
28167
28168    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
28169        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
28170        self.write_keyword(&e.kind);
28171        if let Some(option) = &e.option {
28172            self.write_space();
28173            self.generate_expression(option)?;
28174        }
28175        self.write_space();
28176        self.write_keyword("STATISTICS");
28177        if let Some(this) = &e.this {
28178            self.write_space();
28179            self.generate_expression(this)?;
28180        }
28181        if !e.expressions.is_empty() {
28182            self.write_space();
28183            for (i, expr) in e.expressions.iter().enumerate() {
28184                if i > 0 {
28185                    self.write(", ");
28186                }
28187                self.generate_expression(expr)?;
28188            }
28189        }
28190        Ok(())
28191    }
28192
28193    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
28194        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
28195        self.write_keyword("VALIDATE");
28196        self.write_space();
28197        self.write_keyword(&e.kind);
28198        if let Some(this) = &e.this {
28199            self.write_space();
28200            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
28201            if let Expression::Identifier(id) = this.as_ref() {
28202                self.write_keyword(&id.name);
28203            } else {
28204                self.generate_expression(this)?;
28205            }
28206        }
28207        if let Some(expression) = &e.expression {
28208            self.write_space();
28209            self.write_keyword("INTO");
28210            self.write_space();
28211            self.generate_expression(expression)?;
28212        }
28213        Ok(())
28214    }
28215
28216    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
28217        // Python: return f"WITH {expressions}"
28218        self.write_keyword("WITH");
28219        self.write_space();
28220        for (i, expr) in e.expressions.iter().enumerate() {
28221            if i > 0 {
28222                self.write(", ");
28223            }
28224            self.generate_expression(expr)?;
28225        }
28226        Ok(())
28227    }
28228
28229    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
28230        // Anonymous represents a generic function call: FUNC_NAME(args...)
28231        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
28232        self.generate_expression(&e.this)?;
28233        self.write("(");
28234        for (i, arg) in e.expressions.iter().enumerate() {
28235            if i > 0 {
28236                self.write(", ");
28237            }
28238            self.generate_expression(arg)?;
28239        }
28240        self.write(")");
28241        Ok(())
28242    }
28243
28244    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
28245        // Same as Anonymous but for aggregate functions
28246        self.generate_expression(&e.this)?;
28247        self.write("(");
28248        for (i, arg) in e.expressions.iter().enumerate() {
28249            if i > 0 {
28250                self.write(", ");
28251            }
28252            self.generate_expression(arg)?;
28253        }
28254        self.write(")");
28255        Ok(())
28256    }
28257
28258    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
28259        // Python: return f"{this} APPLY({expr})"
28260        self.generate_expression(&e.this)?;
28261        self.write_space();
28262        self.write_keyword("APPLY");
28263        self.write("(");
28264        self.generate_expression(&e.expression)?;
28265        self.write(")");
28266        Ok(())
28267    }
28268
28269    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
28270        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
28271        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
28272        self.write("(");
28273        self.generate_expression(&e.this)?;
28274        if let Some(percentile) = &e.percentile {
28275            self.write(", ");
28276            self.generate_expression(percentile)?;
28277        }
28278        self.write(")");
28279        Ok(())
28280    }
28281
28282    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
28283        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
28284        self.write_keyword("APPROX_QUANTILE");
28285        self.write("(");
28286        self.generate_expression(&e.this)?;
28287        if let Some(quantile) = &e.quantile {
28288            self.write(", ");
28289            self.generate_expression(quantile)?;
28290        }
28291        if let Some(accuracy) = &e.accuracy {
28292            self.write(", ");
28293            self.generate_expression(accuracy)?;
28294        }
28295        if let Some(weight) = &e.weight {
28296            self.write(", ");
28297            self.generate_expression(weight)?;
28298        }
28299        self.write(")");
28300        Ok(())
28301    }
28302
28303    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
28304        // APPROX_QUANTILES(this, expression)
28305        self.write_keyword("APPROX_QUANTILES");
28306        self.write("(");
28307        self.generate_expression(&e.this)?;
28308        if let Some(expression) = &e.expression {
28309            self.write(", ");
28310            self.generate_expression(expression)?;
28311        }
28312        self.write(")");
28313        Ok(())
28314    }
28315
28316    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
28317        // APPROX_TOP_K(this[, expression][, counters])
28318        self.write_keyword("APPROX_TOP_K");
28319        self.write("(");
28320        self.generate_expression(&e.this)?;
28321        if let Some(expression) = &e.expression {
28322            self.write(", ");
28323            self.generate_expression(expression)?;
28324        }
28325        if let Some(counters) = &e.counters {
28326            self.write(", ");
28327            self.generate_expression(counters)?;
28328        }
28329        self.write(")");
28330        Ok(())
28331    }
28332
28333    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
28334        // APPROX_TOP_K_ACCUMULATE(this[, expression])
28335        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
28336        self.write("(");
28337        self.generate_expression(&e.this)?;
28338        if let Some(expression) = &e.expression {
28339            self.write(", ");
28340            self.generate_expression(expression)?;
28341        }
28342        self.write(")");
28343        Ok(())
28344    }
28345
28346    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
28347        // APPROX_TOP_K_COMBINE(this[, expression])
28348        self.write_keyword("APPROX_TOP_K_COMBINE");
28349        self.write("(");
28350        self.generate_expression(&e.this)?;
28351        if let Some(expression) = &e.expression {
28352            self.write(", ");
28353            self.generate_expression(expression)?;
28354        }
28355        self.write(")");
28356        Ok(())
28357    }
28358
28359    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
28360        // APPROX_TOP_K_ESTIMATE(this[, expression])
28361        self.write_keyword("APPROX_TOP_K_ESTIMATE");
28362        self.write("(");
28363        self.generate_expression(&e.this)?;
28364        if let Some(expression) = &e.expression {
28365            self.write(", ");
28366            self.generate_expression(expression)?;
28367        }
28368        self.write(")");
28369        Ok(())
28370    }
28371
28372    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
28373        // APPROX_TOP_SUM(this, expression[, count])
28374        self.write_keyword("APPROX_TOP_SUM");
28375        self.write("(");
28376        self.generate_expression(&e.this)?;
28377        self.write(", ");
28378        self.generate_expression(&e.expression)?;
28379        if let Some(count) = &e.count {
28380            self.write(", ");
28381            self.generate_expression(count)?;
28382        }
28383        self.write(")");
28384        Ok(())
28385    }
28386
28387    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
28388        // ARG_MAX(this, expression[, count])
28389        self.write_keyword("ARG_MAX");
28390        self.write("(");
28391        self.generate_expression(&e.this)?;
28392        self.write(", ");
28393        self.generate_expression(&e.expression)?;
28394        if let Some(count) = &e.count {
28395            self.write(", ");
28396            self.generate_expression(count)?;
28397        }
28398        self.write(")");
28399        Ok(())
28400    }
28401
28402    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
28403        // ARG_MIN(this, expression[, count])
28404        self.write_keyword("ARG_MIN");
28405        self.write("(");
28406        self.generate_expression(&e.this)?;
28407        self.write(", ");
28408        self.generate_expression(&e.expression)?;
28409        if let Some(count) = &e.count {
28410            self.write(", ");
28411            self.generate_expression(count)?;
28412        }
28413        self.write(")");
28414        Ok(())
28415    }
28416
28417    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
28418        // ARRAY_ALL(this, expression)
28419        self.write_keyword("ARRAY_ALL");
28420        self.write("(");
28421        self.generate_expression(&e.this)?;
28422        self.write(", ");
28423        self.generate_expression(&e.expression)?;
28424        self.write(")");
28425        Ok(())
28426    }
28427
28428    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
28429        // ARRAY_ANY(this, expression) - fallback implementation
28430        self.write_keyword("ARRAY_ANY");
28431        self.write("(");
28432        self.generate_expression(&e.this)?;
28433        self.write(", ");
28434        self.generate_expression(&e.expression)?;
28435        self.write(")");
28436        Ok(())
28437    }
28438
28439    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
28440        // ARRAY_CONSTRUCT_COMPACT(expressions...)
28441        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
28442        self.write("(");
28443        for (i, expr) in e.expressions.iter().enumerate() {
28444            if i > 0 {
28445                self.write(", ");
28446            }
28447            self.generate_expression(expr)?;
28448        }
28449        self.write(")");
28450        Ok(())
28451    }
28452
28453    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
28454        // ARRAY_SUM(this[, expression])
28455        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28456            self.write("arraySum");
28457        } else {
28458            self.write_keyword("ARRAY_SUM");
28459        }
28460        self.write("(");
28461        self.generate_expression(&e.this)?;
28462        if let Some(expression) = &e.expression {
28463            self.write(", ");
28464            self.generate_expression(expression)?;
28465        }
28466        self.write(")");
28467        Ok(())
28468    }
28469
28470    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
28471        // Python: return f"{this} AT {index}"
28472        self.generate_expression(&e.this)?;
28473        self.write_space();
28474        self.write_keyword("AT");
28475        self.write_space();
28476        self.generate_expression(&e.expression)?;
28477        Ok(())
28478    }
28479
28480    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
28481        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
28482        self.write_keyword("ATTACH");
28483        if e.exists {
28484            self.write_space();
28485            self.write_keyword("IF NOT EXISTS");
28486        }
28487        self.write_space();
28488        self.generate_expression(&e.this)?;
28489        if !e.expressions.is_empty() {
28490            self.write(" (");
28491            for (i, expr) in e.expressions.iter().enumerate() {
28492                if i > 0 {
28493                    self.write(", ");
28494                }
28495                self.generate_expression(expr)?;
28496            }
28497            self.write(")");
28498        }
28499        Ok(())
28500    }
28501
28502    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
28503        // AttachOption: this [expression]
28504        // Python sqlglot: no equals sign, just space-separated
28505        self.generate_expression(&e.this)?;
28506        if let Some(expression) = &e.expression {
28507            self.write_space();
28508            self.generate_expression(expression)?;
28509        }
28510        Ok(())
28511    }
28512
28513    /// Generate the auto_increment keyword and options for a column definition.
28514    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
28515    /// GENERATED AS IDENTITY, etc.
28516    fn generate_auto_increment_keyword(
28517        &mut self,
28518        col: &crate::expressions::ColumnDef,
28519    ) -> Result<()> {
28520        use crate::dialects::DialectType;
28521        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
28522            self.write_keyword("IDENTITY");
28523            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28524                self.write("(");
28525                if let Some(ref start) = col.auto_increment_start {
28526                    self.generate_expression(start)?;
28527                } else {
28528                    self.write("0");
28529                }
28530                self.write(", ");
28531                if let Some(ref inc) = col.auto_increment_increment {
28532                    self.generate_expression(inc)?;
28533                } else {
28534                    self.write("1");
28535                }
28536                self.write(")");
28537            }
28538        } else if matches!(
28539            self.config.dialect,
28540            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
28541        ) {
28542            self.write_keyword("AUTOINCREMENT");
28543            if let Some(ref start) = col.auto_increment_start {
28544                self.write_space();
28545                self.write_keyword("START");
28546                self.write_space();
28547                self.generate_expression(start)?;
28548            }
28549            if let Some(ref inc) = col.auto_increment_increment {
28550                self.write_space();
28551                self.write_keyword("INCREMENT");
28552                self.write_space();
28553                self.generate_expression(inc)?;
28554            }
28555            if let Some(order) = col.auto_increment_order {
28556                self.write_space();
28557                if order {
28558                    self.write_keyword("ORDER");
28559                } else {
28560                    self.write_keyword("NOORDER");
28561                }
28562            }
28563        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
28564            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
28565            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28566                self.write(" (");
28567                let mut first = true;
28568                if let Some(ref start) = col.auto_increment_start {
28569                    self.write_keyword("START WITH");
28570                    self.write_space();
28571                    self.generate_expression(start)?;
28572                    first = false;
28573                }
28574                if let Some(ref inc) = col.auto_increment_increment {
28575                    if !first {
28576                        self.write_space();
28577                    }
28578                    self.write_keyword("INCREMENT BY");
28579                    self.write_space();
28580                    self.generate_expression(inc)?;
28581                }
28582                self.write(")");
28583            }
28584        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28585            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
28586            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
28587            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28588                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
28589            } else {
28590                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
28591            }
28592            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28593                self.write(" (");
28594                let mut first = true;
28595                if let Some(ref start) = col.auto_increment_start {
28596                    self.write_keyword("START WITH");
28597                    self.write_space();
28598                    self.generate_expression(start)?;
28599                    first = false;
28600                }
28601                if let Some(ref inc) = col.auto_increment_increment {
28602                    if !first {
28603                        self.write_space();
28604                    }
28605                    self.write_keyword("INCREMENT BY");
28606                    self.write_space();
28607                    self.generate_expression(inc)?;
28608                }
28609                self.write(")");
28610            }
28611        } else if matches!(
28612            self.config.dialect,
28613            Some(DialectType::TSQL) | Some(DialectType::Fabric)
28614        ) {
28615            self.write_keyword("IDENTITY");
28616            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28617                self.write("(");
28618                if let Some(ref start) = col.auto_increment_start {
28619                    self.generate_expression(start)?;
28620                } else {
28621                    self.write("0");
28622                }
28623                self.write(", ");
28624                if let Some(ref inc) = col.auto_increment_increment {
28625                    self.generate_expression(inc)?;
28626                } else {
28627                    self.write("1");
28628                }
28629                self.write(")");
28630            }
28631        } else {
28632            self.write_keyword("AUTO_INCREMENT");
28633            if let Some(ref start) = col.auto_increment_start {
28634                self.write_space();
28635                self.write_keyword("START");
28636                self.write_space();
28637                self.generate_expression(start)?;
28638            }
28639            if let Some(ref inc) = col.auto_increment_increment {
28640                self.write_space();
28641                self.write_keyword("INCREMENT");
28642                self.write_space();
28643                self.generate_expression(inc)?;
28644            }
28645            if let Some(order) = col.auto_increment_order {
28646                self.write_space();
28647                if order {
28648                    self.write_keyword("ORDER");
28649                } else {
28650                    self.write_keyword("NOORDER");
28651                }
28652            }
28653        }
28654        Ok(())
28655    }
28656
28657    fn generate_tidb_auto_random(&mut self, auto_random: &TiDBAutoRandom) -> Result<()> {
28658        let is_tidb = matches!(self.config.dialect, Some(DialectType::TiDB));
28659        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
28660
28661        if !is_tidb && !is_mysql {
28662            return self
28663                .write_unsupported_comment("AUTO_RANDOM is only supported by the TiDB dialect");
28664        }
28665        if is_mysql {
28666            self.unsupported("MySQL ignores TiDB AUTO_RANDOM executable comments")?;
28667        }
28668
28669        let use_comment = auto_random.executable_comment || is_mysql;
28670        if use_comment {
28671            self.write("/*T![auto_rand] ");
28672        }
28673        self.write_keyword("AUTO_RANDOM");
28674        if let Some(shard_bits) = auto_random.shard_bits {
28675            self.write("(");
28676            self.write(&shard_bits.to_string());
28677            if let Some(range_bits) = auto_random.range_bits {
28678                self.write(", ");
28679                self.write(&range_bits.to_string());
28680            }
28681            self.write(")");
28682        }
28683        if use_comment {
28684            self.write(" */");
28685        }
28686        Ok(())
28687    }
28688
28689    fn generate_tidb_table_option(&mut self, option: &TiDBTableOption, force: bool) -> Result<()> {
28690        let is_tidb = matches!(self.config.dialect, Some(DialectType::TiDB));
28691        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
28692        if !is_tidb && !is_mysql {
28693            return self.write_unsupported_comment(
28694                "TiDB table options are not supported by the target dialect",
28695            );
28696        }
28697        if is_mysql {
28698            self.unsupported("MySQL ignores TiDB table-option executable comments")?;
28699        }
28700
28701        let use_comment = option.executable_comment || is_mysql;
28702        if use_comment {
28703            match option.kind {
28704                TiDBTableOptionKind::Ttl { .. }
28705                | TiDBTableOptionKind::TtlEnable { .. }
28706                | TiDBTableOptionKind::TtlJobInterval { .. } => self.write("/*T![ttl] "),
28707                TiDBTableOptionKind::PlacementPolicy { .. } => self.write("/*T![placement] "),
28708                _ => self.write("/*T! "),
28709            }
28710        }
28711        if force {
28712            self.write_keyword("FORCE");
28713            self.write_space();
28714        }
28715        match &option.kind {
28716            TiDBTableOptionKind::ShardRowIdBits { bits } => {
28717                self.write_keyword("SHARD_ROW_ID_BITS");
28718                self.write("=");
28719                self.write(&bits.to_string());
28720            }
28721            TiDBTableOptionKind::PreSplitRegions { regions } => {
28722                self.write_keyword("PRE_SPLIT_REGIONS");
28723                self.write("=");
28724                self.write(&regions.to_string());
28725            }
28726            TiDBTableOptionKind::AutoRandomBase { value } => {
28727                self.write_keyword("AUTO_RANDOM_BASE");
28728                self.write("=");
28729                self.write(&value.to_string());
28730            }
28731            TiDBTableOptionKind::PlacementPolicy { policy } => {
28732                self.write_keyword("PLACEMENT POLICY");
28733                self.write("=");
28734                if let Some(policy) = policy {
28735                    self.generate_identifier(policy)?;
28736                } else {
28737                    self.write_keyword("DEFAULT");
28738                }
28739            }
28740            TiDBTableOptionKind::Ttl {
28741                column,
28742                interval,
28743                enabled,
28744            } => {
28745                self.write_keyword("TTL");
28746                self.write("=");
28747                self.generate_identifier(column)?;
28748                self.write(" + ");
28749                self.generate_tidb_ttl_interval(interval)?;
28750                if let Some(enabled) = enabled {
28751                    self.write_space();
28752                    self.write_keyword("TTL_ENABLE");
28753                    self.write("='");
28754                    self.write(if *enabled { "ON" } else { "OFF" });
28755                    self.write("'");
28756                }
28757            }
28758            TiDBTableOptionKind::TtlEnable { enabled } => {
28759                self.write_keyword("TTL_ENABLE");
28760                self.write("='");
28761                self.write(if *enabled { "ON" } else { "OFF" });
28762                self.write("'");
28763            }
28764            TiDBTableOptionKind::TtlJobInterval { interval } => {
28765                self.write_keyword("TTL_JOB_INTERVAL");
28766                self.write("=");
28767                self.generate_string_literal(interval)?;
28768            }
28769        }
28770        if use_comment {
28771            self.write(" */");
28772        }
28773        Ok(())
28774    }
28775
28776    fn generate_tidb_ttl_interval(&mut self, interval: &Interval) -> Result<()> {
28777        self.write_keyword("INTERVAL");
28778        if let Some(value) = &interval.this {
28779            self.write_space();
28780            if let Expression::Literal(literal) = value {
28781                if let Literal::String(value) = literal.as_ref() {
28782                    if value.parse::<f64>().is_ok() {
28783                        self.write(value);
28784                    } else {
28785                        self.generate_string_literal(value)?;
28786                    }
28787                } else {
28788                    self.generate_expression(value)?;
28789                }
28790            } else {
28791                self.generate_expression(value)?;
28792            }
28793        }
28794        if let Some(unit) = &interval.unit {
28795            self.write_space();
28796            self.write_interval_unit_spec(unit)?;
28797        }
28798        Ok(())
28799    }
28800
28801    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
28802        // AUTO_INCREMENT=value
28803        self.write_keyword("AUTO_INCREMENT");
28804        self.write("=");
28805        self.generate_expression(&e.this)?;
28806        Ok(())
28807    }
28808
28809    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
28810        // AUTO_REFRESH=value
28811        self.write_keyword("AUTO_REFRESH");
28812        self.write("=");
28813        self.generate_expression(&e.this)?;
28814        Ok(())
28815    }
28816
28817    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
28818        // BACKUP YES|NO (Redshift syntax uses space, not equals)
28819        self.write_keyword("BACKUP");
28820        self.write_space();
28821        self.generate_expression(&e.this)?;
28822        Ok(())
28823    }
28824
28825    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
28826        // BASE64_DECODE_BINARY(this[, alphabet])
28827        self.write_keyword("BASE64_DECODE_BINARY");
28828        self.write("(");
28829        self.generate_expression(&e.this)?;
28830        if let Some(alphabet) = &e.alphabet {
28831            self.write(", ");
28832            self.generate_expression(alphabet)?;
28833        }
28834        self.write(")");
28835        Ok(())
28836    }
28837
28838    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
28839        // BASE64_DECODE_STRING(this[, alphabet])
28840        self.write_keyword("BASE64_DECODE_STRING");
28841        self.write("(");
28842        self.generate_expression(&e.this)?;
28843        if let Some(alphabet) = &e.alphabet {
28844            self.write(", ");
28845            self.generate_expression(alphabet)?;
28846        }
28847        self.write(")");
28848        Ok(())
28849    }
28850
28851    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
28852        // BASE64_ENCODE(this[, max_line_length][, alphabet])
28853        self.write_keyword("BASE64_ENCODE");
28854        self.write("(");
28855        self.generate_expression(&e.this)?;
28856        if let Some(max_line_length) = &e.max_line_length {
28857            self.write(", ");
28858            self.generate_expression(max_line_length)?;
28859        }
28860        if let Some(alphabet) = &e.alphabet {
28861            self.write(", ");
28862            self.generate_expression(alphabet)?;
28863        }
28864        self.write(")");
28865        Ok(())
28866    }
28867
28868    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
28869        // BLOCKCOMPRESSION=... (complex Teradata property)
28870        self.write_keyword("BLOCKCOMPRESSION");
28871        self.write("=");
28872        if let Some(autotemp) = &e.autotemp {
28873            self.write_keyword("AUTOTEMP");
28874            self.write("(");
28875            self.generate_expression(autotemp)?;
28876            self.write(")");
28877        }
28878        if let Some(always) = &e.always {
28879            self.generate_expression(always)?;
28880        }
28881        if let Some(default) = &e.default {
28882            self.generate_expression(default)?;
28883        }
28884        if let Some(manual) = &e.manual {
28885            self.generate_expression(manual)?;
28886        }
28887        if let Some(never) = &e.never {
28888            self.generate_expression(never)?;
28889        }
28890        Ok(())
28891    }
28892
28893    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
28894        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
28895        self.write("((");
28896        self.generate_expression(&e.this)?;
28897        self.write(") ");
28898        self.write_keyword("AND");
28899        self.write(" (");
28900        self.generate_expression(&e.expression)?;
28901        self.write("))");
28902        Ok(())
28903    }
28904
28905    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
28906        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
28907        self.write("((");
28908        self.generate_expression(&e.this)?;
28909        self.write(") ");
28910        self.write_keyword("OR");
28911        self.write(" (");
28912        self.generate_expression(&e.expression)?;
28913        self.write("))");
28914        Ok(())
28915    }
28916
28917    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
28918        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
28919        self.write_keyword("BUILD");
28920        self.write_space();
28921        self.generate_expression(&e.this)?;
28922        Ok(())
28923    }
28924
28925    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
28926        // Byte string literal like B'...' or X'...'
28927        self.generate_expression(&e.this)?;
28928        Ok(())
28929    }
28930
28931    fn generate_case_specific_column_constraint(
28932        &mut self,
28933        e: &CaseSpecificColumnConstraint,
28934    ) -> Result<()> {
28935        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
28936        if e.not_.is_some() {
28937            self.write_keyword("NOT");
28938            self.write_space();
28939        }
28940        self.write_keyword("CASESPECIFIC");
28941        Ok(())
28942    }
28943
28944    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
28945        // Cast to string type (dialect-specific)
28946        self.write_keyword("CAST");
28947        self.write("(");
28948        self.generate_expression(&e.this)?;
28949        if self.config.dialect == Some(DialectType::ClickHouse) {
28950            // ClickHouse: CAST(expr, 'type_string')
28951            self.write(", ");
28952        } else {
28953            self.write_space();
28954            self.write_keyword("AS");
28955            self.write_space();
28956        }
28957        if let Some(to) = &e.to {
28958            self.generate_expression(to)?;
28959        }
28960        self.write(")");
28961        Ok(())
28962    }
28963
28964    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
28965        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
28966        // Python: f"CHANGES ({information}){at_before}{end}"
28967        self.write_keyword("CHANGES");
28968        self.write(" (");
28969        if let Some(information) = &e.information {
28970            self.write_keyword("INFORMATION");
28971            self.write(" => ");
28972            self.generate_expression(information)?;
28973        }
28974        self.write(")");
28975        // at_before and end are HistoricalData expressions that generate their own keywords
28976        if let Some(at_before) = &e.at_before {
28977            self.write(" ");
28978            self.generate_expression(at_before)?;
28979        }
28980        if let Some(end) = &e.end {
28981            self.write(" ");
28982            self.generate_expression(end)?;
28983        }
28984        Ok(())
28985    }
28986
28987    fn generate_character_set_column_constraint(
28988        &mut self,
28989        e: &CharacterSetColumnConstraint,
28990    ) -> Result<()> {
28991        // CHARACTER SET charset_name
28992        self.write_keyword("CHARACTER SET");
28993        self.write_space();
28994        self.generate_expression(&e.this)?;
28995        Ok(())
28996    }
28997
28998    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
28999        // [DEFAULT] CHARACTER SET=value
29000        if e.default.is_some() {
29001            self.write_keyword("DEFAULT");
29002            self.write_space();
29003        }
29004        self.write_keyword("CHARACTER SET");
29005        self.write("=");
29006        self.generate_expression(&e.this)?;
29007        Ok(())
29008    }
29009
29010    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
29011        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
29012        self.write_keyword("CHECK");
29013        self.write(" (");
29014        self.generate_expression(&e.this)?;
29015        self.write(")");
29016        if e.enforced.is_some() {
29017            self.write_space();
29018            self.write_keyword("ENFORCED");
29019        }
29020        Ok(())
29021    }
29022
29023    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
29024        // Python: return f"ASSUME ({self.sql(e, 'this')})"
29025        self.write_keyword("ASSUME");
29026        self.write(" (");
29027        self.generate_expression(&e.this)?;
29028        self.write(")");
29029        Ok(())
29030    }
29031
29032    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
29033        // CHECK_JSON(this)
29034        self.write_keyword("CHECK_JSON");
29035        self.write("(");
29036        self.generate_expression(&e.this)?;
29037        self.write(")");
29038        Ok(())
29039    }
29040
29041    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
29042        // CHECK_XML(this)
29043        self.write_keyword("CHECK_XML");
29044        self.write("(");
29045        self.generate_expression(&e.this)?;
29046        self.write(")");
29047        Ok(())
29048    }
29049
29050    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
29051        // CHECKSUM=[ON|OFF|DEFAULT]
29052        self.write_keyword("CHECKSUM");
29053        self.write("=");
29054        if e.on.is_some() {
29055            self.write_keyword("ON");
29056        } else if e.default.is_some() {
29057            self.write_keyword("DEFAULT");
29058        } else {
29059            self.write_keyword("OFF");
29060        }
29061        Ok(())
29062    }
29063
29064    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
29065        // Python: return f"{shallow}{keyword} {this}"
29066        if e.shallow.is_some() {
29067            self.write_keyword("SHALLOW");
29068            self.write_space();
29069        }
29070        if e.copy.is_some() {
29071            self.write_keyword("COPY");
29072        } else {
29073            self.write_keyword("CLONE");
29074        }
29075        self.write_space();
29076        self.generate_expression(&e.this)?;
29077        Ok(())
29078    }
29079
29080    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
29081        // CLUSTER BY (expressions)
29082        self.write_keyword("CLUSTER BY");
29083        self.write(" (");
29084        for (i, ord) in e.expressions.iter().enumerate() {
29085            if i > 0 {
29086                self.write(", ");
29087            }
29088            self.generate_ordered(ord)?;
29089        }
29090        self.write(")");
29091        Ok(())
29092    }
29093
29094    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
29095        // BigQuery table property: CLUSTER BY col1, col2
29096        self.write_keyword("CLUSTER BY");
29097        self.write_space();
29098        for (i, col) in e.columns.iter().enumerate() {
29099            if i > 0 {
29100                self.write(", ");
29101            }
29102            self.generate_identifier(col)?;
29103        }
29104        Ok(())
29105    }
29106
29107    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
29108        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
29109        self.write_keyword("CLUSTERED BY");
29110        self.write(" (");
29111        for (i, expr) in e.expressions.iter().enumerate() {
29112            if i > 0 {
29113                self.write(", ");
29114            }
29115            self.generate_expression(expr)?;
29116        }
29117        self.write(")");
29118        if let Some(sorted_by) = &e.sorted_by {
29119            self.write_space();
29120            self.write_keyword("SORTED BY");
29121            self.write(" (");
29122            // Unwrap Tuple to avoid double parentheses
29123            if let Expression::Tuple(t) = sorted_by.as_ref() {
29124                for (i, expr) in t.expressions.iter().enumerate() {
29125                    if i > 0 {
29126                        self.write(", ");
29127                    }
29128                    self.generate_expression(expr)?;
29129                }
29130            } else {
29131                self.generate_expression(sorted_by)?;
29132            }
29133            self.write(")");
29134        }
29135        if let Some(buckets) = &e.buckets {
29136            self.write_space();
29137            self.write_keyword("INTO");
29138            self.write_space();
29139            self.generate_expression(buckets)?;
29140            self.write_space();
29141            self.write_keyword("BUCKETS");
29142        }
29143        Ok(())
29144    }
29145
29146    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
29147        // [DEFAULT] COLLATE [=] value
29148        // BigQuery uses space: DEFAULT COLLATE 'en'
29149        // Others use equals: COLLATE='en'
29150        if e.default.is_some() {
29151            self.write_keyword("DEFAULT");
29152            self.write_space();
29153        }
29154        self.write_keyword("COLLATE");
29155        // BigQuery uses space between COLLATE and value
29156        match self.config.dialect {
29157            Some(DialectType::BigQuery) => self.write_space(),
29158            _ => self.write("="),
29159        }
29160        self.generate_expression(&e.this)?;
29161        Ok(())
29162    }
29163
29164    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
29165        // ColumnConstraint is an enum
29166        match e {
29167            ColumnConstraint::NotNull => {
29168                self.write_keyword("NOT NULL");
29169            }
29170            ColumnConstraint::Null => {
29171                self.write_keyword("NULL");
29172            }
29173            ColumnConstraint::Unique => {
29174                self.write_keyword("UNIQUE");
29175            }
29176            ColumnConstraint::PrimaryKey => {
29177                self.write_keyword("PRIMARY KEY");
29178            }
29179            ColumnConstraint::Default(expr) => {
29180                self.write_keyword("DEFAULT");
29181                self.write_space();
29182                self.generate_expression(expr)?;
29183            }
29184            ColumnConstraint::Check(expr) => {
29185                self.write_keyword("CHECK");
29186                self.write(" (");
29187                self.generate_expression(expr)?;
29188                self.write(")");
29189            }
29190            ColumnConstraint::References(fk_ref) => {
29191                if fk_ref.has_foreign_key_keywords {
29192                    self.write_keyword("FOREIGN KEY");
29193                    self.write_space();
29194                }
29195                self.write_keyword("REFERENCES");
29196                self.write_space();
29197                self.generate_table(&fk_ref.table)?;
29198                if !fk_ref.columns.is_empty() {
29199                    self.write(" (");
29200                    for (i, col) in fk_ref.columns.iter().enumerate() {
29201                        if i > 0 {
29202                            self.write(", ");
29203                        }
29204                        self.generate_identifier(col)?;
29205                    }
29206                    self.write(")");
29207                }
29208            }
29209            ColumnConstraint::GeneratedAsIdentity(gen) => {
29210                self.write_keyword("GENERATED");
29211                self.write_space();
29212                if gen.always {
29213                    self.write_keyword("ALWAYS");
29214                } else {
29215                    self.write_keyword("BY DEFAULT");
29216                    if gen.on_null {
29217                        self.write_space();
29218                        self.write_keyword("ON NULL");
29219                    }
29220                }
29221                self.write_space();
29222                self.write_keyword("AS IDENTITY");
29223            }
29224            ColumnConstraint::Collate(collation) => {
29225                self.write_keyword("COLLATE");
29226                self.write_space();
29227                self.generate_identifier(collation)?;
29228            }
29229            ColumnConstraint::Comment(comment) => {
29230                self.write_keyword("COMMENT");
29231                self.write(" '");
29232                self.write(comment);
29233                self.write("'");
29234            }
29235            ColumnConstraint::ComputedColumn(cc) => {
29236                self.generate_computed_column_inline(cc)?;
29237            }
29238            ColumnConstraint::GeneratedAsRow(gar) => {
29239                self.generate_generated_as_row_inline(gar)?;
29240            }
29241            ColumnConstraint::Tags(tags) => {
29242                self.write_keyword("TAG");
29243                self.write(" (");
29244                for (i, expr) in tags.expressions.iter().enumerate() {
29245                    if i > 0 {
29246                        self.write(", ");
29247                    }
29248                    self.generate_expression(expr)?;
29249                }
29250                self.write(")");
29251            }
29252            ColumnConstraint::Path(path_expr) => {
29253                self.write_keyword("PATH");
29254                self.write_space();
29255                self.generate_expression(path_expr)?;
29256            }
29257        }
29258        Ok(())
29259    }
29260
29261    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
29262        // ColumnPosition is an enum
29263        match e {
29264            ColumnPosition::First => {
29265                self.write_keyword("FIRST");
29266            }
29267            ColumnPosition::After(ident) => {
29268                self.write_keyword("AFTER");
29269                self.write_space();
29270                self.generate_identifier(ident)?;
29271            }
29272        }
29273        Ok(())
29274    }
29275
29276    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
29277        // column(prefix)
29278        self.generate_expression(&e.this)?;
29279        self.write("(");
29280        self.generate_expression(&e.expression)?;
29281        self.write(")");
29282        Ok(())
29283    }
29284
29285    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
29286        // If unpack is true, this came from * COLUMNS(pattern)
29287        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
29288        if let Some(ref unpack) = e.unpack {
29289            if let Expression::Boolean(b) = unpack.as_ref() {
29290                if b.value {
29291                    self.write("*");
29292                }
29293            }
29294        }
29295        self.write_keyword("COLUMNS");
29296        self.write("(");
29297        self.generate_expression(&e.this)?;
29298        self.write(")");
29299        Ok(())
29300    }
29301
29302    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
29303        // Combined aggregate: FUNC(args) combined
29304        self.generate_expression(&e.this)?;
29305        self.write("(");
29306        for (i, expr) in e.expressions.iter().enumerate() {
29307            if i > 0 {
29308                self.write(", ");
29309            }
29310            self.generate_expression(expr)?;
29311        }
29312        self.write(")");
29313        Ok(())
29314    }
29315
29316    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
29317        // Combined parameterized aggregate: FUNC(params)(expressions)
29318        self.generate_expression(&e.this)?;
29319        self.write("(");
29320        for (i, param) in e.params.iter().enumerate() {
29321            if i > 0 {
29322                self.write(", ");
29323            }
29324            self.generate_expression(param)?;
29325        }
29326        self.write(")(");
29327        for (i, expr) in e.expressions.iter().enumerate() {
29328            if i > 0 {
29329                self.write(", ");
29330            }
29331            self.generate_expression(expr)?;
29332        }
29333        self.write(")");
29334        Ok(())
29335    }
29336
29337    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
29338        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
29339        self.write_keyword("COMMIT");
29340
29341        // TSQL always uses COMMIT TRANSACTION
29342        if e.this.is_none()
29343            && matches!(
29344                self.config.dialect,
29345                Some(DialectType::TSQL) | Some(DialectType::Fabric)
29346            )
29347        {
29348            self.write_space();
29349            self.write_keyword("TRANSACTION");
29350        }
29351
29352        // Check if this has TRANSACTION keyword or transaction name
29353        if let Some(this) = &e.this {
29354            // Check if it's just the "TRANSACTION" marker or an actual transaction name
29355            let is_transaction_marker = matches!(
29356                this.as_ref(),
29357                Expression::Identifier(id) if id.name == "TRANSACTION"
29358            );
29359
29360            self.write_space();
29361            self.write_keyword("TRANSACTION");
29362
29363            // If it's a real transaction name, output it
29364            if !is_transaction_marker {
29365                self.write_space();
29366                self.generate_expression(this)?;
29367            }
29368        }
29369
29370        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
29371        if let Some(durability) = &e.durability {
29372            self.write_space();
29373            self.write_keyword("WITH");
29374            self.write(" (");
29375            self.write_keyword("DELAYED_DURABILITY");
29376            self.write(" = ");
29377            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
29378                self.write_keyword("ON");
29379            } else {
29380                self.write_keyword("OFF");
29381            }
29382            self.write(")");
29383        }
29384
29385        // Output AND [NO] CHAIN
29386        if let Some(chain) = &e.chain {
29387            self.write_space();
29388            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
29389                self.write_keyword("AND NO CHAIN");
29390            } else {
29391                self.write_keyword("AND CHAIN");
29392            }
29393        }
29394        Ok(())
29395    }
29396
29397    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
29398        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
29399        self.write("[");
29400        self.generate_expression(&e.this)?;
29401        self.write_space();
29402        self.write_keyword("FOR");
29403        self.write_space();
29404        self.generate_expression(&e.expression)?;
29405        // Handle optional position variable (for enumerate-like syntax)
29406        if let Some(pos) = &e.position {
29407            self.write(", ");
29408            self.generate_expression(pos)?;
29409        }
29410        if let Some(iterator) = &e.iterator {
29411            self.write_space();
29412            self.write_keyword("IN");
29413            self.write_space();
29414            self.generate_expression(iterator)?;
29415        }
29416        if let Some(condition) = &e.condition {
29417            self.write_space();
29418            self.write_keyword("IF");
29419            self.write_space();
29420            self.generate_expression(condition)?;
29421        }
29422        self.write("]");
29423        Ok(())
29424    }
29425
29426    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
29427        // COMPRESS(this[, method])
29428        self.write_keyword("COMPRESS");
29429        self.write("(");
29430        self.generate_expression(&e.this)?;
29431        if let Some(method) = &e.method {
29432            self.write(", '");
29433            self.write(method);
29434            self.write("'");
29435        }
29436        self.write(")");
29437        Ok(())
29438    }
29439
29440    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
29441        // Python: return f"COMPRESS {this}"
29442        self.write_keyword("COMPRESS");
29443        if let Some(this) = &e.this {
29444            self.write_space();
29445            self.generate_expression(this)?;
29446        }
29447        Ok(())
29448    }
29449
29450    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
29451        // Python: return f"AS {this}{persisted}"
29452        self.write_keyword("AS");
29453        self.write_space();
29454        self.generate_expression(&e.this)?;
29455        if e.not_null.is_some() {
29456            self.write_space();
29457            self.write_keyword("PERSISTED NOT NULL");
29458        } else if e.persisted.is_some() {
29459            self.write_space();
29460            self.write_keyword("PERSISTED");
29461        }
29462        Ok(())
29463    }
29464
29465    /// Generate a ComputedColumn constraint inline within a column definition.
29466    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
29467    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
29468    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
29469        let computed_expr = if matches!(
29470            self.config.dialect,
29471            Some(DialectType::TSQL) | Some(DialectType::Fabric)
29472        ) {
29473            match &*cc.expression {
29474                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
29475                {
29476                    let wrapped = Expression::Cast(Box::new(Cast {
29477                        this: y.this.clone(),
29478                        to: DataType::Date,
29479                        trailing_comments: Vec::new(),
29480                        double_colon_syntax: false,
29481                        format: None,
29482                        default: None,
29483                        inferred_type: None,
29484                    }));
29485                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
29486                }
29487                Expression::Function(f)
29488                    if f.name.eq_ignore_ascii_case("YEAR")
29489                        && f.args.len() == 1
29490                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
29491                {
29492                    let wrapped = Expression::Cast(Box::new(Cast {
29493                        this: f.args[0].clone(),
29494                        to: DataType::Date,
29495                        trailing_comments: Vec::new(),
29496                        double_colon_syntax: false,
29497                        format: None,
29498                        default: None,
29499                        inferred_type: None,
29500                    }));
29501                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
29502                }
29503                _ => *cc.expression.clone(),
29504            }
29505        } else {
29506            *cc.expression.clone()
29507        };
29508
29509        match cc.persistence_kind.as_deref() {
29510            Some("STORED") | Some("VIRTUAL") => {
29511                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
29512                self.write_keyword("GENERATED ALWAYS AS");
29513                self.write(" (");
29514                self.generate_expression(&computed_expr)?;
29515                self.write(")");
29516                self.write_space();
29517                if cc.persisted {
29518                    self.write_keyword("STORED");
29519                } else {
29520                    self.write_keyword("VIRTUAL");
29521                }
29522            }
29523            Some("PERSISTED") => {
29524                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
29525                self.write_keyword("AS");
29526                self.write(" (");
29527                self.generate_expression(&computed_expr)?;
29528                self.write(")");
29529                self.write_space();
29530                self.write_keyword("PERSISTED");
29531                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
29532                if let Some(ref dt) = cc.data_type {
29533                    self.write_space();
29534                    self.generate_data_type(dt)?;
29535                }
29536                if cc.not_null {
29537                    self.write_space();
29538                    self.write_keyword("NOT NULL");
29539                }
29540            }
29541            _ => {
29542                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
29543                // TSQL computed column without PERSISTED: AS (expr)
29544                if matches!(
29545                    self.config.dialect,
29546                    Some(DialectType::Spark)
29547                        | Some(DialectType::Databricks)
29548                        | Some(DialectType::Hive)
29549                ) {
29550                    self.write_keyword("GENERATED ALWAYS AS");
29551                    self.write(" (");
29552                    self.generate_expression(&computed_expr)?;
29553                    self.write(")");
29554                } else if matches!(
29555                    self.config.dialect,
29556                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
29557                ) {
29558                    self.write_keyword("AS");
29559                    let omit_parens = matches!(computed_expr, Expression::Year(_))
29560                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
29561                    if omit_parens {
29562                        self.write_space();
29563                        self.generate_expression(&computed_expr)?;
29564                    } else {
29565                        self.write(" (");
29566                        self.generate_expression(&computed_expr)?;
29567                        self.write(")");
29568                    }
29569                } else {
29570                    self.write_keyword("AS");
29571                    self.write(" (");
29572                    self.generate_expression(&computed_expr)?;
29573                    self.write(")");
29574                }
29575            }
29576        }
29577        Ok(())
29578    }
29579
29580    /// Generate a GeneratedAsRow constraint inline within a column definition.
29581    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29582    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
29583        self.write_keyword("GENERATED ALWAYS AS ROW ");
29584        if gar.start {
29585            self.write_keyword("START");
29586        } else {
29587            self.write_keyword("END");
29588        }
29589        if gar.hidden {
29590            self.write_space();
29591            self.write_keyword("HIDDEN");
29592        }
29593        Ok(())
29594    }
29595
29596    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
29597    fn generate_system_versioning_content(
29598        &mut self,
29599        e: &WithSystemVersioningProperty,
29600    ) -> Result<()> {
29601        let mut parts = Vec::new();
29602
29603        if let Some(this) = &e.this {
29604            let mut s = String::from("HISTORY_TABLE=");
29605            let mut gen = Generator::with_arc_config(self.config.clone());
29606            gen.generate_expression(this)?;
29607            s.push_str(&gen.output);
29608            parts.push(s);
29609        }
29610
29611        if let Some(data_consistency) = &e.data_consistency {
29612            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
29613            let mut gen = Generator::with_arc_config(self.config.clone());
29614            gen.generate_expression(data_consistency)?;
29615            s.push_str(&gen.output);
29616            parts.push(s);
29617        }
29618
29619        if let Some(retention_period) = &e.retention_period {
29620            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
29621            let mut gen = Generator::with_arc_config(self.config.clone());
29622            gen.generate_expression(retention_period)?;
29623            s.push_str(&gen.output);
29624            parts.push(s);
29625        }
29626
29627        self.write_keyword("SYSTEM_VERSIONING");
29628        self.write("=");
29629
29630        if !parts.is_empty() {
29631            self.write_keyword("ON");
29632            self.write("(");
29633            self.write(&parts.join(", "));
29634            self.write(")");
29635        } else if e.on.is_some() {
29636            self.write_keyword("ON");
29637        } else {
29638            self.write_keyword("OFF");
29639        }
29640
29641        Ok(())
29642    }
29643
29644    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
29645        // Conditional INSERT for multi-table inserts
29646        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
29647        if e.else_.is_some() {
29648            self.write_keyword("ELSE");
29649            self.write_space();
29650        } else if let Some(expression) = &e.expression {
29651            self.write_keyword("WHEN");
29652            self.write_space();
29653            self.generate_expression(expression)?;
29654            self.write_space();
29655            self.write_keyword("THEN");
29656            self.write_space();
29657        }
29658
29659        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
29660        // without the "INSERT " prefix
29661        if let Expression::Insert(insert) = e.this.as_ref() {
29662            self.write_keyword("INTO");
29663            self.write_space();
29664            self.generate_table(&insert.table)?;
29665
29666            // Optional column list
29667            if !insert.columns.is_empty() {
29668                self.write(" (");
29669                for (i, col) in insert.columns.iter().enumerate() {
29670                    if i > 0 {
29671                        self.write(", ");
29672                    }
29673                    self.generate_identifier(col)?;
29674                }
29675                self.write(")");
29676            }
29677
29678            // Optional VALUES clause
29679            if !insert.values.is_empty() {
29680                self.write_space();
29681                self.write_keyword("VALUES");
29682                for (row_idx, row) in insert.values.iter().enumerate() {
29683                    if row_idx > 0 {
29684                        self.write(", ");
29685                    }
29686                    self.write(" (");
29687                    for (i, val) in row.iter().enumerate() {
29688                        if i > 0 {
29689                            self.write(", ");
29690                        }
29691                        self.generate_expression(val)?;
29692                    }
29693                    self.write(")");
29694                }
29695            }
29696        } else {
29697            // Fallback for non-Insert expressions
29698            self.generate_expression(&e.this)?;
29699        }
29700        Ok(())
29701    }
29702
29703    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
29704        // Python: return f"CONSTRAINT {this} {expressions}"
29705        self.write_keyword("CONSTRAINT");
29706        self.write_space();
29707        self.generate_expression(&e.this)?;
29708        if !e.expressions.is_empty() {
29709            self.write_space();
29710            for (i, expr) in e.expressions.iter().enumerate() {
29711                if i > 0 {
29712                    self.write_space();
29713                }
29714                self.generate_expression(expr)?;
29715            }
29716        }
29717        Ok(())
29718    }
29719
29720    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
29721        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
29722        self.write_keyword("CONVERT_TIMEZONE");
29723        self.write("(");
29724        let mut first = true;
29725        if let Some(source_tz) = &e.source_tz {
29726            self.generate_expression(source_tz)?;
29727            first = false;
29728        }
29729        if let Some(target_tz) = &e.target_tz {
29730            if !first {
29731                self.write(", ");
29732            }
29733            self.generate_expression(target_tz)?;
29734            first = false;
29735        }
29736        if let Some(timestamp) = &e.timestamp {
29737            if !first {
29738                self.write(", ");
29739            }
29740            self.generate_expression(timestamp)?;
29741        }
29742        self.write(")");
29743        Ok(())
29744    }
29745
29746    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
29747        // CONVERT(this USING dest)
29748        self.write_keyword("CONVERT");
29749        self.write("(");
29750        self.generate_expression(&e.this)?;
29751        if let Some(dest) = &e.dest {
29752            self.write_space();
29753            self.write_keyword("USING");
29754            self.write_space();
29755            self.generate_expression(dest)?;
29756        }
29757        self.write(")");
29758        Ok(())
29759    }
29760
29761    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
29762        self.write_keyword("COPY");
29763        if e.is_into {
29764            self.write_space();
29765            self.write_keyword("INTO");
29766        }
29767        self.write_space();
29768
29769        // Generate target table or query (or stage for COPY INTO @stage)
29770        if let Expression::Literal(lit) = &e.this {
29771            if let Literal::String(s) = lit.as_ref() {
29772                if s.starts_with('@') {
29773                    self.write(s);
29774                } else {
29775                    self.generate_expression(&e.this)?;
29776                }
29777            }
29778        } else {
29779            self.generate_expression(&e.this)?;
29780        }
29781
29782        // FROM or TO based on kind
29783        if e.kind {
29784            // kind=true means FROM (loading into table)
29785            if self.config.pretty {
29786                self.write_newline();
29787            } else {
29788                self.write_space();
29789            }
29790            self.write_keyword("FROM");
29791            self.write_space();
29792        } else if !e.files.is_empty() {
29793            // kind=false means TO (exporting)
29794            if self.config.pretty {
29795                self.write_newline();
29796            } else {
29797                self.write_space();
29798            }
29799            self.write_keyword("TO");
29800            self.write_space();
29801        }
29802
29803        // Generate source/destination files
29804        for (i, file) in e.files.iter().enumerate() {
29805            if i > 0 {
29806                self.write_space();
29807            }
29808            // For stage references (strings starting with @), output without quotes
29809            if let Expression::Literal(lit) = file {
29810                if let Literal::String(s) = lit.as_ref() {
29811                    if s.starts_with('@') {
29812                        self.write(s);
29813                    } else {
29814                        self.generate_expression(file)?;
29815                    }
29816                }
29817            } else if let Expression::Identifier(id) = file {
29818                // Backtick-quoted file path (Databricks style: `s3://link`)
29819                if id.quoted {
29820                    self.write("`");
29821                    self.write(&id.name);
29822                    self.write("`");
29823                } else {
29824                    self.generate_expression(file)?;
29825                }
29826            } else {
29827                self.generate_expression(file)?;
29828            }
29829        }
29830
29831        // Generate credentials if present (Snowflake style - not wrapped in WITH)
29832        if !e.with_wrapped {
29833            if let Some(ref creds) = e.credentials {
29834                if let Some(ref storage) = creds.storage {
29835                    if self.config.pretty {
29836                        self.write_newline();
29837                    } else {
29838                        self.write_space();
29839                    }
29840                    self.write_keyword("STORAGE_INTEGRATION");
29841                    self.write(" = ");
29842                    self.write(storage);
29843                }
29844                if creds.credentials.is_empty() {
29845                    // Empty credentials: CREDENTIALS = ()
29846                    if self.config.pretty {
29847                        self.write_newline();
29848                    } else {
29849                        self.write_space();
29850                    }
29851                    self.write_keyword("CREDENTIALS");
29852                    self.write(" = ()");
29853                } else {
29854                    if self.config.pretty {
29855                        self.write_newline();
29856                    } else {
29857                        self.write_space();
29858                    }
29859                    self.write_keyword("CREDENTIALS");
29860                    // Check if this is Redshift-style (single value with empty key)
29861                    // vs Snowflake-style (multiple key=value pairs)
29862                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
29863                        // Redshift style: CREDENTIALS 'value'
29864                        self.write(" '");
29865                        self.write(&creds.credentials[0].1);
29866                        self.write("'");
29867                    } else {
29868                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
29869                        self.write(" = (");
29870                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
29871                            if i > 0 {
29872                                self.write_space();
29873                            }
29874                            self.write(k);
29875                            self.write("='");
29876                            self.write(v);
29877                            self.write("'");
29878                        }
29879                        self.write(")");
29880                    }
29881                }
29882                if let Some(ref encryption) = creds.encryption {
29883                    self.write_space();
29884                    self.write_keyword("ENCRYPTION");
29885                    self.write(" = ");
29886                    self.write(encryption);
29887                }
29888            }
29889        }
29890
29891        // Generate parameters
29892        if !e.params.is_empty() {
29893            if e.with_wrapped {
29894                // DuckDB/PostgreSQL/TSQL WITH (...) format
29895                self.write_space();
29896                self.write_keyword("WITH");
29897                self.write(" (");
29898                for (i, param) in e.params.iter().enumerate() {
29899                    if i > 0 {
29900                        self.write(", ");
29901                    }
29902                    self.generate_copy_param_with_format(param)?;
29903                }
29904                self.write(")");
29905            } else {
29906                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
29907                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
29908                // For Snowflake: KEY = VALUE
29909                for param in &e.params {
29910                    if self.config.pretty {
29911                        self.write_newline();
29912                    } else {
29913                        self.write_space();
29914                    }
29915                    // Preserve original case of parameter name (important for Redshift COPY options)
29916                    self.write(&param.name);
29917                    if let Some(ref value) = param.value {
29918                        // Use = only if it was present in the original (param.eq)
29919                        if param.eq {
29920                            self.write(" = ");
29921                        } else {
29922                            self.write(" ");
29923                        }
29924                        if !param.values.is_empty() {
29925                            self.write("(");
29926                            for (i, v) in param.values.iter().enumerate() {
29927                                if i > 0 {
29928                                    self.write_space();
29929                                }
29930                                self.generate_copy_nested_param(v)?;
29931                            }
29932                            self.write(")");
29933                        } else {
29934                            // For COPY parameter values, output identifiers without quoting
29935                            self.generate_copy_param_value(value)?;
29936                        }
29937                    } else if !param.values.is_empty() {
29938                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
29939                        if param.eq {
29940                            self.write(" = (");
29941                        } else {
29942                            self.write(" (");
29943                        }
29944                        // Determine separator for values inside parentheses:
29945                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
29946                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
29947                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
29948                        let is_key_value_pairs = param
29949                            .values
29950                            .first()
29951                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
29952                        let sep = if is_key_value_pairs && param.eq {
29953                            " "
29954                        } else {
29955                            ", "
29956                        };
29957                        for (i, v) in param.values.iter().enumerate() {
29958                            if i > 0 {
29959                                self.write(sep);
29960                            }
29961                            self.generate_copy_nested_param(v)?;
29962                        }
29963                        self.write(")");
29964                    }
29965                }
29966            }
29967        }
29968
29969        Ok(())
29970    }
29971
29972    /// Generate a COPY parameter in WITH (...) format
29973    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
29974    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
29975        self.write_keyword(&param.name);
29976        if !param.values.is_empty() {
29977            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
29978            self.write(" = (");
29979            for (i, v) in param.values.iter().enumerate() {
29980                if i > 0 {
29981                    self.write(", ");
29982                }
29983                self.generate_copy_nested_param(v)?;
29984            }
29985            self.write(")");
29986        } else if let Some(ref value) = param.value {
29987            if param.eq {
29988                self.write(" = ");
29989            } else {
29990                self.write(" ");
29991            }
29992            self.generate_expression(value)?;
29993        }
29994        Ok(())
29995    }
29996
29997    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
29998    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
29999        match expr {
30000            Expression::Eq(eq) => {
30001                // Generate key
30002                match &eq.left {
30003                    Expression::Column(c) => self.write(&c.name.name),
30004                    _ => self.generate_expression(&eq.left)?,
30005                }
30006                self.write("=");
30007                // Generate value
30008                match &eq.right {
30009                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30010                        let Literal::String(s) = lit.as_ref() else {
30011                            unreachable!()
30012                        };
30013                        self.write("'");
30014                        self.write(s);
30015                        self.write("'");
30016                    }
30017                    Expression::Tuple(t) => {
30018                        // For lists like NULL_IF=('', 'str1')
30019                        self.write("(");
30020                        if self.config.pretty {
30021                            self.write_newline();
30022                            self.indent_level += 1;
30023                            for (i, item) in t.expressions.iter().enumerate() {
30024                                if i > 0 {
30025                                    self.write(", ");
30026                                }
30027                                self.write_indent();
30028                                self.generate_expression(item)?;
30029                            }
30030                            self.write_newline();
30031                            self.indent_level -= 1;
30032                        } else {
30033                            for (i, item) in t.expressions.iter().enumerate() {
30034                                if i > 0 {
30035                                    self.write(", ");
30036                                }
30037                                self.generate_expression(item)?;
30038                            }
30039                        }
30040                        self.write(")");
30041                    }
30042                    _ => self.generate_expression(&eq.right)?,
30043                }
30044                Ok(())
30045            }
30046            Expression::Column(c) => {
30047                // Standalone keyword like COMPRESSION
30048                self.write(&c.name.name);
30049                Ok(())
30050            }
30051            _ => self.generate_expression(expr),
30052        }
30053    }
30054
30055    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
30056    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
30057    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
30058        match expr {
30059            Expression::Column(c) => {
30060                // Output identifier, preserving quotes if originally quoted
30061                if c.name.quoted {
30062                    self.write("\"");
30063                    self.write(&c.name.name);
30064                    self.write("\"");
30065                } else {
30066                    self.write(&c.name.name);
30067                }
30068                Ok(())
30069            }
30070            Expression::Identifier(id) => {
30071                // Output identifier, preserving quotes if originally quoted
30072                if id.quoted {
30073                    self.write("\"");
30074                    self.write(&id.name);
30075                    self.write("\"");
30076                } else {
30077                    self.write(&id.name);
30078                }
30079                Ok(())
30080            }
30081            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30082                let Literal::String(s) = lit.as_ref() else {
30083                    unreachable!()
30084                };
30085                // Output string with quotes
30086                self.write("'");
30087                self.write(s);
30088                self.write("'");
30089                Ok(())
30090            }
30091            _ => self.generate_expression(expr),
30092        }
30093    }
30094
30095    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
30096        self.write_keyword(&e.name);
30097        if let Some(ref value) = e.value {
30098            if e.eq {
30099                self.write(" = ");
30100            } else {
30101                self.write(" ");
30102            }
30103            self.generate_expression(value)?;
30104        }
30105        if !e.values.is_empty() {
30106            if e.eq {
30107                self.write(" = ");
30108            } else {
30109                self.write(" ");
30110            }
30111            self.write("(");
30112            for (i, v) in e.values.iter().enumerate() {
30113                if i > 0 {
30114                    self.write(", ");
30115                }
30116                self.generate_expression(v)?;
30117            }
30118            self.write(")");
30119        }
30120        Ok(())
30121    }
30122
30123    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
30124        // CORR(this, expression)
30125        self.write_keyword("CORR");
30126        self.write("(");
30127        self.generate_expression(&e.this)?;
30128        self.write(", ");
30129        self.generate_expression(&e.expression)?;
30130        self.write(")");
30131        Ok(())
30132    }
30133
30134    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
30135        // COSINE_DISTANCE(this, expression)
30136        self.write_keyword("COSINE_DISTANCE");
30137        self.write("(");
30138        self.generate_expression(&e.this)?;
30139        self.write(", ");
30140        self.generate_expression(&e.expression)?;
30141        self.write(")");
30142        Ok(())
30143    }
30144
30145    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
30146        // COVAR_POP(this, expression)
30147        self.write_keyword("COVAR_POP");
30148        self.write("(");
30149        self.generate_expression(&e.this)?;
30150        self.write(", ");
30151        self.generate_expression(&e.expression)?;
30152        self.write(")");
30153        Ok(())
30154    }
30155
30156    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
30157        // COVAR_SAMP(this, expression)
30158        self.write_keyword("COVAR_SAMP");
30159        self.write("(");
30160        self.generate_expression(&e.this)?;
30161        self.write(", ");
30162        self.generate_expression(&e.expression)?;
30163        self.write(")");
30164        Ok(())
30165    }
30166
30167    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
30168        // CREDENTIALS (key1='value1', key2='value2')
30169        self.write_keyword("CREDENTIALS");
30170        self.write(" (");
30171        for (i, (key, value)) in e.credentials.iter().enumerate() {
30172            if i > 0 {
30173                self.write(", ");
30174            }
30175            self.write(key);
30176            self.write("='");
30177            self.write(value);
30178            self.write("'");
30179        }
30180        self.write(")");
30181        Ok(())
30182    }
30183
30184    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
30185        // CREDENTIALS=(expressions)
30186        self.write_keyword("CREDENTIALS");
30187        self.write("=(");
30188        for (i, expr) in e.expressions.iter().enumerate() {
30189            if i > 0 {
30190                self.write(", ");
30191            }
30192            self.generate_expression(expr)?;
30193        }
30194        self.write(")");
30195        Ok(())
30196    }
30197
30198    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
30199        use crate::dialects::DialectType;
30200
30201        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
30202        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
30203        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
30204            self.generate_expression(&e.this)?;
30205            self.write_space();
30206            self.write_keyword("AS");
30207            self.write_space();
30208            self.generate_identifier(&e.alias)?;
30209            return Ok(());
30210        }
30211        self.write(&e.alias.name);
30212
30213        // BigQuery doesn't support column aliases in CTE definitions
30214        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
30215
30216        if !e.columns.is_empty() && !skip_cte_columns {
30217            self.write("(");
30218            for (i, col) in e.columns.iter().enumerate() {
30219                if i > 0 {
30220                    self.write(", ");
30221                }
30222                self.write(&col.name);
30223            }
30224            self.write(")");
30225        }
30226        // USING KEY (columns) for DuckDB recursive CTEs
30227        if !e.key_expressions.is_empty() {
30228            self.write_space();
30229            self.write_keyword("USING KEY");
30230            self.write(" (");
30231            for (i, key) in e.key_expressions.iter().enumerate() {
30232                if i > 0 {
30233                    self.write(", ");
30234                }
30235                self.write(&key.name);
30236            }
30237            self.write(")");
30238        }
30239        self.write_space();
30240        self.write_keyword("AS");
30241        self.write_space();
30242        if let Some(materialized) = e.materialized {
30243            if materialized {
30244                self.write_keyword("MATERIALIZED");
30245            } else {
30246                self.write_keyword("NOT MATERIALIZED");
30247            }
30248            self.write_space();
30249        }
30250        self.write("(");
30251        self.generate_expression(&e.this)?;
30252        self.write(")");
30253        Ok(())
30254    }
30255
30256    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
30257        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
30258        if e.expressions.is_empty() {
30259            self.write_keyword("WITH CUBE");
30260        } else {
30261            self.write_keyword("CUBE");
30262            self.write("(");
30263            for (i, expr) in e.expressions.iter().enumerate() {
30264                if i > 0 {
30265                    self.write(", ");
30266                }
30267                self.generate_expression(expr)?;
30268            }
30269            self.write(")");
30270        }
30271        Ok(())
30272    }
30273
30274    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
30275        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
30276        self.write_keyword("CURRENT_DATETIME");
30277        if let Some(this) = &e.this {
30278            self.write("(");
30279            self.generate_expression(this)?;
30280            self.write(")");
30281        }
30282        Ok(())
30283    }
30284
30285    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
30286        // CURRENT_SCHEMA - no arguments
30287        self.write_keyword("CURRENT_SCHEMA");
30288        Ok(())
30289    }
30290
30291    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
30292        // CURRENT_SCHEMAS(include_implicit)
30293        self.write_keyword("CURRENT_SCHEMAS");
30294        self.write("(");
30295        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
30296        if !matches!(
30297            self.config.dialect,
30298            Some(crate::dialects::DialectType::Snowflake)
30299        ) {
30300            if let Some(this) = &e.this {
30301                self.generate_expression(this)?;
30302            }
30303        }
30304        self.write(")");
30305        Ok(())
30306    }
30307
30308    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
30309        // CURRENT_USER or CURRENT_USER()
30310        self.write_keyword("CURRENT_USER");
30311        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
30312        let needs_parens = e.this.is_some()
30313            || matches!(
30314                self.config.dialect,
30315                Some(DialectType::Snowflake)
30316                    | Some(DialectType::Spark)
30317                    | Some(DialectType::Hive)
30318                    | Some(DialectType::DuckDB)
30319                    | Some(DialectType::BigQuery)
30320                    | Some(DialectType::MySQL)
30321                    | Some(DialectType::Databricks)
30322            );
30323        if needs_parens {
30324            self.write("()");
30325        }
30326        Ok(())
30327    }
30328
30329    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
30330        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
30331        if self.config.dialect == Some(DialectType::Solr) {
30332            self.generate_expression(&e.this)?;
30333            self.write(" ");
30334            self.write_keyword("OR");
30335            self.write(" ");
30336            self.generate_expression(&e.expression)?;
30337        } else if self.config.dialect == Some(DialectType::MySQL) {
30338            self.generate_mysql_concat_from_dpipe(e)?;
30339        } else {
30340            // String concatenation: this || expression
30341            self.generate_expression(&e.this)?;
30342            self.write(" || ");
30343            self.generate_expression(&e.expression)?;
30344        }
30345        Ok(())
30346    }
30347
30348    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
30349        // DATABLOCKSIZE=... (Teradata)
30350        self.write_keyword("DATABLOCKSIZE");
30351        self.write("=");
30352        if let Some(size) = e.size {
30353            self.write(&size.to_string());
30354            if let Some(units) = &e.units {
30355                self.write_space();
30356                self.generate_expression(units)?;
30357            }
30358        } else if e.minimum.is_some() {
30359            self.write_keyword("MINIMUM");
30360        } else if e.maximum.is_some() {
30361            self.write_keyword("MAXIMUM");
30362        } else if e.default.is_some() {
30363            self.write_keyword("DEFAULT");
30364        }
30365        Ok(())
30366    }
30367
30368    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
30369        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
30370        self.write_keyword("DATA_DELETION");
30371        self.write("=");
30372
30373        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
30374        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
30375
30376        if is_on {
30377            self.write_keyword("ON");
30378            if has_options {
30379                self.write("(");
30380                let mut first = true;
30381                if let Some(filter_column) = &e.filter_column {
30382                    self.write_keyword("FILTER_COLUMN");
30383                    self.write("=");
30384                    self.generate_expression(filter_column)?;
30385                    first = false;
30386                }
30387                if let Some(retention_period) = &e.retention_period {
30388                    if !first {
30389                        self.write(", ");
30390                    }
30391                    self.write_keyword("RETENTION_PERIOD");
30392                    self.write("=");
30393                    self.generate_expression(retention_period)?;
30394                }
30395                self.write(")");
30396            }
30397        } else {
30398            self.write_keyword("OFF");
30399        }
30400        Ok(())
30401    }
30402
30403    /// Generate a Date function expression
30404    /// For Exasol: {d'value'} -> TO_DATE('value')
30405    /// For other dialects: DATE('value')
30406    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
30407        use crate::dialects::DialectType;
30408        use crate::expressions::Literal;
30409
30410        match self.config.dialect {
30411            // Exasol uses TO_DATE for Date expressions
30412            Some(DialectType::Exasol) => {
30413                self.write_keyword("TO_DATE");
30414                self.write("(");
30415                // Extract the string value from the expression if it's a string literal
30416                match &e.this {
30417                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30418                        let Literal::String(s) = lit.as_ref() else {
30419                            unreachable!()
30420                        };
30421                        self.write("'");
30422                        self.write(s);
30423                        self.write("'");
30424                    }
30425                    _ => {
30426                        self.generate_expression(&e.this)?;
30427                    }
30428                }
30429                self.write(")");
30430            }
30431            // Standard: DATE(value)
30432            _ => {
30433                self.write_keyword("DATE");
30434                self.write("(");
30435                self.generate_expression(&e.this)?;
30436                self.write(")");
30437            }
30438        }
30439        Ok(())
30440    }
30441
30442    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
30443        // DATE_BIN(interval, timestamp[, origin])
30444        self.write_keyword("DATE_BIN");
30445        self.write("(");
30446        self.generate_expression(&e.this)?;
30447        self.write(", ");
30448        self.generate_expression(&e.expression)?;
30449        if let Some(origin) = &e.origin {
30450            self.write(", ");
30451            self.generate_expression(origin)?;
30452        }
30453        self.write(")");
30454        Ok(())
30455    }
30456
30457    fn generate_date_format_column_constraint(
30458        &mut self,
30459        e: &DateFormatColumnConstraint,
30460    ) -> Result<()> {
30461        // FORMAT 'format_string' (Teradata)
30462        self.write_keyword("FORMAT");
30463        self.write_space();
30464        self.generate_expression(&e.this)?;
30465        Ok(())
30466    }
30467
30468    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
30469        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
30470        self.write_keyword("DATE_FROM_PARTS");
30471        self.write("(");
30472        let mut first = true;
30473        if let Some(year) = &e.year {
30474            self.generate_expression(year)?;
30475            first = false;
30476        }
30477        if let Some(month) = &e.month {
30478            if !first {
30479                self.write(", ");
30480            }
30481            self.generate_expression(month)?;
30482            first = false;
30483        }
30484        if let Some(day) = &e.day {
30485            if !first {
30486                self.write(", ");
30487            }
30488            self.generate_expression(day)?;
30489        }
30490        self.write(")");
30491        Ok(())
30492    }
30493
30494    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
30495        // DATETIME(this) or DATETIME(this, expression)
30496        self.write_keyword("DATETIME");
30497        self.write("(");
30498        self.generate_expression(&e.this)?;
30499        if let Some(expr) = &e.expression {
30500            self.write(", ");
30501            self.generate_expression(expr)?;
30502        }
30503        self.write(")");
30504        Ok(())
30505    }
30506
30507    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
30508        // DATETIME_ADD(this, expression, unit)
30509        self.write_keyword("DATETIME_ADD");
30510        self.write("(");
30511        self.generate_expression(&e.this)?;
30512        self.write(", ");
30513        self.generate_expression(&e.expression)?;
30514        if let Some(unit) = &e.unit {
30515            self.write(", ");
30516            self.write_keyword(unit);
30517        }
30518        self.write(")");
30519        Ok(())
30520    }
30521
30522    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
30523        // DATETIME_DIFF(this, expression, unit)
30524        self.write_keyword("DATETIME_DIFF");
30525        self.write("(");
30526        self.generate_expression(&e.this)?;
30527        self.write(", ");
30528        self.generate_expression(&e.expression)?;
30529        if let Some(unit) = &e.unit {
30530            self.write(", ");
30531            self.write_keyword(unit);
30532        }
30533        self.write(")");
30534        Ok(())
30535    }
30536
30537    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
30538        // DATETIME_SUB(this, expression, unit)
30539        self.write_keyword("DATETIME_SUB");
30540        self.write("(");
30541        self.generate_expression(&e.this)?;
30542        self.write(", ");
30543        self.generate_expression(&e.expression)?;
30544        if let Some(unit) = &e.unit {
30545            self.write(", ");
30546            self.write_keyword(unit);
30547        }
30548        self.write(")");
30549        Ok(())
30550    }
30551
30552    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
30553        // DATETIME_TRUNC(this, unit, zone)
30554        self.write_keyword("DATETIME_TRUNC");
30555        self.write("(");
30556        self.generate_expression(&e.this)?;
30557        self.write(", ");
30558        self.write_keyword(&e.unit);
30559        if let Some(zone) = &e.zone {
30560            self.write(", ");
30561            self.generate_expression(zone)?;
30562        }
30563        self.write(")");
30564        Ok(())
30565    }
30566
30567    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
30568        // DAYNAME(this)
30569        self.write_keyword("DAYNAME");
30570        self.write("(");
30571        self.generate_expression(&e.this)?;
30572        self.write(")");
30573        Ok(())
30574    }
30575
30576    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
30577        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
30578        self.write_keyword("DECLARE");
30579        self.write_space();
30580        if e.replace {
30581            self.write_keyword("OR");
30582            self.write_space();
30583            self.write_keyword("REPLACE");
30584            self.write_space();
30585        }
30586        for (i, expr) in e.expressions.iter().enumerate() {
30587            if i > 0 {
30588                self.write(", ");
30589            }
30590            self.generate_expression(expr)?;
30591        }
30592        Ok(())
30593    }
30594
30595    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
30596        use crate::dialects::DialectType;
30597
30598        // variable TYPE [DEFAULT default]
30599        self.generate_expression(&e.this)?;
30600        // BigQuery multi-variable: DECLARE X, Y, Z INT64
30601        for name in &e.additional_names {
30602            self.write(", ");
30603            self.generate_expression(name)?;
30604        }
30605        if let Some(kind) = &e.kind {
30606            self.write_space();
30607            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
30608            // TSQL: Always includes AS (normalization)
30609            // Others: Include AS if present in original
30610            match self.config.dialect {
30611                Some(DialectType::BigQuery) => {
30612                    self.write(kind);
30613                }
30614                Some(DialectType::TSQL) => {
30615                    // TSQL DECLARE: no AS keyword (sqlglot convention)
30616                    // Normalize INT to INTEGER for simple declarations
30617                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
30618                    let is_complex_table = kind.starts_with("TABLE")
30619                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
30620                    if is_complex_table {
30621                        self.write(kind);
30622                    } else if kind == "INT" {
30623                        self.write("INTEGER");
30624                    } else if kind.starts_with("TABLE") {
30625                        // Normalize INT to INTEGER inside simple TABLE column definitions
30626                        let normalized = kind
30627                            .replace(" INT ", " INTEGER ")
30628                            .replace(" INT,", " INTEGER,")
30629                            .replace(" INT)", " INTEGER)")
30630                            .replace("(INT ", "(INTEGER ");
30631                        self.write(&normalized);
30632                    } else {
30633                        self.write(kind);
30634                    }
30635                }
30636                _ => {
30637                    if e.has_as {
30638                        self.write_keyword("AS");
30639                        self.write_space();
30640                    }
30641                    self.write(kind);
30642                }
30643            }
30644        }
30645        if let Some(default) = &e.default {
30646            // BigQuery uses DEFAULT, others use =
30647            match self.config.dialect {
30648                Some(DialectType::BigQuery) => {
30649                    self.write_space();
30650                    self.write_keyword("DEFAULT");
30651                    self.write_space();
30652                }
30653                _ => {
30654                    self.write(" = ");
30655                }
30656            }
30657            self.generate_expression(default)?;
30658        }
30659        Ok(())
30660    }
30661
30662    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
30663        // DECODE(expr, search1, result1, search2, result2, ..., default)
30664        self.write_keyword("DECODE");
30665        self.write("(");
30666        for (i, expr) in e.expressions.iter().enumerate() {
30667            if i > 0 {
30668                self.write(", ");
30669            }
30670            self.generate_expression(expr)?;
30671        }
30672        self.write(")");
30673        Ok(())
30674    }
30675
30676    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
30677        // DECOMPRESS(expr, 'method')
30678        self.write_keyword("DECOMPRESS");
30679        self.write("(");
30680        self.generate_expression(&e.this)?;
30681        self.write(", '");
30682        self.write(&e.method);
30683        self.write("')");
30684        Ok(())
30685    }
30686
30687    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
30688        // DECOMPRESS(expr, 'method')
30689        self.write_keyword("DECOMPRESS");
30690        self.write("(");
30691        self.generate_expression(&e.this)?;
30692        self.write(", '");
30693        self.write(&e.method);
30694        self.write("')");
30695        Ok(())
30696    }
30697
30698    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
30699        // DECRYPT(value, passphrase [, aad [, algorithm]])
30700        self.write_keyword("DECRYPT");
30701        self.write("(");
30702        self.generate_expression(&e.this)?;
30703        if let Some(passphrase) = &e.passphrase {
30704            self.write(", ");
30705            self.generate_expression(passphrase)?;
30706        }
30707        if let Some(aad) = &e.aad {
30708            self.write(", ");
30709            self.generate_expression(aad)?;
30710        }
30711        if let Some(method) = &e.encryption_method {
30712            self.write(", ");
30713            self.generate_expression(method)?;
30714        }
30715        self.write(")");
30716        Ok(())
30717    }
30718
30719    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
30720        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
30721        self.write_keyword("DECRYPT_RAW");
30722        self.write("(");
30723        self.generate_expression(&e.this)?;
30724        if let Some(key) = &e.key {
30725            self.write(", ");
30726            self.generate_expression(key)?;
30727        }
30728        if let Some(iv) = &e.iv {
30729            self.write(", ");
30730            self.generate_expression(iv)?;
30731        }
30732        if let Some(aad) = &e.aad {
30733            self.write(", ");
30734            self.generate_expression(aad)?;
30735        }
30736        if let Some(method) = &e.encryption_method {
30737            self.write(", ");
30738            self.generate_expression(method)?;
30739        }
30740        self.write(")");
30741        Ok(())
30742    }
30743
30744    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
30745        // DEFINER = user
30746        self.write_keyword("DEFINER");
30747        self.write(" = ");
30748        self.generate_expression(&e.this)?;
30749        Ok(())
30750    }
30751
30752    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
30753        // Python: DETACH[DATABASE IF EXISTS] this
30754        self.write_keyword("DETACH");
30755        if e.exists {
30756            self.write_keyword(" DATABASE IF EXISTS");
30757        }
30758        self.write_space();
30759        self.generate_expression(&e.this)?;
30760        Ok(())
30761    }
30762
30763    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
30764        let property_name = match e.this.as_ref() {
30765            Expression::Identifier(id) => id.name.as_str(),
30766            Expression::Var(v) => v.this.as_str(),
30767            _ => "DICTIONARY",
30768        };
30769        self.write_keyword(property_name);
30770        self.write("(");
30771        self.write(&e.kind);
30772        if let Some(settings) = &e.settings {
30773            self.write("(");
30774            if let Expression::Tuple(t) = settings.as_ref() {
30775                if self.config.pretty && !t.expressions.is_empty() {
30776                    self.write_newline();
30777                    self.indent_level += 1;
30778                    for (i, pair) in t.expressions.iter().enumerate() {
30779                        if i > 0 {
30780                            self.write(",");
30781                            self.write_newline();
30782                        }
30783                        self.write_indent();
30784                        if let Expression::Tuple(pair_tuple) = pair {
30785                            if let Some(k) = pair_tuple.expressions.first() {
30786                                self.generate_expression(k)?;
30787                            }
30788                            if let Some(v) = pair_tuple.expressions.get(1) {
30789                                self.write(" ");
30790                                self.generate_expression(v)?;
30791                            }
30792                        } else {
30793                            self.generate_expression(pair)?;
30794                        }
30795                    }
30796                    self.indent_level -= 1;
30797                    self.write_newline();
30798                    self.write_indent();
30799                } else {
30800                    for (i, pair) in t.expressions.iter().enumerate() {
30801                        if i > 0 {
30802                            // ClickHouse dict properties are space-separated, not comma-separated
30803                            self.write(" ");
30804                        }
30805                        if let Expression::Tuple(pair_tuple) = pair {
30806                            if let Some(k) = pair_tuple.expressions.first() {
30807                                self.generate_expression(k)?;
30808                            }
30809                            if let Some(v) = pair_tuple.expressions.get(1) {
30810                                self.write(" ");
30811                                self.generate_expression(v)?;
30812                            }
30813                        } else {
30814                            self.generate_expression(pair)?;
30815                        }
30816                    }
30817                }
30818            } else {
30819                self.generate_expression(settings)?;
30820            }
30821            self.write(")");
30822        } else {
30823            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
30824            self.write("()");
30825        }
30826        self.write(")");
30827        Ok(())
30828    }
30829
30830    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
30831        let property_name = match e.this.as_ref() {
30832            Expression::Identifier(id) => id.name.as_str(),
30833            Expression::Var(v) => v.this.as_str(),
30834            _ => "RANGE",
30835        };
30836        self.write_keyword(property_name);
30837        self.write("(");
30838        if let Some(min) = &e.min {
30839            self.write_keyword("MIN");
30840            self.write_space();
30841            self.generate_expression(min)?;
30842        }
30843        if let Some(max) = &e.max {
30844            self.write_space();
30845            self.write_keyword("MAX");
30846            self.write_space();
30847            self.generate_expression(max)?;
30848        }
30849        self.write(")");
30850        Ok(())
30851    }
30852
30853    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
30854        // Python: {local}DIRECTORY {this}{row_format}
30855        if e.local.is_some() {
30856            self.write_keyword("LOCAL ");
30857        }
30858        self.write_keyword("DIRECTORY");
30859        self.write_space();
30860        self.generate_expression(&e.this)?;
30861        if let Some(row_format) = &e.row_format {
30862            self.write_space();
30863            self.generate_expression(row_format)?;
30864        }
30865        Ok(())
30866    }
30867
30868    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
30869        // Redshift: DISTKEY(column)
30870        self.write_keyword("DISTKEY");
30871        self.write("(");
30872        self.generate_expression(&e.this)?;
30873        self.write(")");
30874        Ok(())
30875    }
30876
30877    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
30878        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
30879        self.write_keyword("DISTSTYLE");
30880        self.write_space();
30881        self.generate_expression(&e.this)?;
30882        Ok(())
30883    }
30884
30885    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
30886        // Python: "DISTRIBUTE BY" expressions
30887        self.write_keyword("DISTRIBUTE BY");
30888        self.write_space();
30889        for (i, expr) in e.expressions.iter().enumerate() {
30890            if i > 0 {
30891                self.write(", ");
30892            }
30893            self.generate_expression(expr)?;
30894        }
30895        Ok(())
30896    }
30897
30898    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
30899        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
30900        self.write_keyword("DISTRIBUTED BY");
30901        self.write_space();
30902        self.write(&e.kind);
30903        if !e.expressions.is_empty() {
30904            self.write(" (");
30905            for (i, expr) in e.expressions.iter().enumerate() {
30906                if i > 0 {
30907                    self.write(", ");
30908                }
30909                self.generate_expression(expr)?;
30910            }
30911            self.write(")");
30912        }
30913        if let Some(buckets) = &e.buckets {
30914            self.write_space();
30915            self.write_keyword("BUCKETS");
30916            self.write_space();
30917            self.generate_expression(buckets)?;
30918        }
30919        if let Some(order) = &e.order {
30920            self.write_space();
30921            self.generate_expression(order)?;
30922        }
30923        Ok(())
30924    }
30925
30926    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
30927        // DOT_PRODUCT(vector1, vector2)
30928        self.write_keyword("DOT_PRODUCT");
30929        self.write("(");
30930        self.generate_expression(&e.this)?;
30931        self.write(", ");
30932        self.generate_expression(&e.expression)?;
30933        self.write(")");
30934        Ok(())
30935    }
30936
30937    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
30938        // Python: DROP{IF EXISTS }expressions
30939        self.write_keyword("DROP");
30940        if e.exists {
30941            self.write_keyword(" IF EXISTS ");
30942        } else {
30943            self.write_space();
30944        }
30945        for (i, expr) in e.expressions.iter().enumerate() {
30946            if i > 0 {
30947                self.write(", ");
30948            }
30949            self.generate_expression(expr)?;
30950        }
30951        Ok(())
30952    }
30953
30954    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
30955        // Python: DUPLICATE KEY (expressions)
30956        self.write_keyword("DUPLICATE KEY");
30957        self.write(" (");
30958        for (i, expr) in e.expressions.iter().enumerate() {
30959            if i > 0 {
30960                self.write(", ");
30961            }
30962            self.generate_expression(expr)?;
30963        }
30964        self.write(")");
30965        Ok(())
30966    }
30967
30968    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
30969        // ELT(index, str1, str2, ...)
30970        self.write_keyword("ELT");
30971        self.write("(");
30972        self.generate_expression(&e.this)?;
30973        for expr in &e.expressions {
30974            self.write(", ");
30975            self.generate_expression(expr)?;
30976        }
30977        self.write(")");
30978        Ok(())
30979    }
30980
30981    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
30982        // ENCODE(string, charset)
30983        self.write_keyword("ENCODE");
30984        self.write("(");
30985        self.generate_expression(&e.this)?;
30986        if let Some(charset) = &e.charset {
30987            self.write(", ");
30988            self.generate_expression(charset)?;
30989        }
30990        self.write(")");
30991        Ok(())
30992    }
30993
30994    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
30995        // Python: [KEY ]ENCODE this [properties]
30996        if e.key.is_some() {
30997            self.write_keyword("KEY ");
30998        }
30999        self.write_keyword("ENCODE");
31000        self.write_space();
31001        self.generate_expression(&e.this)?;
31002        if !e.properties.is_empty() {
31003            self.write(" (");
31004            for (i, prop) in e.properties.iter().enumerate() {
31005                if i > 0 {
31006                    self.write(", ");
31007                }
31008                self.generate_expression(prop)?;
31009            }
31010            self.write(")");
31011        }
31012        Ok(())
31013    }
31014
31015    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
31016        // ENCRYPT(value, passphrase [, aad [, algorithm]])
31017        self.write_keyword("ENCRYPT");
31018        self.write("(");
31019        self.generate_expression(&e.this)?;
31020        if let Some(passphrase) = &e.passphrase {
31021            self.write(", ");
31022            self.generate_expression(passphrase)?;
31023        }
31024        if let Some(aad) = &e.aad {
31025            self.write(", ");
31026            self.generate_expression(aad)?;
31027        }
31028        if let Some(method) = &e.encryption_method {
31029            self.write(", ");
31030            self.generate_expression(method)?;
31031        }
31032        self.write(")");
31033        Ok(())
31034    }
31035
31036    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
31037        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
31038        self.write_keyword("ENCRYPT_RAW");
31039        self.write("(");
31040        self.generate_expression(&e.this)?;
31041        if let Some(key) = &e.key {
31042            self.write(", ");
31043            self.generate_expression(key)?;
31044        }
31045        if let Some(iv) = &e.iv {
31046            self.write(", ");
31047            self.generate_expression(iv)?;
31048        }
31049        if let Some(aad) = &e.aad {
31050            self.write(", ");
31051            self.generate_expression(aad)?;
31052        }
31053        if let Some(method) = &e.encryption_method {
31054            self.write(", ");
31055            self.generate_expression(method)?;
31056        }
31057        self.write(")");
31058        Ok(())
31059    }
31060
31061    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
31062        // MySQL: ENGINE = InnoDB
31063        self.write_keyword("ENGINE");
31064        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
31065            self.write("=");
31066        } else {
31067            self.write(" = ");
31068        }
31069        self.generate_expression(&e.this)?;
31070        Ok(())
31071    }
31072
31073    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
31074        // ENVIRONMENT (expressions)
31075        self.write_keyword("ENVIRONMENT");
31076        self.write(" (");
31077        for (i, expr) in e.expressions.iter().enumerate() {
31078            if i > 0 {
31079                self.write(", ");
31080            }
31081            self.generate_expression(expr)?;
31082        }
31083        self.write(")");
31084        Ok(())
31085    }
31086
31087    fn generate_ephemeral_column_constraint(
31088        &mut self,
31089        e: &EphemeralColumnConstraint,
31090    ) -> Result<()> {
31091        // MySQL: EPHEMERAL [expr]
31092        self.write_keyword("EPHEMERAL");
31093        if let Some(this) = &e.this {
31094            self.write_space();
31095            self.generate_expression(this)?;
31096        }
31097        Ok(())
31098    }
31099
31100    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
31101        // Snowflake: EQUAL_NULL(a, b)
31102        self.write_keyword("EQUAL_NULL");
31103        self.write("(");
31104        self.generate_expression(&e.this)?;
31105        self.write(", ");
31106        self.generate_expression(&e.expression)?;
31107        self.write(")");
31108        Ok(())
31109    }
31110
31111    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
31112        use crate::dialects::DialectType;
31113
31114        // PostgreSQL uses <-> operator syntax
31115        match self.config.dialect {
31116            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
31117                self.generate_expression(&e.this)?;
31118                self.write(" <-> ");
31119                self.generate_expression(&e.expression)?;
31120            }
31121            _ => {
31122                // Other dialects use EUCLIDEAN_DISTANCE function
31123                self.write_keyword("EUCLIDEAN_DISTANCE");
31124                self.write("(");
31125                self.generate_expression(&e.this)?;
31126                self.write(", ");
31127                self.generate_expression(&e.expression)?;
31128                self.write(")");
31129            }
31130        }
31131        Ok(())
31132    }
31133
31134    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
31135        // EXECUTE AS CALLER|OWNER|user
31136        self.write_keyword("EXECUTE AS");
31137        self.write_space();
31138        self.generate_expression(&e.this)?;
31139        Ok(())
31140    }
31141
31142    fn generate_export(&mut self, e: &Export) -> Result<()> {
31143        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
31144        self.write_keyword("EXPORT DATA");
31145        if let Some(connection) = &e.connection {
31146            self.write_space();
31147            self.write_keyword("WITH CONNECTION");
31148            self.write_space();
31149            self.generate_expression(connection)?;
31150        }
31151        if !e.options.is_empty() {
31152            self.write_space();
31153            self.generate_options_clause(&e.options)?;
31154        }
31155        self.write_space();
31156        self.write_keyword("AS");
31157        self.write_space();
31158        self.generate_expression(&e.this)?;
31159        Ok(())
31160    }
31161
31162    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
31163        // EXTERNAL [this]
31164        self.write_keyword("EXTERNAL");
31165        if let Some(this) = &e.this {
31166            self.write_space();
31167            self.generate_expression(this)?;
31168        }
31169        Ok(())
31170    }
31171
31172    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
31173        // Python: {no}FALLBACK{protection}
31174        if e.no.is_some() {
31175            self.write_keyword("NO ");
31176        }
31177        self.write_keyword("FALLBACK");
31178        if e.protection.is_some() {
31179            self.write_keyword(" PROTECTION");
31180        }
31181        Ok(())
31182    }
31183
31184    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
31185        // BigQuery: FARM_FINGERPRINT(value)
31186        self.write_keyword("FARM_FINGERPRINT");
31187        self.write("(");
31188        for (i, expr) in e.expressions.iter().enumerate() {
31189            if i > 0 {
31190                self.write(", ");
31191            }
31192            self.generate_expression(expr)?;
31193        }
31194        self.write(")");
31195        Ok(())
31196    }
31197
31198    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
31199        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
31200        self.write_keyword("FEATURES_AT_TIME");
31201        self.write("(");
31202        self.generate_expression(&e.this)?;
31203        if let Some(time) = &e.time {
31204            self.write(", ");
31205            self.generate_expression(time)?;
31206        }
31207        if let Some(num_rows) = &e.num_rows {
31208            self.write(", ");
31209            self.generate_expression(num_rows)?;
31210        }
31211        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
31212            self.write(", ");
31213            self.generate_expression(ignore_nulls)?;
31214        }
31215        self.write(")");
31216        Ok(())
31217    }
31218
31219    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
31220        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
31221        let use_limit = !e.percent
31222            && !e.with_ties
31223            && e.count.is_some()
31224            && matches!(
31225                self.config.dialect,
31226                Some(DialectType::Spark)
31227                    | Some(DialectType::Hive)
31228                    | Some(DialectType::DuckDB)
31229                    | Some(DialectType::SQLite)
31230                    | Some(DialectType::MySQL)
31231                    | Some(DialectType::BigQuery)
31232                    | Some(DialectType::Databricks)
31233                    | Some(DialectType::StarRocks)
31234                    | Some(DialectType::Doris)
31235                    | Some(DialectType::Athena)
31236                    | Some(DialectType::ClickHouse)
31237            );
31238
31239        if use_limit {
31240            self.write_keyword("LIMIT");
31241            self.write_space();
31242            self.generate_expression(e.count.as_ref().unwrap())?;
31243            return Ok(());
31244        }
31245
31246        // Python: FETCH direction count limit_options
31247        self.write_keyword("FETCH");
31248        if !e.direction.is_empty() {
31249            self.write_space();
31250            self.write_keyword(&e.direction);
31251        }
31252        if let Some(count) = &e.count {
31253            self.write_space();
31254            self.generate_expression(count)?;
31255        }
31256        // Generate PERCENT, ROWS, WITH TIES/ONLY
31257        if e.percent {
31258            self.write_keyword(" PERCENT");
31259        }
31260        if e.rows {
31261            self.write_keyword(" ROWS");
31262        }
31263        if e.with_ties {
31264            self.write_keyword(" WITH TIES");
31265        } else if e.rows {
31266            self.write_keyword(" ONLY");
31267        } else {
31268            self.write_keyword(" ROWS ONLY");
31269        }
31270        Ok(())
31271    }
31272
31273    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
31274        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
31275        // For Spark/Databricks without hive_format: USING this
31276        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
31277        if e.hive_format.is_some() {
31278            // Hive format: STORED AS ...
31279            self.write_keyword("STORED AS");
31280            self.write_space();
31281            if let Some(this) = &e.this {
31282                // Uppercase the format name (e.g., parquet -> PARQUET)
31283                if let Expression::Identifier(id) = this.as_ref() {
31284                    self.write_keyword(&id.name.to_ascii_uppercase());
31285                } else {
31286                    self.generate_expression(this)?;
31287                }
31288            }
31289        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
31290            // Hive: STORED AS format
31291            self.write_keyword("STORED AS");
31292            self.write_space();
31293            if let Some(this) = &e.this {
31294                if let Expression::Identifier(id) = this.as_ref() {
31295                    self.write_keyword(&id.name.to_ascii_uppercase());
31296                } else {
31297                    self.generate_expression(this)?;
31298                }
31299            }
31300        } else if matches!(
31301            self.config.dialect,
31302            Some(DialectType::Spark) | Some(DialectType::Databricks)
31303        ) {
31304            // Spark/Databricks: USING format (e.g., USING DELTA)
31305            self.write_keyword("USING");
31306            self.write_space();
31307            if let Some(this) = &e.this {
31308                self.generate_expression(this)?;
31309            }
31310        } else {
31311            // Snowflake/standard format
31312            self.write_keyword("FILE_FORMAT");
31313            self.write(" = ");
31314            if let Some(this) = &e.this {
31315                self.generate_expression(this)?;
31316            } else if !e.expressions.is_empty() {
31317                self.write("(");
31318                for (i, expr) in e.expressions.iter().enumerate() {
31319                    if i > 0 {
31320                        self.write(", ");
31321                    }
31322                    self.generate_expression(expr)?;
31323                }
31324                self.write(")");
31325            }
31326        }
31327        Ok(())
31328    }
31329
31330    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
31331        // agg_func FILTER(WHERE condition)
31332        self.generate_expression(&e.this)?;
31333        self.write_space();
31334        self.write_keyword("FILTER");
31335        self.write("(");
31336        self.write_keyword("WHERE");
31337        self.write_space();
31338        self.generate_expression(&e.expression)?;
31339        self.write(")");
31340        Ok(())
31341    }
31342
31343    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
31344        // FLOAT64(this) or FLOAT64(this, expression)
31345        self.write_keyword("FLOAT64");
31346        self.write("(");
31347        self.generate_expression(&e.this)?;
31348        if let Some(expr) = &e.expression {
31349            self.write(", ");
31350            self.generate_expression(expr)?;
31351        }
31352        self.write(")");
31353        Ok(())
31354    }
31355
31356    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
31357        // FOR this DO expression
31358        self.write_keyword("FOR");
31359        self.write_space();
31360        self.generate_expression(&e.this)?;
31361        self.write_space();
31362        self.write_keyword("DO");
31363        self.write_space();
31364        self.generate_expression(&e.expression)?;
31365        Ok(())
31366    }
31367
31368    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
31369        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
31370        self.write_keyword("FOREIGN KEY");
31371        if !e.expressions.is_empty() {
31372            self.write(" (");
31373            for (i, expr) in e.expressions.iter().enumerate() {
31374                if i > 0 {
31375                    self.write(", ");
31376                }
31377                self.generate_expression(expr)?;
31378            }
31379            self.write(")");
31380        }
31381        if let Some(reference) = &e.reference {
31382            self.write_space();
31383            self.generate_expression(reference)?;
31384        }
31385        if let Some(delete) = &e.delete {
31386            self.write_space();
31387            self.write_keyword("ON DELETE");
31388            self.write_space();
31389            self.generate_expression(delete)?;
31390        }
31391        if let Some(update) = &e.update {
31392            self.write_space();
31393            self.write_keyword("ON UPDATE");
31394            self.write_space();
31395            self.generate_expression(update)?;
31396        }
31397        if !e.options.is_empty() {
31398            self.write_space();
31399            for (i, opt) in e.options.iter().enumerate() {
31400                if i > 0 {
31401                    self.write_space();
31402                }
31403                self.generate_expression(opt)?;
31404            }
31405        }
31406        Ok(())
31407    }
31408
31409    fn generate_format(&mut self, e: &Format) -> Result<()> {
31410        // FORMAT(this, expressions...)
31411        self.write_keyword("FORMAT");
31412        self.write("(");
31413        self.generate_expression(&e.this)?;
31414        for expr in &e.expressions {
31415            self.write(", ");
31416            self.generate_expression(expr)?;
31417        }
31418        self.write(")");
31419        Ok(())
31420    }
31421
31422    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
31423        // Teradata: column (FORMAT 'format_string')
31424        self.generate_expression(&e.this)?;
31425        self.write(" (");
31426        self.write_keyword("FORMAT");
31427        self.write(" '");
31428        self.write(&e.format);
31429        self.write("')");
31430        Ok(())
31431    }
31432
31433    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
31434        // Python: FREESPACE=this[PERCENT]
31435        self.write_keyword("FREESPACE");
31436        self.write("=");
31437        self.generate_expression(&e.this)?;
31438        if e.percent.is_some() {
31439            self.write_keyword(" PERCENT");
31440        }
31441        Ok(())
31442    }
31443
31444    fn generate_from(&mut self, e: &From) -> Result<()> {
31445        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
31446        self.write_keyword("FROM");
31447        self.write_space();
31448
31449        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
31450        // But keep commas when TABLESAMPLE is present
31451        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
31452        use crate::dialects::DialectType;
31453        let has_tablesample = e
31454            .expressions
31455            .iter()
31456            .any(|expr| matches!(expr, Expression::TableSample(_)));
31457        let is_cross_join_dialect = matches!(
31458            self.config.dialect,
31459            Some(DialectType::BigQuery)
31460                | Some(DialectType::Hive)
31461                | Some(DialectType::Spark)
31462                | Some(DialectType::Databricks)
31463                | Some(DialectType::SQLite)
31464                | Some(DialectType::ClickHouse)
31465        );
31466        let source_is_same_as_target2 = self.config.source_dialect.is_some()
31467            && self.config.source_dialect == self.config.dialect;
31468        let source_is_cross_join_dialect2 = matches!(
31469            self.config.source_dialect,
31470            Some(DialectType::BigQuery)
31471                | Some(DialectType::Hive)
31472                | Some(DialectType::Spark)
31473                | Some(DialectType::Databricks)
31474                | Some(DialectType::SQLite)
31475                | Some(DialectType::ClickHouse)
31476        );
31477        let use_cross_join = !has_tablesample
31478            && is_cross_join_dialect
31479            && (source_is_same_as_target2
31480                || source_is_cross_join_dialect2
31481                || self.config.source_dialect.is_none());
31482
31483        // Snowflake wraps standalone VALUES in FROM clause with parentheses
31484        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
31485
31486        for (i, expr) in e.expressions.iter().enumerate() {
31487            if i > 0 {
31488                if use_cross_join {
31489                    self.write(" CROSS JOIN ");
31490                } else {
31491                    self.write(", ");
31492                }
31493            }
31494            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
31495                self.write("(");
31496                self.generate_expression(expr)?;
31497                self.write(")");
31498            } else {
31499                self.generate_expression(expr)?;
31500            }
31501            // Output leading comments that were on the table name before FROM
31502            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
31503            let leading = Self::extract_table_leading_comments(expr);
31504            for comment in &leading {
31505                self.write_space();
31506                self.write_formatted_comment(comment);
31507            }
31508        }
31509        Ok(())
31510    }
31511
31512    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
31513    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
31514        match expr {
31515            Expression::Table(t) => t.leading_comments.clone(),
31516            Expression::Pivot(p) => {
31517                if let Expression::Table(t) = &p.this {
31518                    t.leading_comments.clone()
31519                } else {
31520                    Vec::new()
31521                }
31522            }
31523            _ => Vec::new(),
31524        }
31525    }
31526
31527    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
31528        // FROM_BASE(this, expression) - convert from base N
31529        self.write_keyword("FROM_BASE");
31530        self.write("(");
31531        self.generate_expression(&e.this)?;
31532        self.write(", ");
31533        self.generate_expression(&e.expression)?;
31534        self.write(")");
31535        Ok(())
31536    }
31537
31538    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
31539        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
31540        self.generate_expression(&e.this)?;
31541        if let Some(zone) = &e.zone {
31542            self.write_space();
31543            self.write_keyword("AT TIME ZONE");
31544            self.write_space();
31545            self.generate_expression(zone)?;
31546            self.write_space();
31547            self.write_keyword("AT TIME ZONE");
31548            self.write(" 'UTC'");
31549        }
31550        Ok(())
31551    }
31552
31553    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
31554        // GAP_FILL(this, ts_column, bucket_width, ...)
31555        self.write_keyword("GAP_FILL");
31556        self.write("(");
31557        self.generate_expression(&e.this)?;
31558        if let Some(ts_column) = &e.ts_column {
31559            self.write(", ");
31560            self.generate_expression(ts_column)?;
31561        }
31562        if let Some(bucket_width) = &e.bucket_width {
31563            self.write(", ");
31564            self.generate_expression(bucket_width)?;
31565        }
31566        if let Some(partitioning_columns) = &e.partitioning_columns {
31567            self.write(", ");
31568            self.generate_expression(partitioning_columns)?;
31569        }
31570        if let Some(value_columns) = &e.value_columns {
31571            self.write(", ");
31572            self.generate_expression(value_columns)?;
31573        }
31574        self.write(")");
31575        Ok(())
31576    }
31577
31578    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
31579        // GENERATE_DATE_ARRAY(start, end, step)
31580        self.write_keyword("GENERATE_DATE_ARRAY");
31581        self.write("(");
31582        let mut first = true;
31583        if let Some(start) = &e.start {
31584            self.generate_expression(start)?;
31585            first = false;
31586        }
31587        if let Some(end) = &e.end {
31588            if !first {
31589                self.write(", ");
31590            }
31591            self.generate_expression(end)?;
31592            first = false;
31593        }
31594        if let Some(step) = &e.step {
31595            if !first {
31596                self.write(", ");
31597            }
31598            self.generate_expression(step)?;
31599        }
31600        self.write(")");
31601        Ok(())
31602    }
31603
31604    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
31605        // ML.GENERATE_EMBEDDING(model, content, params)
31606        self.write_keyword("ML.GENERATE_EMBEDDING");
31607        self.write("(");
31608        self.generate_expression(&e.this)?;
31609        self.write(", ");
31610        self.generate_expression(&e.expression)?;
31611        if let Some(params) = &e.params_struct {
31612            self.write(", ");
31613            self.generate_expression(params)?;
31614        }
31615        self.write(")");
31616        Ok(())
31617    }
31618
31619    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
31620        // Dialect-specific function name
31621        let fn_name = match self.config.dialect {
31622            Some(DialectType::Presto)
31623            | Some(DialectType::Trino)
31624            | Some(DialectType::Athena)
31625            | Some(DialectType::Spark)
31626            | Some(DialectType::Databricks)
31627            | Some(DialectType::Hive) => "SEQUENCE",
31628            _ => "GENERATE_SERIES",
31629        };
31630        self.write_keyword(fn_name);
31631        self.write("(");
31632        let mut first = true;
31633        if let Some(start) = &e.start {
31634            self.generate_expression(start)?;
31635            first = false;
31636        }
31637        if let Some(end) = &e.end {
31638            if !first {
31639                self.write(", ");
31640            }
31641            self.generate_expression(end)?;
31642            first = false;
31643        }
31644        if let Some(step) = &e.step {
31645            if !first {
31646                self.write(", ");
31647            }
31648            // For Presto/Trino: convert WEEK intervals to DAY multiples
31649            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
31650            if matches!(
31651                self.config.dialect,
31652                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
31653            ) {
31654                if let Some(converted) = self.convert_week_interval_to_day(step) {
31655                    self.generate_expression(&converted)?;
31656                } else {
31657                    self.generate_expression(step)?;
31658                }
31659            } else {
31660                self.generate_expression(step)?;
31661            }
31662        }
31663        self.write(")");
31664        Ok(())
31665    }
31666
31667    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
31668    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
31669    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
31670        use crate::expressions::*;
31671        if let Expression::Interval(ref iv) = expr {
31672            // Check for structured WEEK unit
31673            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
31674                unit: IntervalUnit::Week,
31675                ..
31676            }) = &iv.unit
31677            {
31678                // Value is in iv.this
31679                let count = match &iv.this {
31680                    Some(Expression::Literal(lit)) => match lit.as_ref() {
31681                        Literal::String(s) | Literal::Number(s) => s.clone(),
31682                        _ => return None,
31683                    },
31684                    _ => return None,
31685                };
31686                (true, count)
31687            } else if iv.unit.is_none() {
31688                // Check for string-encoded interval like "1 WEEK"
31689                if let Some(Expression::Literal(lit)) = &iv.this {
31690                    if let Literal::String(s) = lit.as_ref() {
31691                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
31692                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
31693                            (true, parts[0].to_string())
31694                        } else {
31695                            (false, String::new())
31696                        }
31697                    } else {
31698                        (false, String::new())
31699                    }
31700                } else {
31701                    (false, String::new())
31702                }
31703            } else {
31704                (false, String::new())
31705            };
31706
31707            if is_week {
31708                // Build: (N * INTERVAL '7' DAY)
31709                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
31710                let day_interval = Expression::Interval(Box::new(Interval {
31711                    this: Some(Expression::Literal(Box::new(Literal::String(
31712                        "7".to_string(),
31713                    )))),
31714                    unit: Some(IntervalUnitSpec::Simple {
31715                        unit: IntervalUnit::Day,
31716                        use_plural: false,
31717                    }),
31718                }));
31719                let mul = Expression::Mul(Box::new(BinaryOp {
31720                    left: count_expr,
31721                    right: day_interval,
31722                    left_comments: vec![],
31723                    operator_comments: vec![],
31724                    trailing_comments: vec![],
31725                    inferred_type: None,
31726                }));
31727                return Some(Expression::Paren(Box::new(Paren {
31728                    this: mul,
31729                    trailing_comments: vec![],
31730                })));
31731            }
31732        }
31733        None
31734    }
31735
31736    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
31737        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
31738        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
31739        self.write("(");
31740        let mut first = true;
31741        if let Some(start) = &e.start {
31742            self.generate_expression(start)?;
31743            first = false;
31744        }
31745        if let Some(end) = &e.end {
31746            if !first {
31747                self.write(", ");
31748            }
31749            self.generate_expression(end)?;
31750            first = false;
31751        }
31752        if let Some(step) = &e.step {
31753            if !first {
31754                self.write(", ");
31755            }
31756            self.generate_expression(step)?;
31757        }
31758        self.write(")");
31759        Ok(())
31760    }
31761
31762    fn generate_generated_as_identity_column_constraint(
31763        &mut self,
31764        e: &GeneratedAsIdentityColumnConstraint,
31765    ) -> Result<()> {
31766        use crate::dialects::DialectType;
31767
31768        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
31769        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31770            self.write_keyword("AUTOINCREMENT");
31771            if let Some(start) = &e.start {
31772                self.write_keyword(" START ");
31773                self.generate_expression(start)?;
31774            }
31775            if let Some(increment) = &e.increment {
31776                self.write_keyword(" INCREMENT ");
31777                self.generate_expression(increment)?;
31778            }
31779            return Ok(());
31780        }
31781
31782        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
31783        self.write_keyword("GENERATED");
31784        if let Some(this) = &e.this {
31785            // Check if it's a truthy boolean expression
31786            if let Expression::Boolean(b) = this.as_ref() {
31787                if b.value {
31788                    self.write_keyword(" ALWAYS");
31789                } else {
31790                    self.write_keyword(" BY DEFAULT");
31791                    if e.on_null.is_some() {
31792                        self.write_keyword(" ON NULL");
31793                    }
31794                }
31795            } else {
31796                self.write_keyword(" ALWAYS");
31797            }
31798        }
31799        self.write_keyword(" AS IDENTITY");
31800        // Add sequence options if any
31801        let has_options = e.start.is_some()
31802            || e.increment.is_some()
31803            || e.minvalue.is_some()
31804            || e.maxvalue.is_some();
31805        if has_options {
31806            self.write(" (");
31807            let mut first = true;
31808            if let Some(start) = &e.start {
31809                self.write_keyword("START WITH ");
31810                self.generate_expression(start)?;
31811                first = false;
31812            }
31813            if let Some(increment) = &e.increment {
31814                if !first {
31815                    self.write(" ");
31816                }
31817                self.write_keyword("INCREMENT BY ");
31818                self.generate_expression(increment)?;
31819                first = false;
31820            }
31821            if let Some(minvalue) = &e.minvalue {
31822                if !first {
31823                    self.write(" ");
31824                }
31825                self.write_keyword("MINVALUE ");
31826                self.generate_expression(minvalue)?;
31827                first = false;
31828            }
31829            if let Some(maxvalue) = &e.maxvalue {
31830                if !first {
31831                    self.write(" ");
31832                }
31833                self.write_keyword("MAXVALUE ");
31834                self.generate_expression(maxvalue)?;
31835            }
31836            self.write(")");
31837        }
31838        Ok(())
31839    }
31840
31841    fn generate_generated_as_row_column_constraint(
31842        &mut self,
31843        e: &GeneratedAsRowColumnConstraint,
31844    ) -> Result<()> {
31845        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
31846        self.write_keyword("GENERATED ALWAYS AS ROW ");
31847        if e.start.is_some() {
31848            self.write_keyword("START");
31849        } else {
31850            self.write_keyword("END");
31851        }
31852        if e.hidden.is_some() {
31853            self.write_keyword(" HIDDEN");
31854        }
31855        Ok(())
31856    }
31857
31858    fn generate_get(&mut self, e: &Get) -> Result<()> {
31859        // GET this target properties
31860        self.write_keyword("GET");
31861        self.write_space();
31862        self.generate_expression(&e.this)?;
31863        if let Some(target) = &e.target {
31864            self.write_space();
31865            self.generate_expression(target)?;
31866        }
31867        for prop in &e.properties {
31868            self.write_space();
31869            self.generate_expression(prop)?;
31870        }
31871        Ok(())
31872    }
31873
31874    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
31875        // GetExtract generates bracket access: this[expression]
31876        self.generate_expression(&e.this)?;
31877        self.write("[");
31878        self.generate_expression(&e.expression)?;
31879        self.write("]");
31880        Ok(())
31881    }
31882
31883    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
31884        // GETBIT(this, expression) or GET_BIT(this, expression)
31885        self.write_keyword("GETBIT");
31886        self.write("(");
31887        self.generate_expression(&e.this)?;
31888        self.write(", ");
31889        self.generate_expression(&e.expression)?;
31890        self.write(")");
31891        Ok(())
31892    }
31893
31894    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
31895        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
31896        if e.is_role {
31897            self.write_keyword("ROLE");
31898            self.write_space();
31899        } else if e.is_group {
31900            self.write_keyword("GROUP");
31901            self.write_space();
31902        } else if e.is_share {
31903            self.write_keyword("SHARE");
31904            self.write_space();
31905        }
31906        self.write(&e.name.name);
31907        Ok(())
31908    }
31909
31910    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
31911        // privilege(columns) or just privilege
31912        self.generate_expression(&e.this)?;
31913        if !e.expressions.is_empty() {
31914            self.write("(");
31915            for (i, expr) in e.expressions.iter().enumerate() {
31916                if i > 0 {
31917                    self.write(", ");
31918                }
31919                self.generate_expression(expr)?;
31920            }
31921            self.write(")");
31922        }
31923        Ok(())
31924    }
31925
31926    fn generate_group(&mut self, e: &Group) -> Result<()> {
31927        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
31928        self.write_keyword("GROUP BY");
31929        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
31930        match e.all {
31931            Some(true) => {
31932                self.write_space();
31933                self.write_keyword("ALL");
31934            }
31935            Some(false) => {
31936                self.write_space();
31937                self.write_keyword("DISTINCT");
31938            }
31939            None => {}
31940        }
31941        if !e.expressions.is_empty() {
31942            self.write_space();
31943            for (i, expr) in e.expressions.iter().enumerate() {
31944                if i > 0 {
31945                    self.write(", ");
31946                }
31947                self.generate_expression(expr)?;
31948            }
31949        }
31950        // Handle CUBE, ROLLUP, GROUPING SETS
31951        if let Some(cube) = &e.cube {
31952            if !e.expressions.is_empty() {
31953                self.write(", ");
31954            } else {
31955                self.write_space();
31956            }
31957            self.generate_expression(cube)?;
31958        }
31959        if let Some(rollup) = &e.rollup {
31960            if !e.expressions.is_empty() || e.cube.is_some() {
31961                self.write(", ");
31962            } else {
31963                self.write_space();
31964            }
31965            self.generate_expression(rollup)?;
31966        }
31967        if let Some(grouping_sets) = &e.grouping_sets {
31968            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
31969                self.write(", ");
31970            } else {
31971                self.write_space();
31972            }
31973            self.generate_expression(grouping_sets)?;
31974        }
31975        if let Some(totals) = &e.totals {
31976            self.write_space();
31977            self.write_keyword("WITH TOTALS");
31978            self.generate_expression(totals)?;
31979        }
31980        Ok(())
31981    }
31982
31983    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
31984        // GROUP BY expressions
31985        self.write_keyword("GROUP BY");
31986        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
31987        match e.all {
31988            Some(true) => {
31989                self.write_space();
31990                self.write_keyword("ALL");
31991            }
31992            Some(false) => {
31993                self.write_space();
31994                self.write_keyword("DISTINCT");
31995            }
31996            None => {}
31997        }
31998
31999        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
32000        // These are represented as Cube/Rollup expressions with empty expressions at the end
32001        let mut trailing_cube = false;
32002        let mut trailing_rollup = false;
32003        let mut regular_expressions: Vec<&Expression> = Vec::new();
32004
32005        for expr in &e.expressions {
32006            match expr {
32007                Expression::Cube(c) if c.expressions.is_empty() => {
32008                    trailing_cube = true;
32009                }
32010                Expression::Rollup(r) if r.expressions.is_empty() => {
32011                    trailing_rollup = true;
32012                }
32013                _ => {
32014                    regular_expressions.push(expr);
32015                }
32016            }
32017        }
32018
32019        // In pretty mode, put columns on separate lines
32020        if self.config.pretty {
32021            self.write_newline();
32022            self.indent_level += 1;
32023            for (i, expr) in regular_expressions.iter().enumerate() {
32024                if i > 0 {
32025                    self.write(",");
32026                    self.write_newline();
32027                }
32028                self.write_indent();
32029                self.generate_expression(expr)?;
32030            }
32031            self.indent_level -= 1;
32032        } else {
32033            self.write_space();
32034            for (i, expr) in regular_expressions.iter().enumerate() {
32035                if i > 0 {
32036                    self.write(", ");
32037                }
32038                self.generate_expression(expr)?;
32039            }
32040        }
32041
32042        // Output trailing WITH CUBE or WITH ROLLUP
32043        if trailing_cube {
32044            self.write_space();
32045            self.write_keyword("WITH CUBE");
32046        } else if trailing_rollup {
32047            self.write_space();
32048            self.write_keyword("WITH ROLLUP");
32049        }
32050
32051        // ClickHouse: WITH TOTALS
32052        if e.totals {
32053            self.write_space();
32054            self.write_keyword("WITH TOTALS");
32055        }
32056
32057        Ok(())
32058    }
32059
32060    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
32061        let function_name = if e.expressions.len() > 1
32062            && matches!(
32063                self.config.dialect,
32064                Some(DialectType::TSQL | DialectType::Fabric)
32065            ) {
32066            "GROUPING_ID"
32067        } else {
32068            "GROUPING"
32069        };
32070        self.write_keyword(function_name);
32071        self.write("(");
32072        for (i, expr) in e.expressions.iter().enumerate() {
32073            if i > 0 {
32074                self.write(", ");
32075            }
32076            self.generate_expression(expr)?;
32077        }
32078        self.write(")");
32079        Ok(())
32080    }
32081
32082    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
32083        // GROUPING_ID(col1, col2, ...)
32084        self.write_keyword("GROUPING_ID");
32085        self.write("(");
32086        for (i, expr) in e.expressions.iter().enumerate() {
32087            if i > 0 {
32088                self.write(", ");
32089            }
32090            self.generate_expression(expr)?;
32091        }
32092        self.write(")");
32093        Ok(())
32094    }
32095
32096    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
32097        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
32098        self.write_keyword("GROUPING SETS");
32099        self.write(" (");
32100        for (i, expr) in e.expressions.iter().enumerate() {
32101            if i > 0 {
32102                self.write(", ");
32103            }
32104            self.generate_expression(expr)?;
32105        }
32106        self.write(")");
32107        Ok(())
32108    }
32109
32110    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
32111        // HASH_AGG(this, expressions...)
32112        self.write_keyword("HASH_AGG");
32113        self.write("(");
32114        self.generate_expression(&e.this)?;
32115        for expr in &e.expressions {
32116            self.write(", ");
32117            self.generate_expression(expr)?;
32118        }
32119        self.write(")");
32120        Ok(())
32121    }
32122
32123    fn generate_having(&mut self, e: &Having) -> Result<()> {
32124        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
32125        self.write_keyword("HAVING");
32126        self.write_space();
32127        self.generate_expression(&e.this)?;
32128        Ok(())
32129    }
32130
32131    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
32132        // Python: this HAVING MAX|MIN expression
32133        self.generate_expression(&e.this)?;
32134        self.write_space();
32135        self.write_keyword("HAVING");
32136        self.write_space();
32137        if e.max.is_some() {
32138            self.write_keyword("MAX");
32139        } else {
32140            self.write_keyword("MIN");
32141        }
32142        self.write_space();
32143        self.generate_expression(&e.expression)?;
32144        Ok(())
32145    }
32146
32147    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
32148        use crate::dialects::DialectType;
32149        // DuckDB: convert dollar-tagged strings to single-quoted
32150        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
32151            // Extract the string content and output as single-quoted
32152            if let Expression::Literal(ref lit) = *e.this {
32153                if let Literal::String(ref s) = lit.as_ref() {
32154                    return self.generate_string_literal(s);
32155                }
32156            }
32157        }
32158        // PostgreSQL: preserve dollar-quoting
32159        if matches!(
32160            self.config.dialect,
32161            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32162        ) {
32163            self.write("$");
32164            if let Some(tag) = &e.tag {
32165                self.generate_expression(tag)?;
32166            }
32167            self.write("$");
32168            self.generate_expression(&e.this)?;
32169            self.write("$");
32170            if let Some(tag) = &e.tag {
32171                self.generate_expression(tag)?;
32172            }
32173            self.write("$");
32174            return Ok(());
32175        }
32176        // Default: output as dollar-tagged
32177        self.write("$");
32178        if let Some(tag) = &e.tag {
32179            self.generate_expression(tag)?;
32180        }
32181        self.write("$");
32182        self.generate_expression(&e.this)?;
32183        self.write("$");
32184        if let Some(tag) = &e.tag {
32185            self.generate_expression(tag)?;
32186        }
32187        self.write("$");
32188        Ok(())
32189    }
32190
32191    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
32192        // HEX_ENCODE(this)
32193        self.write_keyword("HEX_ENCODE");
32194        self.write("(");
32195        self.generate_expression(&e.this)?;
32196        self.write(")");
32197        Ok(())
32198    }
32199
32200    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
32201        // Python: this (kind => expression)
32202        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
32203        match e.this.as_ref() {
32204            Expression::Identifier(id) => self.write(&id.name),
32205            other => self.generate_expression(other)?,
32206        }
32207        self.write(" (");
32208        self.write(&e.kind);
32209        self.write(" => ");
32210        self.generate_expression(&e.expression)?;
32211        self.write(")");
32212        Ok(())
32213    }
32214
32215    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
32216        // HLL(this, expressions...)
32217        self.write_keyword("HLL");
32218        self.write("(");
32219        self.generate_expression(&e.this)?;
32220        for expr in &e.expressions {
32221            self.write(", ");
32222            self.generate_expression(expr)?;
32223        }
32224        self.write(")");
32225        Ok(())
32226    }
32227
32228    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
32229        // Python: IN|OUT|IN OUT
32230        if e.input_.is_some() && e.output.is_some() {
32231            self.write_keyword("IN OUT");
32232        } else if e.input_.is_some() {
32233            self.write_keyword("IN");
32234        } else if e.output.is_some() {
32235            self.write_keyword("OUT");
32236        }
32237        Ok(())
32238    }
32239
32240    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
32241        // Python: INCLUDE this [column_def] [AS alias]
32242        self.write_keyword("INCLUDE");
32243        self.write_space();
32244        self.generate_expression(&e.this)?;
32245        if let Some(column_def) = &e.column_def {
32246            self.write_space();
32247            self.generate_expression(column_def)?;
32248        }
32249        if let Some(alias) = &e.alias {
32250            self.write_space();
32251            self.write_keyword("AS");
32252            self.write_space();
32253            self.write(alias);
32254        }
32255        Ok(())
32256    }
32257
32258    fn generate_index(&mut self, e: &Index) -> Result<()> {
32259        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
32260        if e.unique {
32261            self.write_keyword("UNIQUE");
32262            self.write_space();
32263        }
32264        if e.primary.is_some() {
32265            self.write_keyword("PRIMARY");
32266            self.write_space();
32267        }
32268        if e.amp.is_some() {
32269            self.write_keyword("AMP");
32270            self.write_space();
32271        }
32272        if e.table.is_none() {
32273            self.write_keyword("INDEX");
32274            self.write_space();
32275        }
32276        if let Some(name) = &e.this {
32277            self.generate_expression(name)?;
32278            self.write_space();
32279        }
32280        if let Some(table) = &e.table {
32281            self.write_keyword("ON");
32282            self.write_space();
32283            self.generate_expression(table)?;
32284        }
32285        if !e.params.is_empty() {
32286            self.write("(");
32287            for (i, param) in e.params.iter().enumerate() {
32288                if i > 0 {
32289                    self.write(", ");
32290                }
32291                self.generate_expression(param)?;
32292            }
32293            self.write(")");
32294        }
32295        Ok(())
32296    }
32297
32298    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
32299        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
32300        if let Some(kind) = &e.kind {
32301            self.write(kind);
32302            self.write_space();
32303        }
32304        self.write_keyword("INDEX");
32305        if let Some(this) = &e.this {
32306            self.write_space();
32307            self.generate_expression(this)?;
32308        }
32309        if let Some(index_type) = &e.index_type {
32310            self.write_space();
32311            self.write_keyword("USING");
32312            self.write_space();
32313            self.generate_expression(index_type)?;
32314        }
32315        if !e.expressions.is_empty() {
32316            self.write(" (");
32317            for (i, expr) in e.expressions.iter().enumerate() {
32318                if i > 0 {
32319                    self.write(", ");
32320                }
32321                self.generate_expression(expr)?;
32322            }
32323            self.write(")");
32324        }
32325        for opt in &e.options {
32326            self.write_space();
32327            self.generate_expression(opt)?;
32328        }
32329        Ok(())
32330    }
32331
32332    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
32333        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
32334        if let Some(key_block_size) = &e.key_block_size {
32335            self.write_keyword("KEY_BLOCK_SIZE");
32336            self.write(" = ");
32337            self.generate_expression(key_block_size)?;
32338        } else if let Some(using) = &e.using {
32339            self.write_keyword("USING");
32340            self.write_space();
32341            self.generate_expression(using)?;
32342        } else if let Some(parser) = &e.parser {
32343            self.write_keyword("WITH PARSER");
32344            self.write_space();
32345            self.generate_expression(parser)?;
32346        } else if let Some(comment) = &e.comment {
32347            self.write_keyword("COMMENT");
32348            self.write_space();
32349            self.generate_expression(comment)?;
32350        } else if let Some(visible) = &e.visible {
32351            self.generate_expression(visible)?;
32352        } else if let Some(engine_attr) = &e.engine_attr {
32353            self.write_keyword("ENGINE_ATTRIBUTE");
32354            self.write(" = ");
32355            self.generate_expression(engine_attr)?;
32356        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
32357            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
32358            self.write(" = ");
32359            self.generate_expression(secondary_engine_attr)?;
32360        }
32361        Ok(())
32362    }
32363
32364    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
32365        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
32366        if let Some(using) = &e.using {
32367            self.write_keyword("USING");
32368            self.write_space();
32369            self.generate_expression(using)?;
32370        }
32371        if !e.columns.is_empty() {
32372            self.write("(");
32373            for (i, col) in e.columns.iter().enumerate() {
32374                if i > 0 {
32375                    self.write(", ");
32376                }
32377                self.generate_expression(col)?;
32378            }
32379            self.write(")");
32380        }
32381        if let Some(partition_by) = &e.partition_by {
32382            self.write_space();
32383            self.write_keyword("PARTITION BY");
32384            self.write_space();
32385            self.generate_expression(partition_by)?;
32386        }
32387        if let Some(where_) = &e.where_ {
32388            self.write_space();
32389            self.generate_expression(where_)?;
32390        }
32391        if let Some(include) = &e.include {
32392            self.write_space();
32393            self.write_keyword("INCLUDE");
32394            self.write(" (");
32395            self.generate_expression(include)?;
32396            self.write(")");
32397        }
32398        if let Some(with_storage) = &e.with_storage {
32399            self.write_space();
32400            self.write_keyword("WITH");
32401            self.write(" (");
32402            self.generate_expression(with_storage)?;
32403            self.write(")");
32404        }
32405        if let Some(tablespace) = &e.tablespace {
32406            self.write_space();
32407            self.write_keyword("USING INDEX TABLESPACE");
32408            self.write_space();
32409            self.generate_expression(tablespace)?;
32410        }
32411        Ok(())
32412    }
32413
32414    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
32415        // Python: this INDEX [FOR target] (expressions)
32416        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
32417        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
32418        if let Expression::Identifier(id) = &*e.this {
32419            self.write_keyword(&id.name);
32420        } else {
32421            self.generate_expression(&e.this)?;
32422        }
32423        self.write_space();
32424        self.write_keyword("INDEX");
32425        if let Some(target) = &e.target {
32426            self.write_space();
32427            self.write_keyword("FOR");
32428            self.write_space();
32429            if let Expression::Identifier(id) = &**target {
32430                self.write_keyword(&id.name);
32431            } else {
32432                self.generate_expression(target)?;
32433            }
32434        }
32435        // Always output parentheses (even if empty, e.g. USE INDEX ())
32436        self.write(" (");
32437        for (i, expr) in e.expressions.iter().enumerate() {
32438            if i > 0 {
32439                self.write(", ");
32440            }
32441            self.generate_expression(expr)?;
32442        }
32443        self.write(")");
32444        Ok(())
32445    }
32446
32447    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
32448        // INHERITS (table1, table2, ...)
32449        self.write_keyword("INHERITS");
32450        self.write(" (");
32451        for (i, expr) in e.expressions.iter().enumerate() {
32452            if i > 0 {
32453                self.write(", ");
32454            }
32455            self.generate_expression(expr)?;
32456        }
32457        self.write(")");
32458        Ok(())
32459    }
32460
32461    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
32462        // INPUT(model)
32463        self.write_keyword("INPUT");
32464        self.write("(");
32465        self.generate_expression(&e.this)?;
32466        self.write(")");
32467        Ok(())
32468    }
32469
32470    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
32471        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
32472        if let Some(input_format) = &e.input_format {
32473            self.write_keyword("INPUTFORMAT");
32474            self.write_space();
32475            self.generate_expression(input_format)?;
32476        }
32477        if let Some(output_format) = &e.output_format {
32478            if e.input_format.is_some() {
32479                self.write(" ");
32480            }
32481            self.write_keyword("OUTPUTFORMAT");
32482            self.write_space();
32483            self.generate_expression(output_format)?;
32484        }
32485        Ok(())
32486    }
32487
32488    fn generate_install(&mut self, e: &Install) -> Result<()> {
32489        // [FORCE] INSTALL extension [FROM source]
32490        if e.force.is_some() {
32491            self.write_keyword("FORCE");
32492            self.write_space();
32493        }
32494        self.write_keyword("INSTALL");
32495        self.write_space();
32496        self.generate_expression(&e.this)?;
32497        if let Some(from) = &e.from_ {
32498            self.write_space();
32499            self.write_keyword("FROM");
32500            self.write_space();
32501            self.generate_expression(from)?;
32502        }
32503        Ok(())
32504    }
32505
32506    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
32507        // INTERVAL 'expression' unit
32508        self.write_keyword("INTERVAL");
32509        self.write_space();
32510        // When a unit is specified and the expression is a number,
32511        self.generate_expression(&e.expression)?;
32512        if let Some(unit) = &e.unit {
32513            self.write_space();
32514            self.write(unit);
32515        }
32516        Ok(())
32517    }
32518
32519    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
32520        // unit TO unit (e.g., HOUR TO SECOND)
32521        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
32522        self.write_space();
32523        self.write_keyword("TO");
32524        self.write_space();
32525        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
32526        Ok(())
32527    }
32528
32529    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
32530        // INTO [TEMPORARY|UNLOGGED] table
32531        self.write_keyword("INTO");
32532        if e.temporary {
32533            self.write_keyword(" TEMPORARY");
32534        }
32535        if e.unlogged.is_some() {
32536            self.write_keyword(" UNLOGGED");
32537        }
32538        if let Some(this) = &e.this {
32539            self.write_space();
32540            self.generate_expression(this)?;
32541        }
32542        if !e.expressions.is_empty() {
32543            self.write(" (");
32544            for (i, expr) in e.expressions.iter().enumerate() {
32545                if i > 0 {
32546                    self.write(", ");
32547                }
32548                self.generate_expression(expr)?;
32549            }
32550            self.write(")");
32551        }
32552        Ok(())
32553    }
32554
32555    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
32556        // Python: this expression (e.g., _utf8 'string')
32557        self.generate_expression(&e.this)?;
32558        self.write_space();
32559        self.generate_expression(&e.expression)?;
32560        Ok(())
32561    }
32562
32563    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
32564        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
32565        self.write_keyword("WITH");
32566        if e.no.is_some() {
32567            self.write_keyword(" NO");
32568        }
32569        if e.concurrent.is_some() {
32570            self.write_keyword(" CONCURRENT");
32571        }
32572        self.write_keyword(" ISOLATED LOADING");
32573        if let Some(target) = &e.target {
32574            self.write_space();
32575            self.generate_expression(target)?;
32576        }
32577        Ok(())
32578    }
32579
32580    fn generate_json(&mut self, e: &JSON) -> Result<()> {
32581        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
32582        self.write_keyword("JSON");
32583        if let Some(this) = &e.this {
32584            self.write_space();
32585            self.generate_expression(this)?;
32586        }
32587        if let Some(with_) = &e.with_ {
32588            // Check if it's a truthy boolean
32589            if let Expression::Boolean(b) = with_.as_ref() {
32590                if b.value {
32591                    self.write_keyword(" WITH");
32592                } else {
32593                    self.write_keyword(" WITHOUT");
32594                }
32595            }
32596        }
32597        if e.unique {
32598            self.write_keyword(" UNIQUE KEYS");
32599        }
32600        Ok(())
32601    }
32602
32603    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
32604        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
32605        self.write_keyword("JSON_ARRAY");
32606        self.write("(");
32607        for (i, expr) in e.expressions.iter().enumerate() {
32608            if i > 0 {
32609                self.write(", ");
32610            }
32611            self.generate_expression(expr)?;
32612        }
32613        if let Some(null_handling) = &e.null_handling {
32614            self.write_space();
32615            self.generate_expression(null_handling)?;
32616        }
32617        if let Some(return_type) = &e.return_type {
32618            self.write_space();
32619            self.write_keyword("RETURNING");
32620            self.write_space();
32621            self.generate_expression(return_type)?;
32622        }
32623        if e.strict.is_some() {
32624            self.write_space();
32625            self.write_keyword("STRICT");
32626        }
32627        self.write(")");
32628        Ok(())
32629    }
32630
32631    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
32632        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
32633        self.write_keyword("JSON_ARRAYAGG");
32634        self.write("(");
32635        self.generate_expression(&e.this)?;
32636        if let Some(order) = &e.order {
32637            self.write_space();
32638            // Order is stored as an OrderBy expression
32639            if let Expression::OrderBy(ob) = order.as_ref() {
32640                self.write_keyword("ORDER BY");
32641                self.write_space();
32642                for (i, ord) in ob.expressions.iter().enumerate() {
32643                    if i > 0 {
32644                        self.write(", ");
32645                    }
32646                    self.generate_ordered(ord)?;
32647                }
32648            } else {
32649                // Fallback: generate the expression directly
32650                self.generate_expression(order)?;
32651            }
32652        }
32653        if let Some(null_handling) = &e.null_handling {
32654            self.write_space();
32655            self.generate_expression(null_handling)?;
32656        }
32657        if let Some(return_type) = &e.return_type {
32658            self.write_space();
32659            self.write_keyword("RETURNING");
32660            self.write_space();
32661            self.generate_expression(return_type)?;
32662        }
32663        if e.strict.is_some() {
32664            self.write_space();
32665            self.write_keyword("STRICT");
32666        }
32667        self.write(")");
32668        Ok(())
32669    }
32670
32671    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
32672        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
32673        self.write_keyword("JSON_OBJECTAGG");
32674        self.write("(");
32675        for (i, expr) in e.expressions.iter().enumerate() {
32676            if i > 0 {
32677                self.write(", ");
32678            }
32679            self.generate_expression(expr)?;
32680        }
32681        if let Some(null_handling) = &e.null_handling {
32682            self.write_space();
32683            self.generate_expression(null_handling)?;
32684        }
32685        if let Some(unique_keys) = &e.unique_keys {
32686            self.write_space();
32687            if let Expression::Boolean(b) = unique_keys.as_ref() {
32688                if b.value {
32689                    self.write_keyword("WITH UNIQUE KEYS");
32690                } else {
32691                    self.write_keyword("WITHOUT UNIQUE KEYS");
32692                }
32693            }
32694        }
32695        if let Some(return_type) = &e.return_type {
32696            self.write_space();
32697            self.write_keyword("RETURNING");
32698            self.write_space();
32699            self.generate_expression(return_type)?;
32700        }
32701        self.write(")");
32702        Ok(())
32703    }
32704
32705    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
32706        // JSON_ARRAY_APPEND(this, path, value, ...)
32707        self.write_keyword("JSON_ARRAY_APPEND");
32708        self.write("(");
32709        self.generate_expression(&e.this)?;
32710        for expr in &e.expressions {
32711            self.write(", ");
32712            self.generate_expression(expr)?;
32713        }
32714        self.write(")");
32715        Ok(())
32716    }
32717
32718    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
32719        // JSON_ARRAY_CONTAINS(this, expression)
32720        self.write_keyword("JSON_ARRAY_CONTAINS");
32721        self.write("(");
32722        self.generate_expression(&e.this)?;
32723        self.write(", ");
32724        self.generate_expression(&e.expression)?;
32725        self.write(")");
32726        Ok(())
32727    }
32728
32729    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
32730        // JSON_ARRAY_INSERT(this, path, value, ...)
32731        self.write_keyword("JSON_ARRAY_INSERT");
32732        self.write("(");
32733        self.generate_expression(&e.this)?;
32734        for expr in &e.expressions {
32735            self.write(", ");
32736            self.generate_expression(expr)?;
32737        }
32738        self.write(")");
32739        Ok(())
32740    }
32741
32742    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
32743        // JSONB_EXISTS(this, path)
32744        self.write_keyword("JSONB_EXISTS");
32745        self.write("(");
32746        self.generate_expression(&e.this)?;
32747        if let Some(path) = &e.path {
32748            self.write(", ");
32749            self.generate_expression(path)?;
32750        }
32751        self.write(")");
32752        Ok(())
32753    }
32754
32755    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
32756        // JSONB_EXTRACT_SCALAR(this, expression)
32757        self.write_keyword("JSONB_EXTRACT_SCALAR");
32758        self.write("(");
32759        self.generate_expression(&e.this)?;
32760        self.write(", ");
32761        self.generate_expression(&e.expression)?;
32762        self.write(")");
32763        Ok(())
32764    }
32765
32766    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
32767        // JSONB_OBJECT_AGG(this, expression)
32768        self.write_keyword("JSONB_OBJECT_AGG");
32769        self.write("(");
32770        self.generate_expression(&e.this)?;
32771        self.write(", ");
32772        self.generate_expression(&e.expression)?;
32773        self.write(")");
32774        Ok(())
32775    }
32776
32777    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
32778        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
32779        if let Some(nested_schema) = &e.nested_schema {
32780            self.write_keyword("NESTED");
32781            if let Some(path) = &e.path {
32782                self.write_space();
32783                self.write_keyword("PATH");
32784                self.write_space();
32785                self.generate_expression(path)?;
32786            }
32787            self.write_space();
32788            self.generate_expression(nested_schema)?;
32789        } else {
32790            if let Some(this) = &e.this {
32791                self.generate_expression(this)?;
32792            }
32793            if let Some(kind) = &e.kind {
32794                self.write_space();
32795                self.write(kind);
32796            }
32797            if e.format_json {
32798                self.write_space();
32799                self.write_keyword("FORMAT JSON");
32800            }
32801            if let Some(path) = &e.path {
32802                self.write_space();
32803                self.write_keyword("PATH");
32804                self.write_space();
32805                self.generate_expression(path)?;
32806            }
32807            if e.ordinality.is_some() {
32808                self.write_keyword(" FOR ORDINALITY");
32809            }
32810        }
32811        Ok(())
32812    }
32813
32814    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
32815        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
32816        self.write_keyword("JSON_EXISTS");
32817        self.write("(");
32818        self.generate_expression(&e.this)?;
32819        if let Some(path) = &e.path {
32820            self.write(", ");
32821            self.generate_expression(path)?;
32822        }
32823        if let Some(passing) = &e.passing {
32824            self.write_space();
32825            self.write_keyword("PASSING");
32826            self.write_space();
32827            self.generate_expression(passing)?;
32828        }
32829        if let Some(on_condition) = &e.on_condition {
32830            self.write_space();
32831            self.generate_expression(on_condition)?;
32832        }
32833        self.write(")");
32834        Ok(())
32835    }
32836
32837    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
32838        self.generate_expression(&e.this)?;
32839        self.write(".:");
32840        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
32841        // wrap the entire type string in double quotes.
32842        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
32843        if Self::data_type_has_nested_expressions(&e.to) {
32844            // Generate the data type to a temporary string buffer, then wrap in quotes
32845            let saved = std::mem::take(&mut self.output);
32846            self.generate_data_type(&e.to)?;
32847            let type_sql = std::mem::replace(&mut self.output, saved);
32848            self.write("\"");
32849            self.write(&type_sql);
32850            self.write("\"");
32851        } else {
32852            self.generate_data_type(&e.to)?;
32853        }
32854        Ok(())
32855    }
32856
32857    /// Check if a DataType has nested type expressions (sub-types).
32858    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
32859    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
32860        matches!(
32861            dt,
32862            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
32863        )
32864    }
32865
32866    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
32867        // JSON_EXTRACT_ARRAY(this, expression)
32868        self.write_keyword("JSON_EXTRACT_ARRAY");
32869        self.write("(");
32870        self.generate_expression(&e.this)?;
32871        if let Some(expr) = &e.expression {
32872            self.write(", ");
32873            self.generate_expression(expr)?;
32874        }
32875        self.write(")");
32876        Ok(())
32877    }
32878
32879    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
32880        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
32881        if let Some(option) = &e.option {
32882            self.generate_expression(option)?;
32883            self.write_space();
32884        }
32885        self.write_keyword("QUOTES");
32886        if e.scalar.is_some() {
32887            self.write_keyword(" SCALAR_ONLY");
32888        }
32889        Ok(())
32890    }
32891
32892    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
32893        // JSON_EXTRACT_SCALAR(this, expression)
32894        self.write_keyword("JSON_EXTRACT_SCALAR");
32895        self.write("(");
32896        self.generate_expression(&e.this)?;
32897        self.write(", ");
32898        self.generate_expression(&e.expression)?;
32899        self.write(")");
32900        Ok(())
32901    }
32902
32903    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
32904        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
32905        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
32906        // Otherwise output JSON_EXTRACT(this, expression)
32907        if e.variant_extract.is_some() {
32908            use crate::dialects::DialectType;
32909            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
32910                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
32911                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
32912                // bracket notation: c:["x-y"] instead of c:x-y
32913                self.generate_expression(&e.this)?;
32914                self.write(":");
32915                match e.expression.as_ref() {
32916                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32917                        let Literal::String(s) = lit.as_ref() else {
32918                            unreachable!()
32919                        };
32920                        self.write_databricks_json_path(s);
32921                    }
32922                    _ => {
32923                        // Fallback: generate as-is (shouldn't happen in typical cases)
32924                        self.generate_expression(&e.expression)?;
32925                    }
32926                }
32927            } else {
32928                // Snowflake and others: use GET_PATH(col, 'path')
32929                self.write_keyword("GET_PATH");
32930                self.write("(");
32931                self.generate_expression(&e.this)?;
32932                self.write(", ");
32933                self.generate_expression(&e.expression)?;
32934                self.write(")");
32935            }
32936        } else {
32937            self.write_keyword("JSON_EXTRACT");
32938            self.write("(");
32939            self.generate_expression(&e.this)?;
32940            self.write(", ");
32941            self.generate_expression(&e.expression)?;
32942            for expr in &e.expressions {
32943                self.write(", ");
32944                self.generate_expression(expr)?;
32945            }
32946            self.write(")");
32947        }
32948        Ok(())
32949    }
32950
32951    /// Write a Databricks JSON colon-path, using bracket notation for keys
32952    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
32953    /// Safe identifier regex: ^[_a-zA-Z]\w*$
32954    fn write_databricks_json_path(&mut self, path: &str) {
32955        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
32956        // it was already formatted by the parser - output as-is
32957        if path.starts_with("[\"") || path.starts_with("['") {
32958            self.write(path);
32959            return;
32960        }
32961        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
32962        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
32963        // e.g., "x-y" -> ["x-y"]
32964        let mut first = true;
32965        for segment in path.split('.') {
32966            if !first {
32967                self.write(".");
32968            }
32969            first = false;
32970            // Check if there's a bracket subscript in this segment: "items[0]"
32971            if let Some(bracket_pos) = segment.find('[') {
32972                let key = &segment[..bracket_pos];
32973                let subscript = &segment[bracket_pos..];
32974                if key.is_empty() {
32975                    // Bracket notation at start of segment (e.g., already formatted)
32976                    self.write(segment);
32977                } else if Self::is_safe_json_path_key(key) {
32978                    self.write(key);
32979                    self.write(subscript);
32980                } else {
32981                    self.write("[\"");
32982                    self.write(key);
32983                    self.write("\"]");
32984                    self.write(subscript);
32985                }
32986            } else if Self::is_safe_json_path_key(segment) {
32987                self.write(segment);
32988            } else {
32989                self.write("[\"");
32990                self.write(segment);
32991                self.write("\"]");
32992            }
32993        }
32994    }
32995
32996    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
32997    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
32998    fn is_safe_json_path_key(key: &str) -> bool {
32999        if key.is_empty() {
33000            return false;
33001        }
33002        let mut chars = key.chars();
33003        let first = chars.next().unwrap();
33004        if first != '_' && !first.is_ascii_alphabetic() {
33005            return false;
33006        }
33007        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
33008    }
33009
33010    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
33011        // Output: {expr} FORMAT JSON
33012        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
33013        if let Some(this) = &e.this {
33014            self.generate_expression(this)?;
33015            self.write_space();
33016        }
33017        self.write_keyword("FORMAT JSON");
33018        Ok(())
33019    }
33020
33021    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
33022        // key: value (for JSON objects)
33023        self.generate_expression(&e.this)?;
33024        self.write(": ");
33025        self.generate_expression(&e.expression)?;
33026        Ok(())
33027    }
33028
33029    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
33030        // JSON_KEYS(this, expression, expressions...)
33031        self.write_keyword("JSON_KEYS");
33032        self.write("(");
33033        self.generate_expression(&e.this)?;
33034        if let Some(expr) = &e.expression {
33035            self.write(", ");
33036            self.generate_expression(expr)?;
33037        }
33038        for expr in &e.expressions {
33039            self.write(", ");
33040            self.generate_expression(expr)?;
33041        }
33042        self.write(")");
33043        Ok(())
33044    }
33045
33046    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
33047        // JSON_KEYS(this, expression)
33048        self.write_keyword("JSON_KEYS");
33049        self.write("(");
33050        self.generate_expression(&e.this)?;
33051        if let Some(expr) = &e.expression {
33052            self.write(", ");
33053            self.generate_expression(expr)?;
33054        }
33055        self.write(")");
33056        Ok(())
33057    }
33058
33059    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
33060        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
33061        // The path components are concatenated without spaces
33062        let mut path_str = String::new();
33063        for expr in &e.expressions {
33064            match expr {
33065                Expression::JSONPathRoot(_) => {
33066                    path_str.push('$');
33067                }
33068                Expression::JSONPathKey(k) => {
33069                    // .key or ."key" (quote if key has special characters)
33070                    if let Expression::Literal(lit) = k.this.as_ref() {
33071                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
33072                            path_str.push('.');
33073                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
33074                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
33075                            if needs_quoting {
33076                                path_str.push('"');
33077                                path_str.push_str(s);
33078                                path_str.push('"');
33079                            } else {
33080                                path_str.push_str(s);
33081                            }
33082                        }
33083                    }
33084                }
33085                Expression::JSONPathSubscript(s) => {
33086                    // [index]
33087                    if let Expression::Literal(lit) = s.this.as_ref() {
33088                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
33089                            path_str.push('[');
33090                            path_str.push_str(n);
33091                            path_str.push(']');
33092                        }
33093                    }
33094                }
33095                _ => {
33096                    // For other path parts, try to generate them
33097                    let mut temp_gen = Self::with_arc_config(self.config.clone());
33098                    temp_gen.generate_expression(expr)?;
33099                    path_str.push_str(&temp_gen.output);
33100                }
33101            }
33102        }
33103        // Output as quoted string
33104        self.write("'");
33105        self.write(&path_str);
33106        self.write("'");
33107        Ok(())
33108    }
33109
33110    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
33111        // JSON path filter: ?(predicate)
33112        self.write("?(");
33113        self.generate_expression(&e.this)?;
33114        self.write(")");
33115        Ok(())
33116    }
33117
33118    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
33119        // JSON path key: .key or ["key"]
33120        self.write(".");
33121        self.generate_expression(&e.this)?;
33122        Ok(())
33123    }
33124
33125    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
33126        // JSON path recursive descent: ..
33127        self.write("..");
33128        if let Some(this) = &e.this {
33129            self.generate_expression(this)?;
33130        }
33131        Ok(())
33132    }
33133
33134    fn generate_json_path_root(&mut self) -> Result<()> {
33135        // JSON path root: $
33136        self.write("$");
33137        Ok(())
33138    }
33139
33140    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
33141        // JSON path script: (expression)
33142        self.write("(");
33143        self.generate_expression(&e.this)?;
33144        self.write(")");
33145        Ok(())
33146    }
33147
33148    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
33149        // JSON path selector: *
33150        self.generate_expression(&e.this)?;
33151        Ok(())
33152    }
33153
33154    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
33155        // JSON path slice: [start:end:step]
33156        self.write("[");
33157        if let Some(start) = &e.start {
33158            self.generate_expression(start)?;
33159        }
33160        self.write(":");
33161        if let Some(end) = &e.end {
33162            self.generate_expression(end)?;
33163        }
33164        if let Some(step) = &e.step {
33165            self.write(":");
33166            self.generate_expression(step)?;
33167        }
33168        self.write("]");
33169        Ok(())
33170    }
33171
33172    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
33173        // JSON path subscript: [index] or [*]
33174        self.write("[");
33175        self.generate_expression(&e.this)?;
33176        self.write("]");
33177        Ok(())
33178    }
33179
33180    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
33181        // JSON path union: [key1, key2, ...]
33182        self.write("[");
33183        for (i, expr) in e.expressions.iter().enumerate() {
33184            if i > 0 {
33185                self.write(", ");
33186            }
33187            self.generate_expression(expr)?;
33188        }
33189        self.write("]");
33190        Ok(())
33191    }
33192
33193    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
33194        // JSON_REMOVE(this, path1, path2, ...)
33195        self.write_keyword("JSON_REMOVE");
33196        self.write("(");
33197        self.generate_expression(&e.this)?;
33198        for expr in &e.expressions {
33199            self.write(", ");
33200            self.generate_expression(expr)?;
33201        }
33202        self.write(")");
33203        Ok(())
33204    }
33205
33206    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
33207        // COLUMNS(col1 type, col2 type, ...)
33208        // When pretty printing and content is too wide, format with each column on a separate line
33209        self.write_keyword("COLUMNS");
33210        self.write("(");
33211
33212        if self.config.pretty && !e.expressions.is_empty() {
33213            // First, generate all expressions into strings to check width
33214            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
33215            for expr in &e.expressions {
33216                let mut temp_gen = Generator::with_arc_config(self.config.clone());
33217                temp_gen.generate_expression(expr)?;
33218                expr_strings.push(temp_gen.output);
33219            }
33220
33221            // Check if total width exceeds max_text_width
33222            if self.too_wide(&expr_strings) {
33223                // Pretty print: each column on its own line
33224                self.write_newline();
33225                self.indent_level += 1;
33226                for (i, expr_str) in expr_strings.iter().enumerate() {
33227                    if i > 0 {
33228                        self.write(",");
33229                        self.write_newline();
33230                    }
33231                    self.write_indent();
33232                    self.write(expr_str);
33233                }
33234                self.write_newline();
33235                self.indent_level -= 1;
33236                self.write_indent();
33237            } else {
33238                // Compact: all on one line
33239                for (i, expr_str) in expr_strings.iter().enumerate() {
33240                    if i > 0 {
33241                        self.write(", ");
33242                    }
33243                    self.write(expr_str);
33244                }
33245            }
33246        } else {
33247            // Non-pretty mode: compact format
33248            for (i, expr) in e.expressions.iter().enumerate() {
33249                if i > 0 {
33250                    self.write(", ");
33251                }
33252                self.generate_expression(expr)?;
33253            }
33254        }
33255        self.write(")");
33256        Ok(())
33257    }
33258
33259    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
33260        // JSON_SET(this, path, value, ...)
33261        self.write_keyword("JSON_SET");
33262        self.write("(");
33263        self.generate_expression(&e.this)?;
33264        for expr in &e.expressions {
33265            self.write(", ");
33266            self.generate_expression(expr)?;
33267        }
33268        self.write(")");
33269        Ok(())
33270    }
33271
33272    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
33273        // JSON_STRIP_NULLS(this, expression)
33274        self.write_keyword("JSON_STRIP_NULLS");
33275        self.write("(");
33276        self.generate_expression(&e.this)?;
33277        if let Some(expr) = &e.expression {
33278            self.write(", ");
33279            self.generate_expression(expr)?;
33280        }
33281        self.write(")");
33282        Ok(())
33283    }
33284
33285    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
33286        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
33287        self.write_keyword("JSON_TABLE");
33288        self.write("(");
33289        self.generate_expression(&e.this)?;
33290        if let Some(path) = &e.path {
33291            self.write(", ");
33292            self.generate_expression(path)?;
33293        }
33294        if let Some(error_handling) = &e.error_handling {
33295            self.write_space();
33296            self.generate_expression(error_handling)?;
33297        }
33298        if let Some(empty_handling) = &e.empty_handling {
33299            self.write_space();
33300            self.generate_expression(empty_handling)?;
33301        }
33302        if let Some(schema) = &e.schema {
33303            self.write_space();
33304            self.generate_expression(schema)?;
33305        }
33306        self.write(")");
33307        Ok(())
33308    }
33309
33310    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
33311        // JSON_TYPE(this)
33312        self.write_keyword("JSON_TYPE");
33313        self.write("(");
33314        self.generate_expression(&e.this)?;
33315        self.write(")");
33316        Ok(())
33317    }
33318
33319    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
33320        // JSON_VALUE(this, path RETURNING type ON condition)
33321        self.write_keyword("JSON_VALUE");
33322        self.write("(");
33323        self.generate_expression(&e.this)?;
33324        if let Some(path) = &e.path {
33325            self.write(", ");
33326            self.generate_expression(path)?;
33327        }
33328        if let Some(returning) = &e.returning {
33329            self.write_space();
33330            self.write_keyword("RETURNING");
33331            self.write_space();
33332            self.generate_expression(returning)?;
33333        }
33334        if let Some(on_condition) = &e.on_condition {
33335            self.write_space();
33336            self.generate_expression(on_condition)?;
33337        }
33338        self.write(")");
33339        Ok(())
33340    }
33341
33342    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
33343        // JSON_VALUE_ARRAY(this)
33344        self.write_keyword("JSON_VALUE_ARRAY");
33345        self.write("(");
33346        self.generate_expression(&e.this)?;
33347        self.write(")");
33348        Ok(())
33349    }
33350
33351    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
33352        // JAROWINKLER_SIMILARITY(str1, str2)
33353        self.write_keyword("JAROWINKLER_SIMILARITY");
33354        self.write("(");
33355        self.generate_expression(&e.this)?;
33356        self.write(", ");
33357        self.generate_expression(&e.expression)?;
33358        self.write(")");
33359        Ok(())
33360    }
33361
33362    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
33363        // Python: this(expressions)
33364        self.generate_expression(&e.this)?;
33365        self.write("(");
33366        for (i, expr) in e.expressions.iter().enumerate() {
33367            if i > 0 {
33368                self.write(", ");
33369            }
33370            self.generate_expression(expr)?;
33371        }
33372        self.write(")");
33373        Ok(())
33374    }
33375
33376    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
33377        // Python: {no}{local}{dual}{before}{after}JOURNAL
33378        if e.no.is_some() {
33379            self.write_keyword("NO ");
33380        }
33381        if let Some(local) = &e.local {
33382            self.generate_expression(local)?;
33383            self.write_space();
33384        }
33385        if e.dual.is_some() {
33386            self.write_keyword("DUAL ");
33387        }
33388        if e.before.is_some() {
33389            self.write_keyword("BEFORE ");
33390        }
33391        if e.after.is_some() {
33392            self.write_keyword("AFTER ");
33393        }
33394        self.write_keyword("JOURNAL");
33395        Ok(())
33396    }
33397
33398    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
33399        // LANGUAGE language_name
33400        self.write_keyword("LANGUAGE");
33401        self.write_space();
33402        self.generate_expression(&e.this)?;
33403        Ok(())
33404    }
33405
33406    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
33407        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
33408        if e.view.is_some() {
33409            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
33410            self.write_keyword("LATERAL VIEW");
33411            if e.outer.is_some() {
33412                self.write_space();
33413                self.write_keyword("OUTER");
33414            }
33415            self.write_space();
33416            self.generate_expression(&e.this)?;
33417            if let Some(alias) = &e.alias {
33418                self.write_space();
33419                self.write(alias);
33420            }
33421        } else {
33422            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
33423            self.write_keyword("LATERAL");
33424            self.write_space();
33425            self.generate_expression(&e.this)?;
33426            if e.ordinality.is_some() {
33427                self.write_space();
33428                self.write_keyword("WITH ORDINALITY");
33429            }
33430            if let Some(alias) = &e.alias {
33431                self.write_space();
33432                self.write_keyword("AS");
33433                self.write_space();
33434                self.write(alias);
33435                if !e.column_aliases.is_empty() {
33436                    self.write("(");
33437                    for (i, col) in e.column_aliases.iter().enumerate() {
33438                        if i > 0 {
33439                            self.write(", ");
33440                        }
33441                        self.write(col);
33442                    }
33443                    self.write(")");
33444                }
33445            }
33446        }
33447        Ok(())
33448    }
33449
33450    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
33451        // Python: LIKE this [options]
33452        self.write_keyword("LIKE");
33453        self.write_space();
33454        self.generate_expression(&e.this)?;
33455        for expr in &e.expressions {
33456            self.write_space();
33457            self.generate_expression(expr)?;
33458        }
33459        Ok(())
33460    }
33461
33462    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
33463        self.write_keyword("LIMIT");
33464        self.write_space();
33465        self.write_limit_expr(&e.this)?;
33466        if e.percent {
33467            self.write_space();
33468            self.write_keyword("PERCENT");
33469        }
33470        // Emit any comments that were captured from before the LIMIT keyword
33471        for comment in &e.comments {
33472            self.write(" ");
33473            self.write_formatted_comment(comment);
33474        }
33475        Ok(())
33476    }
33477
33478    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
33479        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
33480        if e.percent.is_some() {
33481            self.write_keyword(" PERCENT");
33482        }
33483        if e.rows.is_some() {
33484            self.write_keyword(" ROWS");
33485        }
33486        if e.with_ties.is_some() {
33487            self.write_keyword(" WITH TIES");
33488        } else if e.rows.is_some() {
33489            self.write_keyword(" ONLY");
33490        }
33491        Ok(())
33492    }
33493
33494    fn generate_list(&mut self, e: &List) -> Result<()> {
33495        use crate::dialects::DialectType;
33496        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
33497
33498        // Check if this is a subquery-based list (LIST(SELECT ...))
33499        if e.expressions.len() == 1 {
33500            if let Expression::Select(_) = &e.expressions[0] {
33501                self.write_keyword("LIST");
33502                self.write("(");
33503                self.generate_expression(&e.expressions[0])?;
33504                self.write(")");
33505                return Ok(());
33506            }
33507        }
33508
33509        // For Materialize, output as LIST[expr, expr, ...]
33510        if is_materialize {
33511            self.write_keyword("LIST");
33512            self.write("[");
33513            for (i, expr) in e.expressions.iter().enumerate() {
33514                if i > 0 {
33515                    self.write(", ");
33516                }
33517                self.generate_expression(expr)?;
33518            }
33519            self.write("]");
33520        } else {
33521            // For other dialects, output as LIST(expr, expr, ...)
33522            self.write_keyword("LIST");
33523            self.write("(");
33524            for (i, expr) in e.expressions.iter().enumerate() {
33525                if i > 0 {
33526                    self.write(", ");
33527                }
33528                self.generate_expression(expr)?;
33529            }
33530            self.write(")");
33531        }
33532        Ok(())
33533    }
33534
33535    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
33536        // Check if this is a subquery-based map (MAP(SELECT ...))
33537        if let Expression::Select(_) = &*e.this {
33538            self.write_keyword("MAP");
33539            self.write("(");
33540            self.generate_expression(&e.this)?;
33541            self.write(")");
33542            return Ok(());
33543        }
33544
33545        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
33546
33547        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
33548        self.write_keyword("MAP");
33549        if is_duckdb {
33550            self.write(" {");
33551        } else {
33552            self.write("[");
33553        }
33554        if let Expression::Struct(s) = &*e.this {
33555            for (i, (_, expr)) in s.fields.iter().enumerate() {
33556                if i > 0 {
33557                    self.write(", ");
33558                }
33559                if let Expression::PropertyEQ(op) = expr {
33560                    self.generate_expression(&op.left)?;
33561                    if is_duckdb {
33562                        self.write(": ");
33563                    } else {
33564                        self.write(" => ");
33565                    }
33566                    self.generate_expression(&op.right)?;
33567                } else {
33568                    self.generate_expression(expr)?;
33569                }
33570            }
33571        }
33572        if is_duckdb {
33573            self.write("}");
33574        } else {
33575            self.write("]");
33576        }
33577        Ok(())
33578    }
33579
33580    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
33581        // Python: LOCALTIME or LOCALTIME(precision)
33582        self.write_keyword("LOCALTIME");
33583        if let Some(precision) = &e.this {
33584            self.write("(");
33585            self.generate_expression(precision)?;
33586            self.write(")");
33587        }
33588        Ok(())
33589    }
33590
33591    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
33592        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
33593        self.write_keyword("LOCALTIMESTAMP");
33594        if let Some(precision) = &e.this {
33595            self.write("(");
33596            self.generate_expression(precision)?;
33597            self.write(")");
33598        }
33599        Ok(())
33600    }
33601
33602    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
33603        // LOCATION 'path'
33604        self.write_keyword("LOCATION");
33605        self.write_space();
33606        self.generate_expression(&e.this)?;
33607        Ok(())
33608    }
33609
33610    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
33611        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
33612        if e.update.is_some() {
33613            if e.key.is_some() {
33614                self.write_keyword("FOR NO KEY UPDATE");
33615            } else {
33616                self.write_keyword("FOR UPDATE");
33617            }
33618        } else {
33619            if e.key.is_some() {
33620                self.write_keyword("FOR KEY SHARE");
33621            } else {
33622                self.write_keyword("FOR SHARE");
33623            }
33624        }
33625        if !e.expressions.is_empty() {
33626            self.write_keyword(" OF ");
33627            for (i, expr) in e.expressions.iter().enumerate() {
33628                if i > 0 {
33629                    self.write(", ");
33630                }
33631                self.generate_expression(expr)?;
33632            }
33633        }
33634        // Handle wait option following Python sqlglot convention:
33635        // - Boolean(true) -> NOWAIT
33636        // - Boolean(false) -> SKIP LOCKED
33637        // - Literal (number) -> WAIT n
33638        if let Some(wait) = &e.wait {
33639            match wait.as_ref() {
33640                Expression::Boolean(b) => {
33641                    if b.value {
33642                        self.write_keyword(" NOWAIT");
33643                    } else {
33644                        self.write_keyword(" SKIP LOCKED");
33645                    }
33646                }
33647                _ => {
33648                    // It's a literal (number), output WAIT n
33649                    self.write_keyword(" WAIT ");
33650                    self.generate_expression(wait)?;
33651                }
33652            }
33653        }
33654        Ok(())
33655    }
33656
33657    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
33658        // LOCK property
33659        self.write_keyword("LOCK");
33660        self.write_space();
33661        self.generate_expression(&e.this)?;
33662        Ok(())
33663    }
33664
33665    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
33666        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
33667        self.write_keyword("LOCKING");
33668        self.write_space();
33669        self.write(&e.kind);
33670        if let Some(this) = &e.this {
33671            self.write_space();
33672            self.generate_expression(this)?;
33673        }
33674        if let Some(for_or_in) = &e.for_or_in {
33675            self.write_space();
33676            self.generate_expression(for_or_in)?;
33677        }
33678        if let Some(lock_type) = &e.lock_type {
33679            self.write_space();
33680            self.generate_expression(lock_type)?;
33681        }
33682        if e.override_.is_some() {
33683            self.write_keyword(" OVERRIDE");
33684        }
33685        Ok(())
33686    }
33687
33688    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
33689        // this expression
33690        self.generate_expression(&e.this)?;
33691        self.write_space();
33692        self.generate_expression(&e.expression)?;
33693        Ok(())
33694    }
33695
33696    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
33697        // [NO] LOG
33698        if e.no.is_some() {
33699            self.write_keyword("NO ");
33700        }
33701        self.write_keyword("LOG");
33702        Ok(())
33703    }
33704
33705    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
33706        // MD5(this, expressions...)
33707        self.write_keyword("MD5");
33708        self.write("(");
33709        self.generate_expression(&e.this)?;
33710        for expr in &e.expressions {
33711            self.write(", ");
33712            self.generate_expression(expr)?;
33713        }
33714        self.write(")");
33715        Ok(())
33716    }
33717
33718    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
33719        // ML.FORECAST(model, [params])
33720        self.write_keyword("ML.FORECAST");
33721        self.write("(");
33722        self.generate_expression(&e.this)?;
33723        if let Some(expression) = &e.expression {
33724            self.write(", ");
33725            self.generate_expression(expression)?;
33726        }
33727        if let Some(params) = &e.params_struct {
33728            self.write(", ");
33729            self.generate_expression(params)?;
33730        }
33731        self.write(")");
33732        Ok(())
33733    }
33734
33735    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
33736        // ML.TRANSLATE(model, input, [params])
33737        self.write_keyword("ML.TRANSLATE");
33738        self.write("(");
33739        self.generate_expression(&e.this)?;
33740        self.write(", ");
33741        self.generate_expression(&e.expression)?;
33742        if let Some(params) = &e.params_struct {
33743            self.write(", ");
33744            self.generate_expression(params)?;
33745        }
33746        self.write(")");
33747        Ok(())
33748    }
33749
33750    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
33751        // MAKE_INTERVAL(years => x, months => y, ...)
33752        self.write_keyword("MAKE_INTERVAL");
33753        self.write("(");
33754        let mut first = true;
33755        if let Some(year) = &e.year {
33756            self.write("years => ");
33757            self.generate_expression(year)?;
33758            first = false;
33759        }
33760        if let Some(month) = &e.month {
33761            if !first {
33762                self.write(", ");
33763            }
33764            self.write("months => ");
33765            self.generate_expression(month)?;
33766            first = false;
33767        }
33768        if let Some(week) = &e.week {
33769            if !first {
33770                self.write(", ");
33771            }
33772            self.write("weeks => ");
33773            self.generate_expression(week)?;
33774            first = false;
33775        }
33776        if let Some(day) = &e.day {
33777            if !first {
33778                self.write(", ");
33779            }
33780            self.write("days => ");
33781            self.generate_expression(day)?;
33782            first = false;
33783        }
33784        if let Some(hour) = &e.hour {
33785            if !first {
33786                self.write(", ");
33787            }
33788            self.write("hours => ");
33789            self.generate_expression(hour)?;
33790            first = false;
33791        }
33792        if let Some(minute) = &e.minute {
33793            if !first {
33794                self.write(", ");
33795            }
33796            self.write("mins => ");
33797            self.generate_expression(minute)?;
33798            first = false;
33799        }
33800        if let Some(second) = &e.second {
33801            if !first {
33802                self.write(", ");
33803            }
33804            self.write("secs => ");
33805            self.generate_expression(second)?;
33806        }
33807        self.write(")");
33808        Ok(())
33809    }
33810
33811    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
33812        // MANHATTAN_DISTANCE(vector1, vector2)
33813        self.write_keyword("MANHATTAN_DISTANCE");
33814        self.write("(");
33815        self.generate_expression(&e.this)?;
33816        self.write(", ");
33817        self.generate_expression(&e.expression)?;
33818        self.write(")");
33819        Ok(())
33820    }
33821
33822    fn generate_map(&mut self, e: &Map) -> Result<()> {
33823        // MAP(key1, value1, key2, value2, ...)
33824        self.write_keyword("MAP");
33825        self.write("(");
33826        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
33827            if i > 0 {
33828                self.write(", ");
33829            }
33830            self.generate_expression(key)?;
33831            self.write(", ");
33832            self.generate_expression(value)?;
33833        }
33834        self.write(")");
33835        Ok(())
33836    }
33837
33838    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
33839        // MAP_CAT(map1, map2)
33840        self.write_keyword("MAP_CAT");
33841        self.write("(");
33842        self.generate_expression(&e.this)?;
33843        self.write(", ");
33844        self.generate_expression(&e.expression)?;
33845        self.write(")");
33846        Ok(())
33847    }
33848
33849    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
33850        // MAP_DELETE(map, key1, key2, ...)
33851        self.write_keyword("MAP_DELETE");
33852        self.write("(");
33853        self.generate_expression(&e.this)?;
33854        for expr in &e.expressions {
33855            self.write(", ");
33856            self.generate_expression(expr)?;
33857        }
33858        self.write(")");
33859        Ok(())
33860    }
33861
33862    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
33863        // MAP_INSERT(map, key, value, [update_flag])
33864        self.write_keyword("MAP_INSERT");
33865        self.write("(");
33866        self.generate_expression(&e.this)?;
33867        if let Some(key) = &e.key {
33868            self.write(", ");
33869            self.generate_expression(key)?;
33870        }
33871        if let Some(value) = &e.value {
33872            self.write(", ");
33873            self.generate_expression(value)?;
33874        }
33875        if let Some(update_flag) = &e.update_flag {
33876            self.write(", ");
33877            self.generate_expression(update_flag)?;
33878        }
33879        self.write(")");
33880        Ok(())
33881    }
33882
33883    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
33884        // MAP_PICK(map, key1, key2, ...)
33885        self.write_keyword("MAP_PICK");
33886        self.write("(");
33887        self.generate_expression(&e.this)?;
33888        for expr in &e.expressions {
33889            self.write(", ");
33890            self.generate_expression(expr)?;
33891        }
33892        self.write(")");
33893        Ok(())
33894    }
33895
33896    fn generate_masking_policy_column_constraint(
33897        &mut self,
33898        e: &MaskingPolicyColumnConstraint,
33899    ) -> Result<()> {
33900        // Python: MASKING POLICY name [USING (cols)]
33901        self.write_keyword("MASKING POLICY");
33902        self.write_space();
33903        self.generate_expression(&e.this)?;
33904        if !e.expressions.is_empty() {
33905            self.write_keyword(" USING");
33906            self.write(" (");
33907            for (i, expr) in e.expressions.iter().enumerate() {
33908                if i > 0 {
33909                    self.write(", ");
33910                }
33911                self.generate_expression(expr)?;
33912            }
33913            self.write(")");
33914        }
33915        Ok(())
33916    }
33917
33918    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
33919        if matches!(
33920            self.config.dialect,
33921            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33922        ) {
33923            if e.expressions.len() > 1 {
33924                self.write("(");
33925            }
33926            for (i, expr) in e.expressions.iter().enumerate() {
33927                if i > 0 {
33928                    self.write_keyword(" OR ");
33929                }
33930                self.generate_expression(expr)?;
33931                self.write_space();
33932                self.write("@@");
33933                self.write_space();
33934                self.generate_expression(&e.this)?;
33935            }
33936            if e.expressions.len() > 1 {
33937                self.write(")");
33938            }
33939            return Ok(());
33940        }
33941
33942        // MATCH(columns) AGAINST(expr [modifier])
33943        self.write_keyword("MATCH");
33944        self.write("(");
33945        for (i, expr) in e.expressions.iter().enumerate() {
33946            if i > 0 {
33947                self.write(", ");
33948            }
33949            self.generate_expression(expr)?;
33950        }
33951        self.write(")");
33952        self.write_keyword(" AGAINST");
33953        self.write("(");
33954        self.generate_expression(&e.this)?;
33955        if let Some(modifier) = &e.modifier {
33956            self.write_space();
33957            self.generate_expression(modifier)?;
33958        }
33959        self.write(")");
33960        Ok(())
33961    }
33962
33963    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
33964        // Python: [window_frame] this
33965        if let Some(window_frame) = &e.window_frame {
33966            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
33967            self.write_space();
33968        }
33969        self.generate_expression(&e.this)?;
33970        Ok(())
33971    }
33972
33973    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
33974        // MATERIALIZED [this]
33975        self.write_keyword("MATERIALIZED");
33976        if let Some(this) = &e.this {
33977            self.write_space();
33978            self.generate_expression(this)?;
33979        }
33980        Ok(())
33981    }
33982
33983    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
33984        // MERGE INTO target USING source ON condition WHEN ...
33985        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
33986        if let Some(with_) = &e.with_ {
33987            if let Expression::With(with_clause) = with_.as_ref() {
33988                self.generate_with(with_clause)?;
33989                self.write_space();
33990            } else {
33991                self.generate_expression(with_)?;
33992                self.write_space();
33993            }
33994        }
33995        self.write_keyword("MERGE INTO");
33996        self.write_space();
33997        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
33998            if let Expression::Alias(alias) = e.this.as_ref() {
33999                self.generate_expression(&alias.this)?;
34000                self.write_space();
34001                self.generate_identifier(&alias.alias)?;
34002            } else {
34003                self.generate_expression(&e.this)?;
34004            }
34005        } else {
34006            self.generate_expression(&e.this)?;
34007        }
34008
34009        // USING clause - newline before in pretty mode
34010        if self.config.pretty {
34011            self.write_newline();
34012            self.write_indent();
34013        } else {
34014            self.write_space();
34015        }
34016        self.write_keyword("USING");
34017        self.write_space();
34018        self.generate_expression(&e.using)?;
34019
34020        // ON clause - newline before in pretty mode
34021        if let Some(on) = &e.on {
34022            if self.config.pretty {
34023                self.write_newline();
34024                self.write_indent();
34025            } else {
34026                self.write_space();
34027            }
34028            self.write_keyword("ON");
34029            self.write_space();
34030            self.generate_expression(on)?;
34031        }
34032        // DuckDB USING (key_columns) clause
34033        if let Some(using_cond) = &e.using_cond {
34034            self.write_space();
34035            self.write_keyword("USING");
34036            self.write_space();
34037            self.write("(");
34038            // using_cond is a Tuple containing the column identifiers
34039            if let Expression::Tuple(tuple) = using_cond.as_ref() {
34040                for (i, col) in tuple.expressions.iter().enumerate() {
34041                    if i > 0 {
34042                        self.write(", ");
34043                    }
34044                    self.generate_expression(col)?;
34045                }
34046            } else {
34047                self.generate_expression(using_cond)?;
34048            }
34049            self.write(")");
34050        }
34051        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
34052        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
34053        if matches!(
34054            self.config.dialect,
34055            Some(crate::DialectType::PostgreSQL)
34056                | Some(crate::DialectType::Redshift)
34057                | Some(crate::DialectType::Trino)
34058                | Some(crate::DialectType::Presto)
34059                | Some(crate::DialectType::Athena)
34060        ) {
34061            let mut names = Vec::new();
34062            match e.this.as_ref() {
34063                Expression::Alias(a) => {
34064                    // e.g., "x AS z" -> strip both "x" and "z"
34065                    if let Expression::Table(t) = &a.this {
34066                        names.push(t.name.name.clone());
34067                    } else if let Expression::Identifier(id) = &a.this {
34068                        names.push(id.name.clone());
34069                    }
34070                    names.push(a.alias.name.clone());
34071                }
34072                Expression::Table(t) => {
34073                    names.push(t.name.name.clone());
34074                }
34075                Expression::Identifier(id) => {
34076                    names.push(id.name.clone());
34077                }
34078                _ => {}
34079            }
34080            self.merge_strip_qualifiers = names;
34081        }
34082
34083        // WHEN clauses - newline before each in pretty mode
34084        if let Some(whens) = &e.whens {
34085            if self.config.pretty {
34086                self.write_newline();
34087                self.write_indent();
34088            } else {
34089                self.write_space();
34090            }
34091            self.generate_expression(whens)?;
34092        }
34093
34094        // Restore merge_strip_qualifiers
34095        self.merge_strip_qualifiers = saved_merge_strip;
34096
34097        // OUTPUT/RETURNING clause - newline before in pretty mode
34098        if let Some(returning) = &e.returning {
34099            if self.config.pretty {
34100                self.write_newline();
34101                self.write_indent();
34102            } else {
34103                self.write_space();
34104            }
34105            self.generate_expression(returning)?;
34106        }
34107        Ok(())
34108    }
34109
34110    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
34111        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
34112        if e.no.is_some() {
34113            self.write_keyword("NO MERGEBLOCKRATIO");
34114        } else if e.default.is_some() {
34115            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
34116        } else {
34117            self.write_keyword("MERGEBLOCKRATIO");
34118            self.write("=");
34119            if let Some(this) = &e.this {
34120                self.generate_expression(this)?;
34121            }
34122            if e.percent.is_some() {
34123                self.write_keyword(" PERCENT");
34124            }
34125        }
34126        Ok(())
34127    }
34128
34129    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
34130        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
34131        self.write_keyword("TTL");
34132        let pretty_clickhouse = self.config.pretty
34133            && matches!(
34134                self.config.dialect,
34135                Some(crate::dialects::DialectType::ClickHouse)
34136            );
34137
34138        if pretty_clickhouse {
34139            self.write_newline();
34140            self.indent_level += 1;
34141            for (i, expr) in e.expressions.iter().enumerate() {
34142                if i > 0 {
34143                    self.write(",");
34144                    self.write_newline();
34145                }
34146                self.write_indent();
34147                self.generate_expression(expr)?;
34148            }
34149            self.indent_level -= 1;
34150        } else {
34151            self.write_space();
34152            for (i, expr) in e.expressions.iter().enumerate() {
34153                if i > 0 {
34154                    self.write(", ");
34155                }
34156                self.generate_expression(expr)?;
34157            }
34158        }
34159
34160        if let Some(where_) = &e.where_ {
34161            if pretty_clickhouse {
34162                self.write_newline();
34163                if let Expression::Where(w) = where_.as_ref() {
34164                    self.write_indent();
34165                    self.write_keyword("WHERE");
34166                    self.write_newline();
34167                    self.indent_level += 1;
34168                    self.write_indent();
34169                    self.generate_expression(&w.this)?;
34170                    self.indent_level -= 1;
34171                } else {
34172                    self.write_indent();
34173                    self.generate_expression(where_)?;
34174                }
34175            } else {
34176                self.write_space();
34177                self.generate_expression(where_)?;
34178            }
34179        }
34180        if let Some(group) = &e.group {
34181            if pretty_clickhouse {
34182                self.write_newline();
34183                if let Expression::Group(g) = group.as_ref() {
34184                    self.write_indent();
34185                    self.write_keyword("GROUP BY");
34186                    self.write_newline();
34187                    self.indent_level += 1;
34188                    for (i, expr) in g.expressions.iter().enumerate() {
34189                        if i > 0 {
34190                            self.write(",");
34191                            self.write_newline();
34192                        }
34193                        self.write_indent();
34194                        self.generate_expression(expr)?;
34195                    }
34196                    self.indent_level -= 1;
34197                } else {
34198                    self.write_indent();
34199                    self.generate_expression(group)?;
34200                }
34201            } else {
34202                self.write_space();
34203                self.generate_expression(group)?;
34204            }
34205        }
34206        if let Some(aggregates) = &e.aggregates {
34207            if pretty_clickhouse {
34208                self.write_newline();
34209                self.write_indent();
34210                self.write_keyword("SET");
34211                self.write_newline();
34212                self.indent_level += 1;
34213                if let Expression::Tuple(t) = aggregates.as_ref() {
34214                    for (i, agg) in t.expressions.iter().enumerate() {
34215                        if i > 0 {
34216                            self.write(",");
34217                            self.write_newline();
34218                        }
34219                        self.write_indent();
34220                        self.generate_expression(agg)?;
34221                    }
34222                } else {
34223                    self.write_indent();
34224                    self.generate_expression(aggregates)?;
34225                }
34226                self.indent_level -= 1;
34227            } else {
34228                self.write_space();
34229                self.write_keyword("SET");
34230                self.write_space();
34231                if let Expression::Tuple(t) = aggregates.as_ref() {
34232                    for (i, agg) in t.expressions.iter().enumerate() {
34233                        if i > 0 {
34234                            self.write(", ");
34235                        }
34236                        self.generate_expression(agg)?;
34237                    }
34238                } else {
34239                    self.generate_expression(aggregates)?;
34240                }
34241            }
34242        }
34243        Ok(())
34244    }
34245
34246    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
34247        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
34248        self.generate_expression(&e.this)?;
34249        if e.delete.is_some() {
34250            self.write_keyword(" DELETE");
34251        }
34252        if let Some(recompress) = &e.recompress {
34253            self.write_keyword(" RECOMPRESS ");
34254            self.generate_expression(recompress)?;
34255        }
34256        if let Some(to_disk) = &e.to_disk {
34257            self.write_keyword(" TO DISK ");
34258            self.generate_expression(to_disk)?;
34259        }
34260        if let Some(to_volume) = &e.to_volume {
34261            self.write_keyword(" TO VOLUME ");
34262            self.generate_expression(to_volume)?;
34263        }
34264        Ok(())
34265    }
34266
34267    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
34268        // MINHASH(this, expressions...)
34269        self.write_keyword("MINHASH");
34270        self.write("(");
34271        self.generate_expression(&e.this)?;
34272        for expr in &e.expressions {
34273            self.write(", ");
34274            self.generate_expression(expr)?;
34275        }
34276        self.write(")");
34277        Ok(())
34278    }
34279
34280    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
34281        // model!attribute - Snowflake syntax
34282        self.generate_expression(&e.this)?;
34283        self.write("!");
34284        self.generate_expression(&e.expression)?;
34285        Ok(())
34286    }
34287
34288    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
34289        // MONTHNAME(this)
34290        self.write_keyword("MONTHNAME");
34291        self.write("(");
34292        self.generate_expression(&e.this)?;
34293        self.write(")");
34294        Ok(())
34295    }
34296
34297    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
34298        // Output leading comments
34299        for comment in &e.leading_comments {
34300            self.write_formatted_comment(comment);
34301            if self.config.pretty {
34302                self.write_newline();
34303                self.write_indent();
34304            } else {
34305                self.write_space();
34306            }
34307        }
34308        // Python: INSERT [OVERWRITE] kind expressions source
34309        self.write_keyword("INSERT");
34310        if e.overwrite {
34311            self.write_space();
34312            self.write_keyword("OVERWRITE");
34313        }
34314        self.write_space();
34315        self.write(&e.kind);
34316        if self.config.pretty {
34317            self.indent_level += 1;
34318            for expr in &e.expressions {
34319                self.write_newline();
34320                self.write_indent();
34321                self.generate_expression(expr)?;
34322            }
34323            self.indent_level -= 1;
34324        } else {
34325            for expr in &e.expressions {
34326                self.write_space();
34327                self.generate_expression(expr)?;
34328            }
34329        }
34330        if let Some(source) = &e.source {
34331            if self.config.pretty {
34332                self.write_newline();
34333                self.write_indent();
34334            } else {
34335                self.write_space();
34336            }
34337            self.generate_expression(source)?;
34338        }
34339        Ok(())
34340    }
34341
34342    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
34343        // Python: NEXT VALUE FOR this [OVER (order)]
34344        self.write_keyword("NEXT VALUE FOR");
34345        self.write_space();
34346        self.generate_expression(&e.this)?;
34347        if let Some(order) = &e.order {
34348            self.write_space();
34349            self.write_keyword("OVER");
34350            self.write(" (");
34351            self.generate_expression(order)?;
34352            self.write(")");
34353        }
34354        Ok(())
34355    }
34356
34357    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
34358        // NORMAL(mean, stddev, gen)
34359        self.write_keyword("NORMAL");
34360        self.write("(");
34361        self.generate_expression(&e.this)?;
34362        if let Some(stddev) = &e.stddev {
34363            self.write(", ");
34364            self.generate_expression(stddev)?;
34365        }
34366        if let Some(gen) = &e.gen {
34367            self.write(", ");
34368            self.generate_expression(gen)?;
34369        }
34370        self.write(")");
34371        Ok(())
34372    }
34373
34374    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
34375        // NORMALIZE(this, form) or CASEFOLD version
34376        if e.is_casefold.is_some() {
34377            self.write_keyword("NORMALIZE_AND_CASEFOLD");
34378        } else {
34379            self.write_keyword("NORMALIZE");
34380        }
34381        self.write("(");
34382        self.generate_expression(&e.this)?;
34383        if let Some(form) = &e.form {
34384            self.write(", ");
34385            self.generate_expression(form)?;
34386        }
34387        self.write(")");
34388        Ok(())
34389    }
34390
34391    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
34392        // Python: [NOT ]NULL
34393        if e.allow_null.is_none() {
34394            self.write_keyword("NOT ");
34395        }
34396        self.write_keyword("NULL");
34397        Ok(())
34398    }
34399
34400    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
34401        // NULLIF(this, expression)
34402        self.write_keyword("NULLIF");
34403        self.write("(");
34404        self.generate_expression(&e.this)?;
34405        self.write(", ");
34406        self.generate_expression(&e.expression)?;
34407        self.write(")");
34408        Ok(())
34409    }
34410
34411    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
34412        // FORMAT(this, format, culture)
34413        self.write_keyword("FORMAT");
34414        self.write("(");
34415        self.generate_expression(&e.this)?;
34416        self.write(", '");
34417        self.write(&e.format);
34418        self.write("'");
34419        if let Some(culture) = &e.culture {
34420            self.write(", ");
34421            self.generate_expression(culture)?;
34422        }
34423        self.write(")");
34424        Ok(())
34425    }
34426
34427    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
34428        // OBJECT_AGG(key, value)
34429        self.write_keyword("OBJECT_AGG");
34430        self.write("(");
34431        self.generate_expression(&e.this)?;
34432        self.write(", ");
34433        self.generate_expression(&e.expression)?;
34434        self.write(")");
34435        Ok(())
34436    }
34437
34438    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
34439        // Python: Just returns the name
34440        self.generate_expression(&e.this)?;
34441        Ok(())
34442    }
34443
34444    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
34445        // OBJECT_INSERT(obj, key, value, [update_flag])
34446        self.write_keyword("OBJECT_INSERT");
34447        self.write("(");
34448        self.generate_expression(&e.this)?;
34449        if let Some(key) = &e.key {
34450            self.write(", ");
34451            self.generate_expression(key)?;
34452        }
34453        if let Some(value) = &e.value {
34454            self.write(", ");
34455            self.generate_expression(value)?;
34456        }
34457        if let Some(update_flag) = &e.update_flag {
34458            self.write(", ");
34459            self.generate_expression(update_flag)?;
34460        }
34461        self.write(")");
34462        Ok(())
34463    }
34464
34465    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
34466        // OFFSET value [ROW|ROWS]
34467        self.write_keyword("OFFSET");
34468        self.write_space();
34469        self.generate_expression(&e.this)?;
34470        // Output ROWS keyword only for TSQL/Oracle targets
34471        if e.rows == Some(true)
34472            && matches!(
34473                self.config.dialect,
34474                Some(crate::dialects::DialectType::TSQL)
34475                    | Some(crate::dialects::DialectType::Oracle)
34476            )
34477        {
34478            self.write_space();
34479            self.write_keyword("ROWS");
34480        }
34481        Ok(())
34482    }
34483
34484    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
34485        // QUALIFY condition (Snowflake/BigQuery)
34486        self.write_keyword("QUALIFY");
34487        self.write_space();
34488        self.generate_expression(&e.this)?;
34489        Ok(())
34490    }
34491
34492    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
34493        // ON CLUSTER cluster_name
34494        self.write_keyword("ON CLUSTER");
34495        self.write_space();
34496        self.generate_expression(&e.this)?;
34497        Ok(())
34498    }
34499
34500    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
34501        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
34502        self.write_keyword("ON COMMIT");
34503        if e.delete.is_some() {
34504            self.write_keyword(" DELETE ROWS");
34505        } else {
34506            self.write_keyword(" PRESERVE ROWS");
34507        }
34508        Ok(())
34509    }
34510
34511    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
34512        // Python: error/empty/null handling
34513        if let Some(empty) = &e.empty {
34514            self.generate_expression(empty)?;
34515            self.write_keyword(" ON EMPTY");
34516        }
34517        if let Some(error) = &e.error {
34518            if e.empty.is_some() {
34519                self.write_space();
34520            }
34521            self.generate_expression(error)?;
34522            self.write_keyword(" ON ERROR");
34523        }
34524        if let Some(null) = &e.null {
34525            if e.empty.is_some() || e.error.is_some() {
34526                self.write_space();
34527            }
34528            self.generate_expression(null)?;
34529            self.write_keyword(" ON NULL");
34530        }
34531        Ok(())
34532    }
34533
34534    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
34535        // Materialize doesn't support ON CONFLICT - skip entirely
34536        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
34537            return Ok(());
34538        }
34539        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
34540        if e.duplicate.is_some() {
34541            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
34542            self.write_keyword("ON DUPLICATE KEY UPDATE");
34543            for (i, expr) in e.expressions.iter().enumerate() {
34544                if i > 0 {
34545                    self.write(",");
34546                }
34547                self.write_space();
34548                self.generate_expression(expr)?;
34549            }
34550            return Ok(());
34551        } else {
34552            self.write_keyword("ON CONFLICT");
34553        }
34554        if let Some(constraint) = &e.constraint {
34555            self.write_keyword(" ON CONSTRAINT ");
34556            self.generate_expression(constraint)?;
34557        }
34558        if let Some(conflict_keys) = &e.conflict_keys {
34559            // conflict_keys can be a Tuple containing expressions
34560            if let Expression::Tuple(t) = conflict_keys.as_ref() {
34561                self.write("(");
34562                for (i, expr) in t.expressions.iter().enumerate() {
34563                    if i > 0 {
34564                        self.write(", ");
34565                    }
34566                    self.generate_expression(expr)?;
34567                }
34568                self.write(")");
34569            } else {
34570                self.write("(");
34571                self.generate_expression(conflict_keys)?;
34572                self.write(")");
34573            }
34574        }
34575        if let Some(index_predicate) = &e.index_predicate {
34576            self.write_keyword(" WHERE ");
34577            self.generate_expression(index_predicate)?;
34578        }
34579        if let Some(action) = &e.action {
34580            // Check if action is "NOTHING" or an UPDATE set
34581            if let Expression::Identifier(id) = action.as_ref() {
34582                if id.name.eq_ignore_ascii_case("NOTHING") {
34583                    self.write_keyword(" DO NOTHING");
34584                } else {
34585                    self.write_keyword(" DO ");
34586                    self.generate_expression(action)?;
34587                }
34588            } else if let Expression::Tuple(t) = action.as_ref() {
34589                // DO UPDATE SET col1 = val1, col2 = val2
34590                self.write_keyword(" DO UPDATE SET ");
34591                for (i, expr) in t.expressions.iter().enumerate() {
34592                    if i > 0 {
34593                        self.write(", ");
34594                    }
34595                    self.generate_expression(expr)?;
34596                }
34597            } else {
34598                self.write_keyword(" DO ");
34599                self.generate_expression(action)?;
34600            }
34601        }
34602        // WHERE clause for the UPDATE action
34603        if let Some(where_) = &e.where_ {
34604            self.write_keyword(" WHERE ");
34605            self.generate_expression(where_)?;
34606        }
34607        Ok(())
34608    }
34609
34610    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
34611        // ON property_value
34612        self.write_keyword("ON");
34613        self.write_space();
34614        self.generate_expression(&e.this)?;
34615        Ok(())
34616    }
34617
34618    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
34619        // Python: this expression (e.g., column opclass)
34620        self.generate_expression(&e.this)?;
34621        self.write_space();
34622        self.generate_expression(&e.expression)?;
34623        Ok(())
34624    }
34625
34626    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
34627        // Python: OPENJSON(this[, path]) [WITH (columns)]
34628        self.write_keyword("OPENJSON");
34629        self.write("(");
34630        self.generate_expression(&e.this)?;
34631        if let Some(path) = &e.path {
34632            self.write(", ");
34633            self.generate_expression(path)?;
34634        }
34635        self.write(")");
34636        if !e.expressions.is_empty() {
34637            self.write_keyword(" WITH");
34638            if self.config.pretty {
34639                self.write(" (\n");
34640                self.indent_level += 2;
34641                for (i, expr) in e.expressions.iter().enumerate() {
34642                    if i > 0 {
34643                        self.write(",\n");
34644                    }
34645                    self.write_indent();
34646                    self.generate_expression(expr)?;
34647                }
34648                self.write("\n");
34649                self.indent_level -= 2;
34650                self.write(")");
34651            } else {
34652                self.write(" (");
34653                for (i, expr) in e.expressions.iter().enumerate() {
34654                    if i > 0 {
34655                        self.write(", ");
34656                    }
34657                    self.generate_expression(expr)?;
34658                }
34659                self.write(")");
34660            }
34661        }
34662        Ok(())
34663    }
34664
34665    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
34666        // Python: this kind [path] [AS JSON]
34667        self.generate_expression(&e.this)?;
34668        self.write_space();
34669        // Use parsed data_type if available, otherwise fall back to kind string
34670        if let Some(ref dt) = e.data_type {
34671            self.generate_data_type(dt)?;
34672        } else if !e.kind.is_empty() {
34673            self.write(&e.kind);
34674        }
34675        if let Some(path) = &e.path {
34676            self.write_space();
34677            self.generate_expression(path)?;
34678        }
34679        if e.as_json.is_some() {
34680            self.write_keyword(" AS JSON");
34681        }
34682        Ok(())
34683    }
34684
34685    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
34686        // this OPERATOR(op) expression
34687        self.generate_expression(&e.this)?;
34688        self.write_space();
34689        if let Some(op) = &e.operator {
34690            self.write_keyword("OPERATOR");
34691            self.write("(");
34692            self.generate_expression(op)?;
34693            self.write(")");
34694        }
34695        // Emit inline comments between OPERATOR() and the RHS
34696        for comment in &e.comments {
34697            self.write_space();
34698            self.write_formatted_comment(comment);
34699        }
34700        self.write_space();
34701        self.generate_expression(&e.expression)?;
34702        Ok(())
34703    }
34704
34705    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
34706        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
34707        self.write_keyword("ORDER BY");
34708        let pretty_clickhouse_single_paren = self.config.pretty
34709            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
34710            && e.expressions.len() == 1
34711            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
34712        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
34713            && e.expressions.len() == 1
34714            && matches!(e.expressions[0].this, Expression::Tuple(_))
34715            && !e.expressions[0].desc
34716            && e.expressions[0].nulls_first.is_none();
34717
34718        if pretty_clickhouse_single_paren {
34719            self.write_space();
34720            if let Expression::Paren(p) = &e.expressions[0].this {
34721                self.write("(");
34722                self.write_newline();
34723                self.indent_level += 1;
34724                self.write_indent();
34725                self.generate_expression(&p.this)?;
34726                self.indent_level -= 1;
34727                self.write_newline();
34728                self.write(")");
34729            }
34730            return Ok(());
34731        }
34732
34733        if clickhouse_single_tuple {
34734            self.write_space();
34735            if let Expression::Tuple(t) = &e.expressions[0].this {
34736                self.write("(");
34737                for (i, expr) in t.expressions.iter().enumerate() {
34738                    if i > 0 {
34739                        self.write(", ");
34740                    }
34741                    self.generate_expression(expr)?;
34742                }
34743                self.write(")");
34744            }
34745            return Ok(());
34746        }
34747
34748        self.write_space();
34749        for (i, ordered) in e.expressions.iter().enumerate() {
34750            if i > 0 {
34751                self.write(", ");
34752            }
34753            self.generate_expression(&ordered.this)?;
34754            if ordered.desc {
34755                self.write_space();
34756                self.write_keyword("DESC");
34757            } else if ordered.explicit_asc {
34758                self.write_space();
34759                self.write_keyword("ASC");
34760            }
34761            if let Some(nulls_first) = ordered.nulls_first {
34762                // In Dremio, NULLS LAST is the default, so skip generating it
34763                let skip_nulls_last =
34764                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
34765                if !skip_nulls_last {
34766                    self.write_space();
34767                    self.write_keyword("NULLS");
34768                    self.write_space();
34769                    if nulls_first {
34770                        self.write_keyword("FIRST");
34771                    } else {
34772                        self.write_keyword("LAST");
34773                    }
34774                }
34775            }
34776        }
34777        Ok(())
34778    }
34779
34780    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
34781        // OUTPUT(model)
34782        self.write_keyword("OUTPUT");
34783        self.write("(");
34784        if self.config.pretty {
34785            self.indent_level += 1;
34786            self.write_newline();
34787            self.write_indent();
34788            self.generate_expression(&e.this)?;
34789            self.indent_level -= 1;
34790            self.write_newline();
34791        } else {
34792            self.generate_expression(&e.this)?;
34793        }
34794        self.write(")");
34795        Ok(())
34796    }
34797
34798    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
34799        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
34800        self.write_keyword("TRUNCATE");
34801        if let Some(this) = &e.this {
34802            self.write_space();
34803            self.generate_expression(this)?;
34804        }
34805        if e.with_count.is_some() {
34806            self.write_keyword(" WITH COUNT");
34807        } else {
34808            self.write_keyword(" WITHOUT COUNT");
34809        }
34810        Ok(())
34811    }
34812
34813    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
34814        // Python: name(expressions)(params)
34815        self.generate_expression(&e.this)?;
34816        self.write("(");
34817        for (i, expr) in e.expressions.iter().enumerate() {
34818            if i > 0 {
34819                self.write(", ");
34820            }
34821            self.generate_expression(expr)?;
34822        }
34823        self.write(")(");
34824        for (i, param) in e.params.iter().enumerate() {
34825            if i > 0 {
34826                self.write(", ");
34827            }
34828            self.generate_expression(param)?;
34829        }
34830        self.write(")");
34831        Ok(())
34832    }
34833
34834    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
34835        // PARSE_DATETIME(format, this) or similar
34836        self.write_keyword("PARSE_DATETIME");
34837        self.write("(");
34838        if let Some(format) = &e.format {
34839            self.write("'");
34840            self.write(format);
34841            self.write("', ");
34842        }
34843        self.generate_expression(&e.this)?;
34844        if let Some(zone) = &e.zone {
34845            self.write(", ");
34846            self.generate_expression(zone)?;
34847        }
34848        self.write(")");
34849        Ok(())
34850    }
34851
34852    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
34853        // PARSE_IP(this, type, permissive)
34854        self.write_keyword("PARSE_IP");
34855        self.write("(");
34856        self.generate_expression(&e.this)?;
34857        if let Some(type_) = &e.type_ {
34858            self.write(", ");
34859            self.generate_expression(type_)?;
34860        }
34861        if let Some(permissive) = &e.permissive {
34862            self.write(", ");
34863            self.generate_expression(permissive)?;
34864        }
34865        self.write(")");
34866        Ok(())
34867    }
34868
34869    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
34870        // PARSE_JSON(this, [expression])
34871        self.write_keyword("PARSE_JSON");
34872        self.write("(");
34873        self.generate_expression(&e.this)?;
34874        if let Some(expression) = &e.expression {
34875            self.write(", ");
34876            self.generate_expression(expression)?;
34877        }
34878        self.write(")");
34879        Ok(())
34880    }
34881
34882    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
34883        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
34884        self.write_keyword("PARSE_TIME");
34885        self.write("(");
34886        self.write(&format!("'{}'", e.format));
34887        self.write(", ");
34888        self.generate_expression(&e.this)?;
34889        self.write(")");
34890        Ok(())
34891    }
34892
34893    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
34894        // PARSE_URL(this, [part_to_extract], [key], [permissive])
34895        self.write_keyword("PARSE_URL");
34896        self.write("(");
34897        self.generate_expression(&e.this)?;
34898        if let Some(part) = &e.part_to_extract {
34899            self.write(", ");
34900            self.generate_expression(part)?;
34901        }
34902        if let Some(key) = &e.key {
34903            self.write(", ");
34904            self.generate_expression(key)?;
34905        }
34906        if let Some(permissive) = &e.permissive {
34907            self.write(", ");
34908            self.generate_expression(permissive)?;
34909        }
34910        self.write(")");
34911        Ok(())
34912    }
34913
34914    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
34915        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
34916        if e.subpartition {
34917            self.write_keyword("SUBPARTITION");
34918        } else {
34919            self.write_keyword("PARTITION");
34920        }
34921        self.write("(");
34922        for (i, expr) in e.expressions.iter().enumerate() {
34923            if i > 0 {
34924                self.write(", ");
34925            }
34926            self.generate_expression(expr)?;
34927        }
34928        self.write(")");
34929        Ok(())
34930    }
34931
34932    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
34933        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
34934        if let Some(this) = &e.this {
34935            if let Some(expression) = &e.expression {
34936                // WITH (MODULUS this, REMAINDER expression)
34937                self.write_keyword("WITH");
34938                self.write(" (");
34939                self.write_keyword("MODULUS");
34940                self.write_space();
34941                self.generate_expression(this)?;
34942                self.write(", ");
34943                self.write_keyword("REMAINDER");
34944                self.write_space();
34945                self.generate_expression(expression)?;
34946                self.write(")");
34947            } else {
34948                // IN (this) - this could be a list
34949                self.write_keyword("IN");
34950                self.write(" (");
34951                self.generate_partition_bound_values(this)?;
34952                self.write(")");
34953            }
34954        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
34955            // FROM (from_expressions) TO (to_expressions)
34956            self.write_keyword("FROM");
34957            self.write(" (");
34958            self.generate_partition_bound_values(from)?;
34959            self.write(") ");
34960            self.write_keyword("TO");
34961            self.write(" (");
34962            self.generate_partition_bound_values(to)?;
34963            self.write(")");
34964        }
34965        Ok(())
34966    }
34967
34968    /// Generate partition bound values - handles Tuple expressions by outputting
34969    /// contents without wrapping parens (since caller provides the parens)
34970    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
34971        if let Expression::Tuple(t) = expr {
34972            for (i, e) in t.expressions.iter().enumerate() {
34973                if i > 0 {
34974                    self.write(", ");
34975                }
34976                self.generate_expression(e)?;
34977            }
34978            Ok(())
34979        } else {
34980            self.generate_expression(expr)
34981        }
34982    }
34983
34984    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
34985        // PARTITION BY LIST (partition_expressions) (create_expressions)
34986        self.write_keyword("PARTITION BY LIST");
34987        if let Some(partition_exprs) = &e.partition_expressions {
34988            self.write(" (");
34989            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
34990            self.generate_doris_partition_expressions(partition_exprs)?;
34991            self.write(")");
34992        }
34993        if let Some(create_exprs) = &e.create_expressions {
34994            self.write(" (");
34995            // Unwrap Tuple for partition definitions
34996            self.generate_doris_partition_definitions(create_exprs)?;
34997            self.write(")");
34998        }
34999        Ok(())
35000    }
35001
35002    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
35003        // PARTITION BY RANGE (partition_expressions) (create_expressions)
35004        self.write_keyword("PARTITION BY RANGE");
35005        if let Some(partition_exprs) = &e.partition_expressions {
35006            self.write(" (");
35007            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
35008            self.generate_doris_partition_expressions(partition_exprs)?;
35009            self.write(")");
35010        }
35011        if let Some(create_exprs) = &e.create_expressions {
35012            self.write(" (");
35013            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
35014            self.generate_doris_partition_definitions(create_exprs)?;
35015            self.write(")");
35016        }
35017        Ok(())
35018    }
35019
35020    /// Generate Doris partition column expressions (unwrap Tuple)
35021    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
35022        if let Expression::Tuple(t) = expr {
35023            for (i, e) in t.expressions.iter().enumerate() {
35024                if i > 0 {
35025                    self.write(", ");
35026                }
35027                self.generate_expression(e)?;
35028            }
35029        } else {
35030            self.generate_expression(expr)?;
35031        }
35032        Ok(())
35033    }
35034
35035    /// Generate Doris partition definitions (comma-separated Partition expressions)
35036    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
35037        match expr {
35038            Expression::Tuple(t) => {
35039                // Multiple partitions, comma-separated
35040                for (i, part) in t.expressions.iter().enumerate() {
35041                    if i > 0 {
35042                        self.write(", ");
35043                    }
35044                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
35045                    if let Expression::Partition(p) = part {
35046                        for (j, inner) in p.expressions.iter().enumerate() {
35047                            if j > 0 {
35048                                self.write(", ");
35049                            }
35050                            self.generate_expression(inner)?;
35051                        }
35052                    } else {
35053                        self.generate_expression(part)?;
35054                    }
35055                }
35056            }
35057            Expression::PartitionByRangePropertyDynamic(_) => {
35058                // Dynamic partition - FROM/TO/INTERVAL
35059                self.generate_expression(expr)?;
35060            }
35061            _ => {
35062                self.generate_expression(expr)?;
35063            }
35064        }
35065        Ok(())
35066    }
35067
35068    fn generate_partition_by_range_property_dynamic(
35069        &mut self,
35070        e: &PartitionByRangePropertyDynamic,
35071    ) -> Result<()> {
35072        if e.use_start_end {
35073            // StarRocks: START ('val') END ('val') EVERY (expr)
35074            if let Some(start) = &e.start {
35075                self.write_keyword("START");
35076                self.write(" (");
35077                self.generate_expression(start)?;
35078                self.write(")");
35079            }
35080            if let Some(end) = &e.end {
35081                self.write_space();
35082                self.write_keyword("END");
35083                self.write(" (");
35084                self.generate_expression(end)?;
35085                self.write(")");
35086            }
35087            if let Some(every) = &e.every {
35088                self.write_space();
35089                self.write_keyword("EVERY");
35090                self.write(" (");
35091                // Use unquoted interval format for StarRocks
35092                self.generate_doris_interval(every)?;
35093                self.write(")");
35094            }
35095        } else {
35096            // Doris: FROM (start) TO (end) INTERVAL n UNIT
35097            if let Some(start) = &e.start {
35098                self.write_keyword("FROM");
35099                self.write(" (");
35100                self.generate_expression(start)?;
35101                self.write(")");
35102            }
35103            if let Some(end) = &e.end {
35104                self.write_space();
35105                self.write_keyword("TO");
35106                self.write(" (");
35107                self.generate_expression(end)?;
35108                self.write(")");
35109            }
35110            if let Some(every) = &e.every {
35111                self.write_space();
35112                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
35113                self.generate_doris_interval(every)?;
35114            }
35115        }
35116        Ok(())
35117    }
35118
35119    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
35120    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
35121        if let Expression::Interval(interval) = expr {
35122            self.write_keyword("INTERVAL");
35123            if let Some(ref value) = interval.this {
35124                self.write_space();
35125                // If the value is a string literal that looks like a number,
35126                // output it without quotes (matching Python sqlglot's
35127                // partitionbyrangepropertydynamic_sql which converts back to number)
35128                match value {
35129                    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()) => {
35130                        if let Literal::String(s) = lit.as_ref() {
35131                            self.write(s);
35132                        }
35133                    }
35134                    _ => {
35135                        self.generate_expression(value)?;
35136                    }
35137                }
35138            }
35139            if let Some(ref unit_spec) = interval.unit {
35140                self.write_space();
35141                self.write_interval_unit_spec(unit_spec)?;
35142            }
35143            Ok(())
35144        } else {
35145            self.generate_expression(expr)
35146        }
35147    }
35148
35149    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
35150        // TRUNCATE(expression, this)
35151        self.write_keyword("TRUNCATE");
35152        self.write("(");
35153        self.generate_expression(&e.expression)?;
35154        self.write(", ");
35155        self.generate_expression(&e.this)?;
35156        self.write(")");
35157        Ok(())
35158    }
35159
35160    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
35161        // Doris: PARTITION name VALUES IN (val1, val2)
35162        self.write_keyword("PARTITION");
35163        self.write_space();
35164        self.generate_expression(&e.this)?;
35165        self.write_space();
35166        self.write_keyword("VALUES IN");
35167        self.write(" (");
35168        for (i, expr) in e.expressions.iter().enumerate() {
35169            if i > 0 {
35170                self.write(", ");
35171            }
35172            self.generate_expression(expr)?;
35173        }
35174        self.write(")");
35175        Ok(())
35176    }
35177
35178    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
35179        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
35180        // TSQL ranges have no expressions and just use `this TO expression`
35181        if e.expressions.is_empty() && e.expression.is_some() {
35182            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
35183            self.generate_expression(&e.this)?;
35184            self.write_space();
35185            self.write_keyword("TO");
35186            self.write_space();
35187            self.generate_expression(e.expression.as_ref().unwrap())?;
35188            return Ok(());
35189        }
35190
35191        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
35192        self.write_keyword("PARTITION");
35193        self.write_space();
35194        self.generate_expression(&e.this)?;
35195        self.write_space();
35196
35197        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
35198        if e.expressions.len() == 1 {
35199            // Single value: VALUES LESS THAN (val)
35200            self.write_keyword("VALUES LESS THAN");
35201            self.write(" (");
35202            self.generate_expression(&e.expressions[0])?;
35203            self.write(")");
35204        } else if !e.expressions.is_empty() {
35205            // Multiple values with Tuple: VALUES [(val1), (val2))
35206            self.write_keyword("VALUES");
35207            self.write(" [");
35208            for (i, expr) in e.expressions.iter().enumerate() {
35209                if i > 0 {
35210                    self.write(", ");
35211                }
35212                // If the expr is a Tuple, generate its contents wrapped in parens
35213                if let Expression::Tuple(t) = expr {
35214                    self.write("(");
35215                    for (j, inner) in t.expressions.iter().enumerate() {
35216                        if j > 0 {
35217                            self.write(", ");
35218                        }
35219                        self.generate_expression(inner)?;
35220                    }
35221                    self.write(")");
35222                } else {
35223                    self.write("(");
35224                    self.generate_expression(expr)?;
35225                    self.write(")");
35226                }
35227            }
35228            self.write(")");
35229        }
35230        Ok(())
35231    }
35232
35233    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
35234        // BUCKET(this, expression)
35235        self.write_keyword("BUCKET");
35236        self.write("(");
35237        self.generate_expression(&e.this)?;
35238        self.write(", ");
35239        self.generate_expression(&e.expression)?;
35240        self.write(")");
35241        Ok(())
35242    }
35243
35244    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
35245        // BigQuery table property: PARTITION BY expression [, expression ...]
35246        self.write_keyword("PARTITION BY");
35247        self.write_space();
35248        for (i, expr) in e.expressions.iter().enumerate() {
35249            if i > 0 {
35250                self.write(", ");
35251            }
35252            self.generate_expression(expr)?;
35253        }
35254        Ok(())
35255    }
35256
35257    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
35258        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
35259        if matches!(
35260            self.config.dialect,
35261            Some(crate::dialects::DialectType::Teradata)
35262                | Some(crate::dialects::DialectType::ClickHouse)
35263        ) {
35264            self.write_keyword("PARTITION BY");
35265        } else {
35266            self.write_keyword("PARTITIONED BY");
35267        }
35268        self.write_space();
35269        // In pretty mode, always use multiline tuple format for PARTITIONED BY
35270        if self.config.pretty {
35271            if let Expression::Tuple(ref tuple) = *e.this {
35272                self.write("(");
35273                self.write_newline();
35274                self.indent_level += 1;
35275                for (i, expr) in tuple.expressions.iter().enumerate() {
35276                    if i > 0 {
35277                        self.write(",");
35278                        self.write_newline();
35279                    }
35280                    self.write_indent();
35281                    self.generate_expression(expr)?;
35282                }
35283                self.indent_level -= 1;
35284                self.write_newline();
35285                self.write(")");
35286            } else {
35287                self.generate_expression(&e.this)?;
35288            }
35289        } else {
35290            self.generate_expression(&e.this)?;
35291        }
35292        Ok(())
35293    }
35294
35295    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
35296        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
35297        self.write_keyword("PARTITION OF");
35298        self.write_space();
35299        self.generate_expression(&e.this)?;
35300        // Check if expression is a PartitionBoundSpec
35301        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
35302            self.write_space();
35303            self.write_keyword("FOR VALUES");
35304            self.write_space();
35305            self.generate_expression(&e.expression)?;
35306        } else {
35307            self.write_space();
35308            self.write_keyword("DEFAULT");
35309        }
35310        Ok(())
35311    }
35312
35313    fn generate_period_for_system_time_constraint(
35314        &mut self,
35315        e: &PeriodForSystemTimeConstraint,
35316    ) -> Result<()> {
35317        // PERIOD FOR SYSTEM_TIME (this, expression)
35318        self.write_keyword("PERIOD FOR SYSTEM_TIME");
35319        self.write(" (");
35320        self.generate_expression(&e.this)?;
35321        self.write(", ");
35322        self.generate_expression(&e.expression)?;
35323        self.write(")");
35324        Ok(())
35325    }
35326
35327    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
35328        // value AS alias
35329        // The alias can be an identifier or an expression (e.g., string concatenation)
35330        self.generate_expression(&e.this)?;
35331        self.write_space();
35332        self.write_keyword("AS");
35333        self.write_space();
35334        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
35335        if self.config.unpivot_aliases_are_identifiers {
35336            match &e.alias {
35337                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35338                    let Literal::String(s) = lit.as_ref() else {
35339                        unreachable!()
35340                    };
35341                    // Convert string literal to identifier
35342                    self.generate_identifier(&Identifier::new(s.clone()))?;
35343                }
35344                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
35345                    let Literal::Number(n) = lit.as_ref() else {
35346                        unreachable!()
35347                    };
35348                    // Convert number literal to quoted identifier
35349                    let mut id = Identifier::new(n.clone());
35350                    id.quoted = true;
35351                    self.generate_identifier(&id)?;
35352                }
35353                other => {
35354                    self.generate_expression(other)?;
35355                }
35356            }
35357        } else {
35358            self.generate_expression(&e.alias)?;
35359        }
35360        Ok(())
35361    }
35362
35363    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
35364        // ANY or ANY [expression]
35365        self.write_keyword("ANY");
35366        if let Some(this) = &e.this {
35367            self.write_space();
35368            self.generate_expression(this)?;
35369        }
35370        Ok(())
35371    }
35372
35373    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
35374        // ML.PREDICT(MODEL this, expression, [params_struct])
35375        self.write_keyword("ML.PREDICT");
35376        self.write("(");
35377        self.write_keyword("MODEL");
35378        self.write_space();
35379        self.generate_expression(&e.this)?;
35380        self.write(", ");
35381        self.generate_expression(&e.expression)?;
35382        if let Some(params) = &e.params_struct {
35383            self.write(", ");
35384            self.generate_expression(params)?;
35385        }
35386        self.write(")");
35387        Ok(())
35388    }
35389
35390    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
35391        // PREVIOUS_DAY(this, expression)
35392        self.write_keyword("PREVIOUS_DAY");
35393        self.write("(");
35394        self.generate_expression(&e.this)?;
35395        self.write(", ");
35396        self.generate_expression(&e.expression)?;
35397        self.write(")");
35398        Ok(())
35399    }
35400
35401    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
35402        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
35403        self.write_keyword("PRIMARY KEY");
35404        if let Some(name) = &e.this {
35405            self.write_space();
35406            self.generate_expression(name)?;
35407        }
35408        if !e.expressions.is_empty() {
35409            self.write(" (");
35410            for (i, expr) in e.expressions.iter().enumerate() {
35411                if i > 0 {
35412                    self.write(", ");
35413                }
35414                self.generate_expression(expr)?;
35415            }
35416            self.write(")");
35417        }
35418        if let Some(include) = &e.include {
35419            self.write_space();
35420            self.generate_expression(include)?;
35421        }
35422        if !e.options.is_empty() {
35423            self.write_space();
35424            for (i, opt) in e.options.iter().enumerate() {
35425                if i > 0 {
35426                    self.write_space();
35427                }
35428                self.generate_expression(opt)?;
35429            }
35430        }
35431        Ok(())
35432    }
35433
35434    fn generate_primary_key_column_constraint(
35435        &mut self,
35436        _e: &PrimaryKeyColumnConstraint,
35437    ) -> Result<()> {
35438        // PRIMARY KEY constraint at column level
35439        self.write_keyword("PRIMARY KEY");
35440        Ok(())
35441    }
35442
35443    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
35444        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
35445        self.write_keyword("PATH");
35446        self.write_space();
35447        self.generate_expression(&e.this)?;
35448        Ok(())
35449    }
35450
35451    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
35452        // PROJECTION this (expression)
35453        self.write_keyword("PROJECTION");
35454        self.write_space();
35455        self.generate_expression(&e.this)?;
35456        self.write(" (");
35457        self.generate_expression(&e.expression)?;
35458        self.write(")");
35459        Ok(())
35460    }
35461
35462    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
35463        // Properties list
35464        for (i, prop) in e.expressions.iter().enumerate() {
35465            if i > 0 {
35466                self.write(", ");
35467            }
35468            self.generate_expression(prop)?;
35469        }
35470        Ok(())
35471    }
35472
35473    fn generate_property(&mut self, e: &Property) -> Result<()> {
35474        // name=value
35475        self.generate_expression(&e.this)?;
35476        if let Some(value) = &e.value {
35477            self.write("=");
35478            self.generate_expression(value)?;
35479        }
35480        Ok(())
35481    }
35482
35483    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
35484        self.write_keyword("OPTIONS");
35485        if e.entries.is_empty() {
35486            self.write(" ()");
35487            return Ok(());
35488        }
35489
35490        if self.config.pretty {
35491            self.write(" (");
35492            self.write_newline();
35493            self.indent_level += 1;
35494            for (i, entry) in e.entries.iter().enumerate() {
35495                if i > 0 {
35496                    self.write(",");
35497                    self.write_newline();
35498                }
35499                self.write_indent();
35500                self.generate_identifier(&entry.key)?;
35501                self.write("=");
35502                self.generate_expression(&entry.value)?;
35503            }
35504            self.indent_level -= 1;
35505            self.write_newline();
35506            self.write(")");
35507        } else {
35508            self.write(" (");
35509            for (i, entry) in e.entries.iter().enumerate() {
35510                if i > 0 {
35511                    self.write(", ");
35512                }
35513                self.generate_identifier(&entry.key)?;
35514                self.write("=");
35515                self.generate_expression(&entry.value)?;
35516            }
35517            self.write(")");
35518        }
35519        Ok(())
35520    }
35521
35522    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
35523    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
35524        self.write_keyword("OPTIONS");
35525        self.write(" (");
35526        for (i, opt) in options.iter().enumerate() {
35527            if i > 0 {
35528                self.write(", ");
35529            }
35530            self.generate_option_expression(opt)?;
35531        }
35532        self.write(")");
35533        Ok(())
35534    }
35535
35536    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
35537    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
35538        self.write_keyword("PROPERTIES");
35539        self.write(" (");
35540        for (i, prop) in properties.iter().enumerate() {
35541            if i > 0 {
35542                self.write(", ");
35543            }
35544            self.generate_option_expression(prop)?;
35545        }
35546        self.write(")");
35547        Ok(())
35548    }
35549
35550    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
35551    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
35552        self.write_keyword("ENVIRONMENT");
35553        self.write(" (");
35554        for (i, env_item) in environment.iter().enumerate() {
35555            if i > 0 {
35556                self.write(", ");
35557            }
35558            self.generate_environment_expression(env_item)?;
35559        }
35560        self.write(")");
35561        Ok(())
35562    }
35563
35564    /// Generate an environment expression with spaces around =
35565    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
35566        match expr {
35567            Expression::Eq(eq) => {
35568                // Generate key = value with spaces (Databricks ENVIRONMENT style)
35569                self.generate_expression(&eq.left)?;
35570                self.write(" = ");
35571                self.generate_expression(&eq.right)?;
35572                Ok(())
35573            }
35574            _ => self.generate_expression(expr),
35575        }
35576    }
35577
35578    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
35579    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
35580        self.write_keyword("TBLPROPERTIES");
35581        if self.config.pretty {
35582            self.write(" (");
35583            self.write_newline();
35584            self.indent_level += 1;
35585            for (i, opt) in options.iter().enumerate() {
35586                if i > 0 {
35587                    self.write(",");
35588                    self.write_newline();
35589                }
35590                self.write_indent();
35591                self.generate_option_expression(opt)?;
35592            }
35593            self.indent_level -= 1;
35594            self.write_newline();
35595            self.write(")");
35596        } else {
35597            self.write(" (");
35598            for (i, opt) in options.iter().enumerate() {
35599                if i > 0 {
35600                    self.write(", ");
35601                }
35602                self.generate_option_expression(opt)?;
35603            }
35604            self.write(")");
35605        }
35606        Ok(())
35607    }
35608
35609    /// Generate an option expression without spaces around =
35610    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
35611        match expr {
35612            Expression::Eq(eq) => {
35613                // Generate key=value without spaces
35614                self.generate_expression(&eq.left)?;
35615                self.write("=");
35616                self.generate_expression(&eq.right)?;
35617                Ok(())
35618            }
35619            _ => self.generate_expression(expr),
35620        }
35621    }
35622
35623    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
35624        // Just output the name
35625        self.generate_expression(&e.this)?;
35626        Ok(())
35627    }
35628
35629    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
35630        // PUT source_file @stage [options]
35631        self.write_keyword("PUT");
35632        self.write_space();
35633
35634        // Source file path - preserve original quoting
35635        if e.source_quoted {
35636            self.write("'");
35637            self.write(&e.source);
35638            self.write("'");
35639        } else {
35640            self.write(&e.source);
35641        }
35642
35643        self.write_space();
35644
35645        // Target stage reference - output the string directly (includes @)
35646        if let Expression::Literal(lit) = &e.target {
35647            if let Literal::String(s) = lit.as_ref() {
35648                self.write(s);
35649            }
35650        } else {
35651            self.generate_expression(&e.target)?;
35652        }
35653
35654        // Optional parameters: KEY=VALUE
35655        for param in &e.params {
35656            self.write_space();
35657            self.write(&param.name);
35658            if let Some(ref value) = param.value {
35659                self.write("=");
35660                self.generate_expression(value)?;
35661            }
35662        }
35663
35664        Ok(())
35665    }
35666
35667    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
35668        // QUANTILE(this, quantile)
35669        self.write_keyword("QUANTILE");
35670        self.write("(");
35671        self.generate_expression(&e.this)?;
35672        if let Some(quantile) = &e.quantile {
35673            self.write(", ");
35674            self.generate_expression(quantile)?;
35675        }
35676        self.write(")");
35677        Ok(())
35678    }
35679
35680    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
35681        // QUERY_BAND = this [UPDATE] [FOR scope]
35682        if matches!(
35683            self.config.dialect,
35684            Some(crate::dialects::DialectType::Teradata)
35685        ) {
35686            self.write_keyword("SET");
35687            self.write_space();
35688        }
35689        self.write_keyword("QUERY_BAND");
35690        self.write(" = ");
35691        self.generate_expression(&e.this)?;
35692        if e.update.is_some() {
35693            self.write_space();
35694            self.write_keyword("UPDATE");
35695        }
35696        if let Some(scope) = &e.scope {
35697            self.write_space();
35698            self.write_keyword("FOR");
35699            self.write_space();
35700            self.generate_expression(scope)?;
35701        }
35702        Ok(())
35703    }
35704
35705    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
35706        // this = expression
35707        self.generate_expression(&e.this)?;
35708        if let Some(expression) = &e.expression {
35709            self.write(" = ");
35710            self.generate_expression(expression)?;
35711        }
35712        Ok(())
35713    }
35714
35715    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
35716        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
35717        self.write_keyword("TRANSFORM");
35718        self.write("(");
35719        for (i, expr) in e.expressions.iter().enumerate() {
35720            if i > 0 {
35721                self.write(", ");
35722            }
35723            self.generate_expression(expr)?;
35724        }
35725        self.write(")");
35726        if let Some(row_format_before) = &e.row_format_before {
35727            self.write_space();
35728            self.generate_expression(row_format_before)?;
35729        }
35730        if let Some(record_writer) = &e.record_writer {
35731            self.write_space();
35732            self.write_keyword("RECORDWRITER");
35733            self.write_space();
35734            self.generate_expression(record_writer)?;
35735        }
35736        if let Some(command_script) = &e.command_script {
35737            self.write_space();
35738            self.write_keyword("USING");
35739            self.write_space();
35740            self.generate_expression(command_script)?;
35741        }
35742        if let Some(schema) = &e.schema {
35743            self.write_space();
35744            self.write_keyword("AS");
35745            self.write_space();
35746            self.generate_expression(schema)?;
35747        }
35748        if let Some(row_format_after) = &e.row_format_after {
35749            self.write_space();
35750            self.generate_expression(row_format_after)?;
35751        }
35752        if let Some(record_reader) = &e.record_reader {
35753            self.write_space();
35754            self.write_keyword("RECORDREADER");
35755            self.write_space();
35756            self.generate_expression(record_reader)?;
35757        }
35758        Ok(())
35759    }
35760
35761    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
35762        // RANDN([seed])
35763        self.write_keyword("RANDN");
35764        self.write("(");
35765        if let Some(this) = &e.this {
35766            self.generate_expression(this)?;
35767        }
35768        self.write(")");
35769        Ok(())
35770    }
35771
35772    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
35773        // RANDSTR(this, [generator])
35774        self.write_keyword("RANDSTR");
35775        self.write("(");
35776        self.generate_expression(&e.this)?;
35777        if let Some(generator) = &e.generator {
35778            self.write(", ");
35779            self.generate_expression(generator)?;
35780        }
35781        self.write(")");
35782        Ok(())
35783    }
35784
35785    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
35786        // RANGE_BUCKET(this, expression)
35787        self.write_keyword("RANGE_BUCKET");
35788        self.write("(");
35789        self.generate_expression(&e.this)?;
35790        self.write(", ");
35791        self.generate_expression(&e.expression)?;
35792        self.write(")");
35793        Ok(())
35794    }
35795
35796    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
35797        // RANGE_N(this BETWEEN expressions [EACH each])
35798        self.write_keyword("RANGE_N");
35799        self.write("(");
35800        self.generate_expression(&e.this)?;
35801        self.write_space();
35802        self.write_keyword("BETWEEN");
35803        self.write_space();
35804        for (i, expr) in e.expressions.iter().enumerate() {
35805            if i > 0 {
35806                self.write(", ");
35807            }
35808            self.generate_expression(expr)?;
35809        }
35810        if let Some(each) = &e.each {
35811            self.write_space();
35812            self.write_keyword("EACH");
35813            self.write_space();
35814            self.generate_expression(each)?;
35815        }
35816        self.write(")");
35817        Ok(())
35818    }
35819
35820    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
35821        // READ_CSV(this, expressions...)
35822        self.write_keyword("READ_CSV");
35823        self.write("(");
35824        self.generate_expression(&e.this)?;
35825        for expr in &e.expressions {
35826            self.write(", ");
35827            self.generate_expression(expr)?;
35828        }
35829        self.write(")");
35830        Ok(())
35831    }
35832
35833    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
35834        // READ_PARQUET(expressions...)
35835        self.write_keyword("READ_PARQUET");
35836        self.write("(");
35837        for (i, expr) in e.expressions.iter().enumerate() {
35838            if i > 0 {
35839                self.write(", ");
35840            }
35841            self.generate_expression(expr)?;
35842        }
35843        self.write(")");
35844        Ok(())
35845    }
35846
35847    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
35848        // SEARCH kind FIRST BY this SET expression [USING using]
35849        // or CYCLE this SET expression [USING using]
35850        if e.kind == "CYCLE" {
35851            self.write_keyword("CYCLE");
35852        } else {
35853            self.write_keyword("SEARCH");
35854            self.write_space();
35855            self.write(&e.kind);
35856            self.write_space();
35857            self.write_keyword("FIRST BY");
35858        }
35859        self.write_space();
35860        self.generate_expression(&e.this)?;
35861        self.write_space();
35862        self.write_keyword("SET");
35863        self.write_space();
35864        self.generate_expression(&e.expression)?;
35865        if let Some(using) = &e.using {
35866            self.write_space();
35867            self.write_keyword("USING");
35868            self.write_space();
35869            self.generate_expression(using)?;
35870        }
35871        Ok(())
35872    }
35873
35874    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
35875        // REDUCE(this, initial, merge, [finish])
35876        self.write_keyword("REDUCE");
35877        self.write("(");
35878        self.generate_expression(&e.this)?;
35879        if let Some(initial) = &e.initial {
35880            self.write(", ");
35881            self.generate_expression(initial)?;
35882        }
35883        if let Some(merge) = &e.merge {
35884            self.write(", ");
35885            self.generate_expression(merge)?;
35886        }
35887        if let Some(finish) = &e.finish {
35888            self.write(", ");
35889            self.generate_expression(finish)?;
35890        }
35891        self.write(")");
35892        Ok(())
35893    }
35894
35895    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
35896        // REFERENCES this (expressions) [options]
35897        self.write_keyword("REFERENCES");
35898        self.write_space();
35899        self.generate_expression(&e.this)?;
35900        if !e.expressions.is_empty() {
35901            self.write(" (");
35902            for (i, expr) in e.expressions.iter().enumerate() {
35903                if i > 0 {
35904                    self.write(", ");
35905                }
35906                self.generate_expression(expr)?;
35907            }
35908            self.write(")");
35909        }
35910        for opt in &e.options {
35911            self.write_space();
35912            self.generate_expression(opt)?;
35913        }
35914        Ok(())
35915    }
35916
35917    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
35918        // REFRESH [kind] this
35919        self.write_keyword("REFRESH");
35920        if !e.kind.is_empty() {
35921            self.write_space();
35922            self.write_keyword(&e.kind);
35923        }
35924        self.write_space();
35925        self.generate_expression(&e.this)?;
35926        Ok(())
35927    }
35928
35929    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
35930        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
35931        self.write_keyword("REFRESH");
35932        self.write_space();
35933        self.write_keyword(&e.method);
35934
35935        if let Some(ref kind) = e.kind {
35936            self.write_space();
35937            self.write_keyword("ON");
35938            self.write_space();
35939            self.write_keyword(kind);
35940
35941            // EVERY n UNIT
35942            if let Some(ref every) = e.every {
35943                self.write_space();
35944                self.write_keyword("EVERY");
35945                self.write_space();
35946                self.generate_expression(every)?;
35947                if let Some(ref unit) = e.unit {
35948                    self.write_space();
35949                    self.write_keyword(unit);
35950                }
35951            }
35952
35953            // STARTS 'datetime'
35954            if let Some(ref starts) = e.starts {
35955                self.write_space();
35956                self.write_keyword("STARTS");
35957                self.write_space();
35958                self.generate_expression(starts)?;
35959            }
35960        }
35961        Ok(())
35962    }
35963
35964    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
35965        // REGEXP_COUNT(this, expression, position, parameters)
35966        self.write_keyword("REGEXP_COUNT");
35967        self.write("(");
35968        self.generate_expression(&e.this)?;
35969        self.write(", ");
35970        self.generate_expression(&e.expression)?;
35971        if let Some(position) = &e.position {
35972            self.write(", ");
35973            self.generate_expression(position)?;
35974        }
35975        if let Some(parameters) = &e.parameters {
35976            self.write(", ");
35977            self.generate_expression(parameters)?;
35978        }
35979        self.write(")");
35980        Ok(())
35981    }
35982
35983    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
35984        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
35985        self.write_keyword("REGEXP_EXTRACT_ALL");
35986        self.write("(");
35987        self.generate_expression(&e.this)?;
35988        self.write(", ");
35989        self.generate_expression(&e.expression)?;
35990        if let Some(group) = &e.group {
35991            self.write(", ");
35992            self.generate_expression(group)?;
35993        }
35994        self.write(")");
35995        Ok(())
35996    }
35997
35998    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
35999        // REGEXP_FULL_MATCH(this, expression)
36000        self.write_keyword("REGEXP_FULL_MATCH");
36001        self.write("(");
36002        self.generate_expression(&e.this)?;
36003        self.write(", ");
36004        self.generate_expression(&e.expression)?;
36005        self.write(")");
36006        Ok(())
36007    }
36008
36009    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
36010        use crate::dialects::DialectType;
36011        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
36012        if matches!(
36013            self.config.dialect,
36014            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
36015        ) && e.flag.is_none()
36016        {
36017            self.generate_expression(&e.this)?;
36018            self.write(" ~* ");
36019            self.generate_expression(&e.expression)?;
36020        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && e.flag.is_none() {
36021            // ClickHouse has no case-insensitive regex operator; use match() with an inline
36022            // (?i) flag. Inline it into a string-literal pattern for readable output, and fall
36023            // back to concat() so the flag also applies to a non-literal pattern expression.
36024            self.write("match(");
36025            self.generate_expression(&e.this)?;
36026            self.write(", ");
36027            if let Expression::Literal(lit) = e.expression.as_ref() {
36028                if let Literal::String(s) = lit.as_ref() {
36029                    let insensitive =
36030                        Expression::Literal(Box::new(Literal::String(format!("(?i){s}"))));
36031                    self.generate_expression(&insensitive)?;
36032                    self.write(")");
36033                    return Ok(());
36034                }
36035            }
36036            self.write("concat('(?i)', ");
36037            self.generate_expression(&e.expression)?;
36038            self.write("))");
36039            return Ok(());
36040        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36041            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
36042            self.write_keyword("REGEXP_LIKE");
36043            self.write("(");
36044            self.generate_expression(&e.this)?;
36045            self.write(", ");
36046            self.generate_expression(&e.expression)?;
36047            self.write(", ");
36048            if let Some(flag) = &e.flag {
36049                self.generate_expression(flag)?;
36050            } else {
36051                self.write("'i'");
36052            }
36053            self.write(")");
36054        } else {
36055            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
36056            self.generate_expression(&e.this)?;
36057            self.write_space();
36058            self.write_keyword("REGEXP_ILIKE");
36059            self.write_space();
36060            self.generate_expression(&e.expression)?;
36061            if let Some(flag) = &e.flag {
36062                self.write(", ");
36063                self.generate_expression(flag)?;
36064            }
36065        }
36066        Ok(())
36067    }
36068
36069    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
36070        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
36071        self.write_keyword("REGEXP_INSTR");
36072        self.write("(");
36073        self.generate_expression(&e.this)?;
36074        self.write(", ");
36075        self.generate_expression(&e.expression)?;
36076        if let Some(position) = &e.position {
36077            self.write(", ");
36078            self.generate_expression(position)?;
36079        }
36080        if let Some(occurrence) = &e.occurrence {
36081            self.write(", ");
36082            self.generate_expression(occurrence)?;
36083        }
36084        if let Some(option) = &e.option {
36085            self.write(", ");
36086            self.generate_expression(option)?;
36087        }
36088        if let Some(parameters) = &e.parameters {
36089            self.write(", ");
36090            self.generate_expression(parameters)?;
36091        }
36092        if let Some(group) = &e.group {
36093            self.write(", ");
36094            self.generate_expression(group)?;
36095        }
36096        self.write(")");
36097        Ok(())
36098    }
36099
36100    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
36101        // REGEXP_SPLIT(this, expression, limit)
36102        self.write_keyword("REGEXP_SPLIT");
36103        self.write("(");
36104        self.generate_expression(&e.this)?;
36105        self.write(", ");
36106        self.generate_expression(&e.expression)?;
36107        if let Some(limit) = &e.limit {
36108            self.write(", ");
36109            self.generate_expression(limit)?;
36110        }
36111        self.write(")");
36112        Ok(())
36113    }
36114
36115    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
36116        // REGR_AVGX(this, expression)
36117        self.write_keyword("REGR_AVGX");
36118        self.write("(");
36119        self.generate_expression(&e.this)?;
36120        self.write(", ");
36121        self.generate_expression(&e.expression)?;
36122        self.write(")");
36123        Ok(())
36124    }
36125
36126    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
36127        // REGR_AVGY(this, expression)
36128        self.write_keyword("REGR_AVGY");
36129        self.write("(");
36130        self.generate_expression(&e.this)?;
36131        self.write(", ");
36132        self.generate_expression(&e.expression)?;
36133        self.write(")");
36134        Ok(())
36135    }
36136
36137    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
36138        // REGR_COUNT(this, expression)
36139        self.write_keyword("REGR_COUNT");
36140        self.write("(");
36141        self.generate_expression(&e.this)?;
36142        self.write(", ");
36143        self.generate_expression(&e.expression)?;
36144        self.write(")");
36145        Ok(())
36146    }
36147
36148    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
36149        // REGR_INTERCEPT(this, expression)
36150        self.write_keyword("REGR_INTERCEPT");
36151        self.write("(");
36152        self.generate_expression(&e.this)?;
36153        self.write(", ");
36154        self.generate_expression(&e.expression)?;
36155        self.write(")");
36156        Ok(())
36157    }
36158
36159    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
36160        // REGR_R2(this, expression)
36161        self.write_keyword("REGR_R2");
36162        self.write("(");
36163        self.generate_expression(&e.this)?;
36164        self.write(", ");
36165        self.generate_expression(&e.expression)?;
36166        self.write(")");
36167        Ok(())
36168    }
36169
36170    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
36171        // REGR_SLOPE(this, expression)
36172        self.write_keyword("REGR_SLOPE");
36173        self.write("(");
36174        self.generate_expression(&e.this)?;
36175        self.write(", ");
36176        self.generate_expression(&e.expression)?;
36177        self.write(")");
36178        Ok(())
36179    }
36180
36181    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
36182        // REGR_SXX(this, expression)
36183        self.write_keyword("REGR_SXX");
36184        self.write("(");
36185        self.generate_expression(&e.this)?;
36186        self.write(", ");
36187        self.generate_expression(&e.expression)?;
36188        self.write(")");
36189        Ok(())
36190    }
36191
36192    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
36193        // REGR_SXY(this, expression)
36194        self.write_keyword("REGR_SXY");
36195        self.write("(");
36196        self.generate_expression(&e.this)?;
36197        self.write(", ");
36198        self.generate_expression(&e.expression)?;
36199        self.write(")");
36200        Ok(())
36201    }
36202
36203    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
36204        // REGR_SYY(this, expression)
36205        self.write_keyword("REGR_SYY");
36206        self.write("(");
36207        self.generate_expression(&e.this)?;
36208        self.write(", ");
36209        self.generate_expression(&e.expression)?;
36210        self.write(")");
36211        Ok(())
36212    }
36213
36214    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
36215        // REGR_VALX(this, expression)
36216        self.write_keyword("REGR_VALX");
36217        self.write("(");
36218        self.generate_expression(&e.this)?;
36219        self.write(", ");
36220        self.generate_expression(&e.expression)?;
36221        self.write(")");
36222        Ok(())
36223    }
36224
36225    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
36226        // REGR_VALY(this, expression)
36227        self.write_keyword("REGR_VALY");
36228        self.write("(");
36229        self.generate_expression(&e.this)?;
36230        self.write(", ");
36231        self.generate_expression(&e.expression)?;
36232        self.write(")");
36233        Ok(())
36234    }
36235
36236    fn generate_remote_with_connection_model_property(
36237        &mut self,
36238        e: &RemoteWithConnectionModelProperty,
36239    ) -> Result<()> {
36240        // REMOTE WITH CONNECTION this
36241        self.write_keyword("REMOTE WITH CONNECTION");
36242        self.write_space();
36243        self.generate_expression(&e.this)?;
36244        Ok(())
36245    }
36246
36247    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
36248        // RENAME COLUMN [IF EXISTS] this TO new_name
36249        self.write_keyword("RENAME COLUMN");
36250        if e.exists {
36251            self.write_space();
36252            self.write_keyword("IF EXISTS");
36253        }
36254        self.write_space();
36255        self.generate_expression(&e.this)?;
36256        if let Some(to) = &e.to {
36257            self.write_space();
36258            self.write_keyword("TO");
36259            self.write_space();
36260            self.generate_expression(to)?;
36261        }
36262        Ok(())
36263    }
36264
36265    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
36266        // REPLACE PARTITION expression [FROM source]
36267        self.write_keyword("REPLACE PARTITION");
36268        self.write_space();
36269        self.generate_expression(&e.expression)?;
36270        if let Some(source) = &e.source {
36271            self.write_space();
36272            self.write_keyword("FROM");
36273            self.write_space();
36274            self.generate_expression(source)?;
36275        }
36276        Ok(())
36277    }
36278
36279    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
36280        // RETURNING expressions [INTO into]
36281        // TSQL and Fabric use OUTPUT instead of RETURNING
36282        let keyword = match self.config.dialect {
36283            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
36284            _ => "RETURNING",
36285        };
36286        self.write_keyword(keyword);
36287        self.write_space();
36288        for (i, expr) in e.expressions.iter().enumerate() {
36289            if i > 0 {
36290                self.write(", ");
36291            }
36292            self.generate_expression(expr)?;
36293        }
36294        if let Some(into) = &e.into {
36295            self.write_space();
36296            self.write_keyword("INTO");
36297            self.write_space();
36298            self.generate_expression(into)?;
36299        }
36300        Ok(())
36301    }
36302
36303    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
36304        // OUTPUT expressions [INTO into_table]
36305        self.write_space();
36306        self.write_keyword("OUTPUT");
36307        self.write_space();
36308        for (i, expr) in output.columns.iter().enumerate() {
36309            if i > 0 {
36310                self.write(", ");
36311            }
36312            self.generate_expression(expr)?;
36313        }
36314        if let Some(into_table) = &output.into_table {
36315            self.write_space();
36316            self.write_keyword("INTO");
36317            self.write_space();
36318            self.generate_expression(into_table)?;
36319        }
36320        Ok(())
36321    }
36322
36323    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
36324        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
36325        self.write_keyword("RETURNS");
36326        if e.is_table.is_some() {
36327            self.write_space();
36328            self.write_keyword("TABLE");
36329        }
36330        if let Some(table) = &e.table {
36331            self.write_space();
36332            self.generate_expression(table)?;
36333        } else if let Some(this) = &e.this {
36334            self.write_space();
36335            self.generate_expression(this)?;
36336        }
36337        if e.null.is_some() {
36338            self.write_space();
36339            self.write_keyword("NULL ON NULL INPUT");
36340        }
36341        Ok(())
36342    }
36343
36344    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
36345        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
36346        self.write_keyword("ROLLBACK");
36347
36348        // TSQL always uses ROLLBACK TRANSACTION
36349        if e.this.is_none()
36350            && matches!(
36351                self.config.dialect,
36352                Some(DialectType::TSQL) | Some(DialectType::Fabric)
36353            )
36354        {
36355            self.write_space();
36356            self.write_keyword("TRANSACTION");
36357        }
36358
36359        // Check if this has TRANSACTION keyword or transaction name
36360        if let Some(this) = &e.this {
36361            // Check if it's just the "TRANSACTION" marker or an actual transaction name
36362            let is_transaction_marker = matches!(
36363                this.as_ref(),
36364                Expression::Identifier(id) if id.name == "TRANSACTION"
36365            );
36366
36367            self.write_space();
36368            self.write_keyword("TRANSACTION");
36369
36370            // If it's a real transaction name, output it
36371            if !is_transaction_marker {
36372                self.write_space();
36373                self.generate_expression(this)?;
36374            }
36375        }
36376
36377        // Output TO savepoint
36378        if let Some(savepoint) = &e.savepoint {
36379            self.write_space();
36380            self.write_keyword("TO");
36381            self.write_space();
36382            self.generate_expression(savepoint)?;
36383        }
36384        Ok(())
36385    }
36386
36387    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
36388        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
36389        if e.expressions.is_empty() {
36390            self.write_keyword("WITH ROLLUP");
36391        } else {
36392            self.write_keyword("ROLLUP");
36393            self.write("(");
36394            for (i, expr) in e.expressions.iter().enumerate() {
36395                if i > 0 {
36396                    self.write(", ");
36397                }
36398                self.generate_expression(expr)?;
36399            }
36400            self.write(")");
36401        }
36402        Ok(())
36403    }
36404
36405    fn generate_row_format_delimited_property(
36406        &mut self,
36407        e: &RowFormatDelimitedProperty,
36408    ) -> Result<()> {
36409        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
36410        self.write_keyword("ROW FORMAT DELIMITED");
36411        if let Some(fields) = &e.fields {
36412            self.write_space();
36413            self.write_keyword("FIELDS TERMINATED BY");
36414            self.write_space();
36415            self.generate_expression(fields)?;
36416        }
36417        if let Some(escaped) = &e.escaped {
36418            self.write_space();
36419            self.write_keyword("ESCAPED BY");
36420            self.write_space();
36421            self.generate_expression(escaped)?;
36422        }
36423        if let Some(items) = &e.collection_items {
36424            self.write_space();
36425            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
36426            self.write_space();
36427            self.generate_expression(items)?;
36428        }
36429        if let Some(keys) = &e.map_keys {
36430            self.write_space();
36431            self.write_keyword("MAP KEYS TERMINATED BY");
36432            self.write_space();
36433            self.generate_expression(keys)?;
36434        }
36435        if let Some(lines) = &e.lines {
36436            self.write_space();
36437            self.write_keyword("LINES TERMINATED BY");
36438            self.write_space();
36439            self.generate_expression(lines)?;
36440        }
36441        if let Some(null) = &e.null {
36442            self.write_space();
36443            self.write_keyword("NULL DEFINED AS");
36444            self.write_space();
36445            self.generate_expression(null)?;
36446        }
36447        if let Some(serde) = &e.serde {
36448            self.write_space();
36449            self.generate_expression(serde)?;
36450        }
36451        Ok(())
36452    }
36453
36454    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
36455        // ROW FORMAT this
36456        self.write_keyword("ROW FORMAT");
36457        self.write_space();
36458        self.generate_expression(&e.this)?;
36459        Ok(())
36460    }
36461
36462    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
36463        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
36464        self.write_keyword("ROW FORMAT SERDE");
36465        self.write_space();
36466        self.generate_expression(&e.this)?;
36467        if let Some(props) = &e.serde_properties {
36468            self.write_space();
36469            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
36470            self.generate_expression(props)?;
36471        }
36472        Ok(())
36473    }
36474
36475    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
36476        // SHA2(this, length)
36477        self.write_keyword("SHA2");
36478        self.write("(");
36479        self.generate_expression(&e.this)?;
36480        if let Some(length) = e.length {
36481            self.write(", ");
36482            self.write(&length.to_string());
36483        }
36484        self.write(")");
36485        Ok(())
36486    }
36487
36488    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
36489        // SHA2_DIGEST(this, length)
36490        self.write_keyword("SHA2_DIGEST");
36491        self.write("(");
36492        self.generate_expression(&e.this)?;
36493        if let Some(length) = e.length {
36494            self.write(", ");
36495            self.write(&length.to_string());
36496        }
36497        self.write(")");
36498        Ok(())
36499    }
36500
36501    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
36502        let name = if matches!(
36503            self.config.dialect,
36504            Some(crate::dialects::DialectType::Spark)
36505                | Some(crate::dialects::DialectType::Databricks)
36506        ) {
36507            "TRY_ADD"
36508        } else {
36509            "SAFE_ADD"
36510        };
36511        self.write_keyword(name);
36512        self.write("(");
36513        self.generate_expression(&e.this)?;
36514        self.write(", ");
36515        self.generate_expression(&e.expression)?;
36516        self.write(")");
36517        Ok(())
36518    }
36519
36520    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
36521        // SAFE_DIVIDE(this, expression)
36522        self.write_keyword("SAFE_DIVIDE");
36523        self.write("(");
36524        self.generate_expression(&e.this)?;
36525        self.write(", ");
36526        self.generate_expression(&e.expression)?;
36527        self.write(")");
36528        Ok(())
36529    }
36530
36531    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
36532        let name = if matches!(
36533            self.config.dialect,
36534            Some(crate::dialects::DialectType::Spark)
36535                | Some(crate::dialects::DialectType::Databricks)
36536        ) {
36537            "TRY_MULTIPLY"
36538        } else {
36539            "SAFE_MULTIPLY"
36540        };
36541        self.write_keyword(name);
36542        self.write("(");
36543        self.generate_expression(&e.this)?;
36544        self.write(", ");
36545        self.generate_expression(&e.expression)?;
36546        self.write(")");
36547        Ok(())
36548    }
36549
36550    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
36551        let name = if matches!(
36552            self.config.dialect,
36553            Some(crate::dialects::DialectType::Spark)
36554                | Some(crate::dialects::DialectType::Databricks)
36555        ) {
36556            "TRY_SUBTRACT"
36557        } else {
36558            "SAFE_SUBTRACT"
36559        };
36560        self.write_keyword(name);
36561        self.write("(");
36562        self.generate_expression(&e.this)?;
36563        self.write(", ");
36564        self.generate_expression(&e.expression)?;
36565        self.write(")");
36566        Ok(())
36567    }
36568
36569    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
36570    /// METHOD (size UNIT) [REPEATABLE (seed)]
36571    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
36572        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
36573        if matches!(sample.method, SampleMethod::Bucket) {
36574            self.write(" (");
36575            self.write_keyword("BUCKET");
36576            self.write_space();
36577            if let Some(ref num) = sample.bucket_numerator {
36578                self.generate_expression(num)?;
36579            }
36580            self.write_space();
36581            self.write_keyword("OUT OF");
36582            self.write_space();
36583            if let Some(ref denom) = sample.bucket_denominator {
36584                self.generate_expression(denom)?;
36585            }
36586            if let Some(ref field) = sample.bucket_field {
36587                self.write_space();
36588                self.write_keyword("ON");
36589                self.write_space();
36590                self.generate_expression(field)?;
36591            }
36592            self.write(")");
36593            return Ok(());
36594        }
36595
36596        // Output method name if explicitly specified, or for dialects that always require it
36597        let is_snowflake = matches!(
36598            self.config.dialect,
36599            Some(crate::dialects::DialectType::Snowflake)
36600        );
36601        let is_postgres = matches!(
36602            self.config.dialect,
36603            Some(crate::dialects::DialectType::PostgreSQL)
36604                | Some(crate::dialects::DialectType::Redshift)
36605        );
36606        // Databricks and Spark don't output method names
36607        let is_databricks = matches!(
36608            self.config.dialect,
36609            Some(crate::dialects::DialectType::Databricks)
36610        );
36611        let is_spark = matches!(
36612            self.config.dialect,
36613            Some(crate::dialects::DialectType::Spark)
36614        );
36615        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
36616        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
36617        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
36618        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
36619            self.write_space();
36620            if !sample.explicit_method && (is_snowflake || force_method) {
36621                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
36622                self.write_keyword("BERNOULLI");
36623            } else {
36624                match sample.method {
36625                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
36626                    SampleMethod::System => self.write_keyword("SYSTEM"),
36627                    SampleMethod::Block => self.write_keyword("BLOCK"),
36628                    SampleMethod::Row => self.write_keyword("ROW"),
36629                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
36630                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
36631                    SampleMethod::Bucket => {} // handled above
36632                }
36633            }
36634        }
36635
36636        // Output size, with or without parentheses depending on dialect
36637        let emit_size_no_parens = !self.config.tablesample_requires_parens;
36638        if emit_size_no_parens {
36639            self.write_space();
36640            match &sample.size {
36641                Expression::Tuple(tuple) => {
36642                    for (i, expr) in tuple.expressions.iter().enumerate() {
36643                        if i > 0 {
36644                            self.write(", ");
36645                        }
36646                        self.generate_expression(expr)?;
36647                    }
36648                }
36649                expr => self.generate_expression(expr)?,
36650            }
36651        } else {
36652            self.write(" (");
36653            self.generate_expression(&sample.size)?;
36654        }
36655
36656        // Determine unit
36657        let is_rows_method = matches!(
36658            sample.method,
36659            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
36660        );
36661        let is_percent = matches!(
36662            sample.method,
36663            SampleMethod::Percent
36664                | SampleMethod::System
36665                | SampleMethod::Bernoulli
36666                | SampleMethod::Block
36667        );
36668
36669        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
36670        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
36671        // For Databricks and Spark, always output PERCENT for percentage samples.
36672        let is_presto = matches!(
36673            self.config.dialect,
36674            Some(crate::dialects::DialectType::Presto)
36675                | Some(crate::dialects::DialectType::Trino)
36676                | Some(crate::dialects::DialectType::Athena)
36677        );
36678        let should_output_unit = if is_databricks || is_spark {
36679            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
36680            is_percent || is_rows_method || sample.unit_after_size
36681        } else if is_snowflake || is_postgres || is_presto {
36682            sample.unit_after_size
36683        } else {
36684            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
36685        };
36686
36687        if should_output_unit {
36688            self.write_space();
36689            if sample.is_percent {
36690                self.write_keyword("PERCENT");
36691            } else if is_rows_method && !sample.unit_after_size {
36692                self.write_keyword("ROWS");
36693            } else if sample.unit_after_size {
36694                match sample.method {
36695                    SampleMethod::Percent
36696                    | SampleMethod::System
36697                    | SampleMethod::Bernoulli
36698                    | SampleMethod::Block => {
36699                        self.write_keyword("PERCENT");
36700                    }
36701                    SampleMethod::Row | SampleMethod::Reservoir => {
36702                        self.write_keyword("ROWS");
36703                    }
36704                    _ => self.write_keyword("ROWS"),
36705                }
36706            } else {
36707                self.write_keyword("PERCENT");
36708            }
36709        }
36710
36711        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36712            if let Some(ref offset) = sample.offset {
36713                self.write_space();
36714                self.write_keyword("OFFSET");
36715                self.write_space();
36716                self.generate_expression(offset)?;
36717            }
36718        }
36719        if !emit_size_no_parens {
36720            self.write(")");
36721        }
36722
36723        Ok(())
36724    }
36725
36726    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
36727        // SAMPLE this (ClickHouse uses SAMPLE BY)
36728        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36729            self.write_keyword("SAMPLE BY");
36730        } else {
36731            self.write_keyword("SAMPLE");
36732        }
36733        self.write_space();
36734        self.generate_expression(&e.this)?;
36735        Ok(())
36736    }
36737
36738    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
36739        // this (expressions...)
36740        if let Some(this) = &e.this {
36741            self.generate_expression(this)?;
36742        }
36743        if !e.expressions.is_empty() {
36744            // Add space before column list if there's a preceding expression
36745            if e.this.is_some() {
36746                self.write_space();
36747            }
36748            self.write("(");
36749            for (i, expr) in e.expressions.iter().enumerate() {
36750                if i > 0 {
36751                    self.write(", ");
36752                }
36753                self.generate_expression(expr)?;
36754            }
36755            self.write(")");
36756        }
36757        Ok(())
36758    }
36759
36760    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
36761        // COMMENT this
36762        self.write_keyword("COMMENT");
36763        self.write_space();
36764        self.generate_expression(&e.this)?;
36765        Ok(())
36766    }
36767
36768    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
36769        // [this::]expression
36770        if let Some(this) = &e.this {
36771            self.generate_expression(this)?;
36772            self.write("::");
36773        }
36774        self.generate_expression(&e.expression)?;
36775        Ok(())
36776    }
36777
36778    fn generate_search(&mut self, e: &Search) -> Result<()> {
36779        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
36780        self.write_keyword("SEARCH");
36781        self.write("(");
36782        self.generate_expression(&e.this)?;
36783        self.write(", ");
36784        self.generate_expression(&e.expression)?;
36785        if let Some(json_scope) = &e.json_scope {
36786            self.write(", ");
36787            self.generate_expression(json_scope)?;
36788        }
36789        if let Some(analyzer) = &e.analyzer {
36790            self.write(", ");
36791            self.generate_expression(analyzer)?;
36792        }
36793        if let Some(analyzer_options) = &e.analyzer_options {
36794            self.write(", ");
36795            self.generate_expression(analyzer_options)?;
36796        }
36797        if let Some(search_mode) = &e.search_mode {
36798            self.write(", ");
36799            self.generate_expression(search_mode)?;
36800        }
36801        self.write(")");
36802        Ok(())
36803    }
36804
36805    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
36806        // SEARCH_IP(this, expression)
36807        self.write_keyword("SEARCH_IP");
36808        self.write("(");
36809        self.generate_expression(&e.this)?;
36810        self.write(", ");
36811        self.generate_expression(&e.expression)?;
36812        self.write(")");
36813        Ok(())
36814    }
36815
36816    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
36817        // SECURITY this
36818        self.write_keyword("SECURITY");
36819        self.write_space();
36820        self.generate_expression(&e.this)?;
36821        Ok(())
36822    }
36823
36824    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
36825        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
36826        self.write("SEMANTIC_VIEW(");
36827
36828        if self.config.pretty {
36829            // Pretty print: each clause on its own line
36830            self.write_newline();
36831            self.indent_level += 1;
36832            self.write_indent();
36833            self.generate_expression(&e.this)?;
36834
36835            if let Some(metrics) = &e.metrics {
36836                self.write_newline();
36837                self.write_indent();
36838                self.write_keyword("METRICS");
36839                self.write_space();
36840                self.generate_semantic_view_tuple(metrics)?;
36841            }
36842            if let Some(dimensions) = &e.dimensions {
36843                self.write_newline();
36844                self.write_indent();
36845                self.write_keyword("DIMENSIONS");
36846                self.write_space();
36847                self.generate_semantic_view_tuple(dimensions)?;
36848            }
36849            if let Some(facts) = &e.facts {
36850                self.write_newline();
36851                self.write_indent();
36852                self.write_keyword("FACTS");
36853                self.write_space();
36854                self.generate_semantic_view_tuple(facts)?;
36855            }
36856            if let Some(where_) = &e.where_ {
36857                self.write_newline();
36858                self.write_indent();
36859                self.write_keyword("WHERE");
36860                self.write_space();
36861                self.generate_expression(where_)?;
36862            }
36863            self.write_newline();
36864            self.indent_level -= 1;
36865            self.write_indent();
36866        } else {
36867            // Compact: all on one line
36868            self.generate_expression(&e.this)?;
36869            if let Some(metrics) = &e.metrics {
36870                self.write_space();
36871                self.write_keyword("METRICS");
36872                self.write_space();
36873                self.generate_semantic_view_tuple(metrics)?;
36874            }
36875            if let Some(dimensions) = &e.dimensions {
36876                self.write_space();
36877                self.write_keyword("DIMENSIONS");
36878                self.write_space();
36879                self.generate_semantic_view_tuple(dimensions)?;
36880            }
36881            if let Some(facts) = &e.facts {
36882                self.write_space();
36883                self.write_keyword("FACTS");
36884                self.write_space();
36885                self.generate_semantic_view_tuple(facts)?;
36886            }
36887            if let Some(where_) = &e.where_ {
36888                self.write_space();
36889                self.write_keyword("WHERE");
36890                self.write_space();
36891                self.generate_expression(where_)?;
36892            }
36893        }
36894        self.write(")");
36895        Ok(())
36896    }
36897
36898    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
36899    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
36900        if let Expression::Tuple(t) = expr {
36901            for (i, e) in t.expressions.iter().enumerate() {
36902                if i > 0 {
36903                    self.write(", ");
36904                }
36905                self.generate_expression(e)?;
36906            }
36907        } else {
36908            self.generate_expression(expr)?;
36909        }
36910        Ok(())
36911    }
36912
36913    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
36914        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
36915        if let Some(start) = &e.start {
36916            self.write_keyword("START WITH");
36917            self.write_space();
36918            self.generate_expression(start)?;
36919        }
36920        if let Some(increment) = &e.increment {
36921            self.write_space();
36922            self.write_keyword("INCREMENT BY");
36923            self.write_space();
36924            self.generate_expression(increment)?;
36925        }
36926        if let Some(minvalue) = &e.minvalue {
36927            self.write_space();
36928            self.write_keyword("MINVALUE");
36929            self.write_space();
36930            self.generate_expression(minvalue)?;
36931        }
36932        if let Some(maxvalue) = &e.maxvalue {
36933            self.write_space();
36934            self.write_keyword("MAXVALUE");
36935            self.write_space();
36936            self.generate_expression(maxvalue)?;
36937        }
36938        if let Some(cache) = &e.cache {
36939            self.write_space();
36940            self.write_keyword("CACHE");
36941            self.write_space();
36942            self.generate_expression(cache)?;
36943        }
36944        if let Some(owned) = &e.owned {
36945            self.write_space();
36946            self.write_keyword("OWNED BY");
36947            self.write_space();
36948            self.generate_expression(owned)?;
36949        }
36950        for opt in &e.options {
36951            self.write_space();
36952            self.generate_expression(opt)?;
36953        }
36954        Ok(())
36955    }
36956
36957    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
36958        // [WITH] SERDEPROPERTIES (expressions)
36959        if e.with_.is_some() {
36960            self.write_keyword("WITH");
36961            self.write_space();
36962        }
36963        self.write_keyword("SERDEPROPERTIES");
36964        self.write(" (");
36965        for (i, expr) in e.expressions.iter().enumerate() {
36966            if i > 0 {
36967                self.write(", ");
36968            }
36969            // Generate key=value without spaces around =
36970            match expr {
36971                Expression::Eq(eq) => {
36972                    self.generate_expression(&eq.left)?;
36973                    self.write("=");
36974                    self.generate_expression(&eq.right)?;
36975                }
36976                _ => self.generate_expression(expr)?,
36977            }
36978        }
36979        self.write(")");
36980        Ok(())
36981    }
36982
36983    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
36984        // @@[kind.]this
36985        self.write("@@");
36986        if let Some(kind) = &e.kind {
36987            self.write(kind);
36988            self.write(".");
36989        }
36990        self.generate_expression(&e.this)?;
36991        Ok(())
36992    }
36993
36994    fn generate_set(&mut self, e: &Set) -> Result<()> {
36995        // SET/UNSET [TAG] expressions
36996        if e.unset.is_some() {
36997            self.write_keyword("UNSET");
36998        } else {
36999            self.write_keyword("SET");
37000        }
37001        if e.tag.is_some() {
37002            self.write_space();
37003            self.write_keyword("TAG");
37004        }
37005        if !e.expressions.is_empty() {
37006            self.write_space();
37007            for (i, expr) in e.expressions.iter().enumerate() {
37008                if i > 0 {
37009                    self.write(", ");
37010                }
37011                self.generate_expression(expr)?;
37012            }
37013        }
37014        Ok(())
37015    }
37016
37017    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
37018        // SET this or SETCONFIG this
37019        self.write_keyword("SET");
37020        self.write_space();
37021        self.generate_expression(&e.this)?;
37022        Ok(())
37023    }
37024
37025    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
37026        // [kind] name = value
37027        if let Some(kind) = &e.kind {
37028            self.write_keyword(kind);
37029            self.write_space();
37030        }
37031        self.generate_expression(&e.name)?;
37032        self.write(" = ");
37033        self.generate_expression(&e.value)?;
37034        Ok(())
37035    }
37036
37037    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
37038        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
37039        if let Some(with_) = &e.with_ {
37040            self.generate_expression(with_)?;
37041            self.write_space();
37042        }
37043        self.generate_expression(&e.this)?;
37044        self.write_space();
37045        // kind should be UNION, INTERSECT, EXCEPT, etc.
37046        if let Some(kind) = &e.kind {
37047            self.write_keyword(kind);
37048        }
37049        if e.distinct {
37050            self.write_space();
37051            self.write_keyword("DISTINCT");
37052        } else {
37053            self.write_space();
37054            self.write_keyword("ALL");
37055        }
37056        if e.by_name.is_some() {
37057            self.write_space();
37058            self.write_keyword("BY NAME");
37059        }
37060        self.write_space();
37061        self.generate_expression(&e.expression)?;
37062        Ok(())
37063    }
37064
37065    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
37066        // SET or MULTISET
37067        if e.multi.is_some() {
37068            self.write_keyword("MULTISET");
37069        } else {
37070            self.write_keyword("SET");
37071        }
37072        Ok(())
37073    }
37074
37075    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
37076        // SETTINGS expressions
37077        self.write_keyword("SETTINGS");
37078        if self.config.pretty && e.expressions.len() > 1 {
37079            // Pretty print: each setting on its own line, indented
37080            self.indent_level += 1;
37081            for (i, expr) in e.expressions.iter().enumerate() {
37082                if i > 0 {
37083                    self.write(",");
37084                }
37085                self.write_newline();
37086                self.write_indent();
37087                self.generate_expression(expr)?;
37088            }
37089            self.indent_level -= 1;
37090        } else {
37091            self.write_space();
37092            for (i, expr) in e.expressions.iter().enumerate() {
37093                if i > 0 {
37094                    self.write(", ");
37095                }
37096                self.generate_expression(expr)?;
37097            }
37098        }
37099        Ok(())
37100    }
37101
37102    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
37103        // SHARING = this
37104        self.write_keyword("SHARING");
37105        if let Some(this) = &e.this {
37106            self.write(" = ");
37107            self.generate_expression(this)?;
37108        }
37109        Ok(())
37110    }
37111
37112    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
37113        // Python array slicing: begin:end:step
37114        if let Some(begin) = &e.this {
37115            self.generate_expression(begin)?;
37116        }
37117        self.write(":");
37118        if let Some(end) = &e.expression {
37119            self.generate_expression(end)?;
37120        }
37121        if let Some(step) = &e.step {
37122            self.write(":");
37123            self.generate_expression(step)?;
37124        }
37125        Ok(())
37126    }
37127
37128    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
37129        // SORT_ARRAY(this, asc)
37130        self.write_keyword("SORT_ARRAY");
37131        self.write("(");
37132        self.generate_expression(&e.this)?;
37133        if let Some(asc) = &e.asc {
37134            self.write(", ");
37135            self.generate_expression(asc)?;
37136        }
37137        self.write(")");
37138        Ok(())
37139    }
37140
37141    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
37142        // SORT BY expressions
37143        self.write_keyword("SORT BY");
37144        self.write_space();
37145        for (i, expr) in e.expressions.iter().enumerate() {
37146            if i > 0 {
37147                self.write(", ");
37148            }
37149            self.generate_ordered(expr)?;
37150        }
37151        Ok(())
37152    }
37153
37154    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
37155        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
37156        if e.compound.is_some() {
37157            self.write_keyword("COMPOUND");
37158            self.write_space();
37159        }
37160        self.write_keyword("SORTKEY");
37161        self.write("(");
37162        // If this is a Tuple, unwrap its contents to avoid double parentheses
37163        if let Expression::Tuple(t) = e.this.as_ref() {
37164            for (i, expr) in t.expressions.iter().enumerate() {
37165                if i > 0 {
37166                    self.write(", ");
37167                }
37168                self.generate_expression(expr)?;
37169            }
37170        } else {
37171            self.generate_expression(&e.this)?;
37172        }
37173        self.write(")");
37174        Ok(())
37175    }
37176
37177    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
37178        // SPLIT_PART(this, delimiter, part_index)
37179        self.write_keyword("SPLIT_PART");
37180        self.write("(");
37181        self.generate_expression(&e.this)?;
37182        if let Some(delimiter) = &e.delimiter {
37183            self.write(", ");
37184            self.generate_expression(delimiter)?;
37185        }
37186        if let Some(part_index) = &e.part_index {
37187            self.write(", ");
37188            self.generate_expression(part_index)?;
37189        }
37190        self.write(")");
37191        Ok(())
37192    }
37193
37194    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
37195        // READS SQL DATA or MODIFIES SQL DATA, etc.
37196        self.generate_expression(&e.this)?;
37197        Ok(())
37198    }
37199
37200    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
37201        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
37202        self.write_keyword("SQL SECURITY");
37203        self.write_space();
37204        self.generate_expression(&e.this)?;
37205        Ok(())
37206    }
37207
37208    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
37209        // ST_DISTANCE(this, expression, [use_spheroid])
37210        self.write_keyword("ST_DISTANCE");
37211        self.write("(");
37212        self.generate_expression(&e.this)?;
37213        self.write(", ");
37214        self.generate_expression(&e.expression)?;
37215        if let Some(use_spheroid) = &e.use_spheroid {
37216            self.write(", ");
37217            self.generate_expression(use_spheroid)?;
37218        }
37219        self.write(")");
37220        Ok(())
37221    }
37222
37223    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
37224        // ST_POINT(this, expression)
37225        self.write_keyword("ST_POINT");
37226        self.write("(");
37227        self.generate_expression(&e.this)?;
37228        self.write(", ");
37229        self.generate_expression(&e.expression)?;
37230        self.write(")");
37231        Ok(())
37232    }
37233
37234    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
37235        // IMMUTABLE, STABLE, VOLATILE
37236        self.generate_expression(&e.this)?;
37237        Ok(())
37238    }
37239
37240    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
37241        // STANDARD_HASH(this, [expression])
37242        self.write_keyword("STANDARD_HASH");
37243        self.write("(");
37244        self.generate_expression(&e.this)?;
37245        if let Some(expression) = &e.expression {
37246            self.write(", ");
37247            self.generate_expression(expression)?;
37248        }
37249        self.write(")");
37250        Ok(())
37251    }
37252
37253    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
37254        // STORED BY this
37255        self.write_keyword("STORED BY");
37256        self.write_space();
37257        self.generate_expression(&e.this)?;
37258        Ok(())
37259    }
37260
37261    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
37262        // STRPOS(this, substr) or STRPOS(this, substr, position)
37263        // Different dialects have different function names
37264        use crate::dialects::DialectType;
37265        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
37266            // Snowflake: CHARINDEX(substr, str[, position])
37267            self.write_keyword("CHARINDEX");
37268            self.write("(");
37269            if let Some(substr) = &e.substr {
37270                self.generate_expression(substr)?;
37271                self.write(", ");
37272            }
37273            self.generate_expression(&e.this)?;
37274            if let Some(position) = &e.position {
37275                self.write(", ");
37276                self.generate_expression(position)?;
37277            }
37278            self.write(")");
37279        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
37280            self.write_keyword("POSITION");
37281            self.write("(");
37282            self.generate_expression(&e.this)?;
37283            if let Some(substr) = &e.substr {
37284                self.write(", ");
37285                self.generate_expression(substr)?;
37286            }
37287            if let Some(position) = &e.position {
37288                self.write(", ");
37289                self.generate_expression(position)?;
37290            }
37291            if let Some(occurrence) = &e.occurrence {
37292                self.write(", ");
37293                self.generate_expression(occurrence)?;
37294            }
37295            self.write(")");
37296        } else if matches!(
37297            self.config.dialect,
37298            Some(DialectType::SQLite)
37299                | Some(DialectType::Oracle)
37300                | Some(DialectType::BigQuery)
37301                | Some(DialectType::Teradata)
37302        ) {
37303            self.write_keyword("INSTR");
37304            self.write("(");
37305            self.generate_expression(&e.this)?;
37306            if let Some(substr) = &e.substr {
37307                self.write(", ");
37308                self.generate_expression(substr)?;
37309            }
37310            if let Some(position) = &e.position {
37311                self.write(", ");
37312                self.generate_expression(position)?;
37313            } else if e.occurrence.is_some() {
37314                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
37315                // Default start position is 1
37316                self.write(", 1");
37317            }
37318            if let Some(occurrence) = &e.occurrence {
37319                self.write(", ");
37320                self.generate_expression(occurrence)?;
37321            }
37322            self.write(")");
37323        } else if matches!(
37324            self.config.dialect,
37325            Some(DialectType::MySQL)
37326                | Some(DialectType::SingleStore)
37327                | Some(DialectType::Doris)
37328                | Some(DialectType::StarRocks)
37329                | Some(DialectType::Hive)
37330                | Some(DialectType::Spark)
37331                | Some(DialectType::Databricks)
37332        ) {
37333            // LOCATE(substr, str[, position]) - substr first
37334            self.write_keyword("LOCATE");
37335            self.write("(");
37336            if let Some(substr) = &e.substr {
37337                self.generate_expression(substr)?;
37338                self.write(", ");
37339            }
37340            self.generate_expression(&e.this)?;
37341            if let Some(position) = &e.position {
37342                self.write(", ");
37343                self.generate_expression(position)?;
37344            }
37345            self.write(")");
37346        } else if matches!(
37347            self.config.dialect,
37348            Some(DialectType::TSQL) | Some(DialectType::Fabric)
37349        ) {
37350            // CHARINDEX(substr, str[, position])
37351            self.write_keyword("CHARINDEX");
37352            self.write("(");
37353            if let Some(substr) = &e.substr {
37354                self.generate_expression(substr)?;
37355                self.write(", ");
37356            }
37357            self.generate_expression(&e.this)?;
37358            if let Some(position) = &e.position {
37359                self.write(", ");
37360                self.generate_expression(position)?;
37361            }
37362            self.write(")");
37363        } else if matches!(
37364            self.config.dialect,
37365            Some(DialectType::PostgreSQL)
37366                | Some(DialectType::Materialize)
37367                | Some(DialectType::RisingWave)
37368                | Some(DialectType::Redshift)
37369        ) {
37370            // POSITION(substr IN str) syntax
37371            self.write_keyword("POSITION");
37372            self.write("(");
37373            if let Some(substr) = &e.substr {
37374                self.generate_expression(substr)?;
37375                self.write(" IN ");
37376            }
37377            self.generate_expression(&e.this)?;
37378            self.write(")");
37379        } else {
37380            self.write_keyword("STRPOS");
37381            self.write("(");
37382            self.generate_expression(&e.this)?;
37383            if let Some(substr) = &e.substr {
37384                self.write(", ");
37385                self.generate_expression(substr)?;
37386            }
37387            if let Some(position) = &e.position {
37388                self.write(", ");
37389                self.generate_expression(position)?;
37390            }
37391            if let Some(occurrence) = &e.occurrence {
37392                self.write(", ");
37393                self.generate_expression(occurrence)?;
37394            }
37395            self.write(")");
37396        }
37397        Ok(())
37398    }
37399
37400    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
37401        match self.config.dialect {
37402            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
37403                self.generate_tsql_str_to_temporal(&e.this, e.format.as_deref(), "DATE")?;
37404            }
37405            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
37406                // TO_DATE(this, java_format)
37407                self.write_keyword("TO_DATE");
37408                self.write("(");
37409                self.generate_expression(&e.this)?;
37410                if let Some(format) = &e.format {
37411                    self.write(", '");
37412                    self.write(&Self::strftime_to_java_format(format));
37413                    self.write("'");
37414                }
37415                self.write(")");
37416            }
37417            Some(DialectType::DuckDB) => {
37418                // CAST(STRPTIME(this, format) AS DATE)
37419                self.write_keyword("CAST");
37420                self.write("(");
37421                self.write_keyword("STRPTIME");
37422                self.write("(");
37423                self.generate_expression(&e.this)?;
37424                if let Some(format) = &e.format {
37425                    self.write(", '");
37426                    self.write(format);
37427                    self.write("'");
37428                }
37429                self.write(")");
37430                self.write_keyword(" AS ");
37431                self.write_keyword("DATE");
37432                self.write(")");
37433            }
37434            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
37435                // TO_DATE(this, pg_format)
37436                self.write_keyword("TO_DATE");
37437                self.write("(");
37438                self.generate_expression(&e.this)?;
37439                if let Some(format) = &e.format {
37440                    self.write(", '");
37441                    self.write(&Self::strftime_to_postgres_format(format));
37442                    self.write("'");
37443                }
37444                self.write(")");
37445            }
37446            Some(DialectType::BigQuery) => {
37447                // PARSE_DATE(format, this) - note: format comes first for BigQuery
37448                self.write_keyword("PARSE_DATE");
37449                self.write("(");
37450                if let Some(format) = &e.format {
37451                    self.write("'");
37452                    self.write(format);
37453                    self.write("'");
37454                    self.write(", ");
37455                }
37456                self.generate_expression(&e.this)?;
37457                self.write(")");
37458            }
37459            Some(DialectType::Teradata) => {
37460                // CAST(this AS DATE FORMAT 'teradata_fmt')
37461                self.write_keyword("CAST");
37462                self.write("(");
37463                self.generate_expression(&e.this)?;
37464                self.write_keyword(" AS ");
37465                self.write_keyword("DATE");
37466                if let Some(format) = &e.format {
37467                    self.write_keyword(" FORMAT ");
37468                    self.write("'");
37469                    self.write(&Self::strftime_to_teradata_format(format));
37470                    self.write("'");
37471                }
37472                self.write(")");
37473            }
37474            _ => {
37475                // STR_TO_DATE(this, format) - MySQL default
37476                self.write_keyword("STR_TO_DATE");
37477                self.write("(");
37478                self.generate_expression(&e.this)?;
37479                if let Some(format) = &e.format {
37480                    self.write(", '");
37481                    self.write(format);
37482                    self.write("'");
37483                }
37484                self.write(")");
37485            }
37486        }
37487        Ok(())
37488    }
37489
37490    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
37491    fn strftime_to_teradata_format(fmt: &str) -> String {
37492        let mut result = String::with_capacity(fmt.len() * 2);
37493        let bytes = fmt.as_bytes();
37494        let len = bytes.len();
37495        let mut i = 0;
37496        while i < len {
37497            if bytes[i] == b'%' && i + 1 < len {
37498                let replacement = match bytes[i + 1] {
37499                    b'Y' => "YYYY",
37500                    b'y' => "YY",
37501                    b'm' => "MM",
37502                    b'B' => "MMMM",
37503                    b'b' => "MMM",
37504                    b'd' => "DD",
37505                    b'j' => "DDD",
37506                    b'H' => "HH",
37507                    b'M' => "MI",
37508                    b'S' => "SS",
37509                    b'f' => "SSSSSS",
37510                    b'A' => "EEEE",
37511                    b'a' => "EEE",
37512                    _ => {
37513                        result.push('%');
37514                        i += 1;
37515                        continue;
37516                    }
37517                };
37518                result.push_str(replacement);
37519                i += 2;
37520            } else {
37521                result.push(bytes[i] as char);
37522                i += 1;
37523            }
37524        }
37525        result
37526    }
37527
37528    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
37529    /// Public static version for use by other modules
37530    pub fn strftime_to_java_format_static(fmt: &str) -> String {
37531        Self::strftime_to_java_format(fmt)
37532    }
37533
37534    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
37535    fn strftime_to_java_format(fmt: &str) -> String {
37536        let mut result = String::with_capacity(fmt.len() * 2);
37537        let bytes = fmt.as_bytes();
37538        let len = bytes.len();
37539        let mut i = 0;
37540        while i < len {
37541            if bytes[i] == b'%' && i + 1 < len {
37542                // Check for non-padded variants (%-X)
37543                if bytes[i + 1] == b'-' && i + 2 < len {
37544                    let replacement = match bytes[i + 2] {
37545                        b'd' => "d",
37546                        b'm' => "M",
37547                        b'H' => "H",
37548                        b'M' => "m",
37549                        b'S' => "s",
37550                        _ => {
37551                            result.push('%');
37552                            i += 1;
37553                            continue;
37554                        }
37555                    };
37556                    result.push_str(replacement);
37557                    i += 3;
37558                } else {
37559                    let replacement = match bytes[i + 1] {
37560                        b'Y' => "yyyy",
37561                        b'y' => "yy",
37562                        b'm' => "MM",
37563                        b'B' => "MMMM",
37564                        b'b' => "MMM",
37565                        b'd' => "dd",
37566                        b'j' => "DDD",
37567                        b'H' => "HH",
37568                        b'M' => "mm",
37569                        b'S' => "ss",
37570                        b'f' => "SSSSSS",
37571                        b'A' => "EEEE",
37572                        b'a' => "EEE",
37573                        _ => {
37574                            result.push('%');
37575                            i += 1;
37576                            continue;
37577                        }
37578                    };
37579                    result.push_str(replacement);
37580                    i += 2;
37581                }
37582            } else {
37583                result.push(bytes[i] as char);
37584                i += 1;
37585            }
37586        }
37587        result
37588    }
37589
37590    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
37591    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
37592    fn strftime_to_tsql_format(fmt: &str) -> String {
37593        let mut result = String::with_capacity(fmt.len() * 2);
37594        let bytes = fmt.as_bytes();
37595        let len = bytes.len();
37596        let mut i = 0;
37597        while i < len {
37598            if bytes[i] == b'%' && i + 1 < len {
37599                // Check for non-padded variants (%-X)
37600                if bytes[i + 1] == b'-' && i + 2 < len {
37601                    let replacement = match bytes[i + 2] {
37602                        b'd' => "d",
37603                        b'm' => "M",
37604                        b'H' => "H",
37605                        b'M' => "m",
37606                        b'S' => "s",
37607                        _ => {
37608                            result.push('%');
37609                            i += 1;
37610                            continue;
37611                        }
37612                    };
37613                    result.push_str(replacement);
37614                    i += 3;
37615                } else {
37616                    let replacement = match bytes[i + 1] {
37617                        b'Y' => "yyyy",
37618                        b'y' => "yy",
37619                        b'm' => "MM",
37620                        b'B' => "MMMM",
37621                        b'b' => "MMM",
37622                        b'd' => "dd",
37623                        b'j' => "DDD",
37624                        b'H' => "HH",
37625                        b'M' => "mm",
37626                        b'S' => "ss",
37627                        b'f' => "ffffff",
37628                        b'A' => "dddd",
37629                        b'a' => "ddd",
37630                        _ => {
37631                            result.push('%');
37632                            i += 1;
37633                            continue;
37634                        }
37635                    };
37636                    result.push_str(replacement);
37637                    i += 2;
37638                }
37639            } else {
37640                result.push(bytes[i] as char);
37641                i += 1;
37642            }
37643        }
37644        result
37645    }
37646
37647    fn tsql_convert_style_for_strftime(fmt: &str) -> Option<u16> {
37648        match fmt.trim() {
37649            "%b %d %Y %-I:%M%p" => Some(100),
37650            "%m/%d/%y" => Some(1),
37651            "%y.%m.%d" => Some(2),
37652            "%d/%m/%y" => Some(3),
37653            "%d.%m.%y" => Some(4),
37654            "%d-%m-%y" => Some(5),
37655            "%d %b %y" => Some(6),
37656            "%b %d, %y" => Some(7),
37657            "%H:%M:%S" => Some(108),
37658            "%b %d %Y %-I:%M:%S:%f%p" => Some(109),
37659            "%m-%d-%y" => Some(10),
37660            "%y/%m/%d" => Some(11),
37661            "%y%m%d" => Some(12),
37662            "%d %b %Y %H:%M:%S:%f" => Some(113),
37663            "%H:%M:%S:%f" => Some(114),
37664            "%m/%d/%Y" => Some(101),
37665            "%Y.%m.%d" => Some(102),
37666            "%d/%m/%Y" => Some(103),
37667            "%d.%m.%Y" => Some(104),
37668            "%d-%m-%Y" => Some(105),
37669            "%d %b %Y" => Some(106),
37670            "%b %d, %Y" => Some(107),
37671            "%m-%d-%Y" => Some(110),
37672            "%Y/%m/%d" => Some(111),
37673            "%Y%m%d" => Some(112),
37674            "%Y-%m-%d %H:%M:%S" => Some(120),
37675            "%Y-%m-%d %H:%M:%S.%f" => Some(121),
37676            "%Y-%m-%dT%H:%M:%S" | "%Y-%m-%dT%H:%M:%S.%f" => Some(126),
37677            "%Y-%m-%d" => Some(23),
37678            _ => None,
37679        }
37680    }
37681
37682    fn generate_tsql_str_to_temporal(
37683        &mut self,
37684        this: &Expression,
37685        format: Option<&str>,
37686        target_type: &str,
37687    ) -> Result<()> {
37688        if let Some(format) = format {
37689            if let Some(style) = Self::tsql_convert_style_for_strftime(format) {
37690                self.write_keyword("CONVERT");
37691                self.write("(");
37692                self.write_keyword(target_type);
37693                self.write(", ");
37694                self.generate_expression(this)?;
37695                self.write(", ");
37696                self.write(&style.to_string());
37697                self.write(")");
37698                return Ok(());
37699            }
37700
37701            self.unsupported(format!(
37702                "T-SQL/Fabric {target_type} parsing format '{format}' has no CONVERT style mapping"
37703            ))?;
37704        }
37705
37706        self.write_keyword("CAST");
37707        self.write("(");
37708        self.generate_expression(this)?;
37709        self.write(" AS ");
37710        self.write_keyword(target_type);
37711        self.write(")");
37712        Ok(())
37713    }
37714
37715    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
37716    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
37717    fn decompose_json_path(path: &str) -> Vec<String> {
37718        let mut parts = Vec::new();
37719        // Strip leading $ and optional .
37720        let path = if path.starts_with("$.") {
37721            &path[2..]
37722        } else if path.starts_with('$') {
37723            &path[1..]
37724        } else {
37725            path
37726        };
37727        if path.is_empty() {
37728            return parts;
37729        }
37730        let mut current = String::new();
37731        let chars: Vec<char> = path.chars().collect();
37732        let mut i = 0;
37733        while i < chars.len() {
37734            match chars[i] {
37735                '.' => {
37736                    if !current.is_empty() {
37737                        parts.push(current.clone());
37738                        current.clear();
37739                    }
37740                    i += 1;
37741                }
37742                '[' => {
37743                    if !current.is_empty() {
37744                        parts.push(current.clone());
37745                        current.clear();
37746                    }
37747                    i += 1;
37748                    // Read the content inside brackets
37749                    let mut bracket_content = String::new();
37750                    while i < chars.len() && chars[i] != ']' {
37751                        // Skip quotes inside brackets
37752                        if chars[i] == '"' || chars[i] == '\'' {
37753                            let quote = chars[i];
37754                            i += 1;
37755                            while i < chars.len() && chars[i] != quote {
37756                                bracket_content.push(chars[i]);
37757                                i += 1;
37758                            }
37759                            if i < chars.len() {
37760                                i += 1;
37761                            } // skip closing quote
37762                        } else {
37763                            bracket_content.push(chars[i]);
37764                            i += 1;
37765                        }
37766                    }
37767                    if i < chars.len() {
37768                        i += 1;
37769                    } // skip ]
37770                      // Skip wildcard [*] - don't add as a part
37771                    if bracket_content != "*" {
37772                        parts.push(bracket_content);
37773                    }
37774                }
37775                _ => {
37776                    current.push(chars[i]);
37777                    i += 1;
37778                }
37779            }
37780        }
37781        if !current.is_empty() {
37782            parts.push(current);
37783        }
37784        parts
37785    }
37786
37787    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
37788    fn strftime_to_postgres_format(fmt: &str) -> String {
37789        let mut result = String::with_capacity(fmt.len() * 2);
37790        let bytes = fmt.as_bytes();
37791        let len = bytes.len();
37792        let mut i = 0;
37793        while i < len {
37794            if bytes[i] == b'%' && i + 1 < len {
37795                // Check for non-padded variants (%-X)
37796                if bytes[i + 1] == b'-' && i + 2 < len {
37797                    let replacement = match bytes[i + 2] {
37798                        b'd' => "FMDD",
37799                        b'm' => "FMMM",
37800                        b'H' => "FMHH24",
37801                        b'M' => "FMMI",
37802                        b'S' => "FMSS",
37803                        _ => {
37804                            result.push('%');
37805                            i += 1;
37806                            continue;
37807                        }
37808                    };
37809                    result.push_str(replacement);
37810                    i += 3;
37811                } else {
37812                    let replacement = match bytes[i + 1] {
37813                        b'Y' => "YYYY",
37814                        b'y' => "YY",
37815                        b'm' => "MM",
37816                        b'B' => "Month",
37817                        b'b' => "Mon",
37818                        b'd' => "DD",
37819                        b'j' => "DDD",
37820                        b'H' => "HH24",
37821                        b'M' => "MI",
37822                        b'S' => "SS",
37823                        b'f' => "US",
37824                        b'A' => "Day",
37825                        b'a' => "Dy",
37826                        _ => {
37827                            result.push('%');
37828                            i += 1;
37829                            continue;
37830                        }
37831                    };
37832                    result.push_str(replacement);
37833                    i += 2;
37834                }
37835            } else {
37836                result.push(bytes[i] as char);
37837                i += 1;
37838            }
37839        }
37840        result
37841    }
37842
37843    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
37844    fn strftime_to_snowflake_format(fmt: &str) -> String {
37845        let mut result = String::with_capacity(fmt.len() * 2);
37846        let bytes = fmt.as_bytes();
37847        let len = bytes.len();
37848        let mut i = 0;
37849        while i < len {
37850            if bytes[i] == b'%' && i + 1 < len {
37851                // Check for non-padded variants (%-X)
37852                if bytes[i + 1] == b'-' && i + 2 < len {
37853                    let replacement = match bytes[i + 2] {
37854                        b'd' => "dd",
37855                        b'm' => "mm",
37856                        _ => {
37857                            result.push('%');
37858                            i += 1;
37859                            continue;
37860                        }
37861                    };
37862                    result.push_str(replacement);
37863                    i += 3;
37864                } else {
37865                    let replacement = match bytes[i + 1] {
37866                        b'Y' => "yyyy",
37867                        b'y' => "yy",
37868                        b'm' => "mm",
37869                        b'd' => "DD",
37870                        b'H' => "hh24",
37871                        b'M' => "mi",
37872                        b'S' => "ss",
37873                        b'f' => "ff",
37874                        _ => {
37875                            result.push('%');
37876                            i += 1;
37877                            continue;
37878                        }
37879                    };
37880                    result.push_str(replacement);
37881                    i += 2;
37882                }
37883            } else {
37884                result.push(bytes[i] as char);
37885                i += 1;
37886            }
37887        }
37888        result
37889    }
37890
37891    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
37892        // STR_TO_MAP(this, pair_delim, key_value_delim)
37893        self.write_keyword("STR_TO_MAP");
37894        self.write("(");
37895        self.generate_expression(&e.this)?;
37896        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
37897        let needs_defaults = matches!(
37898            self.config.dialect,
37899            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
37900        );
37901        if let Some(pair_delim) = &e.pair_delim {
37902            self.write(", ");
37903            self.generate_expression(pair_delim)?;
37904        } else if needs_defaults {
37905            self.write(", ','");
37906        }
37907        if let Some(key_value_delim) = &e.key_value_delim {
37908            self.write(", ");
37909            self.generate_expression(key_value_delim)?;
37910        } else if needs_defaults {
37911            self.write(", ':'");
37912        }
37913        self.write(")");
37914        Ok(())
37915    }
37916
37917    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
37918        // Detect format style: strftime (starts with %) vs Snowflake/Java
37919        let is_strftime = e.format.contains('%');
37920        // Helper: get strftime format from whatever style is stored
37921        let to_strftime = |f: &str| -> String {
37922            if is_strftime {
37923                f.to_string()
37924            } else {
37925                Self::snowflake_format_to_strftime(f)
37926            }
37927        };
37928        // Helper: get Java format
37929        let to_java = |f: &str| -> String {
37930            if is_strftime {
37931                Self::strftime_to_java_format(f)
37932            } else {
37933                Self::snowflake_format_to_spark(f)
37934            }
37935        };
37936        // Helper: get PG format
37937        let to_pg = |f: &str| -> String {
37938            if is_strftime {
37939                Self::strftime_to_postgres_format(f)
37940            } else {
37941                Self::convert_strptime_to_postgres_format(f)
37942            }
37943        };
37944
37945        match self.config.dialect {
37946            Some(DialectType::Exasol) => {
37947                self.write_keyword("TO_DATE");
37948                self.write("(");
37949                self.generate_expression(&e.this)?;
37950                self.write(", '");
37951                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
37952                self.write("'");
37953                self.write(")");
37954            }
37955            Some(DialectType::BigQuery) => {
37956                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
37957                let fmt = to_strftime(&e.format);
37958                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
37959                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
37960                self.write_keyword("PARSE_TIMESTAMP");
37961                self.write("('");
37962                self.write(&fmt);
37963                self.write("', ");
37964                self.generate_expression(&e.this)?;
37965                self.write(")");
37966            }
37967            Some(DialectType::Hive) => {
37968                // Hive: CAST(x AS TIMESTAMP) for simple date formats
37969                // Check both the raw format and the converted format (in case it's already Java)
37970                let java_fmt = to_java(&e.format);
37971                if java_fmt == "yyyy-MM-dd HH:mm:ss"
37972                    || java_fmt == "yyyy-MM-dd"
37973                    || e.format == "yyyy-MM-dd HH:mm:ss"
37974                    || e.format == "yyyy-MM-dd"
37975                {
37976                    self.write_keyword("CAST");
37977                    self.write("(");
37978                    self.generate_expression(&e.this)?;
37979                    self.write(" ");
37980                    self.write_keyword("AS TIMESTAMP");
37981                    self.write(")");
37982                } else {
37983                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
37984                    self.write_keyword("CAST");
37985                    self.write("(");
37986                    self.write_keyword("FROM_UNIXTIME");
37987                    self.write("(");
37988                    self.write_keyword("UNIX_TIMESTAMP");
37989                    self.write("(");
37990                    self.generate_expression(&e.this)?;
37991                    self.write(", '");
37992                    self.write(&java_fmt);
37993                    self.write("')");
37994                    self.write(") ");
37995                    self.write_keyword("AS TIMESTAMP");
37996                    self.write(")");
37997                }
37998            }
37999            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38000                // Spark: TO_TIMESTAMP(value, java_format)
38001                let java_fmt = to_java(&e.format);
38002                self.write_keyword("TO_TIMESTAMP");
38003                self.write("(");
38004                self.generate_expression(&e.this)?;
38005                self.write(", '");
38006                self.write(&java_fmt);
38007                self.write("')");
38008            }
38009            Some(DialectType::MySQL) => {
38010                // MySQL: STR_TO_DATE(value, format)
38011                let mut fmt = to_strftime(&e.format);
38012                // MySQL uses %e for non-padded day, %T for %H:%M:%S
38013                fmt = fmt.replace("%-d", "%e");
38014                fmt = fmt.replace("%-m", "%c");
38015                fmt = fmt.replace("%H:%M:%S", "%T");
38016                self.write_keyword("STR_TO_DATE");
38017                self.write("(");
38018                self.generate_expression(&e.this)?;
38019                self.write(", '");
38020                self.write(&fmt);
38021                self.write("')");
38022            }
38023            Some(DialectType::Drill) => {
38024                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
38025                let java_fmt = to_java(&e.format);
38026                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
38027                let java_fmt = java_fmt.replace('T', "''T''");
38028                self.write_keyword("TO_TIMESTAMP");
38029                self.write("(");
38030                self.generate_expression(&e.this)?;
38031                self.write(", '");
38032                self.write(&java_fmt);
38033                self.write("')");
38034            }
38035            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
38036                // Presto: DATE_PARSE(value, strftime_format)
38037                let mut fmt = to_strftime(&e.format);
38038                // Presto uses %e for non-padded day, %T for %H:%M:%S
38039                fmt = fmt.replace("%-d", "%e");
38040                fmt = fmt.replace("%-m", "%c");
38041                fmt = fmt.replace("%H:%M:%S", "%T");
38042                self.write_keyword("DATE_PARSE");
38043                self.write("(");
38044                self.generate_expression(&e.this)?;
38045                self.write(", '");
38046                self.write(&fmt);
38047                self.write("')");
38048            }
38049            Some(DialectType::DuckDB) => {
38050                // DuckDB: STRPTIME(value, strftime_format)
38051                let fmt = to_strftime(&e.format);
38052                self.write_keyword("STRPTIME");
38053                self.write("(");
38054                self.generate_expression(&e.this)?;
38055                self.write(", '");
38056                self.write(&fmt);
38057                self.write("')");
38058            }
38059            Some(DialectType::PostgreSQL)
38060            | Some(DialectType::Redshift)
38061            | Some(DialectType::Materialize) => {
38062                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
38063                let pg_fmt = to_pg(&e.format);
38064                self.write_keyword("TO_TIMESTAMP");
38065                self.write("(");
38066                self.generate_expression(&e.this)?;
38067                self.write(", '");
38068                self.write(&pg_fmt);
38069                self.write("')");
38070            }
38071            Some(DialectType::Oracle) => {
38072                // Oracle: TO_TIMESTAMP(value, pg_format)
38073                let pg_fmt = to_pg(&e.format);
38074                self.write_keyword("TO_TIMESTAMP");
38075                self.write("(");
38076                self.generate_expression(&e.this)?;
38077                self.write(", '");
38078                self.write(&pg_fmt);
38079                self.write("')");
38080            }
38081            Some(DialectType::Snowflake) => {
38082                // Snowflake: TO_TIMESTAMP(value, format) - native format
38083                self.write_keyword("TO_TIMESTAMP");
38084                self.write("(");
38085                self.generate_expression(&e.this)?;
38086                self.write(", '");
38087                self.write(&e.format);
38088                self.write("')");
38089            }
38090            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
38091                self.generate_tsql_str_to_temporal(&e.this, Some(&e.format), "DATETIME2")?;
38092            }
38093            _ => {
38094                // Default: STR_TO_TIME(this, format)
38095                self.write_keyword("STR_TO_TIME");
38096                self.write("(");
38097                self.generate_expression(&e.this)?;
38098                self.write(", '");
38099                self.write(&e.format);
38100                self.write("'");
38101                self.write(")");
38102            }
38103        }
38104        Ok(())
38105    }
38106
38107    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
38108    fn snowflake_format_to_strftime(format: &str) -> String {
38109        let mut result = String::new();
38110        let chars: Vec<char> = format.chars().collect();
38111        let mut i = 0;
38112        while i < chars.len() {
38113            let remaining = &format[i..];
38114            if remaining.starts_with("yyyy") {
38115                result.push_str("%Y");
38116                i += 4;
38117            } else if remaining.starts_with("yy") {
38118                result.push_str("%y");
38119                i += 2;
38120            } else if remaining.starts_with("mmmm") {
38121                result.push_str("%B"); // full month name
38122                i += 4;
38123            } else if remaining.starts_with("mon") {
38124                result.push_str("%b"); // abbreviated month
38125                i += 3;
38126            } else if remaining.starts_with("mm") {
38127                result.push_str("%m");
38128                i += 2;
38129            } else if remaining.starts_with("DD") {
38130                result.push_str("%d");
38131                i += 2;
38132            } else if remaining.starts_with("dy") {
38133                result.push_str("%a"); // abbreviated day name
38134                i += 2;
38135            } else if remaining.starts_with("hh24") {
38136                result.push_str("%H");
38137                i += 4;
38138            } else if remaining.starts_with("hh12") {
38139                result.push_str("%I");
38140                i += 4;
38141            } else if remaining.starts_with("hh") {
38142                result.push_str("%H");
38143                i += 2;
38144            } else if remaining.starts_with("mi") {
38145                result.push_str("%M");
38146                i += 2;
38147            } else if remaining.starts_with("ss") {
38148                result.push_str("%S");
38149                i += 2;
38150            } else if remaining.starts_with("ff") {
38151                // Fractional seconds
38152                result.push_str("%f");
38153                i += 2;
38154                // Skip digits after ff (ff3, ff6, ff9)
38155                while i < chars.len() && chars[i].is_ascii_digit() {
38156                    i += 1;
38157                }
38158            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
38159                result.push_str("%p");
38160                i += 2;
38161            } else if remaining.starts_with("tz") {
38162                result.push_str("%Z");
38163                i += 2;
38164            } else {
38165                result.push(chars[i]);
38166                i += 1;
38167            }
38168        }
38169        result
38170    }
38171
38172    /// Convert Snowflake normalized format to Spark format (Java-style)
38173    fn snowflake_format_to_spark(format: &str) -> String {
38174        let mut result = String::new();
38175        let chars: Vec<char> = format.chars().collect();
38176        let mut i = 0;
38177        while i < chars.len() {
38178            let remaining = &format[i..];
38179            if remaining.starts_with("yyyy") {
38180                result.push_str("yyyy");
38181                i += 4;
38182            } else if remaining.starts_with("yy") {
38183                result.push_str("yy");
38184                i += 2;
38185            } else if remaining.starts_with("mmmm") {
38186                result.push_str("MMMM"); // full month name
38187                i += 4;
38188            } else if remaining.starts_with("mon") {
38189                result.push_str("MMM"); // abbreviated month
38190                i += 3;
38191            } else if remaining.starts_with("mm") {
38192                result.push_str("MM");
38193                i += 2;
38194            } else if remaining.starts_with("DD") {
38195                result.push_str("dd");
38196                i += 2;
38197            } else if remaining.starts_with("dy") {
38198                result.push_str("EEE"); // abbreviated day name
38199                i += 2;
38200            } else if remaining.starts_with("hh24") {
38201                result.push_str("HH");
38202                i += 4;
38203            } else if remaining.starts_with("hh12") {
38204                result.push_str("hh");
38205                i += 4;
38206            } else if remaining.starts_with("hh") {
38207                result.push_str("HH");
38208                i += 2;
38209            } else if remaining.starts_with("mi") {
38210                result.push_str("mm");
38211                i += 2;
38212            } else if remaining.starts_with("ss") {
38213                result.push_str("ss");
38214                i += 2;
38215            } else if remaining.starts_with("ff") {
38216                result.push_str("SSS"); // milliseconds
38217                i += 2;
38218                // Skip digits after ff
38219                while i < chars.len() && chars[i].is_ascii_digit() {
38220                    i += 1;
38221                }
38222            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
38223                result.push_str("a");
38224                i += 2;
38225            } else if remaining.starts_with("tz") {
38226                result.push_str("z");
38227                i += 2;
38228            } else {
38229                result.push(chars[i]);
38230                i += 1;
38231            }
38232        }
38233        result
38234    }
38235
38236    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
38237        match self.config.dialect {
38238            Some(DialectType::DuckDB) => {
38239                // DuckDB: EPOCH(STRPTIME(value, format))
38240                self.write_keyword("EPOCH");
38241                self.write("(");
38242                self.write_keyword("STRPTIME");
38243                self.write("(");
38244                if let Some(this) = &e.this {
38245                    self.generate_expression(this)?;
38246                }
38247                if let Some(format) = &e.format {
38248                    self.write(", '");
38249                    self.write(format);
38250                    self.write("'");
38251                }
38252                self.write("))");
38253            }
38254            Some(DialectType::Hive) => {
38255                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
38256                self.write_keyword("UNIX_TIMESTAMP");
38257                self.write("(");
38258                if let Some(this) = &e.this {
38259                    self.generate_expression(this)?;
38260                }
38261                if let Some(format) = &e.format {
38262                    let java_fmt = Self::strftime_to_java_format(format);
38263                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
38264                        self.write(", '");
38265                        self.write(&java_fmt);
38266                        self.write("'");
38267                    }
38268                }
38269                self.write(")");
38270            }
38271            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38272                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
38273                self.write_keyword("UNIX_TIMESTAMP");
38274                self.write("(");
38275                if let Some(this) = &e.this {
38276                    self.generate_expression(this)?;
38277                }
38278                if let Some(format) = &e.format {
38279                    self.write(", '");
38280                    self.write(format);
38281                    self.write("'");
38282                }
38283                self.write(")");
38284            }
38285            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38286                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
38287                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
38288                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
38289                let java_fmt = Self::strftime_to_java_format(c_fmt);
38290                self.write_keyword("TO_UNIXTIME");
38291                self.write("(");
38292                self.write_keyword("COALESCE");
38293                self.write("(");
38294                self.write_keyword("TRY");
38295                self.write("(");
38296                self.write_keyword("DATE_PARSE");
38297                self.write("(");
38298                self.write_keyword("CAST");
38299                self.write("(");
38300                if let Some(this) = &e.this {
38301                    self.generate_expression(this)?;
38302                }
38303                self.write(" ");
38304                self.write_keyword("AS VARCHAR");
38305                self.write("), '");
38306                self.write(c_fmt);
38307                self.write("')), ");
38308                self.write_keyword("PARSE_DATETIME");
38309                self.write("(");
38310                self.write_keyword("DATE_FORMAT");
38311                self.write("(");
38312                self.write_keyword("CAST");
38313                self.write("(");
38314                if let Some(this) = &e.this {
38315                    self.generate_expression(this)?;
38316                }
38317                self.write(" ");
38318                self.write_keyword("AS TIMESTAMP");
38319                self.write("), '");
38320                self.write(c_fmt);
38321                self.write("'), '");
38322                self.write(&java_fmt);
38323                self.write("')))");
38324            }
38325            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38326                // Spark: UNIX_TIMESTAMP(value, java_format)
38327                self.write_keyword("UNIX_TIMESTAMP");
38328                self.write("(");
38329                if let Some(this) = &e.this {
38330                    self.generate_expression(this)?;
38331                }
38332                if let Some(format) = &e.format {
38333                    let java_fmt = Self::strftime_to_java_format(format);
38334                    self.write(", '");
38335                    self.write(&java_fmt);
38336                    self.write("'");
38337                }
38338                self.write(")");
38339            }
38340            _ => {
38341                // Default: STR_TO_UNIX(this, format)
38342                self.write_keyword("STR_TO_UNIX");
38343                self.write("(");
38344                if let Some(this) = &e.this {
38345                    self.generate_expression(this)?;
38346                }
38347                if let Some(format) = &e.format {
38348                    self.write(", '");
38349                    self.write(format);
38350                    self.write("'");
38351                }
38352                self.write(")");
38353            }
38354        }
38355        Ok(())
38356    }
38357
38358    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
38359        // STRING_TO_ARRAY(this, delimiter, null_string)
38360        self.write_keyword("STRING_TO_ARRAY");
38361        self.write("(");
38362        self.generate_expression(&e.this)?;
38363        if let Some(expression) = &e.expression {
38364            self.write(", ");
38365            self.generate_expression(expression)?;
38366        }
38367        if let Some(null_val) = &e.null {
38368            self.write(", ");
38369            self.generate_expression(null_val)?;
38370        }
38371        self.write(")");
38372        Ok(())
38373    }
38374
38375    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
38376        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
38377            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
38378            self.write_keyword("OBJECT_CONSTRUCT");
38379            self.write("(");
38380            for (i, (name, expr)) in e.fields.iter().enumerate() {
38381                if i > 0 {
38382                    self.write(", ");
38383                }
38384                if let Some(name) = name {
38385                    self.write("'");
38386                    self.write(name);
38387                    self.write("'");
38388                    self.write(", ");
38389                } else {
38390                    self.write("'_");
38391                    self.write(&i.to_string());
38392                    self.write("'");
38393                    self.write(", ");
38394                }
38395                self.generate_expression(expr)?;
38396            }
38397            self.write(")");
38398        } else if self.config.struct_curly_brace_notation {
38399            // DuckDB-style: {'key': value, ...}
38400            self.write("{");
38401            for (i, (name, expr)) in e.fields.iter().enumerate() {
38402                if i > 0 {
38403                    self.write(", ");
38404                }
38405                if let Some(name) = name {
38406                    // Quote the key as a string literal
38407                    self.write("'");
38408                    self.write(name);
38409                    self.write("'");
38410                    self.write(": ");
38411                } else {
38412                    // Unnamed field: use positional key
38413                    self.write("'_");
38414                    self.write(&i.to_string());
38415                    self.write("'");
38416                    self.write(": ");
38417                }
38418                self.generate_expression(expr)?;
38419            }
38420            self.write("}");
38421        } else {
38422            // Standard SQL struct notation
38423            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
38424            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
38425            let value_as_name = matches!(
38426                self.config.dialect,
38427                Some(DialectType::BigQuery)
38428                    | Some(DialectType::Spark)
38429                    | Some(DialectType::Databricks)
38430                    | Some(DialectType::Hive)
38431            );
38432            self.write_keyword("STRUCT");
38433            self.write("(");
38434            for (i, (name, expr)) in e.fields.iter().enumerate() {
38435                if i > 0 {
38436                    self.write(", ");
38437                }
38438                if let Some(name) = name {
38439                    if value_as_name {
38440                        // STRUCT(value AS name)
38441                        self.generate_expression(expr)?;
38442                        self.write_space();
38443                        self.write_keyword("AS");
38444                        self.write_space();
38445                        // Quote name if it contains spaces or special chars
38446                        let needs_quoting = name.contains(' ') || name.contains('-');
38447                        if needs_quoting {
38448                            if matches!(
38449                                self.config.dialect,
38450                                Some(DialectType::Spark)
38451                                    | Some(DialectType::Databricks)
38452                                    | Some(DialectType::Hive)
38453                            ) {
38454                                self.write("`");
38455                                self.write(name);
38456                                self.write("`");
38457                            } else {
38458                                self.write(name);
38459                            }
38460                        } else {
38461                            self.write(name);
38462                        }
38463                    } else {
38464                        // STRUCT(name AS value)
38465                        self.write(name);
38466                        self.write_space();
38467                        self.write_keyword("AS");
38468                        self.write_space();
38469                        self.generate_expression(expr)?;
38470                    }
38471                } else {
38472                    self.generate_expression(expr)?;
38473                }
38474            }
38475            self.write(")");
38476        }
38477        Ok(())
38478    }
38479
38480    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
38481        // STUFF(this, start, length, expression)
38482        self.write_keyword("STUFF");
38483        self.write("(");
38484        self.generate_expression(&e.this)?;
38485        if let Some(start) = &e.start {
38486            self.write(", ");
38487            self.generate_expression(start)?;
38488        }
38489        if let Some(length) = e.length {
38490            self.write(", ");
38491            self.write(&length.to_string());
38492        }
38493        self.write(", ");
38494        self.generate_expression(&e.expression)?;
38495        self.write(")");
38496        Ok(())
38497    }
38498
38499    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
38500        // SUBSTRING_INDEX(this, delimiter, count)
38501        self.write_keyword("SUBSTRING_INDEX");
38502        self.write("(");
38503        self.generate_expression(&e.this)?;
38504        if let Some(delimiter) = &e.delimiter {
38505            self.write(", ");
38506            self.generate_expression(delimiter)?;
38507        }
38508        if let Some(count) = &e.count {
38509            self.write(", ");
38510            self.generate_expression(count)?;
38511        }
38512        self.write(")");
38513        Ok(())
38514    }
38515
38516    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
38517        // SUMMARIZE [TABLE] this
38518        self.write_keyword("SUMMARIZE");
38519        if e.table.is_some() {
38520            self.write_space();
38521            self.write_keyword("TABLE");
38522        }
38523        self.write_space();
38524        self.generate_expression(&e.this)?;
38525        Ok(())
38526    }
38527
38528    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
38529        // SYSTIMESTAMP
38530        self.write_keyword("SYSTIMESTAMP");
38531        Ok(())
38532    }
38533
38534    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
38535        // alias (columns...)
38536        if let Some(this) = &e.this {
38537            self.generate_expression(this)?;
38538        }
38539        if !e.columns.is_empty() {
38540            self.write("(");
38541            for (i, col) in e.columns.iter().enumerate() {
38542                if i > 0 {
38543                    self.write(", ");
38544                }
38545                self.generate_expression(col)?;
38546            }
38547            self.write(")");
38548        }
38549        Ok(())
38550    }
38551
38552    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
38553        // TABLE(this) [AS alias]
38554        self.write_keyword("TABLE");
38555        self.write("(");
38556        self.generate_expression(&e.this)?;
38557        self.write(")");
38558        if let Some(alias) = &e.alias {
38559            self.write_space();
38560            self.write_keyword("AS");
38561            self.write_space();
38562            self.write(alias);
38563        }
38564        Ok(())
38565    }
38566
38567    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
38568        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
38569        self.write_keyword("ROWS FROM");
38570        self.write(" (");
38571        for (i, expr) in e.expressions.iter().enumerate() {
38572            if i > 0 {
38573                self.write(", ");
38574            }
38575            // Each expression is either:
38576            // - A plain function (no alias)
38577            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
38578            match expr {
38579                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
38580                    // First element is the function, second is the TableAlias
38581                    self.generate_expression(&tuple.expressions[0])?;
38582                    self.write_space();
38583                    self.write_keyword("AS");
38584                    self.write_space();
38585                    self.generate_expression(&tuple.expressions[1])?;
38586                }
38587                _ => {
38588                    self.generate_expression(expr)?;
38589                }
38590            }
38591        }
38592        self.write(")");
38593        if e.ordinality {
38594            self.write_space();
38595            self.write_keyword("WITH ORDINALITY");
38596        }
38597        if let Some(alias) = &e.alias {
38598            self.write_space();
38599            self.write_keyword("AS");
38600            self.write_space();
38601            self.generate_expression(alias)?;
38602        }
38603        Ok(())
38604    }
38605
38606    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
38607        use crate::dialects::DialectType;
38608
38609        // New wrapper pattern: expression + Sample struct
38610        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
38611            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
38612            if self.config.alias_post_tablesample {
38613                // Handle Subquery with alias and Alias wrapper
38614                if let Expression::Subquery(ref s) = **this {
38615                    if let Some(ref alias) = s.alias {
38616                        // Create a clone without alias for output
38617                        let mut subquery_no_alias = (**s).clone();
38618                        subquery_no_alias.alias = None;
38619                        subquery_no_alias.column_aliases = Vec::new();
38620                        self.generate_expression(&Expression::Subquery(Box::new(
38621                            subquery_no_alias,
38622                        )))?;
38623                        self.write_space();
38624                        self.write_keyword(self.config.tablesample_keywords);
38625                        self.generate_sample_body(sample)?;
38626                        if let Some(ref seed) = sample.seed {
38627                            self.write_space();
38628                            let use_seed = sample.use_seed_keyword
38629                                && !matches!(
38630                                    self.config.dialect,
38631                                    Some(crate::dialects::DialectType::Databricks)
38632                                        | Some(crate::dialects::DialectType::Spark)
38633                                );
38634                            if use_seed {
38635                                self.write_keyword("SEED");
38636                            } else {
38637                                self.write_keyword("REPEATABLE");
38638                            }
38639                            self.write(" (");
38640                            self.generate_expression(seed)?;
38641                            self.write(")");
38642                        }
38643                        self.write_space();
38644                        self.write_keyword("AS");
38645                        self.write_space();
38646                        self.generate_identifier(alias)?;
38647                        return Ok(());
38648                    }
38649                } else if let Expression::Alias(ref a) = **this {
38650                    // Output the base expression without alias
38651                    self.generate_expression(&a.this)?;
38652                    self.write_space();
38653                    self.write_keyword(self.config.tablesample_keywords);
38654                    self.generate_sample_body(sample)?;
38655                    if let Some(ref seed) = sample.seed {
38656                        self.write_space();
38657                        let use_seed = sample.use_seed_keyword
38658                            && !matches!(
38659                                self.config.dialect,
38660                                Some(crate::dialects::DialectType::Databricks)
38661                                    | Some(crate::dialects::DialectType::Spark)
38662                            );
38663                        if use_seed {
38664                            self.write_keyword("SEED");
38665                        } else {
38666                            self.write_keyword("REPEATABLE");
38667                        }
38668                        self.write(" (");
38669                        self.generate_expression(seed)?;
38670                        self.write(")");
38671                    }
38672                    // Output alias after TABLESAMPLE
38673                    self.write_space();
38674                    self.write_keyword("AS");
38675                    self.write_space();
38676                    self.generate_identifier(&a.alias)?;
38677                    return Ok(());
38678                }
38679            }
38680            // Default: generate wrapped expression first, then TABLESAMPLE
38681            self.generate_expression(this)?;
38682            self.write_space();
38683            self.write_keyword(self.config.tablesample_keywords);
38684            self.generate_sample_body(sample)?;
38685            // Seed for table-level sample
38686            if let Some(ref seed) = sample.seed {
38687                self.write_space();
38688                // Databricks uses REPEATABLE, not SEED
38689                let use_seed = sample.use_seed_keyword
38690                    && !matches!(
38691                        self.config.dialect,
38692                        Some(crate::dialects::DialectType::Databricks)
38693                            | Some(crate::dialects::DialectType::Spark)
38694                    );
38695                if use_seed {
38696                    self.write_keyword("SEED");
38697                } else {
38698                    self.write_keyword("REPEATABLE");
38699                }
38700                self.write(" (");
38701                self.generate_expression(seed)?;
38702                self.write(")");
38703            }
38704            return Ok(());
38705        }
38706
38707        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
38708        self.write_keyword(self.config.tablesample_keywords);
38709        if let Some(method) = &e.method {
38710            self.write_space();
38711            self.write_keyword(method);
38712        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
38713            // Snowflake defaults to BERNOULLI when no method is specified
38714            self.write_space();
38715            self.write_keyword("BERNOULLI");
38716        }
38717        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
38718            self.write_space();
38719            self.write_keyword("BUCKET");
38720            self.write_space();
38721            self.generate_expression(numerator)?;
38722            self.write_space();
38723            self.write_keyword("OUT OF");
38724            self.write_space();
38725            self.generate_expression(denominator)?;
38726            if let Some(field) = &e.bucket_field {
38727                self.write_space();
38728                self.write_keyword("ON");
38729                self.write_space();
38730                self.generate_expression(field)?;
38731            }
38732        } else if !e.expressions.is_empty() {
38733            self.write(" (");
38734            for (i, expr) in e.expressions.iter().enumerate() {
38735                if i > 0 {
38736                    self.write(", ");
38737                }
38738                self.generate_expression(expr)?;
38739            }
38740            self.write(")");
38741        } else if let Some(percent) = &e.percent {
38742            self.write(" (");
38743            self.generate_expression(percent)?;
38744            self.write_space();
38745            self.write_keyword("PERCENT");
38746            self.write(")");
38747        }
38748        Ok(())
38749    }
38750
38751    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
38752        // [prefix]this[postfix]
38753        if let Some(prefix) = &e.prefix {
38754            self.generate_expression(prefix)?;
38755        }
38756        if let Some(this) = &e.this {
38757            self.generate_expression(this)?;
38758        }
38759        if let Some(postfix) = &e.postfix {
38760            self.generate_expression(postfix)?;
38761        }
38762        Ok(())
38763    }
38764
38765    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
38766        // TAG (expressions)
38767        self.write_keyword("TAG");
38768        self.write(" (");
38769        for (i, expr) in e.expressions.iter().enumerate() {
38770            if i > 0 {
38771                self.write(", ");
38772            }
38773            self.generate_expression(expr)?;
38774        }
38775        self.write(")");
38776        Ok(())
38777    }
38778
38779    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
38780        // TEMPORARY or TEMP or [this] TEMPORARY
38781        if let Some(this) = &e.this {
38782            self.generate_expression(this)?;
38783            self.write_space();
38784        }
38785        self.write_keyword("TEMPORARY");
38786        Ok(())
38787    }
38788
38789    /// Generate a Time function expression
38790    /// For most dialects: TIME('value')
38791    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
38792        // Standard: TIME(value)
38793        self.write_keyword("TIME");
38794        self.write("(");
38795        self.generate_expression(&e.this)?;
38796        self.write(")");
38797        Ok(())
38798    }
38799
38800    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
38801        // TIME_ADD(this, expression, unit)
38802        self.write_keyword("TIME_ADD");
38803        self.write("(");
38804        self.generate_expression(&e.this)?;
38805        self.write(", ");
38806        self.generate_expression(&e.expression)?;
38807        if let Some(unit) = &e.unit {
38808            self.write(", ");
38809            self.write_keyword(unit);
38810        }
38811        self.write(")");
38812        Ok(())
38813    }
38814
38815    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
38816        // TIME_DIFF(this, expression, unit)
38817        self.write_keyword("TIME_DIFF");
38818        self.write("(");
38819        self.generate_expression(&e.this)?;
38820        self.write(", ");
38821        self.generate_expression(&e.expression)?;
38822        if let Some(unit) = &e.unit {
38823            self.write(", ");
38824            self.write_keyword(unit);
38825        }
38826        self.write(")");
38827        Ok(())
38828    }
38829
38830    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
38831        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
38832        self.write_keyword("TIME_FROM_PARTS");
38833        self.write("(");
38834        let mut first = true;
38835        if let Some(hour) = &e.hour {
38836            self.generate_expression(hour)?;
38837            first = false;
38838        }
38839        if let Some(minute) = &e.min {
38840            if !first {
38841                self.write(", ");
38842            }
38843            self.generate_expression(minute)?;
38844            first = false;
38845        }
38846        if let Some(second) = &e.sec {
38847            if !first {
38848                self.write(", ");
38849            }
38850            self.generate_expression(second)?;
38851            first = false;
38852        }
38853        if let Some(ns) = &e.nano {
38854            if !first {
38855                self.write(", ");
38856            }
38857            self.generate_expression(ns)?;
38858        }
38859        self.write(")");
38860        Ok(())
38861    }
38862
38863    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
38864        // TIME_SLICE(this, expression, unit)
38865        self.write_keyword("TIME_SLICE");
38866        self.write("(");
38867        self.generate_expression(&e.this)?;
38868        self.write(", ");
38869        self.generate_expression(&e.expression)?;
38870        self.write(", ");
38871        self.write_keyword(&e.unit);
38872        self.write(")");
38873        Ok(())
38874    }
38875
38876    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
38877        // TIME_STR_TO_TIME(this)
38878        self.write_keyword("TIME_STR_TO_TIME");
38879        self.write("(");
38880        self.generate_expression(&e.this)?;
38881        self.write(")");
38882        Ok(())
38883    }
38884
38885    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
38886        // TIME_SUB(this, expression, unit)
38887        self.write_keyword("TIME_SUB");
38888        self.write("(");
38889        self.generate_expression(&e.this)?;
38890        self.write(", ");
38891        self.generate_expression(&e.expression)?;
38892        if let Some(unit) = &e.unit {
38893            self.write(", ");
38894            self.write_keyword(unit);
38895        }
38896        self.write(")");
38897        Ok(())
38898    }
38899
38900    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
38901        match self.config.dialect {
38902            Some(DialectType::Exasol) => {
38903                // Exasol uses TO_CHAR with Exasol-specific format
38904                self.write_keyword("TO_CHAR");
38905                self.write("(");
38906                self.generate_expression(&e.this)?;
38907                self.write(", '");
38908                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
38909                self.write("'");
38910                self.write(")");
38911            }
38912            Some(DialectType::PostgreSQL)
38913            | Some(DialectType::Redshift)
38914            | Some(DialectType::Materialize) => {
38915                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
38916                self.write_keyword("TO_CHAR");
38917                self.write("(");
38918                self.generate_expression(&e.this)?;
38919                self.write(", '");
38920                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
38921                self.write("'");
38922                self.write(")");
38923            }
38924            Some(DialectType::Oracle) => {
38925                // Oracle uses TO_CHAR with PG-like format
38926                self.write_keyword("TO_CHAR");
38927                self.write("(");
38928                self.generate_expression(&e.this)?;
38929                self.write(", '");
38930                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
38931                self.write("'");
38932                self.write(")");
38933            }
38934            Some(DialectType::Drill) => {
38935                // Drill: TO_CHAR with Java format
38936                self.write_keyword("TO_CHAR");
38937                self.write("(");
38938                self.generate_expression(&e.this)?;
38939                self.write(", '");
38940                self.write(&Self::strftime_to_java_format(&e.format));
38941                self.write("'");
38942                self.write(")");
38943            }
38944            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
38945                // TSQL: FORMAT(value, format) with .NET-style format
38946                self.write_keyword("FORMAT");
38947                self.write("(");
38948                self.generate_expression(&e.this)?;
38949                self.write(", '");
38950                self.write(&Self::strftime_to_tsql_format(&e.format));
38951                self.write("'");
38952                self.write(")");
38953            }
38954            Some(DialectType::DuckDB) => {
38955                // DuckDB: STRFTIME(value, format) - keeps C format
38956                self.write_keyword("STRFTIME");
38957                self.write("(");
38958                self.generate_expression(&e.this)?;
38959                self.write(", '");
38960                self.write(&e.format);
38961                self.write("'");
38962                self.write(")");
38963            }
38964            Some(DialectType::BigQuery) => {
38965                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
38966                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
38967                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
38968                self.write_keyword("FORMAT_DATE");
38969                self.write("('");
38970                self.write(&fmt);
38971                self.write("', ");
38972                self.generate_expression(&e.this)?;
38973                self.write(")");
38974            }
38975            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38976                // Hive/Spark: DATE_FORMAT(value, java_format)
38977                self.write_keyword("DATE_FORMAT");
38978                self.write("(");
38979                self.generate_expression(&e.this)?;
38980                self.write(", '");
38981                self.write(&Self::strftime_to_java_format(&e.format));
38982                self.write("'");
38983                self.write(")");
38984            }
38985            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
38986                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
38987                self.write_keyword("DATE_FORMAT");
38988                self.write("(");
38989                self.generate_expression(&e.this)?;
38990                self.write(", '");
38991                self.write(&e.format);
38992                self.write("'");
38993                self.write(")");
38994            }
38995            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38996                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
38997                self.write_keyword("DATE_FORMAT");
38998                self.write("(");
38999                self.generate_expression(&e.this)?;
39000                self.write(", '");
39001                self.write(&e.format);
39002                self.write("'");
39003                self.write(")");
39004            }
39005            _ => {
39006                // Default: TIME_TO_STR(this, format)
39007                self.write_keyword("TIME_TO_STR");
39008                self.write("(");
39009                self.generate_expression(&e.this)?;
39010                self.write(", '");
39011                self.write(&e.format);
39012                self.write("'");
39013                self.write(")");
39014            }
39015        }
39016        Ok(())
39017    }
39018
39019    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
39020        match self.config.dialect {
39021            Some(DialectType::DuckDB) => {
39022                // DuckDB: EPOCH(x)
39023                self.write_keyword("EPOCH");
39024                self.write("(");
39025                self.generate_expression(&e.this)?;
39026                self.write(")");
39027            }
39028            Some(DialectType::Hive)
39029            | Some(DialectType::Spark)
39030            | Some(DialectType::Databricks)
39031            | Some(DialectType::Doris)
39032            | Some(DialectType::StarRocks)
39033            | Some(DialectType::Drill) => {
39034                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
39035                self.write_keyword("UNIX_TIMESTAMP");
39036                self.write("(");
39037                self.generate_expression(&e.this)?;
39038                self.write(")");
39039            }
39040            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39041                // Presto: TO_UNIXTIME(x)
39042                self.write_keyword("TO_UNIXTIME");
39043                self.write("(");
39044                self.generate_expression(&e.this)?;
39045                self.write(")");
39046            }
39047            _ => {
39048                // Default: TIME_TO_UNIX(x)
39049                self.write_keyword("TIME_TO_UNIX");
39050                self.write("(");
39051                self.generate_expression(&e.this)?;
39052                self.write(")");
39053            }
39054        }
39055        Ok(())
39056    }
39057
39058    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
39059        match self.config.dialect {
39060            Some(DialectType::Hive) => {
39061                // Hive: TO_DATE(x)
39062                self.write_keyword("TO_DATE");
39063                self.write("(");
39064                self.generate_expression(&e.this)?;
39065                self.write(")");
39066            }
39067            _ => {
39068                // Default: TIME_STR_TO_DATE(x)
39069                self.write_keyword("TIME_STR_TO_DATE");
39070                self.write("(");
39071                self.generate_expression(&e.this)?;
39072                self.write(")");
39073            }
39074        }
39075        Ok(())
39076    }
39077
39078    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
39079        // TIME_TRUNC(this, unit)
39080        self.write_keyword("TIME_TRUNC");
39081        self.write("(");
39082        self.generate_expression(&e.this)?;
39083        self.write(", ");
39084        self.write_keyword(&e.unit);
39085        self.write(")");
39086        Ok(())
39087    }
39088
39089    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
39090        // Just output the unit name
39091        if let Some(unit) = &e.unit {
39092            self.write_keyword(unit);
39093        }
39094        Ok(())
39095    }
39096
39097    /// Generate a Timestamp function expression
39098    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
39099    /// For other dialects: TIMESTAMP('value')
39100    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
39101        use crate::dialects::DialectType;
39102        use crate::expressions::Literal;
39103
39104        match self.config.dialect {
39105            // Exasol uses TO_TIMESTAMP for Timestamp expressions
39106            Some(DialectType::Exasol) => {
39107                self.write_keyword("TO_TIMESTAMP");
39108                self.write("(");
39109                // Extract the string value from the expression if it's a string literal
39110                if let Some(this) = &e.this {
39111                    match this.as_ref() {
39112                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
39113                            let Literal::String(s) = lit.as_ref() else {
39114                                unreachable!()
39115                            };
39116                            self.write("'");
39117                            self.write(s);
39118                            self.write("'");
39119                        }
39120                        _ => {
39121                            self.generate_expression(this)?;
39122                        }
39123                    }
39124                }
39125                self.write(")");
39126            }
39127            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
39128            _ => {
39129                self.write_keyword("TIMESTAMP");
39130                self.write("(");
39131                if let Some(this) = &e.this {
39132                    self.generate_expression(this)?;
39133                }
39134                if let Some(zone) = &e.zone {
39135                    self.write(", ");
39136                    self.generate_expression(zone)?;
39137                }
39138                self.write(")");
39139            }
39140        }
39141        Ok(())
39142    }
39143
39144    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
39145        // TIMESTAMP_ADD(this, expression, unit)
39146        self.write_keyword("TIMESTAMP_ADD");
39147        self.write("(");
39148        self.generate_expression(&e.this)?;
39149        self.write(", ");
39150        self.generate_expression(&e.expression)?;
39151        if let Some(unit) = &e.unit {
39152            self.write(", ");
39153            self.write_keyword(unit);
39154        }
39155        self.write(")");
39156        Ok(())
39157    }
39158
39159    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
39160        // TIMESTAMP_DIFF(this, expression, unit)
39161        self.write_keyword("TIMESTAMP_DIFF");
39162        self.write("(");
39163        self.generate_expression(&e.this)?;
39164        self.write(", ");
39165        self.generate_expression(&e.expression)?;
39166        if let Some(unit) = &e.unit {
39167            self.write(", ");
39168            self.write_keyword(unit);
39169        }
39170        self.write(")");
39171        Ok(())
39172    }
39173
39174    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
39175        // TIMESTAMP_FROM_PARTS(this, expression)
39176        self.write_keyword("TIMESTAMP_FROM_PARTS");
39177        self.write("(");
39178        if let Some(this) = &e.this {
39179            self.generate_expression(this)?;
39180        }
39181        if let Some(expression) = &e.expression {
39182            self.write(", ");
39183            self.generate_expression(expression)?;
39184        }
39185        if let Some(zone) = &e.zone {
39186            self.write(", ");
39187            self.generate_expression(zone)?;
39188        }
39189        if let Some(milli) = &e.milli {
39190            self.write(", ");
39191            self.generate_expression(milli)?;
39192        }
39193        self.write(")");
39194        Ok(())
39195    }
39196
39197    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
39198        // TIMESTAMP_SUB(this, INTERVAL expression unit)
39199        self.write_keyword("TIMESTAMP_SUB");
39200        self.write("(");
39201        self.generate_expression(&e.this)?;
39202        self.write(", ");
39203        self.write_keyword("INTERVAL");
39204        self.write_space();
39205        self.generate_expression(&e.expression)?;
39206        if let Some(unit) = &e.unit {
39207            self.write_space();
39208            self.write_keyword(unit);
39209        }
39210        self.write(")");
39211        Ok(())
39212    }
39213
39214    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
39215        // TIMESTAMP_TZ_FROM_PARTS(...)
39216        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
39217        self.write("(");
39218        if let Some(zone) = &e.zone {
39219            self.generate_expression(zone)?;
39220        }
39221        self.write(")");
39222        Ok(())
39223    }
39224
39225    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
39226        // TO_BINARY(this, [format])
39227        self.write_keyword("TO_BINARY");
39228        self.write("(");
39229        self.generate_expression(&e.this)?;
39230        if let Some(format) = &e.format {
39231            self.write(", '");
39232            self.write(format);
39233            self.write("'");
39234        }
39235        self.write(")");
39236        Ok(())
39237    }
39238
39239    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
39240        // TO_BOOLEAN(this)
39241        self.write_keyword("TO_BOOLEAN");
39242        self.write("(");
39243        self.generate_expression(&e.this)?;
39244        self.write(")");
39245        Ok(())
39246    }
39247
39248    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
39249        // TO_CHAR(this, [format], [nlsparam])
39250        self.write_keyword("TO_CHAR");
39251        self.write("(");
39252        self.generate_expression(&e.this)?;
39253        if let Some(format) = &e.format {
39254            self.write(", '");
39255            self.write(format);
39256            self.write("'");
39257        }
39258        if let Some(nlsparam) = &e.nlsparam {
39259            self.write(", ");
39260            self.generate_expression(nlsparam)?;
39261        }
39262        self.write(")");
39263        Ok(())
39264    }
39265
39266    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
39267        // TO_DECFLOAT(this, [format])
39268        self.write_keyword("TO_DECFLOAT");
39269        self.write("(");
39270        self.generate_expression(&e.this)?;
39271        if let Some(format) = &e.format {
39272            self.write(", '");
39273            self.write(format);
39274            self.write("'");
39275        }
39276        self.write(")");
39277        Ok(())
39278    }
39279
39280    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
39281        // TO_DOUBLE(this, [format])
39282        self.write_keyword("TO_DOUBLE");
39283        self.write("(");
39284        self.generate_expression(&e.this)?;
39285        if let Some(format) = &e.format {
39286            self.write(", '");
39287            self.write(format);
39288            self.write("'");
39289        }
39290        self.write(")");
39291        Ok(())
39292    }
39293
39294    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
39295        // TO_FILE(this, path)
39296        self.write_keyword("TO_FILE");
39297        self.write("(");
39298        self.generate_expression(&e.this)?;
39299        if let Some(path) = &e.path {
39300            self.write(", ");
39301            self.generate_expression(path)?;
39302        }
39303        self.write(")");
39304        Ok(())
39305    }
39306
39307    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
39308        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
39309        // If safe flag is set, output TRY_TO_NUMBER
39310        let is_safe = e.safe.is_some();
39311        if is_safe {
39312            self.write_keyword("TRY_TO_NUMBER");
39313        } else {
39314            self.write_keyword("TO_NUMBER");
39315        }
39316        self.write("(");
39317        self.generate_expression(&e.this)?;
39318        let precision_is_snowflake_default = e.precision.is_none()
39319            || matches!(
39320                e.precision.as_deref(),
39321                Some(Expression::Literal(lit))
39322                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
39323            );
39324        let is_snowflake_default_precision =
39325            matches!(self.config.dialect, Some(DialectType::Snowflake))
39326                && e.nlsparam.is_none()
39327                && e.scale.is_none()
39328                && matches!(
39329                    e.format.as_deref(),
39330                    Some(Expression::Literal(lit))
39331                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
39332                )
39333                && precision_is_snowflake_default;
39334
39335        if !is_snowflake_default_precision {
39336            if let Some(format) = &e.format {
39337                self.write(", ");
39338                self.generate_expression(format)?;
39339            }
39340            if let Some(nlsparam) = &e.nlsparam {
39341                self.write(", ");
39342                self.generate_expression(nlsparam)?;
39343            }
39344            if let Some(precision) = &e.precision {
39345                self.write(", ");
39346                self.generate_expression(precision)?;
39347            }
39348            if let Some(scale) = &e.scale {
39349                self.write(", ");
39350                self.generate_expression(scale)?;
39351            }
39352        }
39353        self.write(")");
39354        Ok(())
39355    }
39356
39357    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
39358        // TO_TABLE this
39359        self.write_keyword("TO_TABLE");
39360        self.write_space();
39361        self.generate_expression(&e.this)?;
39362        Ok(())
39363    }
39364
39365    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
39366        // Check mark to determine the format
39367        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
39368            Expression::Identifier(id) => id.name.clone(),
39369            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
39370                let Literal::String(s) = lit.as_ref() else {
39371                    unreachable!()
39372                };
39373                s.clone()
39374            }
39375            _ => String::new(),
39376        });
39377
39378        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
39379        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
39380        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
39381            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
39382        });
39383
39384        // For Presto/Trino: always use START TRANSACTION
39385        let use_start_transaction = matches!(
39386            self.config.dialect,
39387            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
39388        );
39389        // For most dialects: strip TRANSACTION keyword
39390        let strip_transaction = matches!(
39391            self.config.dialect,
39392            Some(DialectType::Snowflake)
39393                | Some(DialectType::PostgreSQL)
39394                | Some(DialectType::Redshift)
39395                | Some(DialectType::MySQL)
39396                | Some(DialectType::Hive)
39397                | Some(DialectType::Spark)
39398                | Some(DialectType::Databricks)
39399                | Some(DialectType::DuckDB)
39400                | Some(DialectType::Oracle)
39401                | Some(DialectType::Doris)
39402                | Some(DialectType::StarRocks)
39403                | Some(DialectType::Materialize)
39404                | Some(DialectType::ClickHouse)
39405        );
39406
39407        if is_start || use_start_transaction {
39408            // START TRANSACTION [modes]
39409            self.write_keyword("START TRANSACTION");
39410            if let Some(modes) = &e.modes {
39411                self.write_space();
39412                self.generate_expression(modes)?;
39413            }
39414        } else {
39415            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
39416            self.write_keyword("BEGIN");
39417
39418            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
39419            let is_kind = e.this.as_ref().map_or(false, |t| {
39420                if let Expression::Identifier(id) = t.as_ref() {
39421                    id.name.eq_ignore_ascii_case("DEFERRED")
39422                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
39423                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
39424                } else {
39425                    false
39426                }
39427            });
39428
39429            // Output kind before TRANSACTION keyword
39430            if is_kind {
39431                if let Some(this) = &e.this {
39432                    self.write_space();
39433                    if let Expression::Identifier(id) = this.as_ref() {
39434                        self.write_keyword(&id.name);
39435                    }
39436                }
39437            }
39438
39439            // Output TRANSACTION keyword if it was present and target supports it
39440            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
39441                self.write_space();
39442                self.write_keyword("TRANSACTION");
39443            }
39444
39445            // Output transaction name (not kind)
39446            if !is_kind {
39447                if let Some(this) = &e.this {
39448                    self.write_space();
39449                    self.generate_expression(this)?;
39450                }
39451            }
39452
39453            // Output WITH MARK 'description' for TSQL
39454            if has_with_mark {
39455                self.write_space();
39456                self.write_keyword("WITH MARK");
39457                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
39458                    if let Literal::String(desc) = lit.as_ref() {
39459                        if !desc.is_empty() {
39460                            self.write_space();
39461                            self.write(&format!("'{}'", desc));
39462                        }
39463                    }
39464                }
39465            }
39466
39467            // Output modes (isolation levels, etc.)
39468            if let Some(modes) = &e.modes {
39469                self.write_space();
39470                self.generate_expression(modes)?;
39471            }
39472        }
39473        Ok(())
39474    }
39475
39476    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
39477        // TRANSFORM(this, expression)
39478        self.write_keyword("TRANSFORM");
39479        self.write("(");
39480        self.generate_expression(&e.this)?;
39481        self.write(", ");
39482        self.generate_expression(&e.expression)?;
39483        self.write(")");
39484        Ok(())
39485    }
39486
39487    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
39488        // TRANSFORM(expressions)
39489        self.write_keyword("TRANSFORM");
39490        self.write("(");
39491        if self.config.pretty && !e.expressions.is_empty() {
39492            self.indent_level += 1;
39493            for (i, expr) in e.expressions.iter().enumerate() {
39494                if i > 0 {
39495                    self.write(",");
39496                }
39497                self.write_newline();
39498                self.write_indent();
39499                self.generate_expression(expr)?;
39500            }
39501            self.indent_level -= 1;
39502            self.write_newline();
39503            self.write(")");
39504        } else {
39505            for (i, expr) in e.expressions.iter().enumerate() {
39506                if i > 0 {
39507                    self.write(", ");
39508                }
39509                self.generate_expression(expr)?;
39510            }
39511            self.write(")");
39512        }
39513        Ok(())
39514    }
39515
39516    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
39517        use crate::dialects::DialectType;
39518        // TRANSIENT is Snowflake-specific; skip for other dialects
39519        if let Some(this) = &e.this {
39520            self.generate_expression(this)?;
39521            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
39522                self.write_space();
39523            }
39524        }
39525        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
39526            self.write_keyword("TRANSIENT");
39527        }
39528        Ok(())
39529    }
39530
39531    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
39532        // TRANSLATE(this, from_, to)
39533        self.write_keyword("TRANSLATE");
39534        self.write("(");
39535        self.generate_expression(&e.this)?;
39536        if let Some(from) = &e.from_ {
39537            self.write(", ");
39538            self.generate_expression(from)?;
39539        }
39540        if let Some(to) = &e.to {
39541            self.write(", ");
39542            self.generate_expression(to)?;
39543        }
39544        self.write(")");
39545        Ok(())
39546    }
39547
39548    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
39549        // TRANSLATE(this USING expression)
39550        self.write_keyword("TRANSLATE");
39551        self.write("(");
39552        self.generate_expression(&e.this)?;
39553        self.write_space();
39554        self.write_keyword("USING");
39555        self.write_space();
39556        self.generate_expression(&e.expression)?;
39557        if e.with_error.is_some() {
39558            self.write_space();
39559            self.write_keyword("WITH ERROR");
39560        }
39561        self.write(")");
39562        Ok(())
39563    }
39564
39565    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
39566        // TRUNCATE TABLE table1, table2, ...
39567        self.write_keyword("TRUNCATE TABLE");
39568        self.write_space();
39569        for (i, expr) in e.expressions.iter().enumerate() {
39570            if i > 0 {
39571                self.write(", ");
39572            }
39573            self.generate_expression(expr)?;
39574        }
39575        Ok(())
39576    }
39577
39578    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
39579        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
39580        self.write_keyword("TRY_BASE64_DECODE_BINARY");
39581        self.write("(");
39582        self.generate_expression(&e.this)?;
39583        if let Some(alphabet) = &e.alphabet {
39584            self.write(", ");
39585            self.generate_expression(alphabet)?;
39586        }
39587        self.write(")");
39588        Ok(())
39589    }
39590
39591    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
39592        // TRY_BASE64_DECODE_STRING(this, [alphabet])
39593        self.write_keyword("TRY_BASE64_DECODE_STRING");
39594        self.write("(");
39595        self.generate_expression(&e.this)?;
39596        if let Some(alphabet) = &e.alphabet {
39597            self.write(", ");
39598            self.generate_expression(alphabet)?;
39599        }
39600        self.write(")");
39601        Ok(())
39602    }
39603
39604    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
39605        // TRY_TO_DECFLOAT(this, [format])
39606        self.write_keyword("TRY_TO_DECFLOAT");
39607        self.write("(");
39608        self.generate_expression(&e.this)?;
39609        if let Some(format) = &e.format {
39610            self.write(", '");
39611            self.write(format);
39612            self.write("'");
39613        }
39614        self.write(")");
39615        Ok(())
39616    }
39617
39618    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
39619        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
39620        self.write_keyword("TS_OR_DS_ADD");
39621        self.write("(");
39622        self.generate_expression(&e.this)?;
39623        self.write(", ");
39624        self.generate_expression(&e.expression)?;
39625        if let Some(unit) = &e.unit {
39626            self.write(", ");
39627            self.write_keyword(unit);
39628        }
39629        if let Some(return_type) = &e.return_type {
39630            self.write(", ");
39631            self.generate_expression(return_type)?;
39632        }
39633        self.write(")");
39634        Ok(())
39635    }
39636
39637    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
39638        // TS_OR_DS_DIFF(this, expression, [unit])
39639        self.write_keyword("TS_OR_DS_DIFF");
39640        self.write("(");
39641        self.generate_expression(&e.this)?;
39642        self.write(", ");
39643        self.generate_expression(&e.expression)?;
39644        if let Some(unit) = &e.unit {
39645            self.write(", ");
39646            self.write_keyword(unit);
39647        }
39648        self.write(")");
39649        Ok(())
39650    }
39651
39652    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
39653        let default_time_format = "%Y-%m-%d %H:%M:%S";
39654        let default_date_format = "%Y-%m-%d";
39655        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
39656            f != default_time_format && f != default_date_format
39657        });
39658
39659        if has_non_default_format {
39660            // With non-default format: dialect-specific handling
39661            let fmt = e.format.as_ref().unwrap();
39662            match self.config.dialect {
39663                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
39664                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
39665                    // STR_TO_DATE is the MySQL-native form of StrToTime
39666                    let str_to_time = crate::expressions::StrToTime {
39667                        this: Box::new((*e.this).clone()),
39668                        format: fmt.clone(),
39669                        zone: None,
39670                        safe: None,
39671                        target_type: None,
39672                    };
39673                    self.generate_str_to_time(&str_to_time)?;
39674                }
39675                Some(DialectType::Hive)
39676                | Some(DialectType::Spark)
39677                | Some(DialectType::Databricks) => {
39678                    // Hive/Spark: TO_DATE(x, java_fmt)
39679                    self.write_keyword("TO_DATE");
39680                    self.write("(");
39681                    self.generate_expression(&e.this)?;
39682                    self.write(", '");
39683                    self.write(&Self::strftime_to_java_format(fmt));
39684                    self.write("')");
39685                }
39686                Some(DialectType::Snowflake) => {
39687                    // Snowflake: TO_DATE(x, snowflake_fmt)
39688                    self.write_keyword("TO_DATE");
39689                    self.write("(");
39690                    self.generate_expression(&e.this)?;
39691                    self.write(", '");
39692                    self.write(&Self::strftime_to_snowflake_format(fmt));
39693                    self.write("')");
39694                }
39695                Some(DialectType::Doris) => {
39696                    // Doris: TO_DATE(x) - ignores format
39697                    self.write_keyword("TO_DATE");
39698                    self.write("(");
39699                    self.generate_expression(&e.this)?;
39700                    self.write(")");
39701                }
39702                _ => {
39703                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
39704                    self.write_keyword("CAST");
39705                    self.write("(");
39706                    let str_to_time = crate::expressions::StrToTime {
39707                        this: Box::new((*e.this).clone()),
39708                        format: fmt.clone(),
39709                        zone: None,
39710                        safe: None,
39711                        target_type: None,
39712                    };
39713                    self.generate_str_to_time(&str_to_time)?;
39714                    self.write_keyword(" AS ");
39715                    self.write_keyword("DATE");
39716                    self.write(")");
39717                }
39718            }
39719        } else {
39720            // Without format (or default format): simple date conversion
39721            match self.config.dialect {
39722                Some(DialectType::MySQL)
39723                | Some(DialectType::SQLite)
39724                | Some(DialectType::StarRocks) => {
39725                    // MySQL/SQLite/StarRocks: DATE(x)
39726                    self.write_keyword("DATE");
39727                    self.write("(");
39728                    self.generate_expression(&e.this)?;
39729                    self.write(")");
39730                }
39731                Some(DialectType::Hive)
39732                | Some(DialectType::Spark)
39733                | Some(DialectType::Databricks)
39734                | Some(DialectType::Snowflake)
39735                | Some(DialectType::Doris) => {
39736                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
39737                    self.write_keyword("TO_DATE");
39738                    self.write("(");
39739                    self.generate_expression(&e.this)?;
39740                    self.write(")");
39741                }
39742                Some(DialectType::Presto)
39743                | Some(DialectType::Trino)
39744                | Some(DialectType::Athena) => {
39745                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
39746                    self.write_keyword("CAST");
39747                    self.write("(");
39748                    self.write_keyword("CAST");
39749                    self.write("(");
39750                    self.generate_expression(&e.this)?;
39751                    self.write_keyword(" AS ");
39752                    self.write_keyword("TIMESTAMP");
39753                    self.write(")");
39754                    self.write_keyword(" AS ");
39755                    self.write_keyword("DATE");
39756                    self.write(")");
39757                }
39758                Some(DialectType::ClickHouse) => {
39759                    // ClickHouse: CAST(x AS Nullable(DATE))
39760                    self.write_keyword("CAST");
39761                    self.write("(");
39762                    self.generate_expression(&e.this)?;
39763                    self.write_keyword(" AS ");
39764                    self.write("Nullable(DATE)");
39765                    self.write(")");
39766                }
39767                _ => {
39768                    // Default: CAST(x AS DATE)
39769                    self.write_keyword("CAST");
39770                    self.write("(");
39771                    self.generate_expression(&e.this)?;
39772                    self.write_keyword(" AS ");
39773                    self.write_keyword("DATE");
39774                    self.write(")");
39775                }
39776            }
39777        }
39778        Ok(())
39779    }
39780
39781    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
39782        // TS_OR_DS_TO_TIME(this, [format])
39783        self.write_keyword("TS_OR_DS_TO_TIME");
39784        self.write("(");
39785        self.generate_expression(&e.this)?;
39786        if let Some(format) = &e.format {
39787            self.write(", '");
39788            self.write(format);
39789            self.write("'");
39790        }
39791        self.write(")");
39792        Ok(())
39793    }
39794
39795    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
39796        // UNHEX(this, [expression])
39797        self.write_keyword("UNHEX");
39798        self.write("(");
39799        self.generate_expression(&e.this)?;
39800        if let Some(expression) = &e.expression {
39801            self.write(", ");
39802            self.generate_expression(expression)?;
39803        }
39804        self.write(")");
39805        Ok(())
39806    }
39807
39808    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
39809        // U&this [UESCAPE escape]
39810        self.write("U&");
39811        self.generate_expression(&e.this)?;
39812        if let Some(escape) = &e.escape {
39813            self.write_space();
39814            self.write_keyword("UESCAPE");
39815            self.write_space();
39816            self.generate_expression(escape)?;
39817        }
39818        Ok(())
39819    }
39820
39821    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
39822        // UNIFORM(this, expression, [gen], [seed])
39823        self.write_keyword("UNIFORM");
39824        self.write("(");
39825        self.generate_expression(&e.this)?;
39826        self.write(", ");
39827        self.generate_expression(&e.expression)?;
39828        if let Some(gen) = &e.gen {
39829            self.write(", ");
39830            self.generate_expression(gen)?;
39831        }
39832        if let Some(seed) = &e.seed {
39833            self.write(", ");
39834            self.generate_expression(seed)?;
39835        }
39836        self.write(")");
39837        Ok(())
39838    }
39839
39840    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
39841        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
39842        self.write_keyword("UNIQUE");
39843        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
39844        if e.nulls.is_some() {
39845            self.write(" NULLS NOT DISTINCT");
39846        }
39847        if let Some(this) = &e.this {
39848            self.write_space();
39849            self.generate_expression(this)?;
39850        }
39851        if let Some(index_type) = &e.index_type {
39852            self.write(" USING ");
39853            self.generate_expression(index_type)?;
39854        }
39855        if let Some(on_conflict) = &e.on_conflict {
39856            self.write_space();
39857            self.generate_expression(on_conflict)?;
39858        }
39859        for opt in &e.options {
39860            self.write_space();
39861            self.generate_expression(opt)?;
39862        }
39863        Ok(())
39864    }
39865
39866    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
39867        // UNIQUE KEY (expressions)
39868        self.write_keyword("UNIQUE KEY");
39869        self.write(" (");
39870        for (i, expr) in e.expressions.iter().enumerate() {
39871            if i > 0 {
39872                self.write(", ");
39873            }
39874            self.generate_expression(expr)?;
39875        }
39876        self.write(")");
39877        Ok(())
39878    }
39879
39880    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
39881        // ROLLUP (r1(col1, col2), r2(col1))
39882        self.write_keyword("ROLLUP");
39883        self.write(" (");
39884        for (i, index) in e.expressions.iter().enumerate() {
39885            if i > 0 {
39886                self.write(", ");
39887            }
39888            self.generate_identifier(&index.name)?;
39889            self.write("(");
39890            for (j, col) in index.expressions.iter().enumerate() {
39891                if j > 0 {
39892                    self.write(", ");
39893                }
39894                self.generate_identifier(col)?;
39895            }
39896            self.write(")");
39897        }
39898        self.write(")");
39899        Ok(())
39900    }
39901
39902    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
39903        match self.config.dialect {
39904            Some(DialectType::DuckDB) => {
39905                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
39906                self.write_keyword("STRFTIME");
39907                self.write("(");
39908                self.write_keyword("TO_TIMESTAMP");
39909                self.write("(");
39910                self.generate_expression(&e.this)?;
39911                self.write("), '");
39912                if let Some(format) = &e.format {
39913                    self.write(format);
39914                }
39915                self.write("')");
39916            }
39917            Some(DialectType::Hive) => {
39918                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
39919                self.write_keyword("FROM_UNIXTIME");
39920                self.write("(");
39921                self.generate_expression(&e.this)?;
39922                if let Some(format) = &e.format {
39923                    if format != "yyyy-MM-dd HH:mm:ss" {
39924                        self.write(", '");
39925                        self.write(format);
39926                        self.write("'");
39927                    }
39928                }
39929                self.write(")");
39930            }
39931            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39932                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
39933                self.write_keyword("DATE_FORMAT");
39934                self.write("(");
39935                self.write_keyword("FROM_UNIXTIME");
39936                self.write("(");
39937                self.generate_expression(&e.this)?;
39938                self.write("), '");
39939                if let Some(format) = &e.format {
39940                    self.write(format);
39941                }
39942                self.write("')");
39943            }
39944            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
39945                // Spark: FROM_UNIXTIME(value, format)
39946                self.write_keyword("FROM_UNIXTIME");
39947                self.write("(");
39948                self.generate_expression(&e.this)?;
39949                if let Some(format) = &e.format {
39950                    self.write(", '");
39951                    self.write(format);
39952                    self.write("'");
39953                }
39954                self.write(")");
39955            }
39956            _ => {
39957                // Default: UNIX_TO_STR(this, [format])
39958                self.write_keyword("UNIX_TO_STR");
39959                self.write("(");
39960                self.generate_expression(&e.this)?;
39961                if let Some(format) = &e.format {
39962                    self.write(", '");
39963                    self.write(format);
39964                    self.write("'");
39965                }
39966                self.write(")");
39967            }
39968        }
39969        Ok(())
39970    }
39971
39972    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
39973        use crate::dialects::DialectType;
39974        let scale = e.scale.unwrap_or(0); // 0 = seconds
39975
39976        match self.config.dialect {
39977            Some(DialectType::Snowflake) => {
39978                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
39979                self.write_keyword("TO_TIMESTAMP");
39980                self.write("(");
39981                self.generate_expression(&e.this)?;
39982                if let Some(s) = e.scale {
39983                    if s > 0 {
39984                        self.write(", ");
39985                        self.write(&s.to_string());
39986                    }
39987                }
39988                self.write(")");
39989            }
39990            Some(DialectType::BigQuery) => {
39991                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
39992                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
39993                match scale {
39994                    0 => {
39995                        self.write_keyword("TIMESTAMP_SECONDS");
39996                        self.write("(");
39997                        self.generate_expression(&e.this)?;
39998                        self.write(")");
39999                    }
40000                    3 => {
40001                        self.write_keyword("TIMESTAMP_MILLIS");
40002                        self.write("(");
40003                        self.generate_expression(&e.this)?;
40004                        self.write(")");
40005                    }
40006                    6 => {
40007                        self.write_keyword("TIMESTAMP_MICROS");
40008                        self.write("(");
40009                        self.generate_expression(&e.this)?;
40010                        self.write(")");
40011                    }
40012                    _ => {
40013                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
40014                        self.write_keyword("TIMESTAMP_SECONDS");
40015                        self.write("(CAST(");
40016                        self.generate_expression(&e.this)?;
40017                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
40018                    }
40019                }
40020            }
40021            Some(DialectType::Spark) => {
40022                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
40023                // TIMESTAMP_MILLIS(value) for scale=3
40024                // TIMESTAMP_MICROS(value) for scale=6
40025                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
40026                match scale {
40027                    0 => {
40028                        self.write_keyword("CAST");
40029                        self.write("(");
40030                        self.write_keyword("FROM_UNIXTIME");
40031                        self.write("(");
40032                        self.generate_expression(&e.this)?;
40033                        self.write(") ");
40034                        self.write_keyword("AS TIMESTAMP");
40035                        self.write(")");
40036                    }
40037                    3 => {
40038                        self.write_keyword("TIMESTAMP_MILLIS");
40039                        self.write("(");
40040                        self.generate_expression(&e.this)?;
40041                        self.write(")");
40042                    }
40043                    6 => {
40044                        self.write_keyword("TIMESTAMP_MICROS");
40045                        self.write("(");
40046                        self.generate_expression(&e.this)?;
40047                        self.write(")");
40048                    }
40049                    _ => {
40050                        self.write_keyword("TIMESTAMP_SECONDS");
40051                        self.write("(");
40052                        self.generate_expression(&e.this)?;
40053                        self.write(&format!(" / POWER(10, {}))", scale));
40054                    }
40055                }
40056            }
40057            Some(DialectType::Databricks) => {
40058                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
40059                // TIMESTAMP_MILLIS(value) for scale=3
40060                // TIMESTAMP_MICROS(value) for scale=6
40061                match scale {
40062                    0 => {
40063                        self.write_keyword("CAST");
40064                        self.write("(");
40065                        self.write_keyword("FROM_UNIXTIME");
40066                        self.write("(");
40067                        self.generate_expression(&e.this)?;
40068                        self.write(") ");
40069                        self.write_keyword("AS TIMESTAMP");
40070                        self.write(")");
40071                    }
40072                    3 => {
40073                        self.write_keyword("TIMESTAMP_MILLIS");
40074                        self.write("(");
40075                        self.generate_expression(&e.this)?;
40076                        self.write(")");
40077                    }
40078                    6 => {
40079                        self.write_keyword("TIMESTAMP_MICROS");
40080                        self.write("(");
40081                        self.generate_expression(&e.this)?;
40082                        self.write(")");
40083                    }
40084                    _ => {
40085                        self.write_keyword("TIMESTAMP_SECONDS");
40086                        self.write("(");
40087                        self.generate_expression(&e.this)?;
40088                        self.write(&format!(" / POWER(10, {}))", scale));
40089                    }
40090                }
40091            }
40092            Some(DialectType::Hive) => {
40093                // Hive: FROM_UNIXTIME(value)
40094                if scale == 0 {
40095                    self.write_keyword("FROM_UNIXTIME");
40096                    self.write("(");
40097                    self.generate_expression(&e.this)?;
40098                    self.write(")");
40099                } else {
40100                    self.write_keyword("FROM_UNIXTIME");
40101                    self.write("(");
40102                    self.generate_expression(&e.this)?;
40103                    self.write(&format!(" / POWER(10, {})", scale));
40104                    self.write(")");
40105                }
40106            }
40107            Some(DialectType::Presto) | Some(DialectType::Trino) => {
40108                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
40109                // FROM_UNIXTIME(value) for scale=0
40110                if scale == 0 {
40111                    self.write_keyword("FROM_UNIXTIME");
40112                    self.write("(");
40113                    self.generate_expression(&e.this)?;
40114                    self.write(")");
40115                } else {
40116                    self.write_keyword("FROM_UNIXTIME");
40117                    self.write("(CAST(");
40118                    self.generate_expression(&e.this)?;
40119                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
40120                }
40121            }
40122            Some(DialectType::DuckDB) => {
40123                // DuckDB: TO_TIMESTAMP(value) for scale=0
40124                // EPOCH_MS(value) for scale=3
40125                // MAKE_TIMESTAMP(value) for scale=6
40126                match scale {
40127                    0 => {
40128                        self.write_keyword("TO_TIMESTAMP");
40129                        self.write("(");
40130                        self.generate_expression(&e.this)?;
40131                        self.write(")");
40132                    }
40133                    3 => {
40134                        self.write_keyword("EPOCH_MS");
40135                        self.write("(");
40136                        self.generate_expression(&e.this)?;
40137                        self.write(")");
40138                    }
40139                    6 => {
40140                        self.write_keyword("MAKE_TIMESTAMP");
40141                        self.write("(");
40142                        self.generate_expression(&e.this)?;
40143                        self.write(")");
40144                    }
40145                    _ => {
40146                        self.write_keyword("TO_TIMESTAMP");
40147                        self.write("(");
40148                        self.generate_expression(&e.this)?;
40149                        self.write(&format!(" / POWER(10, {}))", scale));
40150                        self.write_keyword(" AT TIME ZONE");
40151                        self.write(" 'UTC'");
40152                    }
40153                }
40154            }
40155            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
40156                // Doris/StarRocks: FROM_UNIXTIME(value)
40157                self.write_keyword("FROM_UNIXTIME");
40158                self.write("(");
40159                self.generate_expression(&e.this)?;
40160                self.write(")");
40161            }
40162            Some(DialectType::Oracle) => {
40163                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
40164                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
40165                self.generate_expression(&e.this)?;
40166                self.write(" / 86400)");
40167            }
40168            Some(DialectType::Redshift) => {
40169                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
40170                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
40171                self.write("(TIMESTAMP 'epoch' + ");
40172                if scale == 0 {
40173                    self.generate_expression(&e.this)?;
40174                } else {
40175                    self.write("(");
40176                    self.generate_expression(&e.this)?;
40177                    self.write(&format!(" / POWER(10, {}))", scale));
40178                }
40179                self.write(" * INTERVAL '1 SECOND')");
40180            }
40181            Some(DialectType::Exasol) => {
40182                // Exasol: FROM_POSIX_TIME(value)
40183                self.write_keyword("FROM_POSIX_TIME");
40184                self.write("(");
40185                self.generate_expression(&e.this)?;
40186                self.write(")");
40187            }
40188            _ => {
40189                // Default: TO_TIMESTAMP(value[, scale])
40190                self.write_keyword("TO_TIMESTAMP");
40191                self.write("(");
40192                self.generate_expression(&e.this)?;
40193                if let Some(s) = e.scale {
40194                    self.write(", ");
40195                    self.write(&s.to_string());
40196                }
40197                self.write(")");
40198            }
40199        }
40200        Ok(())
40201    }
40202
40203    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
40204        // NAME col VALUE col1, col2, ...
40205        if !matches!(&*e.this, Expression::Null(_)) {
40206            self.write_keyword("NAME");
40207            self.write_space();
40208            self.generate_expression(&e.this)?;
40209        }
40210        if !e.expressions.is_empty() {
40211            self.write_space();
40212            self.write_keyword("VALUE");
40213            self.write_space();
40214            for (i, expr) in e.expressions.iter().enumerate() {
40215                if i > 0 {
40216                    self.write(", ");
40217                }
40218                self.generate_expression(expr)?;
40219            }
40220        }
40221        Ok(())
40222    }
40223
40224    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
40225        // this(expressions) or (this)(expressions)
40226        if e.wrapped.is_some() {
40227            self.write("(");
40228        }
40229        self.generate_expression(&e.this)?;
40230        if e.wrapped.is_some() {
40231            self.write(")");
40232        }
40233        self.write("(");
40234        for (i, expr) in e.expressions.iter().enumerate() {
40235            if i > 0 {
40236                self.write(", ");
40237            }
40238            self.generate_expression(expr)?;
40239        }
40240        self.write(")");
40241        Ok(())
40242    }
40243
40244    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
40245        // USING TEMPLATE this
40246        self.write_keyword("USING TEMPLATE");
40247        self.write_space();
40248        self.generate_expression(&e.this)?;
40249        Ok(())
40250    }
40251
40252    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
40253        // UTC_TIME
40254        self.write_keyword("UTC_TIME");
40255        Ok(())
40256    }
40257
40258    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
40259        if matches!(
40260            self.config.dialect,
40261            Some(crate::dialects::DialectType::ClickHouse)
40262        ) {
40263            self.write_keyword("CURRENT_TIMESTAMP");
40264            self.write("('UTC')");
40265        } else {
40266            self.write_keyword("UTC_TIMESTAMP");
40267        }
40268        Ok(())
40269    }
40270
40271    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
40272        use crate::dialects::DialectType;
40273        // Choose UUID function name based on target dialect
40274        let func_name = match self.config.dialect {
40275            Some(DialectType::Snowflake) => "UUID_STRING",
40276            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
40277            Some(DialectType::BigQuery) => "GENERATE_UUID",
40278            _ => {
40279                if let Some(name) = &e.name {
40280                    name.as_str()
40281                } else {
40282                    "UUID"
40283                }
40284            }
40285        };
40286        self.write_keyword(func_name);
40287        self.write("(");
40288        if let Some(this) = &e.this {
40289            self.generate_expression(this)?;
40290        }
40291        self.write(")");
40292        Ok(())
40293    }
40294
40295    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
40296        // MAP(key1, value1, key2, value2, ...)
40297        self.write_keyword("MAP");
40298        self.write("(");
40299        let mut first = true;
40300        for (k, v) in e.keys.iter().zip(e.values.iter()) {
40301            if !first {
40302                self.write(", ");
40303            }
40304            self.generate_expression(k)?;
40305            self.write(", ");
40306            self.generate_expression(v)?;
40307            first = false;
40308        }
40309        self.write(")");
40310        Ok(())
40311    }
40312
40313    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
40314        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
40315        self.write_keyword("VECTOR_SEARCH");
40316        self.write("(");
40317        self.generate_expression(&e.this)?;
40318        if let Some(col) = &e.column_to_search {
40319            self.write(", ");
40320            self.generate_expression(col)?;
40321        }
40322        if let Some(query_table) = &e.query_table {
40323            self.write(", ");
40324            self.generate_expression(query_table)?;
40325        }
40326        if let Some(query_col) = &e.query_column_to_search {
40327            self.write(", ");
40328            self.generate_expression(query_col)?;
40329        }
40330        if let Some(top_k) = &e.top_k {
40331            self.write(", ");
40332            self.generate_expression(top_k)?;
40333        }
40334        if let Some(dist_type) = &e.distance_type {
40335            self.write(", ");
40336            self.generate_expression(dist_type)?;
40337        }
40338        self.write(")");
40339        Ok(())
40340    }
40341
40342    fn generate_version(&mut self, e: &Version) -> Result<()> {
40343        // Python: f"FOR {expression.name} {kind} {expr}"
40344        // e.this = Identifier("TIMESTAMP" or "VERSION")
40345        // e.kind = "AS OF" (or "BETWEEN", etc.)
40346        // e.expression = the value expression
40347        // Hive does NOT use the FOR prefix for time travel
40348        use crate::dialects::DialectType;
40349        let skip_for = matches!(
40350            self.config.dialect,
40351            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
40352        );
40353        if !skip_for {
40354            self.write_keyword("FOR");
40355            self.write_space();
40356        }
40357        // Extract the name from this (which is an Identifier expression)
40358        match e.this.as_ref() {
40359            Expression::Identifier(ident) => {
40360                self.write_keyword(&ident.name);
40361            }
40362            _ => {
40363                self.generate_expression(&e.this)?;
40364            }
40365        }
40366        self.write_space();
40367        self.write_keyword(&e.kind);
40368        if let Some(expression) = &e.expression {
40369            self.write_space();
40370            self.generate_expression(expression)?;
40371        }
40372        Ok(())
40373    }
40374
40375    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
40376        // Python: return self.sql(expression, "this")
40377        self.generate_expression(&e.this)?;
40378        Ok(())
40379    }
40380
40381    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
40382        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
40383        if e.this.is_some() {
40384            self.write_keyword("NOT VOLATILE");
40385        } else {
40386            self.write_keyword("VOLATILE");
40387        }
40388        Ok(())
40389    }
40390
40391    fn generate_watermark_column_constraint(
40392        &mut self,
40393        e: &WatermarkColumnConstraint,
40394    ) -> Result<()> {
40395        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
40396        self.write_keyword("WATERMARK FOR");
40397        self.write_space();
40398        self.generate_expression(&e.this)?;
40399        self.write_space();
40400        self.write_keyword("AS");
40401        self.write_space();
40402        self.generate_expression(&e.expression)?;
40403        Ok(())
40404    }
40405
40406    fn generate_week(&mut self, e: &Week) -> Result<()> {
40407        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
40408        self.write_keyword("WEEK");
40409        self.write("(");
40410        self.generate_expression(&e.this)?;
40411        if let Some(mode) = &e.mode {
40412            self.write(", ");
40413            self.generate_expression(mode)?;
40414        }
40415        self.write(")");
40416        Ok(())
40417    }
40418
40419    fn generate_when(&mut self, e: &When) -> Result<()> {
40420        // Python: WHEN {matched}{source}{condition} THEN {then}
40421        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
40422        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
40423        self.write_keyword("WHEN");
40424        self.write_space();
40425
40426        // Check if matched
40427        if let Some(matched) = &e.matched {
40428            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
40429            match matched.as_ref() {
40430                Expression::Boolean(b) if b.value => {
40431                    self.write_keyword("MATCHED");
40432                }
40433                _ => {
40434                    self.write_keyword("NOT MATCHED");
40435                }
40436            }
40437        } else {
40438            self.write_keyword("NOT MATCHED");
40439        }
40440
40441        // BY SOURCE / BY TARGET
40442        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
40443        // BY TARGET is the default and typically omitted in output
40444        // Only emit if the dialect supports BY SOURCE syntax
40445        if self.config.matched_by_source {
40446            if let Some(source) = &e.source {
40447                if let Expression::Boolean(b) = source.as_ref() {
40448                    if b.value {
40449                        // BY SOURCE
40450                        self.write_space();
40451                        self.write_keyword("BY SOURCE");
40452                    }
40453                    // BY TARGET (b.value == false) is omitted as it's the default
40454                } else {
40455                    // For non-boolean source, output as BY SOURCE (legacy behavior)
40456                    self.write_space();
40457                    self.write_keyword("BY SOURCE");
40458                }
40459            }
40460        }
40461
40462        // Condition
40463        if let Some(condition) = &e.condition {
40464            self.write_space();
40465            self.write_keyword("AND");
40466            self.write_space();
40467            self.generate_expression(condition)?;
40468        }
40469
40470        self.write_space();
40471        self.write_keyword("THEN");
40472        self.write_space();
40473
40474        // Generate the then expression (could be INSERT, UPDATE, DELETE)
40475        // MERGE actions are stored as Tuples with the action keyword as first element
40476        self.generate_merge_action(&e.then)?;
40477
40478        Ok(())
40479    }
40480
40481    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
40482        match action {
40483            Expression::Tuple(tuple) => {
40484                let elements = &tuple.expressions;
40485                if elements.is_empty() {
40486                    return self.generate_expression(action);
40487                }
40488                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
40489                match &elements[0] {
40490                    Expression::Var(v) if v.this == "INSERT" => {
40491                        self.write_keyword("INSERT");
40492                        // Spark: INSERT * (insert all columns)
40493                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
40494                            self.write(" *");
40495                            if let Some(Expression::Where(w)) = elements.get(2) {
40496                                self.write_space();
40497                                self.generate_where(w)?;
40498                            }
40499                        } else {
40500                            let mut values_idx = 1;
40501                            // Check if second element is column list (Tuple)
40502                            if elements.len() > 1 {
40503                                if let Expression::Tuple(cols) = &elements[1] {
40504                                    // Could be columns or values - if there's a third element, second is columns
40505                                    if elements.len() > 2 {
40506                                        // Second is columns, third is values
40507                                        self.write(" (");
40508                                        for (i, col) in cols.expressions.iter().enumerate() {
40509                                            if i > 0 {
40510                                                self.write(", ");
40511                                            }
40512                                            // Strip MERGE target qualifiers from INSERT column list
40513                                            if !self.merge_strip_qualifiers.is_empty() {
40514                                                let stripped = self.strip_merge_qualifier(col);
40515                                                self.generate_expression(&stripped)?;
40516                                            } else {
40517                                                self.generate_expression(col)?;
40518                                            }
40519                                        }
40520                                        self.write(")");
40521                                        values_idx = 2;
40522                                    } else {
40523                                        // Only two elements: INSERT + values (no explicit columns)
40524                                        values_idx = 1;
40525                                    }
40526                                }
40527                            }
40528                            let mut next_idx = values_idx;
40529                            // Generate VALUES clause
40530                            if values_idx < elements.len()
40531                                && !matches!(&elements[values_idx], Expression::Where(_))
40532                            {
40533                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
40534                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
40535                                if !is_row {
40536                                    self.write_space();
40537                                    self.write_keyword("VALUES");
40538                                }
40539                                self.write(" ");
40540                                if let Expression::Tuple(vals) = &elements[values_idx] {
40541                                    self.write("(");
40542                                    for (i, val) in vals.expressions.iter().enumerate() {
40543                                        if i > 0 {
40544                                            self.write(", ");
40545                                        }
40546                                        self.generate_expression(val)?;
40547                                    }
40548                                    self.write(")");
40549                                } else {
40550                                    self.generate_expression(&elements[values_idx])?;
40551                                }
40552                                next_idx += 1;
40553                            }
40554                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
40555                                self.write_space();
40556                                self.generate_where(w)?;
40557                            }
40558                        } // close else for INSERT * check
40559                    }
40560                    Expression::Var(v) if v.this == "UPDATE" => {
40561                        self.write_keyword("UPDATE");
40562                        // Spark: UPDATE * (update all columns)
40563                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
40564                            self.write(" *");
40565                            if let Some(Expression::Where(w)) = elements.get(2) {
40566                                self.write_space();
40567                                self.generate_where(w)?;
40568                            }
40569                        } else if elements.len() > 1 {
40570                            self.write_space();
40571                            self.write_keyword("SET");
40572                            // In pretty mode, put assignments on next line with extra indent
40573                            if self.config.pretty {
40574                                self.write_newline();
40575                                self.indent_level += 1;
40576                                self.write_indent();
40577                            } else {
40578                                self.write_space();
40579                            }
40580                            if let Expression::Tuple(assignments) = &elements[1] {
40581                                for (i, assignment) in assignments.expressions.iter().enumerate() {
40582                                    if i > 0 {
40583                                        if self.config.pretty {
40584                                            self.write(",");
40585                                            self.write_newline();
40586                                            self.write_indent();
40587                                        } else {
40588                                            self.write(", ");
40589                                        }
40590                                    }
40591                                    // Strip MERGE target qualifiers from left side of UPDATE SET
40592                                    if !self.merge_strip_qualifiers.is_empty() {
40593                                        self.generate_merge_set_assignment(assignment)?;
40594                                    } else {
40595                                        self.generate_expression(assignment)?;
40596                                    }
40597                                }
40598                            } else {
40599                                self.generate_expression(&elements[1])?;
40600                            }
40601                            if self.config.pretty {
40602                                self.indent_level -= 1;
40603                            }
40604                            if let Some(Expression::Where(w)) = elements.get(2) {
40605                                self.write_space();
40606                                self.generate_where(w)?;
40607                            }
40608                        }
40609                    }
40610                    Expression::Var(v) if v.this == "DELETE" => {
40611                        self.write_keyword("DELETE");
40612                        if let Some(Expression::Where(w)) = elements.get(1) {
40613                            self.write_space();
40614                            self.generate_where(w)?;
40615                        }
40616                    }
40617                    _ => {
40618                        // Fallback: generic tuple generation
40619                        self.generate_expression(action)?;
40620                    }
40621                }
40622            }
40623            Expression::Var(v)
40624                if v.this == "INSERT"
40625                    || v.this == "UPDATE"
40626                    || v.this == "DELETE"
40627                    || v.this == "DO NOTHING" =>
40628            {
40629                self.write_keyword(&v.this);
40630            }
40631            _ => {
40632                self.generate_expression(action)?;
40633            }
40634        }
40635        Ok(())
40636    }
40637
40638    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
40639    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
40640        match assignment {
40641            Expression::Eq(eq) => {
40642                // Strip qualifier from the left side if it matches a MERGE target name
40643                let stripped_left = self.strip_merge_qualifier(&eq.left);
40644                self.generate_expression(&stripped_left)?;
40645                self.write(" = ");
40646                self.generate_expression(&eq.right)?;
40647                Ok(())
40648            }
40649            other => self.generate_expression(other),
40650        }
40651    }
40652
40653    /// Strip table qualifier from a column reference if it matches a MERGE target name
40654    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
40655        match expr {
40656            Expression::Column(col) => {
40657                if let Some(ref table_ident) = col.table {
40658                    if self
40659                        .merge_strip_qualifiers
40660                        .iter()
40661                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
40662                    {
40663                        // Strip the table qualifier
40664                        let mut col = col.clone();
40665                        col.table = None;
40666                        return Expression::Column(col);
40667                    }
40668                }
40669                expr.clone()
40670            }
40671            Expression::Dot(dot) => {
40672                // table.column -> column (strip qualifier)
40673                if let Expression::Identifier(id) = &dot.this {
40674                    if self
40675                        .merge_strip_qualifiers
40676                        .iter()
40677                        .any(|n| n.eq_ignore_ascii_case(&id.name))
40678                    {
40679                        return Expression::Identifier(dot.field.clone());
40680                    }
40681                }
40682                expr.clone()
40683            }
40684            _ => expr.clone(),
40685        }
40686    }
40687
40688    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
40689        // Python: return self.expressions(expression, sep=" ", indent=False)
40690        for (i, expr) in e.expressions.iter().enumerate() {
40691            if i > 0 {
40692                // In pretty mode, each WHEN clause on its own line
40693                if self.config.pretty {
40694                    self.write_newline();
40695                    self.write_indent();
40696                } else {
40697                    self.write_space();
40698                }
40699            }
40700            self.generate_expression(expr)?;
40701        }
40702        Ok(())
40703    }
40704
40705    fn generate_where(&mut self, e: &Where) -> Result<()> {
40706        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
40707        self.write_keyword("WHERE");
40708        self.write_space();
40709        self.generate_expression(&e.this)?;
40710        Ok(())
40711    }
40712
40713    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
40714        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
40715        self.write_keyword("WIDTH_BUCKET");
40716        self.write("(");
40717        self.generate_expression(&e.this)?;
40718        if let Some(min_value) = &e.min_value {
40719            self.write(", ");
40720            self.generate_expression(min_value)?;
40721        }
40722        if let Some(max_value) = &e.max_value {
40723            self.write(", ");
40724            self.generate_expression(max_value)?;
40725        }
40726        if let Some(num_buckets) = &e.num_buckets {
40727            self.write(", ");
40728            self.generate_expression(num_buckets)?;
40729        }
40730        self.write(")");
40731        Ok(())
40732    }
40733
40734    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
40735        // Window specification: PARTITION BY ... ORDER BY ... frame
40736        self.generate_window_spec(e)
40737    }
40738
40739    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
40740        // Window specification: PARTITION BY ... ORDER BY ... frame
40741        let mut has_content = false;
40742
40743        // PARTITION BY
40744        if !e.partition_by.is_empty() {
40745            self.write_keyword("PARTITION BY");
40746            self.write_space();
40747            for (i, expr) in e.partition_by.iter().enumerate() {
40748                if i > 0 {
40749                    self.write(", ");
40750                }
40751                self.generate_expression(expr)?;
40752            }
40753            has_content = true;
40754        }
40755
40756        // ORDER BY
40757        if !e.order_by.is_empty() {
40758            if has_content {
40759                self.write_space();
40760            }
40761            self.write_keyword("ORDER BY");
40762            self.write_space();
40763            for (i, ordered) in e.order_by.iter().enumerate() {
40764                if i > 0 {
40765                    self.write(", ");
40766                }
40767                self.generate_expression(&ordered.this)?;
40768                if ordered.desc {
40769                    self.write_space();
40770                    self.write_keyword("DESC");
40771                } else if ordered.explicit_asc {
40772                    self.write_space();
40773                    self.write_keyword("ASC");
40774                }
40775                if let Some(nulls_first) = ordered.nulls_first {
40776                    self.write_space();
40777                    self.write_keyword("NULLS");
40778                    self.write_space();
40779                    if nulls_first {
40780                        self.write_keyword("FIRST");
40781                    } else {
40782                        self.write_keyword("LAST");
40783                    }
40784                }
40785            }
40786            has_content = true;
40787        }
40788
40789        // Frame specification
40790        if let Some(frame) = &e.frame {
40791            if has_content {
40792                self.write_space();
40793            }
40794            self.generate_window_frame(frame)?;
40795        }
40796
40797        Ok(())
40798    }
40799
40800    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
40801        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
40802        self.write_keyword("WITH");
40803        self.write_space();
40804        if e.no.is_some() {
40805            self.write_keyword("NO");
40806            self.write_space();
40807        }
40808        self.write_keyword("DATA");
40809
40810        // statistics
40811        if let Some(statistics) = &e.statistics {
40812            self.write_space();
40813            self.write_keyword("AND");
40814            self.write_space();
40815            // Check if statistics is true or false
40816            match statistics.as_ref() {
40817                Expression::Boolean(b) if !b.value => {
40818                    self.write_keyword("NO");
40819                    self.write_space();
40820                }
40821                _ => {}
40822            }
40823            self.write_keyword("STATISTICS");
40824        }
40825        Ok(())
40826    }
40827
40828    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
40829        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
40830        self.write_keyword("WITH FILL");
40831
40832        if let Some(from_) = &e.from_ {
40833            self.write_space();
40834            self.write_keyword("FROM");
40835            self.write_space();
40836            self.generate_expression(from_)?;
40837        }
40838
40839        if let Some(to) = &e.to {
40840            self.write_space();
40841            self.write_keyword("TO");
40842            self.write_space();
40843            self.generate_expression(to)?;
40844        }
40845
40846        if let Some(step) = &e.step {
40847            self.write_space();
40848            self.write_keyword("STEP");
40849            self.write_space();
40850            self.generate_expression(step)?;
40851        }
40852
40853        if let Some(staleness) = &e.staleness {
40854            self.write_space();
40855            self.write_keyword("STALENESS");
40856            self.write_space();
40857            self.generate_expression(staleness)?;
40858        }
40859
40860        if let Some(interpolate) = &e.interpolate {
40861            self.write_space();
40862            self.write_keyword("INTERPOLATE");
40863            self.write(" (");
40864            // INTERPOLATE items use reversed alias format: name AS expression
40865            self.generate_interpolate_item(interpolate)?;
40866            self.write(")");
40867        }
40868
40869        Ok(())
40870    }
40871
40872    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
40873    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
40874        match expr {
40875            Expression::Alias(alias) => {
40876                // Output as: alias_name AS expression
40877                self.generate_identifier(&alias.alias)?;
40878                self.write_space();
40879                self.write_keyword("AS");
40880                self.write_space();
40881                self.generate_expression(&alias.this)?;
40882            }
40883            Expression::Tuple(tuple) => {
40884                for (i, item) in tuple.expressions.iter().enumerate() {
40885                    if i > 0 {
40886                        self.write(", ");
40887                    }
40888                    self.generate_interpolate_item(item)?;
40889                }
40890            }
40891            other => {
40892                self.generate_expression(other)?;
40893            }
40894        }
40895        Ok(())
40896    }
40897
40898    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
40899        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
40900        self.write_keyword("WITH JOURNAL TABLE");
40901        self.write("=");
40902        self.generate_expression(&e.this)?;
40903        Ok(())
40904    }
40905
40906    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
40907        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
40908        self.generate_expression(&e.this)?;
40909        self.write_space();
40910        self.write_keyword("WITH");
40911        self.write_space();
40912        self.write_keyword(&e.op);
40913        Ok(())
40914    }
40915
40916    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
40917        // Python: return f"WITH {self.expressions(expression, flat=True)}"
40918        self.write_keyword("WITH");
40919        self.write_space();
40920        for (i, expr) in e.expressions.iter().enumerate() {
40921            if i > 0 {
40922                self.write(", ");
40923            }
40924            self.generate_expression(expr)?;
40925        }
40926        Ok(())
40927    }
40928
40929    fn generate_with_schema_binding_property(
40930        &mut self,
40931        e: &WithSchemaBindingProperty,
40932    ) -> Result<()> {
40933        // Python: return f"WITH {self.sql(expression, 'this')}"
40934        self.write_keyword("WITH");
40935        self.write_space();
40936        self.generate_expression(&e.this)?;
40937        Ok(())
40938    }
40939
40940    fn generate_with_system_versioning_property(
40941        &mut self,
40942        e: &WithSystemVersioningProperty,
40943    ) -> Result<()> {
40944        // Python: complex logic for SYSTEM_VERSIONING with options
40945        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
40946        // or SYSTEM_VERSIONING=ON/OFF
40947        // with WITH(...) wrapper if with_ is set
40948
40949        let mut parts = Vec::new();
40950
40951        if let Some(this) = &e.this {
40952            // HISTORY_TABLE=...
40953            let mut s = String::from("HISTORY_TABLE=");
40954            let mut gen = Generator::new();
40955            gen.generate_expression(this)?;
40956            s.push_str(&gen.output);
40957            parts.push(s);
40958        }
40959
40960        if let Some(data_consistency) = &e.data_consistency {
40961            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
40962            let mut gen = Generator::new();
40963            gen.generate_expression(data_consistency)?;
40964            s.push_str(&gen.output);
40965            parts.push(s);
40966        }
40967
40968        if let Some(retention_period) = &e.retention_period {
40969            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
40970            let mut gen = Generator::new();
40971            gen.generate_expression(retention_period)?;
40972            s.push_str(&gen.output);
40973            parts.push(s);
40974        }
40975
40976        self.write_keyword("SYSTEM_VERSIONING");
40977        self.write("=");
40978
40979        if !parts.is_empty() {
40980            self.write_keyword("ON");
40981            self.write("(");
40982            self.write(&parts.join(", "));
40983            self.write(")");
40984        } else if e.on.is_some() {
40985            self.write_keyword("ON");
40986        } else {
40987            self.write_keyword("OFF");
40988        }
40989
40990        // Wrap in WITH(...) if with_ is set
40991        if e.with_.is_some() {
40992            let inner = self.output.clone();
40993            self.output.clear();
40994            self.write("WITH(");
40995            self.write(&inner);
40996            self.write(")");
40997        }
40998
40999        Ok(())
41000    }
41001
41002    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
41003        // Python: f"WITH ({self.expressions(expression, flat=True)})"
41004        self.write_keyword("WITH");
41005        self.write(" (");
41006        for (i, expr) in e.expressions.iter().enumerate() {
41007            if i > 0 {
41008                self.write(", ");
41009            }
41010            self.generate_expression(expr)?;
41011        }
41012        self.write(")");
41013        Ok(())
41014    }
41015
41016    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
41017        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
41018        // return self.func("XMLELEMENT", name, *expression.expressions)
41019        self.write_keyword("XMLELEMENT");
41020        self.write("(");
41021
41022        if e.evalname.is_some() {
41023            self.write_keyword("EVALNAME");
41024        } else {
41025            self.write_keyword("NAME");
41026        }
41027        self.write_space();
41028        self.generate_expression(&e.this)?;
41029
41030        for expr in &e.expressions {
41031            self.write(", ");
41032            self.generate_expression(expr)?;
41033        }
41034        self.write(")");
41035        Ok(())
41036    }
41037
41038    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
41039        // XMLGET(this, expression [, instance])
41040        self.write_keyword("XMLGET");
41041        self.write("(");
41042        self.generate_expression(&e.this)?;
41043        self.write(", ");
41044        self.generate_expression(&e.expression)?;
41045        if let Some(instance) = &e.instance {
41046            self.write(", ");
41047            self.generate_expression(instance)?;
41048        }
41049        self.write(")");
41050        Ok(())
41051    }
41052
41053    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
41054        // Python: this + optional (expr)
41055        self.generate_expression(&e.this)?;
41056        if let Some(expression) = &e.expression {
41057            self.write("(");
41058            self.generate_expression(expression)?;
41059            self.write(")");
41060        }
41061        Ok(())
41062    }
41063
41064    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
41065        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
41066        self.write_keyword("XMLTABLE");
41067        self.write("(");
41068
41069        if self.config.pretty {
41070            self.indent_level += 1;
41071            self.write_newline();
41072            self.write_indent();
41073            self.generate_expression(&e.this)?;
41074
41075            if let Some(passing) = &e.passing {
41076                self.write_newline();
41077                self.write_indent();
41078                self.write_keyword("PASSING");
41079                if let Expression::Tuple(tuple) = passing.as_ref() {
41080                    for expr in &tuple.expressions {
41081                        self.write_newline();
41082                        self.indent_level += 1;
41083                        self.write_indent();
41084                        self.generate_expression(expr)?;
41085                        self.indent_level -= 1;
41086                    }
41087                } else {
41088                    self.write_newline();
41089                    self.indent_level += 1;
41090                    self.write_indent();
41091                    self.generate_expression(passing)?;
41092                    self.indent_level -= 1;
41093                }
41094            }
41095
41096            if e.by_ref.is_some() {
41097                self.write_newline();
41098                self.write_indent();
41099                self.write_keyword("RETURNING SEQUENCE BY REF");
41100            }
41101
41102            if !e.columns.is_empty() {
41103                self.write_newline();
41104                self.write_indent();
41105                self.write_keyword("COLUMNS");
41106                for (i, col) in e.columns.iter().enumerate() {
41107                    self.write_newline();
41108                    self.indent_level += 1;
41109                    self.write_indent();
41110                    self.generate_expression(col)?;
41111                    self.indent_level -= 1;
41112                    if i < e.columns.len() - 1 {
41113                        self.write(",");
41114                    }
41115                }
41116            }
41117
41118            self.indent_level -= 1;
41119            self.write_newline();
41120            self.write_indent();
41121            self.write(")");
41122            return Ok(());
41123        }
41124
41125        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
41126        if let Some(namespaces) = &e.namespaces {
41127            self.write_keyword("XMLNAMESPACES");
41128            self.write("(");
41129            // Unwrap Tuple if present to avoid extra parentheses
41130            if let Expression::Tuple(tuple) = namespaces.as_ref() {
41131                for (i, expr) in tuple.expressions.iter().enumerate() {
41132                    if i > 0 {
41133                        self.write(", ");
41134                    }
41135                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
41136                    // See xmlnamespace_sql in generator.py
41137                    if !matches!(expr, Expression::Alias(_)) {
41138                        self.write_keyword("DEFAULT");
41139                        self.write_space();
41140                    }
41141                    self.generate_expression(expr)?;
41142                }
41143            } else {
41144                // Single namespace - check if DEFAULT
41145                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
41146                    self.write_keyword("DEFAULT");
41147                    self.write_space();
41148                }
41149                self.generate_expression(namespaces)?;
41150            }
41151            self.write("), ");
41152        }
41153
41154        // XPath expression
41155        self.generate_expression(&e.this)?;
41156
41157        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
41158        if let Some(passing) = &e.passing {
41159            self.write_space();
41160            self.write_keyword("PASSING");
41161            self.write_space();
41162            // Unwrap Tuple if present to avoid extra parentheses
41163            if let Expression::Tuple(tuple) = passing.as_ref() {
41164                for (i, expr) in tuple.expressions.iter().enumerate() {
41165                    if i > 0 {
41166                        self.write(", ");
41167                    }
41168                    self.generate_expression(expr)?;
41169                }
41170            } else {
41171                self.generate_expression(passing)?;
41172            }
41173        }
41174
41175        // RETURNING SEQUENCE BY REF
41176        if e.by_ref.is_some() {
41177            self.write_space();
41178            self.write_keyword("RETURNING SEQUENCE BY REF");
41179        }
41180
41181        // COLUMNS clause
41182        if !e.columns.is_empty() {
41183            self.write_space();
41184            self.write_keyword("COLUMNS");
41185            self.write_space();
41186            for (i, col) in e.columns.iter().enumerate() {
41187                if i > 0 {
41188                    self.write(", ");
41189                }
41190                self.generate_expression(col)?;
41191            }
41192        }
41193
41194        self.write(")");
41195        Ok(())
41196    }
41197
41198    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
41199        // Python: return self.connector_sql(expression, "XOR", stack)
41200        // Handles: this XOR expression or expressions joined by XOR
41201        if let Some(this) = &e.this {
41202            self.generate_expression(this)?;
41203            if let Some(expression) = &e.expression {
41204                self.write_space();
41205                self.write_keyword("XOR");
41206                self.write_space();
41207                self.generate_expression(expression)?;
41208            }
41209        }
41210
41211        // Handle multiple expressions
41212        for (i, expr) in e.expressions.iter().enumerate() {
41213            if i > 0 || e.this.is_some() {
41214                self.write_space();
41215                self.write_keyword("XOR");
41216                self.write_space();
41217            }
41218            self.generate_expression(expr)?;
41219        }
41220        Ok(())
41221    }
41222
41223    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
41224        // ZIPF(this, elementcount [, gen])
41225        self.write_keyword("ZIPF");
41226        self.write("(");
41227        self.generate_expression(&e.this)?;
41228        if let Some(elementcount) = &e.elementcount {
41229            self.write(", ");
41230            self.generate_expression(elementcount)?;
41231        }
41232        if let Some(gen) = &e.gen {
41233            self.write(", ");
41234            self.generate_expression(gen)?;
41235        }
41236        self.write(")");
41237        Ok(())
41238    }
41239}
41240
41241impl Default for Generator {
41242    fn default() -> Self {
41243        Self::new()
41244    }
41245}
41246
41247#[cfg(test)]
41248mod tests {
41249    use super::*;
41250    use crate::parser::Parser;
41251
41252    fn roundtrip(sql: &str) -> String {
41253        let ast = Parser::parse_sql(sql).unwrap();
41254        Generator::sql(&ast[0]).unwrap()
41255    }
41256
41257    #[test]
41258    fn test_simple_select() {
41259        let result = roundtrip("SELECT 1");
41260        assert_eq!(result, "SELECT 1");
41261    }
41262
41263    #[test]
41264    fn test_select_from() {
41265        let result = roundtrip("SELECT a, b FROM t");
41266        assert_eq!(result, "SELECT a, b FROM t");
41267    }
41268
41269    #[test]
41270    fn test_select_where() {
41271        let result = roundtrip("SELECT * FROM t WHERE x = 1");
41272        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
41273    }
41274
41275    #[test]
41276    fn test_select_join() {
41277        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
41278        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
41279    }
41280
41281    #[test]
41282    fn test_insert() {
41283        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
41284        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
41285    }
41286
41287    #[test]
41288    fn test_pretty_print() {
41289        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
41290        let result = Generator::pretty_sql(&ast[0]).unwrap();
41291        assert!(result.contains('\n'));
41292    }
41293
41294    #[test]
41295    fn test_window_function() {
41296        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
41297        assert_eq!(
41298            result,
41299            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
41300        );
41301    }
41302
41303    #[test]
41304    fn test_window_function_with_frame() {
41305        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
41306        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
41307    }
41308
41309    #[test]
41310    fn test_aggregate_with_filter() {
41311        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
41312        assert_eq!(
41313            result,
41314            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
41315        );
41316    }
41317
41318    #[test]
41319    fn test_subscript() {
41320        let result = roundtrip("SELECT arr[0]");
41321        assert_eq!(result, "SELECT arr[0]");
41322    }
41323
41324    // DDL tests
41325    #[test]
41326    fn test_create_table() {
41327        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
41328        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
41329    }
41330
41331    #[test]
41332    fn test_create_table_with_constraints() {
41333        let result = roundtrip(
41334            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
41335        );
41336        assert_eq!(
41337            result,
41338            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
41339        );
41340    }
41341
41342    #[test]
41343    fn test_create_table_if_not_exists() {
41344        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
41345        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
41346    }
41347
41348    #[test]
41349    fn test_drop_table() {
41350        let result = roundtrip("DROP TABLE users");
41351        assert_eq!(result, "DROP TABLE users");
41352    }
41353
41354    #[test]
41355    fn test_drop_table_if_exists_cascade() {
41356        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
41357        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
41358    }
41359
41360    #[test]
41361    fn test_alter_table_add_column() {
41362        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
41363        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
41364    }
41365
41366    #[test]
41367    fn test_alter_table_drop_column() {
41368        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
41369        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
41370    }
41371
41372    #[test]
41373    fn test_create_index() {
41374        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
41375        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
41376    }
41377
41378    #[test]
41379    fn test_create_unique_index() {
41380        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
41381        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
41382    }
41383
41384    #[test]
41385    fn test_drop_index() {
41386        let result = roundtrip("DROP INDEX idx_name");
41387        assert_eq!(result, "DROP INDEX idx_name");
41388
41389        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
41390        assert_eq!(
41391            result,
41392            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
41393        );
41394
41395        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
41396        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
41397    }
41398
41399    #[test]
41400    fn test_create_view() {
41401        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
41402        assert_eq!(
41403            result,
41404            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
41405        );
41406    }
41407
41408    #[test]
41409    fn test_drop_view() {
41410        let result = roundtrip("DROP VIEW active_users");
41411        assert_eq!(result, "DROP VIEW active_users");
41412    }
41413
41414    #[test]
41415    fn test_truncate() {
41416        let result = roundtrip("TRUNCATE TABLE users");
41417        assert_eq!(result, "TRUNCATE TABLE users");
41418    }
41419
41420    #[test]
41421    fn test_string_literal_escaping_default() {
41422        // Default: double single quotes
41423        let result = roundtrip("SELECT 'hello'");
41424        assert_eq!(result, "SELECT 'hello'");
41425
41426        // Single quotes are doubled
41427        let result = roundtrip("SELECT 'it''s a test'");
41428        assert_eq!(result, "SELECT 'it''s a test'");
41429    }
41430
41431    #[test]
41432    fn test_not_in_style_prefix_default_generic() {
41433        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
41434        assert_eq!(
41435            result,
41436            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
41437        );
41438    }
41439
41440    #[test]
41441    fn test_not_in_style_infix_generic_override() {
41442        let ast =
41443            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
41444                .unwrap();
41445        let config = GeneratorConfig {
41446            not_in_style: NotInStyle::Infix,
41447            ..Default::default()
41448        };
41449        let mut gen = Generator::with_config(config);
41450        let result = gen.generate(&ast[0]).unwrap();
41451        assert_eq!(
41452            result,
41453            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
41454        );
41455    }
41456
41457    #[test]
41458    fn test_string_literal_escaping_mysql() {
41459        use crate::dialects::DialectType;
41460
41461        let config = GeneratorConfig {
41462            dialect: Some(DialectType::MySQL),
41463            ..Default::default()
41464        };
41465
41466        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
41467        let mut gen = Generator::with_config(config.clone());
41468        let result = gen.generate(&ast[0]).unwrap();
41469        assert_eq!(result, "SELECT 'hello'");
41470
41471        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
41472        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
41473        let mut gen = Generator::with_config(config.clone());
41474        let result = gen.generate(&ast[0]).unwrap();
41475        assert_eq!(result, "SELECT 'it''s'");
41476    }
41477
41478    #[test]
41479    fn test_string_literal_escaping_postgres() {
41480        use crate::dialects::DialectType;
41481
41482        let config = GeneratorConfig {
41483            dialect: Some(DialectType::PostgreSQL),
41484            ..Default::default()
41485        };
41486
41487        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
41488        let mut gen = Generator::with_config(config.clone());
41489        let result = gen.generate(&ast[0]).unwrap();
41490        assert_eq!(result, "SELECT 'hello'");
41491
41492        // PostgreSQL uses doubled quotes for regular strings
41493        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
41494        let mut gen = Generator::with_config(config.clone());
41495        let result = gen.generate(&ast[0]).unwrap();
41496        assert_eq!(result, "SELECT 'it''s'");
41497    }
41498
41499    #[test]
41500    fn test_string_literal_escaping_bigquery() {
41501        use crate::dialects::DialectType;
41502
41503        let config = GeneratorConfig {
41504            dialect: Some(DialectType::BigQuery),
41505            ..Default::default()
41506        };
41507
41508        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
41509        let mut gen = Generator::with_config(config.clone());
41510        let result = gen.generate(&ast[0]).unwrap();
41511        assert_eq!(result, "SELECT 'hello'");
41512
41513        // BigQuery escapes single quotes with backslash
41514        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
41515        let mut gen = Generator::with_config(config.clone());
41516        let result = gen.generate(&ast[0]).unwrap();
41517        assert_eq!(result, "SELECT 'it\\'s'");
41518    }
41519
41520    #[test]
41521    fn test_generate_deep_and_chain_without_stack_growth() {
41522        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
41523            Expression::column("c0"),
41524            Expression::number(0),
41525        )));
41526
41527        for i in 1..2500 {
41528            let predicate = Expression::Eq(Box::new(BinaryOp::new(
41529                Expression::column(format!("c{i}")),
41530                Expression::number(i as i64),
41531            )));
41532            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
41533        }
41534
41535        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
41536        assert!(sql.contains("c2499 = 2499"), "{}", sql);
41537    }
41538
41539    #[test]
41540    fn test_generate_deep_or_chain_without_stack_growth() {
41541        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
41542            Expression::column("c0"),
41543            Expression::number(0),
41544        )));
41545
41546        for i in 1..2500 {
41547            let predicate = Expression::Eq(Box::new(BinaryOp::new(
41548                Expression::column(format!("c{i}")),
41549                Expression::number(i as i64),
41550            )));
41551            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
41552        }
41553
41554        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
41555        assert!(sql.contains("c2499 = 2499"), "{}", sql);
41556    }
41557}