Skip to main content

polyglot_sql/
generator.rs

1//! SQL Generator -- converts an AST back into SQL strings.
2//!
3//! The central type is [`Generator`], which walks an [`Expression`] tree and
4//! emits a SQL string. Generation is controlled by a [`GeneratorConfig`] that
5//! specifies the target dialect, formatting preferences, identifier quoting
6//! style, function name casing, and many other dialect-specific flags.
7//!
8//! For one-off generation the static helpers [`Generator::sql`] and
9//! [`Generator::pretty_sql`] are the simplest entry points. For repeated
10//! generation, construct a `Generator` once with [`Generator::with_config`]
11//! and call [`Generator::generate`] for each expression.
12
13use std::borrow::Cow;
14use std::sync::Arc;
15
16use crate::error::Result;
17use crate::expressions::*;
18use crate::guard::{enforce_generate_ast, ComplexityGuardOptions};
19use crate::DialectType;
20use serde::{Deserialize, Serialize};
21
22/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
23///
24/// The generator walks the expression tree and emits dialect-specific SQL text.
25/// It supports pretty-printing with configurable indentation, identifier quoting,
26/// keyword casing, function name normalization, and more than 30 SQL dialects.
27///
28/// # Usage
29///
30/// ```rust,ignore
31/// use polyglot_sql::generator::Generator;
32/// use polyglot_sql::parser::Parser;
33///
34/// let ast = Parser::parse_sql("SELECT 1")?;
35/// // Quick one-shot generation (default config):
36/// let sql = Generator::sql(&ast[0])?;
37///
38/// // Pretty-printed output:
39/// let pretty = Generator::pretty_sql(&ast[0])?;
40///
41/// // Custom config (e.g. for a specific dialect):
42/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
43/// let mut gen = Generator::with_config(config);
44/// let sql = gen.generate(&ast[0])?;
45/// ```
46pub struct Generator {
47    config: Arc<GeneratorConfig>,
48    output: String,
49    unsupported_messages: Vec<String>,
50    indent_level: usize,
51    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
52    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
53    athena_hive_context: bool,
54    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
55    sqlite_inline_pk_columns: std::collections::HashSet<String>,
56    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
57    merge_strip_qualifiers: Vec<String>,
58    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
59    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
60    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
61    /// Negative values indicate map key context (should NOT be wrapped).
62    clickhouse_nullable_depth: i32,
63}
64
65/// Controls how SQL function names are cased in generated output.
66///
67/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
68/// - `Lower` -- `count`, `sum`, `coalesce`
69/// - `None` -- preserve the original casing from the parsed input
70#[derive(Debug, Clone, Copy, PartialEq, Default)]
71pub enum NormalizeFunctions {
72    /// Emit function names in UPPER CASE (default).
73    #[default]
74    Upper,
75    /// Emit function names in lower case.
76    Lower,
77    /// Preserve the original casing from the parsed input.
78    None,
79}
80
81/// Strategy for generating row-limiting clauses across SQL dialects.
82#[derive(Debug, Clone, Copy, PartialEq, Default)]
83pub enum LimitFetchStyle {
84    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
85    #[default]
86    Limit,
87    /// `TOP n` -- TSQL (SQL Server).
88    Top,
89    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
90    FetchFirst,
91}
92
93/// Strategy for rendering negated IN predicates.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum NotInStyle {
96    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
97    #[default]
98    Prefix,
99    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
100    Infix,
101}
102
103/// Controls how the generator reacts when it encounters unsupported output.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum UnsupportedLevel {
107    /// Ignore unsupported diagnostics and continue generation.
108    Ignore,
109    /// Collect unsupported diagnostics and continue generation.
110    #[default]
111    Warn,
112    /// Collect unsupported diagnostics and raise after generation completes.
113    Raise,
114    /// Raise immediately when the first unsupported feature is encountered.
115    Immediate,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum ConnectorOperator {
120    And,
121    Or,
122}
123
124impl ConnectorOperator {
125    fn keyword(self) -> &'static str {
126        match self {
127            Self::And => "AND",
128            Self::Or => "OR",
129        }
130    }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134enum TsqlDatePart {
135    Native(String),
136    Epoch,
137    IsoDayOfWeek,
138    Unsupported(String),
139}
140
141/// Identifier quote style (start/end characters)
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct IdentifierQuoteStyle {
144    /// Start character for quoting identifiers (e.g., '"', '`', '[')
145    pub start: char,
146    /// End character for quoting identifiers (e.g., '"', '`', ']')
147    pub end: char,
148}
149
150impl Default for IdentifierQuoteStyle {
151    fn default() -> Self {
152        Self {
153            start: '"',
154            end: '"',
155        }
156    }
157}
158
159impl IdentifierQuoteStyle {
160    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
161    pub const DOUBLE_QUOTE: Self = Self {
162        start: '"',
163        end: '"',
164    };
165    /// Backtick style (MySQL, BigQuery, Spark, Hive)
166    pub const BACKTICK: Self = Self {
167        start: '`',
168        end: '`',
169    };
170    /// Square bracket style (TSQL, SQLite)
171    pub const BRACKET: Self = Self {
172        start: '[',
173        end: ']',
174    };
175}
176
177/// Configuration for the SQL [`Generator`].
178///
179/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
180/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
181/// syntax, feature support flags, and more.
182///
183/// Most users should start from `GeneratorConfig::default()` and override only the
184/// fields they need. Dialect-specific presets are applied automatically when
185/// `dialect` is set via the higher-level transpilation API.
186///
187/// # Key fields
188///
189/// | Field | Default | Purpose |
190/// |-------|---------|---------|
191/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
192/// | `pretty` | `false` | Enable multi-line, indented output |
193/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
194/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
195/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
196/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
197/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
198#[derive(Debug, Clone)]
199pub struct GeneratorConfig {
200    // ===== Basic formatting =====
201    /// Pretty print with indentation
202    pub pretty: bool,
203    /// Indentation string (default 2 spaces)
204    pub indent: &'static str,
205    /// Maximum text width before wrapping (default 80)
206    pub max_text_width: usize,
207    /// Quote identifier style (deprecated, use identifier_quote_style instead)
208    pub identifier_quote: char,
209    /// Identifier quote style with separate start/end characters
210    pub identifier_quote_style: IdentifierQuoteStyle,
211    /// Uppercase keywords
212    pub uppercase_keywords: bool,
213    /// Normalize identifiers to lowercase when generating
214    pub normalize_identifiers: bool,
215    /// Dialect type for dialect-specific generation
216    pub dialect: Option<crate::dialects::DialectType>,
217    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
218    pub source_dialect: Option<crate::dialects::DialectType>,
219    /// How unsupported generation should be handled.
220    pub unsupported_level: UnsupportedLevel,
221    /// Maximum number of unsupported diagnostics to include in raised errors.
222    pub max_unsupported: usize,
223    /// Complexity guard limits for recursive generation paths.
224    pub complexity_guard: ComplexityGuardOptions,
225    /// How to output function names (UPPER, lower, or as-is)
226    pub normalize_functions: NormalizeFunctions,
227    /// String escape character
228    pub string_escape: char,
229    /// Whether identifiers are case-sensitive
230    pub case_sensitive_identifiers: bool,
231    /// Whether unquoted identifiers can start with a digit
232    pub identifiers_can_start_with_digit: bool,
233    /// Whether to always quote identifiers regardless of reserved keyword status
234    /// Used by dialects like Athena/Presto that prefer quoted identifiers
235    pub always_quote_identifiers: bool,
236    /// How to render negated IN predicates in generic output.
237    pub not_in_style: NotInStyle,
238
239    // ===== Null handling =====
240    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
241    /// True: Full Support, false: No support
242    pub null_ordering_supported: bool,
243    /// Whether ignore nulls is inside the agg or outside
244    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
245    pub ignore_nulls_in_func: bool,
246    /// Whether the NVL2 function is supported
247    pub nvl2_supported: bool,
248
249    // ===== Limit/Fetch =====
250    /// How to output LIMIT clauses
251    pub limit_fetch_style: LimitFetchStyle,
252    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
253    pub limit_is_top: bool,
254    /// Whether limit and fetch allows expressions or just literals
255    pub limit_only_literals: bool,
256
257    // ===== Interval =====
258    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
259    pub single_string_interval: bool,
260    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
261    pub interval_allows_plural_form: bool,
262
263    // ===== CTE =====
264    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
265    pub cte_recursive_keyword_required: bool,
266
267    // ===== VALUES =====
268    /// Whether VALUES can be used as a table source
269    pub values_as_table: bool,
270    /// Wrap derived values in parens (standard but Spark doesn't support)
271    pub wrap_derived_values: bool,
272
273    // ===== TABLESAMPLE =====
274    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
275    pub tablesample_seed_keyword: &'static str,
276    /// Whether parentheses are required around the table sample's expression
277    pub tablesample_requires_parens: bool,
278    /// Whether a table sample clause's size needs to be followed by ROWS keyword
279    pub tablesample_size_is_rows: bool,
280    /// The keyword(s) to use when generating a sample clause
281    pub tablesample_keywords: &'static str,
282    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
283    pub tablesample_with_method: bool,
284    /// Whether the table alias comes after tablesample (Oracle, Hive)
285    pub alias_post_tablesample: bool,
286
287    // ===== Aggregate =====
288    /// Whether aggregate FILTER (WHERE ...) is supported
289    pub aggregate_filter_supported: bool,
290    /// Whether DISTINCT can be followed by multiple args in an AggFunc
291    pub multi_arg_distinct: bool,
292    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
293    pub quantified_no_paren_space: bool,
294    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
295    pub supports_median: bool,
296
297    // ===== SELECT =====
298    /// Whether SELECT ... INTO is supported
299    pub supports_select_into: bool,
300    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
301    pub locking_reads_supported: bool,
302
303    // ===== Table/Join =====
304    /// Whether a table is allowed to be renamed with a db
305    pub rename_table_with_db: bool,
306    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
307    pub semi_anti_join_with_side: bool,
308    /// Whether named columns are allowed in table aliases
309    pub supports_table_alias_columns: bool,
310    /// Whether join hints should be generated
311    pub join_hints: bool,
312    /// Whether table hints should be generated
313    pub table_hints: bool,
314    /// Whether query hints should be generated
315    pub query_hints: bool,
316    /// What kind of separator to use for query hints
317    pub query_hint_sep: &'static str,
318    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
319    pub supports_column_join_marks: bool,
320
321    // ===== DDL =====
322    /// Whether CREATE INDEX USING method should have no space before column parens
323    /// true: `USING btree(col)`, false: `USING btree (col)`
324    pub index_using_no_space: bool,
325    /// Whether UNLOGGED tables can be created
326    pub supports_unlogged_tables: bool,
327    /// Whether CREATE TABLE LIKE statement is supported
328    pub supports_create_table_like: bool,
329    /// Whether the LikeProperty needs to be inside the schema clause
330    pub like_property_inside_schema: bool,
331    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
332    pub alter_table_include_column_keyword: bool,
333    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
334    pub supports_table_copy: bool,
335    /// The syntax to use when altering the type of a column
336    pub alter_set_type: &'static str,
337    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
338    pub alter_set_wrapped: bool,
339
340    // ===== Timestamp/Timezone =====
341    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
342    pub tz_to_with_time_zone: bool,
343    /// Whether CONVERT_TIMEZONE() is supported
344    pub supports_convert_timezone: bool,
345
346    // ===== JSON =====
347    /// Whether the JSON extraction operators expect a value of type JSON
348    pub json_type_required_for_extraction: bool,
349    /// Whether bracketed keys like ["foo"] are supported in JSON paths
350    pub json_path_bracketed_key_supported: bool,
351    /// Whether to escape keys using single quotes in JSON paths
352    pub json_path_single_quote_escape: bool,
353    /// Whether to quote the generated expression of JsonPath
354    pub quote_json_path: bool,
355    /// What delimiter to use for separating JSON key/value pairs
356    pub json_key_value_pair_sep: &'static str,
357
358    // ===== COPY =====
359    /// Whether parameters from COPY statement are wrapped in parentheses
360    pub copy_params_are_wrapped: bool,
361    /// Whether values of params are set with "=" token or empty space
362    pub copy_params_eq_required: bool,
363    /// Whether COPY statement has INTO keyword
364    pub copy_has_into_keyword: bool,
365
366    // ===== Window functions =====
367    /// Whether EXCLUDE in window specification is supported
368    pub supports_window_exclude: bool,
369    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
370    pub unnest_with_ordinality: bool,
371    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
372    /// Exasol uses lowercase for these specific keywords
373    pub lowercase_window_frame_keywords: bool,
374    /// Whether to normalize single-bound window frames to BETWEEN form
375    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
376    pub normalize_window_frame_between: bool,
377
378    // ===== Array =====
379    /// Whether ARRAY_CONCAT can be generated with varlen args
380    pub array_concat_is_var_len: bool,
381    /// Whether exp.ArraySize should generate the dimension arg too
382    /// None -> Doesn't support, false -> optional, true -> required
383    pub array_size_dim_required: Option<bool>,
384    /// Whether any(f(x) for x in array) can be implemented
385    pub can_implement_array_any: bool,
386    /// Function used for array size
387    pub array_size_name: &'static str,
388
389    // ===== BETWEEN =====
390    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
391    pub supports_between_flags: bool,
392
393    // ===== Boolean =====
394    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
395    pub is_bool_allowed: bool,
396    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
397    pub ensure_bools: bool,
398
399    // ===== EXTRACT =====
400    /// Whether to generate an unquoted value for EXTRACT's date part argument
401    pub extract_allows_quotes: bool,
402    /// Whether to normalize date parts in EXTRACT
403    pub normalize_extract_date_parts: bool,
404
405    // ===== Other features =====
406    /// Whether the conditional TRY(expression) function is supported
407    pub try_supported: bool,
408    /// Whether the UESCAPE syntax in unicode strings is supported
409    pub supports_uescape: bool,
410    /// Whether the function TO_NUMBER is supported
411    pub supports_to_number: bool,
412    /// Whether CONCAT requires >1 arguments
413    pub supports_single_arg_concat: bool,
414    /// Whether LAST_DAY function supports a date part argument
415    pub last_day_supports_date_part: bool,
416    /// Whether a projection can explode into multiple rows
417    pub supports_exploding_projections: bool,
418    /// Whether UNIX_SECONDS(timestamp) is supported
419    pub supports_unix_seconds: bool,
420    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
421    pub supports_like_quantifiers: bool,
422    /// Whether multi-argument DECODE(...) function is supported
423    pub supports_decode_case: bool,
424    /// Whether set op modifiers apply to the outer set op or select
425    pub set_op_modifiers: bool,
426    /// Whether FROM is supported in UPDATE statements
427    pub update_statement_supports_from: bool,
428
429    // ===== COLLATE =====
430    /// Whether COLLATE is a function instead of a binary operator
431    pub collate_is_func: bool,
432
433    // ===== INSERT =====
434    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
435    pub duplicate_key_update_with_set: bool,
436    /// INSERT OVERWRITE TABLE x override
437    pub insert_overwrite: &'static str,
438
439    // ===== RETURNING =====
440    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
441    pub returning_end: bool,
442
443    // ===== MERGE =====
444    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
445    pub matched_by_source: bool,
446
447    // ===== CREATE FUNCTION =====
448    /// Whether create function uses an AS before the RETURN
449    pub create_function_return_as: bool,
450    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
451    pub parameter_default_equals: bool,
452
453    // ===== COMPUTED COLUMN =====
454    /// Whether to include the type of a computed column in the CREATE DDL
455    pub computed_column_with_type: bool,
456
457    // ===== UNPIVOT =====
458    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
459    pub unpivot_aliases_are_identifiers: bool,
460
461    // ===== STAR =====
462    /// The keyword to use when generating a star projection with excluded columns
463    pub star_except: &'static str,
464
465    // ===== HEX =====
466    /// The HEX function name
467    pub hex_func: &'static str,
468
469    // ===== WITH =====
470    /// The keywords to use when prefixing WITH based properties
471    pub with_properties_prefix: &'static str,
472
473    // ===== PAD =====
474    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
475    pub pad_fill_pattern_is_required: bool,
476
477    // ===== INDEX =====
478    /// The string used for creating an index on a table
479    pub index_on: &'static str,
480
481    // ===== GROUPING =====
482    /// The separator for grouping sets and rollups
483    pub groupings_sep: &'static str,
484
485    // ===== STRUCT =====
486    /// Delimiters for STRUCT type
487    pub struct_delimiter: (&'static str, &'static str),
488    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
489    pub struct_curly_brace_notation: bool,
490    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
491    pub array_bracket_only: bool,
492    /// Separator between struct field name and type (": " for Hive, " " for others)
493    pub struct_field_sep: &'static str,
494
495    // ===== EXCEPT/INTERSECT =====
496    /// Whether EXCEPT and INTERSECT operations can return duplicates
497    pub except_intersect_support_all_clause: bool,
498
499    // ===== PARAMETERS/PLACEHOLDERS =====
500    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
501    pub parameter_token: &'static str,
502    /// Named placeholder token (: for most, % for PostgreSQL)
503    pub named_placeholder_token: &'static str,
504
505    // ===== DATA TYPES =====
506    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
507    pub data_type_specifiers_allowed: bool,
508
509    // ===== COMMENT =====
510    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
511    /// StarRocks and Doris use naked COMMENT syntax without `=`
512    pub schema_comment_with_eq: bool,
513}
514
515impl Default for GeneratorConfig {
516    fn default() -> Self {
517        Self {
518            // ===== Basic formatting =====
519            pretty: false,
520            indent: "  ",
521            max_text_width: 80,
522            identifier_quote: '"',
523            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
524            uppercase_keywords: true,
525            normalize_identifiers: false,
526            dialect: None,
527            source_dialect: None,
528            unsupported_level: UnsupportedLevel::Warn,
529            max_unsupported: 3,
530            complexity_guard: ComplexityGuardOptions::default(),
531            normalize_functions: NormalizeFunctions::Upper,
532            string_escape: '\'',
533            case_sensitive_identifiers: false,
534            identifiers_can_start_with_digit: false,
535            always_quote_identifiers: false,
536            not_in_style: NotInStyle::Prefix,
537
538            // ===== Null handling =====
539            null_ordering_supported: true,
540            ignore_nulls_in_func: false,
541            nvl2_supported: true,
542
543            // ===== Limit/Fetch =====
544            limit_fetch_style: LimitFetchStyle::Limit,
545            limit_is_top: false,
546            limit_only_literals: false,
547
548            // ===== Interval =====
549            single_string_interval: false,
550            interval_allows_plural_form: true,
551
552            // ===== CTE =====
553            cte_recursive_keyword_required: true,
554
555            // ===== VALUES =====
556            values_as_table: true,
557            wrap_derived_values: true,
558
559            // ===== TABLESAMPLE =====
560            tablesample_seed_keyword: "SEED",
561            tablesample_requires_parens: true,
562            tablesample_size_is_rows: true,
563            tablesample_keywords: "TABLESAMPLE",
564            tablesample_with_method: true,
565            alias_post_tablesample: false,
566
567            // ===== Aggregate =====
568            aggregate_filter_supported: true,
569            multi_arg_distinct: true,
570            quantified_no_paren_space: false,
571            supports_median: true,
572
573            // ===== SELECT =====
574            supports_select_into: false,
575            locking_reads_supported: true,
576
577            // ===== Table/Join =====
578            rename_table_with_db: true,
579            semi_anti_join_with_side: true,
580            supports_table_alias_columns: true,
581            join_hints: true,
582            table_hints: true,
583            query_hints: true,
584            query_hint_sep: ", ",
585            supports_column_join_marks: false,
586
587            // ===== DDL =====
588            index_using_no_space: false,
589            supports_unlogged_tables: false,
590            supports_create_table_like: true,
591            like_property_inside_schema: false,
592            alter_table_include_column_keyword: true,
593            supports_table_copy: true,
594            alter_set_type: "SET DATA TYPE",
595            alter_set_wrapped: false,
596
597            // ===== Timestamp/Timezone =====
598            tz_to_with_time_zone: false,
599            supports_convert_timezone: false,
600
601            // ===== JSON =====
602            json_type_required_for_extraction: false,
603            json_path_bracketed_key_supported: true,
604            json_path_single_quote_escape: false,
605            quote_json_path: true,
606            json_key_value_pair_sep: ":",
607
608            // ===== COPY =====
609            copy_params_are_wrapped: true,
610            copy_params_eq_required: false,
611            copy_has_into_keyword: true,
612
613            // ===== Window functions =====
614            supports_window_exclude: false,
615            unnest_with_ordinality: true,
616            lowercase_window_frame_keywords: false,
617            normalize_window_frame_between: false,
618
619            // ===== Array =====
620            array_concat_is_var_len: true,
621            array_size_dim_required: None,
622            can_implement_array_any: false,
623            array_size_name: "ARRAY_LENGTH",
624
625            // ===== BETWEEN =====
626            supports_between_flags: false,
627
628            // ===== Boolean =====
629            is_bool_allowed: true,
630            ensure_bools: false,
631
632            // ===== EXTRACT =====
633            extract_allows_quotes: true,
634            normalize_extract_date_parts: false,
635
636            // ===== Other features =====
637            try_supported: true,
638            supports_uescape: true,
639            supports_to_number: true,
640            supports_single_arg_concat: true,
641            last_day_supports_date_part: true,
642            supports_exploding_projections: true,
643            supports_unix_seconds: false,
644            supports_like_quantifiers: true,
645            supports_decode_case: true,
646            set_op_modifiers: true,
647            update_statement_supports_from: true,
648
649            // ===== COLLATE =====
650            collate_is_func: false,
651
652            // ===== INSERT =====
653            duplicate_key_update_with_set: true,
654            insert_overwrite: " OVERWRITE TABLE",
655
656            // ===== RETURNING =====
657            returning_end: true,
658
659            // ===== MERGE =====
660            matched_by_source: true,
661
662            // ===== CREATE FUNCTION =====
663            create_function_return_as: true,
664            parameter_default_equals: false,
665
666            // ===== COMPUTED COLUMN =====
667            computed_column_with_type: true,
668
669            // ===== UNPIVOT =====
670            unpivot_aliases_are_identifiers: true,
671
672            // ===== STAR =====
673            star_except: "EXCEPT",
674
675            // ===== HEX =====
676            hex_func: "HEX",
677
678            // ===== WITH =====
679            with_properties_prefix: "WITH",
680
681            // ===== PAD =====
682            pad_fill_pattern_is_required: false,
683
684            // ===== INDEX =====
685            index_on: "ON",
686
687            // ===== GROUPING =====
688            groupings_sep: ",",
689
690            // ===== STRUCT =====
691            struct_delimiter: ("<", ">"),
692            struct_curly_brace_notation: false,
693            array_bracket_only: false,
694            struct_field_sep: " ",
695
696            // ===== EXCEPT/INTERSECT =====
697            except_intersect_support_all_clause: true,
698
699            // ===== PARAMETERS/PLACEHOLDERS =====
700            parameter_token: "@",
701            named_placeholder_token: ":",
702
703            // ===== DATA TYPES =====
704            data_type_specifiers_allowed: false,
705
706            // ===== COMMENT =====
707            schema_comment_with_eq: true,
708        }
709    }
710}
711
712/// SQL reserved keywords that require quoting when used as identifiers
713/// Based on ANSI SQL standards and common dialect-specific reserved words
714mod reserved_keywords {
715    use std::collections::HashSet;
716    use std::sync::LazyLock;
717
718    /// Standard SQL reserved keywords (ANSI SQL:2016)
719    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
720        [
721            "all",
722            "alter",
723            "and",
724            "any",
725            "array",
726            "as",
727            "asc",
728            "at",
729            "authorization",
730            "begin",
731            "between",
732            "both",
733            "by",
734            "case",
735            "cast",
736            "check",
737            "collate",
738            "column",
739            "commit",
740            "constraint",
741            "create",
742            "cross",
743            "cube",
744            "current",
745            "current_date",
746            "current_time",
747            "current_timestamp",
748            "current_user",
749            "default",
750            "delete",
751            "desc",
752            "distinct",
753            "drop",
754            "else",
755            "end",
756            "escape",
757            "except",
758            "execute",
759            "exists",
760            "external",
761            "false",
762            "fetch",
763            "filter",
764            "for",
765            "foreign",
766            "from",
767            "full",
768            "function",
769            "grant",
770            "group",
771            "grouping",
772            "having",
773            "if",
774            "in",
775            "index",
776            "inner",
777            "insert",
778            "intersect",
779            "interval",
780            "into",
781            "is",
782            "join",
783            "key",
784            "leading",
785            "left",
786            "like",
787            "limit",
788            "local",
789            "localtime",
790            "localtimestamp",
791            "match",
792            "merge",
793            "natural",
794            "no",
795            "not",
796            "null",
797            "of",
798            "offset",
799            "on",
800            "only",
801            "or",
802            "order",
803            "outer",
804            "over",
805            "partition",
806            "primary",
807            "procedure",
808            "range",
809            "references",
810            "right",
811            "rollback",
812            "rollup",
813            "row",
814            "rows",
815            "select",
816            "session_user",
817            "set",
818            "some",
819            "table",
820            "tablesample",
821            "then",
822            "to",
823            "trailing",
824            "true",
825            "truncate",
826            "union",
827            "unique",
828            "unknown",
829            "update",
830            "user",
831            "using",
832            "values",
833            "view",
834            "when",
835            "where",
836            "window",
837            "with",
838        ]
839        .into_iter()
840        .collect()
841    });
842
843    /// BigQuery-specific reserved keywords
844    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
845    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
846        let mut set = SQL_RESERVED.clone();
847        set.extend([
848            "assert_rows_modified",
849            "at",
850            "contains",
851            "cube",
852            "current",
853            "define",
854            "enum",
855            "escape",
856            "exclude",
857            "following",
858            "for",
859            "groups",
860            "hash",
861            "ignore",
862            "lateral",
863            "lookup",
864            "new",
865            "no",
866            "nulls",
867            "of",
868            "over",
869            "preceding",
870            "proto",
871            "qualify",
872            "recursive",
873            "respect",
874            "struct",
875            "tablesample",
876            "treat",
877            "unbounded",
878            "unnest",
879            "window",
880            "within",
881        ]);
882        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
883        set.remove("grant");
884        set.remove("key");
885        set.remove("index");
886        set.remove("offset");
887        set.remove("values");
888        set.remove("table");
889        set
890    });
891
892    /// MySQL-specific reserved keywords
893    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
894        let mut set = SQL_RESERVED.clone();
895        set.extend([
896            "accessible",
897            "add",
898            "analyze",
899            "asensitive",
900            "before",
901            "bigint",
902            "binary",
903            "blob",
904            "call",
905            "cascade",
906            "change",
907            "char",
908            "character",
909            "condition",
910            "continue",
911            "convert",
912            "current_date",
913            "current_time",
914            "current_timestamp",
915            "current_user",
916            "cursor",
917            "database",
918            "databases",
919            "day_hour",
920            "day_microsecond",
921            "day_minute",
922            "day_second",
923            "dec",
924            "decimal",
925            "declare",
926            "delayed",
927            "describe",
928            "deterministic",
929            "distinctrow",
930            "div",
931            "double",
932            "dual",
933            "each",
934            "elseif",
935            "enclosed",
936            "escaped",
937            "exit",
938            "explain",
939            "float",
940            "float4",
941            "float8",
942            "force",
943            "get",
944            "high_priority",
945            "hour_microsecond",
946            "hour_minute",
947            "hour_second",
948            "ignore",
949            "infile",
950            "inout",
951            "insensitive",
952            "int",
953            "int1",
954            "int2",
955            "int3",
956            "int4",
957            "int8",
958            "integer",
959            "iterate",
960            "keys",
961            "kill",
962            "leave",
963            "linear",
964            "lines",
965            "load",
966            "lock",
967            "long",
968            "longblob",
969            "longtext",
970            "loop",
971            "low_priority",
972            "master_ssl_verify_server_cert",
973            "maxvalue",
974            "mediumblob",
975            "mediumint",
976            "mediumtext",
977            "middleint",
978            "minute_microsecond",
979            "minute_second",
980            "mod",
981            "modifies",
982            "no_write_to_binlog",
983            "numeric",
984            "optimize",
985            "option",
986            "optionally",
987            "out",
988            "outfile",
989            "precision",
990            "purge",
991            "read",
992            "reads",
993            "real",
994            "regexp",
995            "release",
996            "rename",
997            "repeat",
998            "replace",
999            "require",
1000            "resignal",
1001            "restrict",
1002            "return",
1003            "revoke",
1004            "rlike",
1005            "schema",
1006            "schemas",
1007            "second_microsecond",
1008            "sensitive",
1009            "separator",
1010            "show",
1011            "signal",
1012            "smallint",
1013            "spatial",
1014            "specific",
1015            "sql",
1016            "sql_big_result",
1017            "sql_calc_found_rows",
1018            "sql_small_result",
1019            "sqlexception",
1020            "sqlstate",
1021            "sqlwarning",
1022            "ssl",
1023            "starting",
1024            "straight_join",
1025            "terminated",
1026            "text",
1027            "tinyblob",
1028            "tinyint",
1029            "tinytext",
1030            "trigger",
1031            "undo",
1032            "unlock",
1033            "unsigned",
1034            "usage",
1035            "utc_date",
1036            "utc_time",
1037            "utc_timestamp",
1038            "varbinary",
1039            "varchar",
1040            "varcharacter",
1041            "varying",
1042            "while",
1043            "write",
1044            "xor",
1045            "year_month",
1046            "zerofill",
1047        ]);
1048        set.remove("table");
1049        set
1050    });
1051
1052    /// Doris-specific reserved keywords
1053    /// Extends MySQL reserved with additional Doris-specific words
1054    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1055        let mut set = MYSQL_RESERVED.clone();
1056        set.extend([
1057            "aggregate",
1058            "anti",
1059            "array",
1060            "backend",
1061            "backup",
1062            "begin",
1063            "bitmap",
1064            "boolean",
1065            "broker",
1066            "buckets",
1067            "cached",
1068            "cancel",
1069            "cast",
1070            "catalog",
1071            "charset",
1072            "cluster",
1073            "collation",
1074            "columns",
1075            "comment",
1076            "commit",
1077            "config",
1078            "connection",
1079            "count",
1080            "current",
1081            "data",
1082            "date",
1083            "datetime",
1084            "day",
1085            "deferred",
1086            "distributed",
1087            "dynamic",
1088            "enable",
1089            "end",
1090            "events",
1091            "export",
1092            "external",
1093            "fields",
1094            "first",
1095            "follower",
1096            "format",
1097            "free",
1098            "frontend",
1099            "full",
1100            "functions",
1101            "global",
1102            "grants",
1103            "hash",
1104            "help",
1105            "hour",
1106            "install",
1107            "intermediate",
1108            "json",
1109            "label",
1110            "last",
1111            "less",
1112            "level",
1113            "link",
1114            "local",
1115            "location",
1116            "max",
1117            "merge",
1118            "min",
1119            "minute",
1120            "modify",
1121            "month",
1122            "name",
1123            "names",
1124            "negative",
1125            "nulls",
1126            "observer",
1127            "offset",
1128            "only",
1129            "open",
1130            "overwrite",
1131            "password",
1132            "path",
1133            "plan",
1134            "plugin",
1135            "plugins",
1136            "policy",
1137            "process",
1138            "properties",
1139            "property",
1140            "query",
1141            "quota",
1142            "recover",
1143            "refresh",
1144            "repair",
1145            "replica",
1146            "repository",
1147            "resource",
1148            "restore",
1149            "resume",
1150            "role",
1151            "roles",
1152            "rollback",
1153            "rollup",
1154            "routine",
1155            "sample",
1156            "second",
1157            "semi",
1158            "session",
1159            "signed",
1160            "snapshot",
1161            "start",
1162            "stats",
1163            "status",
1164            "stop",
1165            "stream",
1166            "string",
1167            "sum",
1168            "tables",
1169            "tablet",
1170            "temporary",
1171            "text",
1172            "timestamp",
1173            "transaction",
1174            "trash",
1175            "trim",
1176            "truncate",
1177            "type",
1178            "user",
1179            "value",
1180            "variables",
1181            "verbose",
1182            "version",
1183            "view",
1184            "warnings",
1185            "week",
1186            "work",
1187            "year",
1188        ]);
1189        set
1190    });
1191
1192    /// PostgreSQL-specific reserved keywords
1193    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1194        let mut set = SQL_RESERVED.clone();
1195        set.extend([
1196            "analyse",
1197            "analyze",
1198            "asymmetric",
1199            "binary",
1200            "collation",
1201            "concurrently",
1202            "current_catalog",
1203            "current_role",
1204            "current_schema",
1205            "deferrable",
1206            "do",
1207            "freeze",
1208            "ilike",
1209            "initially",
1210            "isnull",
1211            "lateral",
1212            "notnull",
1213            "placing",
1214            "returning",
1215            "similar",
1216            "symmetric",
1217            "variadic",
1218            "verbose",
1219        ]);
1220        // PostgreSQL doesn't require quoting for these keywords
1221        set.remove("default");
1222        set.remove("interval");
1223        set.remove("match");
1224        set.remove("offset");
1225        set.remove("table");
1226        set
1227    });
1228
1229    /// Redshift-specific reserved keywords
1230    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1231    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1232    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1233        [
1234            "aes128",
1235            "aes256",
1236            "all",
1237            "allowoverwrite",
1238            "analyse",
1239            "analyze",
1240            "and",
1241            "any",
1242            "array",
1243            "as",
1244            "asc",
1245            "authorization",
1246            "az64",
1247            "backup",
1248            "between",
1249            "binary",
1250            "blanksasnull",
1251            "both",
1252            "bytedict",
1253            "bzip2",
1254            "case",
1255            "cast",
1256            "check",
1257            "collate",
1258            "column",
1259            "constraint",
1260            "create",
1261            "credentials",
1262            "cross",
1263            "current_date",
1264            "current_time",
1265            "current_timestamp",
1266            "current_user",
1267            "current_user_id",
1268            "default",
1269            "deferrable",
1270            "deflate",
1271            "defrag",
1272            "delta",
1273            "delta32k",
1274            "desc",
1275            "disable",
1276            "distinct",
1277            "do",
1278            "else",
1279            "emptyasnull",
1280            "enable",
1281            "encode",
1282            "encrypt",
1283            "encryption",
1284            "end",
1285            "except",
1286            "explicit",
1287            "false",
1288            "for",
1289            "foreign",
1290            "freeze",
1291            "from",
1292            "full",
1293            "globaldict256",
1294            "globaldict64k",
1295            "grant",
1296            "group",
1297            "gzip",
1298            "having",
1299            "identity",
1300            "ignore",
1301            "ilike",
1302            "in",
1303            "initially",
1304            "inner",
1305            "intersect",
1306            "interval",
1307            "into",
1308            "is",
1309            "isnull",
1310            "join",
1311            "leading",
1312            "left",
1313            "like",
1314            "limit",
1315            "localtime",
1316            "localtimestamp",
1317            "lun",
1318            "luns",
1319            "lzo",
1320            "lzop",
1321            "minus",
1322            "mostly16",
1323            "mostly32",
1324            "mostly8",
1325            "natural",
1326            "new",
1327            "not",
1328            "notnull",
1329            "null",
1330            "nulls",
1331            "off",
1332            "offline",
1333            "offset",
1334            "oid",
1335            "old",
1336            "on",
1337            "only",
1338            "open",
1339            "or",
1340            "order",
1341            "outer",
1342            "overlaps",
1343            "parallel",
1344            "partition",
1345            "percent",
1346            "permissions",
1347            "pivot",
1348            "placing",
1349            "primary",
1350            "raw",
1351            "readratio",
1352            "recover",
1353            "references",
1354            "rejectlog",
1355            "resort",
1356            "respect",
1357            "restore",
1358            "right",
1359            "select",
1360            "session_user",
1361            "similar",
1362            "snapshot",
1363            "some",
1364            "sysdate",
1365            "system",
1366            "table",
1367            "tag",
1368            "tdes",
1369            "text255",
1370            "text32k",
1371            "then",
1372            "timestamp",
1373            "to",
1374            "top",
1375            "trailing",
1376            "true",
1377            "truncatecolumns",
1378            "type",
1379            "union",
1380            "unique",
1381            "unnest",
1382            "unpivot",
1383            "user",
1384            "using",
1385            "verbose",
1386            "wallet",
1387            "when",
1388            "where",
1389            "with",
1390            "without",
1391        ]
1392        .into_iter()
1393        .collect()
1394    });
1395
1396    /// DuckDB-specific reserved keywords
1397    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1398        let mut set = POSTGRES_RESERVED.clone();
1399        set.extend([
1400            "anti",
1401            "asof",
1402            "columns",
1403            "describe",
1404            "groups",
1405            "macro",
1406            "pivot",
1407            "pivot_longer",
1408            "pivot_wider",
1409            "qualify",
1410            "replace",
1411            "respect",
1412            "semi",
1413            "show",
1414            "table",
1415            "unpivot",
1416        ]);
1417        set.remove("at");
1418        set.remove("key");
1419        set.remove("range");
1420        set.remove("row");
1421        set.remove("values");
1422        set
1423    });
1424
1425    /// Presto/Trino/Athena-specific reserved keywords
1426    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1427        let mut set = SQL_RESERVED.clone();
1428        set.extend([
1429            "alter",
1430            "and",
1431            "as",
1432            "between",
1433            "by",
1434            "case",
1435            "cast",
1436            "constraint",
1437            "create",
1438            "cross",
1439            "cube",
1440            "current_catalog",
1441            "current_date",
1442            "current_path",
1443            "current_role",
1444            "current_schema",
1445            "current_time",
1446            "current_timestamp",
1447            "current_user",
1448            "deallocate",
1449            "delete",
1450            "describe",
1451            "distinct",
1452            "drop",
1453            "else",
1454            "end",
1455            "escape",
1456            "except",
1457            "execute",
1458            "exists",
1459            "extract",
1460            "false",
1461            "for",
1462            "from",
1463            "full",
1464            "group",
1465            "grouping",
1466            "having",
1467            "in",
1468            "inner",
1469            "insert",
1470            "intersect",
1471            "into",
1472            "is",
1473            "join",
1474            "json_array",
1475            "json_exists",
1476            "json_object",
1477            "json_query",
1478            "json_table",
1479            "json_value",
1480            "left",
1481            "like",
1482            "listagg",
1483            "localtime",
1484            "localtimestamp",
1485            "natural",
1486            "normalize",
1487            "not",
1488            "null",
1489            "on",
1490            "or",
1491            "order",
1492            "outer",
1493            "prepare",
1494            "recursive",
1495            "right",
1496            "rollup",
1497            "select",
1498            "skip",
1499            "table",
1500            "then",
1501            "trim",
1502            "true",
1503            "uescape",
1504            "union",
1505            "unnest",
1506            "using",
1507            "values",
1508            "when",
1509            "where",
1510            "with",
1511        ]);
1512        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1513        set.remove("key");
1514        set
1515    });
1516
1517    /// StarRocks-specific reserved keywords
1518    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1519    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1520        [
1521            "add",
1522            "all",
1523            "alter",
1524            "analyze",
1525            "and",
1526            "array",
1527            "as",
1528            "asc",
1529            "between",
1530            "bigint",
1531            "bitmap",
1532            "both",
1533            "by",
1534            "case",
1535            "char",
1536            "character",
1537            "check",
1538            "collate",
1539            "column",
1540            "compaction",
1541            "convert",
1542            "create",
1543            "cross",
1544            "cube",
1545            "current_date",
1546            "current_role",
1547            "current_time",
1548            "current_timestamp",
1549            "current_user",
1550            "database",
1551            "databases",
1552            "decimal",
1553            "decimalv2",
1554            "decimal32",
1555            "decimal64",
1556            "decimal128",
1557            "default",
1558            "deferred",
1559            "delete",
1560            "dense_rank",
1561            "desc",
1562            "describe",
1563            "distinct",
1564            "double",
1565            "drop",
1566            "dual",
1567            "else",
1568            "except",
1569            "exists",
1570            "explain",
1571            "false",
1572            "first_value",
1573            "float",
1574            "for",
1575            "force",
1576            "from",
1577            "full",
1578            "function",
1579            "grant",
1580            "group",
1581            "grouping",
1582            "grouping_id",
1583            "groups",
1584            "having",
1585            "hll",
1586            "host",
1587            "if",
1588            "ignore",
1589            "immediate",
1590            "in",
1591            "index",
1592            "infile",
1593            "inner",
1594            "insert",
1595            "int",
1596            "integer",
1597            "intersect",
1598            "into",
1599            "is",
1600            "join",
1601            "json",
1602            "key",
1603            "keys",
1604            "kill",
1605            "lag",
1606            "largeint",
1607            "last_value",
1608            "lateral",
1609            "lead",
1610            "left",
1611            "like",
1612            "limit",
1613            "load",
1614            "localtime",
1615            "localtimestamp",
1616            "maxvalue",
1617            "minus",
1618            "mod",
1619            "not",
1620            "ntile",
1621            "null",
1622            "on",
1623            "or",
1624            "order",
1625            "outer",
1626            "outfile",
1627            "over",
1628            "partition",
1629            "percentile",
1630            "primary",
1631            "procedure",
1632            "qualify",
1633            "range",
1634            "rank",
1635            "read",
1636            "regexp",
1637            "release",
1638            "rename",
1639            "replace",
1640            "revoke",
1641            "right",
1642            "rlike",
1643            "row",
1644            "row_number",
1645            "rows",
1646            "schema",
1647            "schemas",
1648            "select",
1649            "set",
1650            "set_var",
1651            "show",
1652            "smallint",
1653            "system",
1654            "table",
1655            "terminated",
1656            "text",
1657            "then",
1658            "tinyint",
1659            "to",
1660            "true",
1661            "union",
1662            "unique",
1663            "unsigned",
1664            "update",
1665            "use",
1666            "using",
1667            "values",
1668            "varchar",
1669            "when",
1670            "where",
1671            "with",
1672        ]
1673        .into_iter()
1674        .collect()
1675    });
1676
1677    /// SingleStore-specific reserved keywords
1678    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1679    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1680        let mut set = MYSQL_RESERVED.clone();
1681        set.extend([
1682            // Additional SingleStore reserved keywords from Python sqlglot
1683            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1684            "abs",
1685            "account",
1686            "acos",
1687            "adddate",
1688            "addtime",
1689            "admin",
1690            "aes_decrypt",
1691            "aes_encrypt",
1692            "aggregate",
1693            "aggregates",
1694            "aggregator",
1695            "anti_join",
1696            "any_value",
1697            "approx_count_distinct",
1698            "approx_percentile",
1699            "arrange",
1700            "arrangement",
1701            "asin",
1702            "atan",
1703            "atan2",
1704            "attach",
1705            "autostats",
1706            "avro",
1707            "background",
1708            "backup",
1709            "batch",
1710            "batches",
1711            "boot_strapping",
1712            "ceil",
1713            "ceiling",
1714            "coercibility",
1715            "columnar",
1716            "columnstore",
1717            "compile",
1718            "concurrent",
1719            "connection_id",
1720            "cos",
1721            "cot",
1722            "current_security_groups",
1723            "current_security_roles",
1724            "dayname",
1725            "dayofmonth",
1726            "dayofweek",
1727            "dayofyear",
1728            "degrees",
1729            "dot_product",
1730            "dump",
1731            "durability",
1732            "earliest",
1733            "echo",
1734            "election",
1735            "euclidean_distance",
1736            "exp",
1737            "extractor",
1738            "extractors",
1739            "floor",
1740            "foreground",
1741            "found_rows",
1742            "from_base64",
1743            "from_days",
1744            "from_unixtime",
1745            "fs",
1746            "fulltext",
1747            "gc",
1748            "gcs",
1749            "geography",
1750            "geography_area",
1751            "geography_contains",
1752            "geography_distance",
1753            "geography_intersects",
1754            "geography_latitude",
1755            "geography_length",
1756            "geography_longitude",
1757            "geographypoint",
1758            "geography_point",
1759            "geography_within_distance",
1760            "geometry",
1761            "geometry_area",
1762            "geometry_contains",
1763            "geometry_distance",
1764            "geometry_filter",
1765            "geometry_intersects",
1766            "geometry_length",
1767            "geometrypoint",
1768            "geometry_point",
1769            "geometry_within_distance",
1770            "geometry_x",
1771            "geometry_y",
1772            "greatest",
1773            "groups",
1774            "group_concat",
1775            "gzip",
1776            "hdfs",
1777            "hex",
1778            "highlight",
1779            "ifnull",
1780            "ilike",
1781            "inet_aton",
1782            "inet_ntoa",
1783            "inet6_aton",
1784            "inet6_ntoa",
1785            "initcap",
1786            "instr",
1787            "interpreter_mode",
1788            "isnull",
1789            "json",
1790            "json_agg",
1791            "json_array_contains_double",
1792            "json_array_contains_json",
1793            "json_array_contains_string",
1794            "json_delete_key",
1795            "json_extract_double",
1796            "json_extract_json",
1797            "json_extract_string",
1798            "json_extract_bigint",
1799            "json_get_type",
1800            "json_length",
1801            "json_set_double",
1802            "json_set_json",
1803            "json_set_string",
1804            "kafka",
1805            "lag",
1806            "last_day",
1807            "last_insert_id",
1808            "latest",
1809            "lcase",
1810            "lead",
1811            "leaf",
1812            "least",
1813            "leaves",
1814            "length",
1815            "license",
1816            "links",
1817            "llvm",
1818            "ln",
1819            "load",
1820            "locate",
1821            "log",
1822            "log10",
1823            "log2",
1824            "lpad",
1825            "lz4",
1826            "management",
1827            "match",
1828            "mbc",
1829            "md5",
1830            "median",
1831            "memsql",
1832            "memsql_deserialize",
1833            "memsql_serialize",
1834            "metadata",
1835            "microsecond",
1836            "minute",
1837            "model",
1838            "monthname",
1839            "months_between",
1840            "mpl",
1841            "namespace",
1842            "node",
1843            "noparam",
1844            "now",
1845            "nth_value",
1846            "ntile",
1847            "nullcols",
1848            "nullif",
1849            "object",
1850            "octet_length",
1851            "offsets",
1852            "online",
1853            "optimizer",
1854            "orphan",
1855            "parquet",
1856            "partitions",
1857            "pause",
1858            "percentile_cont",
1859            "percentile_disc",
1860            "periodic",
1861            "persisted",
1862            "pi",
1863            "pipeline",
1864            "pipelines",
1865            "plancache",
1866            "plugins",
1867            "pool",
1868            "pools",
1869            "pow",
1870            "power",
1871            "process",
1872            "processlist",
1873            "profile",
1874            "profiles",
1875            "quarter",
1876            "queries",
1877            "query",
1878            "radians",
1879            "rand",
1880            "record",
1881            "reduce",
1882            "redundancy",
1883            "regexp_match",
1884            "regexp_substr",
1885            "remote",
1886            "replication",
1887            "resource",
1888            "resource_pool",
1889            "restore",
1890            "retry",
1891            "role",
1892            "roles",
1893            "round",
1894            "rpad",
1895            "rtrim",
1896            "running",
1897            "s3",
1898            "scalar",
1899            "sec_to_time",
1900            "second",
1901            "security_lists_intersect",
1902            "semi_join",
1903            "sha",
1904            "sha1",
1905            "sha2",
1906            "shard",
1907            "sharded",
1908            "sharded_id",
1909            "sigmoid",
1910            "sign",
1911            "sin",
1912            "skip",
1913            "sleep",
1914            "snapshot",
1915            "soname",
1916            "sparse",
1917            "spatial_check_index",
1918            "split",
1919            "sqrt",
1920            "standalone",
1921            "std",
1922            "stddev",
1923            "stddev_pop",
1924            "stddev_samp",
1925            "stop",
1926            "str_to_date",
1927            "subdate",
1928            "substr",
1929            "substring_index",
1930            "success",
1931            "synchronize",
1932            "table_checksum",
1933            "tan",
1934            "task",
1935            "timediff",
1936            "time_bucket",
1937            "time_format",
1938            "time_to_sec",
1939            "timestampadd",
1940            "timestampdiff",
1941            "to_base64",
1942            "to_char",
1943            "to_date",
1944            "to_days",
1945            "to_json",
1946            "to_number",
1947            "to_seconds",
1948            "to_timestamp",
1949            "tracelogs",
1950            "transform",
1951            "trim",
1952            "trunc",
1953            "truncate",
1954            "ucase",
1955            "unhex",
1956            "unix_timestamp",
1957            "utc_date",
1958            "utc_time",
1959            "utc_timestamp",
1960            "vacuum",
1961            "variance",
1962            "var_pop",
1963            "var_samp",
1964            "vector_sub",
1965            "voting",
1966            "week",
1967            "weekday",
1968            "weekofyear",
1969            "workload",
1970            "year",
1971        ]);
1972        // Remove "all" because ORDER BY ALL needs ALL unquoted
1973        set.remove("all");
1974        set
1975    });
1976
1977    /// SQLite-specific reserved keywords
1978    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1979    /// Reference: https://www.sqlite.org/lang_keywords.html
1980    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1981        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1982        [
1983            "abort",
1984            "action",
1985            "add",
1986            "after",
1987            "all",
1988            "alter",
1989            "always",
1990            "analyze",
1991            "and",
1992            "as",
1993            "asc",
1994            "attach",
1995            "autoincrement",
1996            "before",
1997            "begin",
1998            "between",
1999            "by",
2000            "cascade",
2001            "case",
2002            "cast",
2003            "check",
2004            "collate",
2005            "column",
2006            "commit",
2007            "conflict",
2008            "constraint",
2009            "create",
2010            "cross",
2011            "current",
2012            "current_date",
2013            "current_time",
2014            "current_timestamp",
2015            "database",
2016            "default",
2017            "deferrable",
2018            "deferred",
2019            "delete",
2020            "desc",
2021            "detach",
2022            "distinct",
2023            "do",
2024            "drop",
2025            "each",
2026            "else",
2027            "end",
2028            "escape",
2029            "except",
2030            "exclude",
2031            "exclusive",
2032            "exists",
2033            "explain",
2034            "fail",
2035            "filter",
2036            "first",
2037            "following",
2038            "for",
2039            "foreign",
2040            "from",
2041            "full",
2042            "generated",
2043            "glob",
2044            "group",
2045            "groups",
2046            "having",
2047            "if",
2048            "ignore",
2049            "immediate",
2050            "in",
2051            "index",
2052            "indexed",
2053            "initially",
2054            "inner",
2055            "insert",
2056            "instead",
2057            "intersect",
2058            "into",
2059            "is",
2060            "isnull",
2061            "join",
2062            "key",
2063            "last",
2064            "left",
2065            "like",
2066            "limit",
2067            "natural",
2068            "no",
2069            "not",
2070            "nothing",
2071            "notnull",
2072            "null",
2073            "nulls",
2074            "of",
2075            "offset",
2076            "on",
2077            "or",
2078            "order",
2079            "others",
2080            "outer",
2081            "partition",
2082            "plan",
2083            "pragma",
2084            "preceding",
2085            "primary",
2086            "query",
2087            "raise",
2088            "range",
2089            "recursive",
2090            "references",
2091            "regexp",
2092            "reindex",
2093            "release",
2094            "rename",
2095            "replace",
2096            "restrict",
2097            "returning",
2098            "right",
2099            "rollback",
2100            "row",
2101            "rows",
2102            "savepoint",
2103            "select",
2104            "set",
2105            "table",
2106            "temp",
2107            "temporary",
2108            "then",
2109            "ties",
2110            "to",
2111            "transaction",
2112            "trigger",
2113            "unbounded",
2114            "union",
2115            "unique",
2116            "update",
2117            "using",
2118            "vacuum",
2119            "values",
2120            "view",
2121            "virtual",
2122            "when",
2123            "where",
2124            "window",
2125            "with",
2126            "without",
2127        ]
2128        .into_iter()
2129        .collect()
2130    });
2131}
2132
2133impl Generator {
2134    /// Create a new generator with the default configuration.
2135    ///
2136    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2137    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2138    /// and no dialect-specific transformations.
2139    pub fn new() -> Self {
2140        Self::with_config(GeneratorConfig::default())
2141    }
2142
2143    /// Create a generator with a custom [`GeneratorConfig`].
2144    ///
2145    /// Use this when you need dialect-specific output, pretty-printing, or other
2146    /// non-default settings.
2147    pub fn with_config(config: GeneratorConfig) -> Self {
2148        Self::with_arc_config(Arc::new(config))
2149    }
2150
2151    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2152    ///
2153    /// This avoids cloning the configuration when multiple generators share the
2154    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2155    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2156        Self {
2157            config,
2158            output: String::new(),
2159            unsupported_messages: Vec::new(),
2160            indent_level: 0,
2161            athena_hive_context: false,
2162            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2163            merge_strip_qualifiers: Vec::new(),
2164            clickhouse_nullable_depth: 0,
2165        }
2166    }
2167
2168    /// Add column aliases to a query expression for TSQL SELECT INTO.
2169    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2170    /// Recursively processes all SELECT expressions in the query tree.
2171    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2172        match expr {
2173            Expression::Select(mut select) => {
2174                // Add aliases to all select expressions that don't already have them
2175                select.expressions = select
2176                    .expressions
2177                    .into_iter()
2178                    .map(|e| Self::add_alias_to_expression(e))
2179                    .collect();
2180
2181                // Recursively process subqueries in FROM clause
2182                if let Some(ref mut from) = select.from {
2183                    from.expressions = from
2184                        .expressions
2185                        .iter()
2186                        .cloned()
2187                        .map(|e| Self::add_column_aliases_to_query(e))
2188                        .collect();
2189                }
2190
2191                Expression::Select(select)
2192            }
2193            Expression::Subquery(mut sq) => {
2194                sq.this = Self::add_column_aliases_to_query(sq.this);
2195                Expression::Subquery(sq)
2196            }
2197            Expression::Paren(mut p) => {
2198                p.this = Self::add_column_aliases_to_query(p.this);
2199                Expression::Paren(p)
2200            }
2201            // For other expressions (Union, Intersect, etc.), pass through
2202            other => other,
2203        }
2204    }
2205
2206    /// Add an alias to a single select expression if it doesn't already have one.
2207    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2208    fn add_alias_to_expression(expr: Expression) -> Expression {
2209        use crate::expressions::Alias;
2210
2211        match &expr {
2212            // Already aliased - just return it
2213            Expression::Alias(_) => expr,
2214
2215            // Column reference: add alias from column name
2216            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2217                this: expr.clone(),
2218                alias: col.name.clone(),
2219                column_aliases: Vec::new(),
2220                alias_explicit_as: false,
2221                alias_keyword: None,
2222                pre_alias_comments: Vec::new(),
2223                trailing_comments: Vec::new(),
2224                inferred_type: None,
2225            })),
2226
2227            // Identifier: add alias from identifier name
2228            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2229                this: expr.clone(),
2230                alias: ident.clone(),
2231                column_aliases: Vec::new(),
2232                alias_explicit_as: false,
2233                alias_keyword: None,
2234                pre_alias_comments: Vec::new(),
2235                trailing_comments: Vec::new(),
2236                inferred_type: None,
2237            })),
2238
2239            // Subquery: recursively process and add alias if inner returns a named column
2240            Expression::Subquery(sq) => {
2241                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2242                // Subqueries that are already aliased keep their alias
2243                if sq.alias.is_some() {
2244                    processed
2245                } else {
2246                    // If there's no alias, keep it as-is (let TSQL handle it)
2247                    processed
2248                }
2249            }
2250
2251            // Star expressions (*) - don't alias
2252            Expression::Star(_) => expr,
2253
2254            // For other expressions, don't add an alias
2255            // (function calls, literals, etc. would need explicit aliases anyway)
2256            _ => expr,
2257        }
2258    }
2259
2260    /// Try to evaluate a constant arithmetic expression to a number literal.
2261    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2262    /// otherwise returns the original expression.
2263    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2264        match expr {
2265            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2266                let Literal::Number(n) = lit.as_ref() else {
2267                    unreachable!()
2268                };
2269                n.parse::<i64>().ok()
2270            }
2271            Expression::Add(op) => {
2272                let left = Self::try_evaluate_constant(&op.left)?;
2273                let right = Self::try_evaluate_constant(&op.right)?;
2274                Some(left + right)
2275            }
2276            Expression::Sub(op) => {
2277                let left = Self::try_evaluate_constant(&op.left)?;
2278                let right = Self::try_evaluate_constant(&op.right)?;
2279                Some(left - right)
2280            }
2281            Expression::Mul(op) => {
2282                let left = Self::try_evaluate_constant(&op.left)?;
2283                let right = Self::try_evaluate_constant(&op.right)?;
2284                Some(left * right)
2285            }
2286            Expression::Div(op) => {
2287                let left = Self::try_evaluate_constant(&op.left)?;
2288                let right = Self::try_evaluate_constant(&op.right)?;
2289                if right != 0 {
2290                    Some(left / right)
2291                } else {
2292                    None
2293                }
2294            }
2295            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2296            _ => None,
2297        }
2298    }
2299
2300    /// Check if an identifier is a reserved keyword for the current dialect
2301    fn is_reserved_keyword(&self, name: &str) -> bool {
2302        use crate::dialects::DialectType;
2303        let mut buf = [0u8; 128];
2304        let lower_ref: &str = if name.len() <= 128 {
2305            for (i, b) in name.bytes().enumerate() {
2306                buf[i] = b.to_ascii_lowercase();
2307            }
2308            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2309            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2310        } else {
2311            return false;
2312        };
2313
2314        match self.config.dialect {
2315            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2316            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2317                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2318            }
2319            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2320            Some(DialectType::SingleStore) => {
2321                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2322            }
2323            Some(DialectType::StarRocks) => {
2324                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2325            }
2326            Some(DialectType::PostgreSQL)
2327            | Some(DialectType::CockroachDB)
2328            | Some(DialectType::Materialize)
2329            | Some(DialectType::RisingWave) => {
2330                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2331            }
2332            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2333            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2334            // meaning it never quotes identifiers based on reserved word status.
2335            Some(DialectType::Snowflake) => false,
2336            // ClickHouse: don't quote reserved keywords to preserve identity output
2337            Some(DialectType::ClickHouse) => false,
2338            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2339            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2340            Some(DialectType::Teradata) => false,
2341            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2342            Some(DialectType::TSQL)
2343            | Some(DialectType::Fabric)
2344            | Some(DialectType::Oracle)
2345            | Some(DialectType::Spark)
2346            | Some(DialectType::Databricks)
2347            | Some(DialectType::Hive)
2348            | Some(DialectType::Solr) => false,
2349            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2350                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2351            }
2352            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2353            // For Generic dialect or None, don't add extra quoting to preserve identity
2354            Some(DialectType::Generic) | None => false,
2355            // For other dialects, use standard SQL reserved keywords
2356            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2357        }
2358    }
2359
2360    /// Normalize function name based on dialect settings
2361    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2362        match self.config.normalize_functions {
2363            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2364            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2365            NormalizeFunctions::None => Cow::Borrowed(name),
2366        }
2367    }
2368
2369    /// Generate a SQL string from an AST expression.
2370    ///
2371    /// This is the primary generation method. It clears any previous internal state,
2372    /// walks the expression tree, and returns the resulting SQL text. The output
2373    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2374    ///
2375    /// The generator can be reused across multiple calls; each call to `generate`
2376    /// resets the internal buffer.
2377    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2378        self.output.clear();
2379        self.unsupported_messages.clear();
2380        enforce_generate_ast(expr, &self.config.complexity_guard)?;
2381        self.generate_expression(expr)?;
2382        if self.config.unsupported_level == UnsupportedLevel::Raise
2383            && !self.unsupported_messages.is_empty()
2384        {
2385            return Err(crate::error::Error::generate(
2386                self.format_unsupported_messages(),
2387            ));
2388        }
2389        Ok(std::mem::take(&mut self.output))
2390    }
2391
2392    /// Returns the unsupported diagnostics collected during the most recent generate call.
2393    pub fn unsupported_messages(&self) -> &[String] {
2394        &self.unsupported_messages
2395    }
2396
2397    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2398        let message = message.into();
2399        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2400            return Err(crate::error::Error::generate(message));
2401        }
2402        self.unsupported_messages.push(message);
2403        Ok(())
2404    }
2405
2406    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2407        self.unsupported(message.to_string())?;
2408        self.write("/* ");
2409        self.write(message);
2410        self.write(" */");
2411        Ok(())
2412    }
2413
2414    fn format_unsupported_messages(&self) -> String {
2415        let limit = self.config.max_unsupported.max(1);
2416        if self.unsupported_messages.len() <= limit {
2417            return self.unsupported_messages.join("; ");
2418        }
2419
2420        let mut messages = self
2421            .unsupported_messages
2422            .iter()
2423            .take(limit)
2424            .cloned()
2425            .collect::<Vec<_>>();
2426        messages.push(format!(
2427            "... and {} more",
2428            self.unsupported_messages.len() - limit
2429        ));
2430        messages.join("; ")
2431    }
2432
2433    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2434    ///
2435    /// This is a static helper that creates a throwaway `Generator` internally.
2436    /// For repeated generation, prefer constructing a `Generator` once and calling
2437    /// [`generate`](Self::generate) on it.
2438    pub fn sql(expr: &Expression) -> Result<String> {
2439        let mut gen = Generator::new();
2440        gen.generate(expr)
2441    }
2442
2443    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2444    ///
2445    /// Produces human-readable output with newlines and indentation. A trailing
2446    /// semicolon is appended automatically if not already present.
2447    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2448        let config = GeneratorConfig {
2449            pretty: true,
2450            ..Default::default()
2451        };
2452        let mut gen = Generator::with_config(config);
2453        let mut sql = gen.generate(expr)?;
2454        // Add semicolon for pretty output
2455        if !sql.ends_with(';') {
2456            sql.push(';');
2457        }
2458        Ok(sql)
2459    }
2460
2461    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2462        #[cfg(feature = "stacker")]
2463        {
2464            let red_zone = if cfg!(debug_assertions) {
2465                4 * 1024 * 1024
2466            } else {
2467                1024 * 1024
2468            };
2469            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2470                self.generate_expression_inner(expr)
2471            })
2472        }
2473        #[cfg(not(feature = "stacker"))]
2474        {
2475            self.generate_expression_inner(expr)
2476        }
2477    }
2478
2479    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2480        match expr {
2481            Expression::Select(select) => self.generate_select(select),
2482            Expression::Union(union) => self.generate_union(union),
2483            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2484            Expression::Except(except) => self.generate_except(except),
2485            Expression::Insert(insert) => self.generate_insert(insert),
2486            Expression::Update(update) => self.generate_update(update),
2487            Expression::Delete(delete) => self.generate_delete(delete),
2488            Expression::Literal(lit) => self.generate_literal(lit),
2489            Expression::Boolean(b) => self.generate_boolean(b),
2490            Expression::Null(_) => {
2491                self.write_keyword("NULL");
2492                Ok(())
2493            }
2494            Expression::Identifier(id) => self.generate_identifier(id),
2495            Expression::Column(col) => self.generate_column(col),
2496            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2497            Expression::Connect(c) => self.generate_connect_expr(c),
2498            Expression::Prior(p) => self.generate_prior(p),
2499            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2500            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2501            Expression::Table(table) => self.generate_table(table),
2502            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2503            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2504            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2505            Expression::Star(star) => self.generate_star(star),
2506            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2507            Expression::Alias(alias) => self.generate_alias(alias),
2508            Expression::Cast(cast) => self.generate_cast(cast),
2509            Expression::Collation(coll) => self.generate_collation(coll),
2510            Expression::Case(case) => self.generate_case(case),
2511            Expression::Function(func) => self.generate_function(func),
2512            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2513            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2514            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2515            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2516            Expression::Interval(interval) => self.generate_interval(interval),
2517
2518            // String functions
2519            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2520            Expression::Substring(f) => self.generate_substring(f),
2521            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2522            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2523            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2524            Expression::Trim(f) => self.generate_trim(f),
2525            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2526            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2527            Expression::Replace(f) => self.generate_replace(f),
2528            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2529            Expression::Left(f) => self.generate_left_right("LEFT", f),
2530            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2531            Expression::Repeat(f) => self.generate_repeat(f),
2532            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2533            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2534            Expression::Split(f) => self.generate_split(f),
2535            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2536            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2537            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2538            Expression::Overlay(f) => self.generate_overlay(f),
2539
2540            // Math functions
2541            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2542            Expression::Round(f) => self.generate_round(f),
2543            Expression::Floor(f) => self.generate_floor(f),
2544            Expression::Ceil(f) => self.generate_ceil(f),
2545            Expression::Power(f) => self.generate_power(f),
2546            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2547            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2548            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2549            Expression::Log(f) => self.generate_log(f),
2550            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2551            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2552            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2553            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2554
2555            // Date/time functions
2556            Expression::CurrentDate(_) => {
2557                self.write_keyword("CURRENT_DATE");
2558                Ok(())
2559            }
2560            Expression::CurrentTime(f) => self.generate_current_time(f),
2561            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2562            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2563            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2564            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2565            Expression::DateDiff(f) => self.generate_datediff(f),
2566            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2567            Expression::Extract(f) => self.generate_extract(f),
2568            Expression::ToDate(f) => self.generate_to_date(f),
2569            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2570
2571            // Control flow functions
2572            Expression::Coalesce(f) => {
2573                // Use original function name if preserved (COALESCE, IFNULL)
2574                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2575                self.generate_vararg_func(func_name, &f.expressions)
2576            }
2577            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2578            Expression::IfFunc(f) => self.generate_if_func(f),
2579            Expression::IfNull(f) => self.generate_ifnull(f),
2580            Expression::Nvl(f) => self.generate_nvl(f),
2581            Expression::Nvl2(f) => self.generate_nvl2(f),
2582
2583            // Type conversion
2584            Expression::TryCast(cast) => self.generate_try_cast(cast),
2585            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2586
2587            // Typed aggregate functions
2588            Expression::Count(f) => self.generate_count(f),
2589            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2590            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2591            Expression::Min(f) => self.generate_agg_func("MIN", f),
2592            Expression::Max(f) => self.generate_agg_func("MAX", f),
2593            Expression::GroupConcat(f) => self.generate_group_concat(f),
2594            Expression::StringAgg(f) => self.generate_string_agg(f),
2595            Expression::ListAgg(f) => self.generate_listagg(f),
2596            Expression::ArrayAgg(f) => {
2597                // Allow cross-dialect transforms to override the function name
2598                // (e.g., COLLECT_LIST for Spark)
2599                let override_name = f
2600                    .name
2601                    .as_ref()
2602                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2603                    .map(|n| n.to_ascii_uppercase());
2604                match override_name {
2605                    Some(name) => self.generate_agg_func(&name, f),
2606                    None => self.generate_agg_func("ARRAY_AGG", f),
2607                }
2608            }
2609            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2610            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2611            Expression::SumIf(f) => self.generate_sum_if(f),
2612            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2613            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2614            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2615            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2616            Expression::VarPop(f) => {
2617                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2618                    "VARIANCE_POP"
2619                } else {
2620                    "VAR_POP"
2621                };
2622                self.generate_agg_func(name, f)
2623            }
2624            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2625            Expression::Skewness(f) => {
2626                let name = match self.config.dialect {
2627                    Some(DialectType::Snowflake) => "SKEW",
2628                    _ => "SKEWNESS",
2629                };
2630                self.generate_agg_func(name, f)
2631            }
2632            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2633            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2634            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2635            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2636            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2637            Expression::ApproxDistinct(f) => {
2638                match self.config.dialect {
2639                    Some(DialectType::Hive)
2640                    | Some(DialectType::Spark)
2641                    | Some(DialectType::Databricks)
2642                    | Some(DialectType::BigQuery) => {
2643                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2644                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2645                    }
2646                    Some(DialectType::Redshift) => {
2647                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2648                        self.write_keyword("APPROXIMATE COUNT");
2649                        self.write("(");
2650                        self.write_keyword("DISTINCT");
2651                        self.write(" ");
2652                        self.generate_expression(&f.this)?;
2653                        self.write(")");
2654                        Ok(())
2655                    }
2656                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2657                }
2658            }
2659            Expression::ApproxCountDistinct(f) => {
2660                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2661            }
2662            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2663            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2664            Expression::LogicalAnd(f) => {
2665                let name = match self.config.dialect {
2666                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2667                    Some(DialectType::Spark)
2668                    | Some(DialectType::Databricks)
2669                    | Some(DialectType::PostgreSQL)
2670                    | Some(DialectType::DuckDB)
2671                    | Some(DialectType::Redshift) => "BOOL_AND",
2672                    Some(DialectType::Oracle)
2673                    | Some(DialectType::SQLite)
2674                    | Some(DialectType::MySQL) => "MIN",
2675                    _ => "BOOL_AND",
2676                };
2677                self.generate_agg_func(name, f)
2678            }
2679            Expression::LogicalOr(f) => {
2680                let name = match self.config.dialect {
2681                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2682                    Some(DialectType::Spark)
2683                    | Some(DialectType::Databricks)
2684                    | Some(DialectType::PostgreSQL)
2685                    | Some(DialectType::DuckDB)
2686                    | Some(DialectType::Redshift) => "BOOL_OR",
2687                    Some(DialectType::Oracle)
2688                    | Some(DialectType::SQLite)
2689                    | Some(DialectType::MySQL) => "MAX",
2690                    _ => "BOOL_OR",
2691                };
2692                self.generate_agg_func(name, f)
2693            }
2694
2695            // Typed window functions
2696            Expression::RowNumber(_) => {
2697                if self.config.dialect == Some(DialectType::ClickHouse) {
2698                    self.write("row_number");
2699                } else {
2700                    self.write_keyword("ROW_NUMBER");
2701                }
2702                self.write("()");
2703                Ok(())
2704            }
2705            Expression::Rank(r) => {
2706                self.write_keyword("RANK");
2707                self.write("(");
2708                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2709                if !r.args.is_empty() {
2710                    for (i, arg) in r.args.iter().enumerate() {
2711                        if i > 0 {
2712                            self.write(", ");
2713                        }
2714                        self.generate_expression(arg)?;
2715                    }
2716                } else if let Some(order_by) = &r.order_by {
2717                    // DuckDB: RANK(ORDER BY col)
2718                    self.write_keyword(" ORDER BY ");
2719                    for (i, ob) in order_by.iter().enumerate() {
2720                        if i > 0 {
2721                            self.write(", ");
2722                        }
2723                        self.generate_ordered(ob)?;
2724                    }
2725                }
2726                self.write(")");
2727                Ok(())
2728            }
2729            Expression::DenseRank(dr) => {
2730                self.write_keyword("DENSE_RANK");
2731                self.write("(");
2732                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2733                for (i, arg) in dr.args.iter().enumerate() {
2734                    if i > 0 {
2735                        self.write(", ");
2736                    }
2737                    self.generate_expression(arg)?;
2738                }
2739                self.write(")");
2740                Ok(())
2741            }
2742            Expression::NTile(f) => self.generate_ntile(f),
2743            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2744            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2745            Expression::FirstValue(f) => {
2746                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2747            }
2748            Expression::LastValue(f) => {
2749                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2750            }
2751            Expression::NthValue(f) => self.generate_nth_value(f),
2752            Expression::PercentRank(pr) => {
2753                self.write_keyword("PERCENT_RANK");
2754                self.write("(");
2755                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2756                if !pr.args.is_empty() {
2757                    for (i, arg) in pr.args.iter().enumerate() {
2758                        if i > 0 {
2759                            self.write(", ");
2760                        }
2761                        self.generate_expression(arg)?;
2762                    }
2763                } else if let Some(order_by) = &pr.order_by {
2764                    // DuckDB: PERCENT_RANK(ORDER BY col)
2765                    self.write_keyword(" ORDER BY ");
2766                    for (i, ob) in order_by.iter().enumerate() {
2767                        if i > 0 {
2768                            self.write(", ");
2769                        }
2770                        self.generate_ordered(ob)?;
2771                    }
2772                }
2773                self.write(")");
2774                Ok(())
2775            }
2776            Expression::CumeDist(cd) => {
2777                self.write_keyword("CUME_DIST");
2778                self.write("(");
2779                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2780                if !cd.args.is_empty() {
2781                    for (i, arg) in cd.args.iter().enumerate() {
2782                        if i > 0 {
2783                            self.write(", ");
2784                        }
2785                        self.generate_expression(arg)?;
2786                    }
2787                } else if let Some(order_by) = &cd.order_by {
2788                    // DuckDB: CUME_DIST(ORDER BY col)
2789                    self.write_keyword(" ORDER BY ");
2790                    for (i, ob) in order_by.iter().enumerate() {
2791                        if i > 0 {
2792                            self.write(", ");
2793                        }
2794                        self.generate_ordered(ob)?;
2795                    }
2796                }
2797                self.write(")");
2798                Ok(())
2799            }
2800            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2801            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2802
2803            // Additional string functions
2804            Expression::Contains(f) => {
2805                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2806            }
2807            Expression::StartsWith(f) => {
2808                let name = match self.config.dialect {
2809                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2810                    _ => "STARTS_WITH",
2811                };
2812                self.generate_binary_func(name, &f.this, &f.expression)
2813            }
2814            Expression::EndsWith(f) => {
2815                let name = match self.config.dialect {
2816                    Some(DialectType::Snowflake) => "ENDSWITH",
2817                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2818                    Some(DialectType::ClickHouse) => "endsWith",
2819                    _ => "ENDS_WITH",
2820                };
2821                self.generate_binary_func(name, &f.this, &f.expression)
2822            }
2823            Expression::Position(f) => self.generate_position(f),
2824            Expression::Initcap(f) => match self.config.dialect {
2825                Some(DialectType::Presto)
2826                | Some(DialectType::Trino)
2827                | Some(DialectType::Athena) => {
2828                    self.write_keyword("REGEXP_REPLACE");
2829                    self.write("(");
2830                    self.generate_expression(&f.this)?;
2831                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2832                    Ok(())
2833                }
2834                _ => self.generate_simple_func("INITCAP", &f.this),
2835            },
2836            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2837            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2838            Expression::CharFunc(f) => self.generate_char_func(f),
2839            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2840            Expression::Levenshtein(f) => {
2841                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2842            }
2843
2844            // Additional math functions
2845            Expression::ModFunc(f) => self.generate_mod_func(f),
2846            Expression::Random(_) => {
2847                self.write_keyword("RANDOM");
2848                self.write("()");
2849                Ok(())
2850            }
2851            Expression::Rand(f) => self.generate_rand(f),
2852            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2853            Expression::Pi(_) => {
2854                self.write_keyword("PI");
2855                self.write("()");
2856                Ok(())
2857            }
2858            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2859            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2860            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2861            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2862            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2863            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2864            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2865            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2866            Expression::Atan2(f) => {
2867                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2868                self.generate_binary_func(name, &f.this, &f.expression)
2869            }
2870
2871            // Control flow
2872            Expression::Decode(f) => self.generate_decode(f),
2873
2874            // Additional date/time functions
2875            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2876            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2877            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2878            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2879            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2880            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2881            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2882            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2883            Expression::DayOfWeek(f) => {
2884                let name = match self.config.dialect {
2885                    Some(DialectType::Presto)
2886                    | Some(DialectType::Trino)
2887                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2888                    Some(DialectType::DuckDB) => "ISODOW",
2889                    _ => "DAYOFWEEK",
2890                };
2891                self.generate_simple_func(name, &f.this)
2892            }
2893            Expression::DayOfMonth(f) => {
2894                let name = match self.config.dialect {
2895                    Some(DialectType::Presto)
2896                    | Some(DialectType::Trino)
2897                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2898                    _ => "DAYOFMONTH",
2899                };
2900                self.generate_simple_func(name, &f.this)
2901            }
2902            Expression::DayOfYear(f) => {
2903                let name = match self.config.dialect {
2904                    Some(DialectType::Presto)
2905                    | Some(DialectType::Trino)
2906                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2907                    _ => "DAYOFYEAR",
2908                };
2909                self.generate_simple_func(name, &f.this)
2910            }
2911            Expression::WeekOfYear(f) => {
2912                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2913                let name = match self.config.dialect {
2914                    Some(DialectType::Hive)
2915                    | Some(DialectType::DuckDB)
2916                    | Some(DialectType::Spark)
2917                    | Some(DialectType::Databricks)
2918                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2919                    _ => "WEEK_OF_YEAR",
2920                };
2921                self.generate_simple_func(name, &f.this)
2922            }
2923            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2924            Expression::AddMonths(f) => {
2925                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2926            }
2927            Expression::MonthsBetween(f) => {
2928                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2929            }
2930            Expression::LastDay(f) => self.generate_last_day(f),
2931            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2932            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2933            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2934            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2935            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2936            Expression::MakeDate(f) => self.generate_make_date(f),
2937            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2938            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2939
2940            // Array functions
2941            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2942            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2943            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2944            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2945            Expression::ArrayContains(f) => {
2946                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2947            }
2948            Expression::ArrayPosition(f) => {
2949                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2950            }
2951            Expression::ArrayAppend(f) => {
2952                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2953            }
2954            Expression::ArrayPrepend(f) => {
2955                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2956            }
2957            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2958            Expression::ArraySort(f) => self.generate_array_sort(f),
2959            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2960            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2961            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2962            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2963            Expression::Unnest(f) => self.generate_unnest(f),
2964            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2965            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2966            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2967            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2968            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2969            Expression::ArrayCompact(f) => {
2970                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2971                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2972                    self.write("LIST_FILTER(");
2973                    self.generate_expression(&f.this)?;
2974                    self.write(", _u -> NOT _u IS NULL)");
2975                    Ok(())
2976                } else {
2977                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2978                }
2979            }
2980            Expression::ArrayIntersect(f) => {
2981                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2982                self.generate_vararg_func(func_name, &f.expressions)
2983            }
2984            Expression::ArrayUnion(f) => {
2985                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2986            }
2987            Expression::ArrayExcept(f) => {
2988                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2989            }
2990            Expression::ArrayRemove(f) => {
2991                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2992            }
2993            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2994            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2995            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2996
2997            // Struct functions
2998            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2999            Expression::StructExtract(f) => self.generate_struct_extract(f),
3000            Expression::NamedStruct(f) => self.generate_named_struct(f),
3001
3002            // Map functions
3003            Expression::MapFunc(f) => self.generate_map_constructor(f),
3004            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
3005            Expression::MapFromArrays(f) => {
3006                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
3007            }
3008            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
3009            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
3010            Expression::MapContainsKey(f) => {
3011                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
3012            }
3013            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
3014            Expression::ElementAt(f) => {
3015                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
3016            }
3017            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
3018            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3019
3020            // JSON functions
3021            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3022            Expression::JsonExtractScalar(f) => {
3023                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3024            }
3025            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3026            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3027            Expression::JsonObject(f) => self.generate_json_object(f),
3028            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3029            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3030            Expression::JsonArrayLength(f) => {
3031                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3032            }
3033            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3034            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3035            Expression::ParseJson(f) => {
3036                let name = match self.config.dialect {
3037                    Some(DialectType::Presto)
3038                    | Some(DialectType::Trino)
3039                    | Some(DialectType::Athena) => "JSON_PARSE",
3040                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3041                        // PostgreSQL: CAST(x AS JSON)
3042                        self.write_keyword("CAST");
3043                        self.write("(");
3044                        self.generate_expression(&f.this)?;
3045                        self.write_keyword(" AS ");
3046                        self.write_keyword("JSON");
3047                        self.write(")");
3048                        return Ok(());
3049                    }
3050                    Some(DialectType::Hive)
3051                    | Some(DialectType::Spark)
3052                    | Some(DialectType::MySQL)
3053                    | Some(DialectType::SingleStore)
3054                    | Some(DialectType::TiDB)
3055                    | Some(DialectType::TSQL) => {
3056                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3057                        self.generate_expression(&f.this)?;
3058                        return Ok(());
3059                    }
3060                    Some(DialectType::DuckDB) => "JSON",
3061                    _ => "PARSE_JSON",
3062                };
3063                self.generate_simple_func(name, &f.this)
3064            }
3065            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3066            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3067            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3068            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3069            Expression::JsonMergePatch(f) => {
3070                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3071            }
3072            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3073            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3074
3075            // Type casting/conversion
3076            Expression::Convert(f) => self.generate_convert(f),
3077            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3078
3079            // Additional expressions
3080            Expression::Lambda(f) => self.generate_lambda(f),
3081            Expression::Parameter(f) => self.generate_parameter(f),
3082            Expression::Placeholder(f) => self.generate_placeholder(f),
3083            Expression::NamedArgument(f) => self.generate_named_argument(f),
3084            Expression::TableArgument(f) => self.generate_table_argument(f),
3085            Expression::SqlComment(f) => self.generate_sql_comment(f),
3086
3087            // Additional predicates
3088            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3089            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3090            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3091            Expression::SimilarTo(f) => self.generate_similar_to(f),
3092            Expression::Any(f) => self.generate_quantified("ANY", f),
3093            Expression::All(f) => self.generate_quantified("ALL", f),
3094            Expression::Overlaps(f) => self.generate_overlaps(f),
3095
3096            // Bitwise operations
3097            Expression::BitwiseLeftShift(op) => {
3098                if matches!(
3099                    self.config.dialect,
3100                    Some(DialectType::Presto) | Some(DialectType::Trino)
3101                ) {
3102                    self.write_keyword("BITWISE_LEFT_SHIFT");
3103                    self.write("(");
3104                    self.generate_expression(&op.left)?;
3105                    self.write(", ");
3106                    self.generate_expression(&op.right)?;
3107                    self.write(")");
3108                    Ok(())
3109                } else if matches!(
3110                    self.config.dialect,
3111                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3112                ) {
3113                    self.write_keyword("SHIFTLEFT");
3114                    self.write("(");
3115                    self.generate_expression(&op.left)?;
3116                    self.write(", ");
3117                    self.generate_expression(&op.right)?;
3118                    self.write(")");
3119                    Ok(())
3120                } else {
3121                    self.generate_binary_op(op, "<<")
3122                }
3123            }
3124            Expression::BitwiseRightShift(op) => {
3125                if matches!(
3126                    self.config.dialect,
3127                    Some(DialectType::Presto) | Some(DialectType::Trino)
3128                ) {
3129                    self.write_keyword("BITWISE_RIGHT_SHIFT");
3130                    self.write("(");
3131                    self.generate_expression(&op.left)?;
3132                    self.write(", ");
3133                    self.generate_expression(&op.right)?;
3134                    self.write(")");
3135                    Ok(())
3136                } else if matches!(
3137                    self.config.dialect,
3138                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3139                ) {
3140                    self.write_keyword("SHIFTRIGHT");
3141                    self.write("(");
3142                    self.generate_expression(&op.left)?;
3143                    self.write(", ");
3144                    self.generate_expression(&op.right)?;
3145                    self.write(")");
3146                    Ok(())
3147                } else {
3148                    self.generate_binary_op(op, ">>")
3149                }
3150            }
3151            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3152            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3153            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3154
3155            // Array/struct/map access
3156            Expression::Subscript(s) => self.generate_subscript(s),
3157            Expression::Dot(d) => self.generate_dot_access(d),
3158            Expression::MethodCall(m) => self.generate_method_call(m),
3159            Expression::ArraySlice(s) => self.generate_array_slice(s),
3160
3161            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3162            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3163            Expression::Add(op) => self.generate_binary_op(op, "+"),
3164            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3165            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3166            Expression::Div(op) => self.generate_binary_op(op, "/"),
3167            Expression::IntDiv(f) => {
3168                use crate::dialects::DialectType;
3169                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3170                    // DuckDB uses // operator for integer division
3171                    self.generate_expression(&f.this)?;
3172                    self.write(" // ");
3173                    self.generate_expression(&f.expression)?;
3174                    Ok(())
3175                } else if matches!(
3176                    self.config.dialect,
3177                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3178                ) {
3179                    // Hive/Spark use DIV as an infix operator
3180                    self.generate_expression(&f.this)?;
3181                    self.write(" ");
3182                    self.write_keyword("DIV");
3183                    self.write(" ");
3184                    self.generate_expression(&f.expression)?;
3185                    Ok(())
3186                } else {
3187                    // Other dialects use DIV function
3188                    self.write_keyword("DIV");
3189                    self.write("(");
3190                    self.generate_expression(&f.this)?;
3191                    self.write(", ");
3192                    self.generate_expression(&f.expression)?;
3193                    self.write(")");
3194                    Ok(())
3195                }
3196            }
3197            Expression::Mod(op) => {
3198                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3199                    self.generate_binary_op(op, "MOD")
3200                } else {
3201                    self.generate_binary_op(op, "%")
3202                }
3203            }
3204            Expression::Eq(op) => self.generate_binary_op(op, "="),
3205            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3206            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3207            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3208            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3209            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3210            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3211            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3212            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3213            Expression::Concat(op) => {
3214                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3215                if self.config.dialect == Some(DialectType::Solr) {
3216                    self.generate_binary_op(op, "OR")
3217                } else if self.config.dialect == Some(DialectType::MySQL) {
3218                    self.generate_mysql_concat_from_concat(op)
3219                } else {
3220                    self.generate_binary_op(op, "||")
3221                }
3222            }
3223            Expression::BitwiseAnd(op) => {
3224                // Presto/Trino use BITWISE_AND function
3225                if matches!(
3226                    self.config.dialect,
3227                    Some(DialectType::Presto) | Some(DialectType::Trino)
3228                ) {
3229                    self.write_keyword("BITWISE_AND");
3230                    self.write("(");
3231                    self.generate_expression(&op.left)?;
3232                    self.write(", ");
3233                    self.generate_expression(&op.right)?;
3234                    self.write(")");
3235                    Ok(())
3236                } else {
3237                    self.generate_binary_op(op, "&")
3238                }
3239            }
3240            Expression::BitwiseOr(op) => {
3241                // Presto/Trino use BITWISE_OR function
3242                if matches!(
3243                    self.config.dialect,
3244                    Some(DialectType::Presto) | Some(DialectType::Trino)
3245                ) {
3246                    self.write_keyword("BITWISE_OR");
3247                    self.write("(");
3248                    self.generate_expression(&op.left)?;
3249                    self.write(", ");
3250                    self.generate_expression(&op.right)?;
3251                    self.write(")");
3252                    Ok(())
3253                } else {
3254                    self.generate_binary_op(op, "|")
3255                }
3256            }
3257            Expression::BitwiseXor(op) => {
3258                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3259                if matches!(
3260                    self.config.dialect,
3261                    Some(DialectType::Presto) | Some(DialectType::Trino)
3262                ) {
3263                    self.write_keyword("BITWISE_XOR");
3264                    self.write("(");
3265                    self.generate_expression(&op.left)?;
3266                    self.write(", ");
3267                    self.generate_expression(&op.right)?;
3268                    self.write(")");
3269                    Ok(())
3270                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3271                    self.generate_binary_op(op, "#")
3272                } else {
3273                    self.generate_binary_op(op, "^")
3274                }
3275            }
3276            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3277            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3278            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3279            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3280            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3281            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3282            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3283            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3284            Expression::JSONBContains(f) => {
3285                // PostgreSQL JSONB contains key/path operators: a ? b, a @? b
3286                self.generate_expression(&f.this)?;
3287                self.write_space();
3288                self.write(f.original_name.as_deref().unwrap_or("?"));
3289                self.write_space();
3290                self.generate_expression(&f.expression)
3291            }
3292            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3293            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3294            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3295            Expression::Not(op) => match &op.this {
3296                Expression::Like(like) => self.generate_like_op_negated(like, "LIKE"),
3297                Expression::ILike(like) => self.generate_like_op_negated(like, "ILIKE"),
3298                _ => self.generate_unary_op(op, "NOT"),
3299            },
3300            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3301            Expression::BitwiseNot(op) => {
3302                // Presto/Trino use BITWISE_NOT function
3303                if matches!(
3304                    self.config.dialect,
3305                    Some(DialectType::Presto) | Some(DialectType::Trino)
3306                ) {
3307                    self.write_keyword("BITWISE_NOT");
3308                    self.write("(");
3309                    self.generate_expression(&op.this)?;
3310                    self.write(")");
3311                    Ok(())
3312                } else {
3313                    self.generate_unary_op(op, "~")
3314                }
3315            }
3316            Expression::In(in_expr) => self.generate_in(in_expr),
3317            Expression::Between(between) => self.generate_between(between),
3318            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3319            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3320            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3321            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3322            Expression::Is(is_expr) => self.generate_is(is_expr),
3323            Expression::Exists(exists) => self.generate_exists(exists),
3324            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3325            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3326            Expression::Paren(paren) => {
3327                // JoinedTable already outputs its own parentheses, so don't double-wrap
3328                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3329
3330                if !skip_parens {
3331                    self.write("(");
3332                    if self.config.pretty {
3333                        self.write_newline();
3334                        self.indent_level += 1;
3335                        self.write_indent();
3336                    }
3337                }
3338                self.generate_expression(&paren.this)?;
3339                if !skip_parens {
3340                    if self.config.pretty {
3341                        self.write_newline();
3342                        self.indent_level -= 1;
3343                        self.write_indent();
3344                    }
3345                    self.write(")");
3346                }
3347                // Output trailing comments after closing paren
3348                for comment in &paren.trailing_comments {
3349                    self.write(" ");
3350                    self.write_formatted_comment(comment);
3351                }
3352                Ok(())
3353            }
3354            Expression::Array(arr) => self.generate_array(arr),
3355            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3356            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3357            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3358            Expression::DataType(dt) => self.generate_data_type(dt),
3359            Expression::Raw(raw) => {
3360                self.write(&raw.sql);
3361                Ok(())
3362            }
3363            Expression::CreateTask(task) => self.generate_create_task(task),
3364            Expression::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3365            Expression::Command(cmd) => {
3366                self.write(&cmd.this);
3367                if matches!(self.config.dialect, Some(DialectType::ClickHouse))
3368                    && cmd
3369                        .this
3370                        .trim_start()
3371                        .get(..7)
3372                        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("EXPLAIN"))
3373                {
3374                    for _ in 0..Self::missing_closing_parens_outside_quotes(&cmd.this) {
3375                        self.write(")");
3376                    }
3377                }
3378                Ok(())
3379            }
3380            Expression::Kill(kill) => {
3381                self.write_keyword("KILL");
3382                if let Some(kind) = &kill.kind {
3383                    self.write_space();
3384                    self.write_keyword(kind);
3385                }
3386                self.write_space();
3387                self.generate_expression(&kill.this)?;
3388                Ok(())
3389            }
3390            Expression::Prepare(prepare) => self.generate_prepare(prepare),
3391            Expression::Execute(exec) => {
3392                self.write_keyword("EXECUTE");
3393                self.write_space();
3394                self.generate_expression(&exec.this)?;
3395                if exec.prepared {
3396                    if !exec.arguments.is_empty() {
3397                        self.write("(");
3398                        for (i, argument) in exec.arguments.iter().enumerate() {
3399                            if i > 0 {
3400                                self.write(", ");
3401                            }
3402                            self.generate_expression(argument)?;
3403                        }
3404                        self.write(")");
3405                    }
3406                    return Ok(());
3407                }
3408                for (i, param) in exec.parameters.iter().enumerate() {
3409                    if i == 0 {
3410                        self.write_space();
3411                    } else {
3412                        self.write(", ");
3413                    }
3414                    self.write(&param.name);
3415                    // Only write = value for named parameters (not positional)
3416                    if !param.positional {
3417                        self.write(" = ");
3418                        self.generate_expression(&param.value)?;
3419                    }
3420                    if param.output {
3421                        self.write_space();
3422                        self.write_keyword("OUTPUT");
3423                    }
3424                }
3425                if let Some(ref suffix) = exec.suffix {
3426                    self.write_space();
3427                    self.write(suffix);
3428                }
3429                Ok(())
3430            }
3431            Expression::Annotated(annotated) => {
3432                self.generate_expression(&annotated.this)?;
3433                for comment in &annotated.trailing_comments {
3434                    self.write(" ");
3435                    self.write_formatted_comment(comment);
3436                }
3437                Ok(())
3438            }
3439
3440            // DDL statements
3441            Expression::CreateTable(ct) => self.generate_create_table(ct),
3442            Expression::DropTable(dt) => self.generate_drop_table(dt),
3443            Expression::Undrop(u) => self.generate_undrop(u),
3444            Expression::AlterTable(at) => self.generate_alter_table(at),
3445            Expression::SplitTable(st) => self.generate_split_table(st),
3446            Expression::FlashbackTable(ft) => self.generate_flashback_table(ft),
3447            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3448            Expression::DropIndex(di) => self.generate_drop_index(di),
3449            Expression::CreateView(cv) => self.generate_create_view(cv),
3450            Expression::DropView(dv) => self.generate_drop_view(dv),
3451            Expression::AlterView(av) => self.generate_alter_view(av),
3452            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3453            Expression::Truncate(tr) => self.generate_truncate(tr),
3454            Expression::Use(u) => self.generate_use(u),
3455            // Phase 4: Additional DDL statements
3456            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3457            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3458            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3459            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3460            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3461            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3462            Expression::DropFunction(df) => self.generate_drop_function(df),
3463            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3464            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3465            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3466            Expression::CreateSynonym(cs) => {
3467                self.write_keyword("CREATE SYNONYM");
3468                self.write_space();
3469                self.generate_table(&cs.name)?;
3470                self.write_space();
3471                self.write_keyword("FOR");
3472                self.write_space();
3473                self.generate_table(&cs.target)?;
3474                Ok(())
3475            }
3476            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3477            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3478            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3479            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3480            Expression::CreateType(ct) => self.generate_create_type(ct),
3481            Expression::DropType(dt) => self.generate_drop_type(dt),
3482            Expression::Describe(d) => self.generate_describe(d),
3483            Expression::Show(s) => self.generate_show(s),
3484
3485            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3486            Expression::Cache(c) => self.generate_cache(c),
3487            Expression::Uncache(u) => self.generate_uncache(u),
3488            Expression::LoadData(l) => self.generate_load_data(l),
3489            Expression::Pragma(p) => self.generate_pragma(p),
3490            Expression::Grant(g) => self.generate_grant(g),
3491            Expression::Revoke(r) => self.generate_revoke(r),
3492            Expression::Comment(c) => self.generate_comment(c),
3493            Expression::SetStatement(s) => self.generate_set_statement(s),
3494
3495            // PIVOT/UNPIVOT
3496            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3497            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3498
3499            // VALUES table constructor
3500            Expression::Values(values) => self.generate_values(values),
3501
3502            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3503            Expression::AIAgg(e) => self.generate_ai_agg(e),
3504            Expression::AIClassify(e) => self.generate_ai_classify(e),
3505            Expression::AddPartition(e) => self.generate_add_partition(e),
3506            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3507            Expression::Aliases(e) => self.generate_aliases(e),
3508            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3509            Expression::AlterColumn(e) => self.generate_alter_column(e),
3510            Expression::AlterSession(e) => self.generate_alter_session(e),
3511            Expression::AlterSet(e) => self.generate_alter_set(e),
3512            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3513            Expression::Analyze(e) => self.generate_analyze(e),
3514            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3515            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3516            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3517            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3518            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3519            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3520            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3521            Expression::Anonymous(e) => self.generate_anonymous(e),
3522            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3523            Expression::Apply(e) => self.generate_apply(e),
3524            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3525            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3526            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3527            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3528            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3529            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3530            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3531            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3532            Expression::ArgMax(e) => self.generate_arg_max(e),
3533            Expression::ArgMin(e) => self.generate_arg_min(e),
3534            Expression::ArrayAll(e) => self.generate_array_all(e),
3535            Expression::ArrayAny(e) => self.generate_array_any(e),
3536            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3537            Expression::ArraySum(e) => self.generate_array_sum(e),
3538            Expression::AtIndex(e) => self.generate_at_index(e),
3539            Expression::Attach(e) => self.generate_attach(e),
3540            Expression::AttachOption(e) => self.generate_attach_option(e),
3541            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3542            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3543            Expression::BackupProperty(e) => self.generate_backup_property(e),
3544            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3545            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3546            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3547            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3548            Expression::Booland(e) => self.generate_booland(e),
3549            Expression::Boolor(e) => self.generate_boolor(e),
3550            Expression::BuildProperty(e) => self.generate_build_property(e),
3551            Expression::ByteString(e) => self.generate_byte_string(e),
3552            Expression::CaseSpecificColumnConstraint(e) => {
3553                self.generate_case_specific_column_constraint(e)
3554            }
3555            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3556            Expression::Changes(e) => self.generate_changes(e),
3557            Expression::CharacterSetColumnConstraint(e) => {
3558                self.generate_character_set_column_constraint(e)
3559            }
3560            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3561            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3562            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3563            Expression::CheckJson(e) => self.generate_check_json(e),
3564            Expression::CheckXml(e) => self.generate_check_xml(e),
3565            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3566            Expression::Clone(e) => self.generate_clone(e),
3567            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3568            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3569            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3570            Expression::CollateProperty(e) => self.generate_collate_property(e),
3571            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3572            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3573            Expression::ColumnPosition(e) => self.generate_column_position(e),
3574            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3575            Expression::Columns(e) => self.generate_columns(e),
3576            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3577            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3578            Expression::Commit(e) => self.generate_commit(e),
3579            Expression::Comprehension(e) => self.generate_comprehension(e),
3580            Expression::Compress(e) => self.generate_compress(e),
3581            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3582            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3583            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3584            Expression::Constraint(e) => self.generate_constraint(e),
3585            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3586            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3587            Expression::Copy(e) => self.generate_copy(e),
3588            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3589            Expression::Corr(e) => self.generate_corr(e),
3590            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3591            Expression::CovarPop(e) => self.generate_covar_pop(e),
3592            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3593            Expression::Credentials(e) => self.generate_credentials(e),
3594            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3595            Expression::Cte(e) => self.generate_cte(e),
3596            Expression::Cube(e) => self.generate_cube(e),
3597            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3598            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3599            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3600            Expression::CurrentUser(e) => self.generate_current_user(e),
3601            Expression::DPipe(e) => self.generate_d_pipe(e),
3602            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3603            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3604            Expression::Date(e) => self.generate_date_func(e),
3605            Expression::DateBin(e) => self.generate_date_bin(e),
3606            Expression::DateFormatColumnConstraint(e) => {
3607                self.generate_date_format_column_constraint(e)
3608            }
3609            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3610            Expression::Datetime(e) => self.generate_datetime(e),
3611            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3612            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3613            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3614            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3615            Expression::Dayname(e) => self.generate_dayname(e),
3616            Expression::Declare(e) => self.generate_declare(e),
3617            Expression::DeclareItem(e) => self.generate_declare_item(e),
3618            Expression::DecodeCase(e) => self.generate_decode_case(e),
3619            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3620            Expression::DecompressString(e) => self.generate_decompress_string(e),
3621            Expression::Decrypt(e) => self.generate_decrypt(e),
3622            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3623            Expression::DefaultColumnConstraint(e) => {
3624                self.write_keyword("DEFAULT");
3625                self.write_space();
3626                self.generate_expression(&e.this)?;
3627                if let Some(ref col) = e.for_column {
3628                    self.write_space();
3629                    self.write_keyword("FOR");
3630                    self.write_space();
3631                    self.generate_identifier(col)?;
3632                }
3633                Ok(())
3634            }
3635            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3636            Expression::Detach(e) => self.generate_detach(e),
3637            Expression::DictProperty(e) => self.generate_dict_property(e),
3638            Expression::DictRange(e) => self.generate_dict_range(e),
3639            Expression::Directory(e) => self.generate_directory(e),
3640            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3641            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3642            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3643            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3644            Expression::DotProduct(e) => self.generate_dot_product(e),
3645            Expression::DropPartition(e) => self.generate_drop_partition(e),
3646            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3647            Expression::Elt(e) => self.generate_elt(e),
3648            Expression::Encode(e) => self.generate_encode(e),
3649            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3650            Expression::Encrypt(e) => self.generate_encrypt(e),
3651            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3652            Expression::EngineProperty(e) => self.generate_engine_property(e),
3653            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3654            Expression::EphemeralColumnConstraint(e) => {
3655                self.generate_ephemeral_column_constraint(e)
3656            }
3657            Expression::EqualNull(e) => self.generate_equal_null(e),
3658            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3659            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3660            Expression::Export(e) => self.generate_export(e),
3661            Expression::ExternalProperty(e) => self.generate_external_property(e),
3662            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3663            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3664            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3665            Expression::Fetch(e) => self.generate_fetch(e),
3666            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3667            Expression::Filter(e) => self.generate_filter(e),
3668            Expression::Float64(e) => self.generate_float64(e),
3669            Expression::ForIn(e) => self.generate_for_in(e),
3670            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3671            Expression::Format(e) => self.generate_format(e),
3672            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3673            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3674            Expression::From(e) => self.generate_from(e),
3675            Expression::FromBase(e) => self.generate_from_base(e),
3676            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3677            Expression::GapFill(e) => self.generate_gap_fill(e),
3678            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3679            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3680            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3681            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3682            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3683                self.generate_generated_as_identity_column_constraint(e)
3684            }
3685            Expression::GeneratedAsRowColumnConstraint(e) => {
3686                self.generate_generated_as_row_column_constraint(e)
3687            }
3688            Expression::Get(e) => self.generate_get(e),
3689            Expression::GetExtract(e) => self.generate_get_extract(e),
3690            Expression::Getbit(e) => self.generate_getbit(e),
3691            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3692            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3693            Expression::Group(e) => self.generate_group(e),
3694            Expression::GroupBy(e) => self.generate_group_by(e),
3695            Expression::Grouping(e) => self.generate_grouping(e),
3696            Expression::GroupingId(e) => self.generate_grouping_id(e),
3697            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3698            Expression::HashAgg(e) => self.generate_hash_agg(e),
3699            Expression::Having(e) => self.generate_having(e),
3700            Expression::HavingMax(e) => self.generate_having_max(e),
3701            Expression::Heredoc(e) => self.generate_heredoc(e),
3702            Expression::HexEncode(e) => self.generate_hex_encode(e),
3703            Expression::Hll(e) => self.generate_hll(e),
3704            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3705            Expression::IncludeProperty(e) => self.generate_include_property(e),
3706            Expression::Index(e) => self.generate_index(e),
3707            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3708            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3709            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3710            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3711            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3712            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3713            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3714            Expression::Install(e) => self.generate_install(e),
3715            Expression::IntervalOp(e) => self.generate_interval_op(e),
3716            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3717            Expression::IntoClause(e) => self.generate_into_clause(e),
3718            Expression::Introducer(e) => self.generate_introducer(e),
3719            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3720            Expression::JSON(e) => self.generate_json(e),
3721            Expression::JSONArray(e) => self.generate_json_array(e),
3722            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3723            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3724            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3725            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3726            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3727            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3728            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3729            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3730            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3731            Expression::JSONExists(e) => self.generate_json_exists(e),
3732            Expression::JSONCast(e) => self.generate_json_cast(e),
3733            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3734            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3735            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3736            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3737            Expression::JSONFormat(e) => self.generate_json_format(e),
3738            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3739            Expression::JSONKeys(e) => self.generate_json_keys(e),
3740            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3741            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3742            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3743            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3744            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3745            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3746            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3747            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3748            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3749            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3750            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3751            Expression::JSONRemove(e) => self.generate_json_remove(e),
3752            Expression::JSONSchema(e) => self.generate_json_schema(e),
3753            Expression::JSONSet(e) => self.generate_json_set(e),
3754            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3755            Expression::JSONTable(e) => self.generate_json_table(e),
3756            Expression::JSONType(e) => self.generate_json_type(e),
3757            Expression::JSONValue(e) => self.generate_json_value(e),
3758            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3759            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3760            Expression::JoinHint(e) => self.generate_join_hint(e),
3761            Expression::JournalProperty(e) => self.generate_journal_property(e),
3762            Expression::LanguageProperty(e) => self.generate_language_property(e),
3763            Expression::Lateral(e) => self.generate_lateral(e),
3764            Expression::LikeProperty(e) => self.generate_like_property(e),
3765            Expression::Limit(e) => self.generate_limit(e),
3766            Expression::LimitOptions(e) => self.generate_limit_options(e),
3767            Expression::List(e) => self.generate_list(e),
3768            Expression::ToMap(e) => self.generate_tomap(e),
3769            Expression::Localtime(e) => self.generate_localtime(e),
3770            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3771            Expression::LocationProperty(e) => self.generate_location_property(e),
3772            Expression::Lock(e) => self.generate_lock(e),
3773            Expression::LockProperty(e) => self.generate_lock_property(e),
3774            Expression::LockingProperty(e) => self.generate_locking_property(e),
3775            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3776            Expression::LogProperty(e) => self.generate_log_property(e),
3777            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3778            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3779            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3780            Expression::MakeInterval(e) => self.generate_make_interval(e),
3781            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3782            Expression::Map(e) => self.generate_map(e),
3783            Expression::MapCat(e) => self.generate_map_cat(e),
3784            Expression::MapDelete(e) => self.generate_map_delete(e),
3785            Expression::MapInsert(e) => self.generate_map_insert(e),
3786            Expression::MapPick(e) => self.generate_map_pick(e),
3787            Expression::MaskingPolicyColumnConstraint(e) => {
3788                self.generate_masking_policy_column_constraint(e)
3789            }
3790            Expression::MatchAgainst(e) => self.generate_match_against(e),
3791            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3792            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3793            Expression::Merge(e) => self.generate_merge(e),
3794            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3795            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3796            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3797            Expression::Minhash(e) => self.generate_minhash(e),
3798            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3799            Expression::Monthname(e) => self.generate_monthname(e),
3800            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3801            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3802            Expression::Normal(e) => self.generate_normal(e),
3803            Expression::Normalize(e) => self.generate_normalize(e),
3804            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3805            Expression::Nullif(e) => self.generate_nullif(e),
3806            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3807            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3808            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3809            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3810            Expression::Offset(e) => self.generate_offset(e),
3811            Expression::Qualify(e) => self.generate_qualify(e),
3812            Expression::OnCluster(e) => self.generate_on_cluster(e),
3813            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3814            Expression::OnCondition(e) => self.generate_on_condition(e),
3815            Expression::OnConflict(e) => self.generate_on_conflict(e),
3816            Expression::OnProperty(e) => self.generate_on_property(e),
3817            Expression::Opclass(e) => self.generate_opclass(e),
3818            Expression::OpenJSON(e) => self.generate_open_json(e),
3819            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3820            Expression::Operator(e) => self.generate_operator(e),
3821            Expression::OrderBy(e) => self.generate_order_by(e),
3822            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3823            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3824            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3825            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3826            Expression::ParseIp(e) => self.generate_parse_ip(e),
3827            Expression::ParseJSON(e) => self.generate_parse_json(e),
3828            Expression::ParseTime(e) => self.generate_parse_time(e),
3829            Expression::ParseUrl(e) => self.generate_parse_url(e),
3830            Expression::Partition(e) => self.generate_partition_expr(e),
3831            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3832            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3833            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3834            Expression::PartitionByRangePropertyDynamic(e) => {
3835                self.generate_partition_by_range_property_dynamic(e)
3836            }
3837            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3838            Expression::PartitionList(e) => self.generate_partition_list(e),
3839            Expression::PartitionRange(e) => self.generate_partition_range(e),
3840            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3841            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3842            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3843            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3844            Expression::PeriodForSystemTimeConstraint(e) => {
3845                self.generate_period_for_system_time_constraint(e)
3846            }
3847            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3848            Expression::PivotAny(e) => self.generate_pivot_any(e),
3849            Expression::Predict(e) => self.generate_predict(e),
3850            Expression::PreviousDay(e) => self.generate_previous_day(e),
3851            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3852            Expression::PrimaryKeyColumnConstraint(e) => {
3853                self.generate_primary_key_column_constraint(e)
3854            }
3855            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3856            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3857            Expression::OptionsProperty(e) => self.generate_options_property(e),
3858            Expression::Properties(e) => self.generate_properties(e),
3859            Expression::Property(e) => self.generate_property(e),
3860            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3861            Expression::Put(e) => self.generate_put(e),
3862            Expression::Quantile(e) => self.generate_quantile(e),
3863            Expression::QueryBand(e) => self.generate_query_band(e),
3864            Expression::QueryOption(e) => self.generate_query_option(e),
3865            Expression::QueryTransform(e) => self.generate_query_transform(e),
3866            Expression::Randn(e) => self.generate_randn(e),
3867            Expression::Randstr(e) => self.generate_randstr(e),
3868            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3869            Expression::RangeN(e) => self.generate_range_n(e),
3870            Expression::ReadCSV(e) => self.generate_read_csv(e),
3871            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3872            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3873            Expression::Reduce(e) => self.generate_reduce(e),
3874            Expression::Reference(e) => self.generate_reference(e),
3875            Expression::Refresh(e) => self.generate_refresh(e),
3876            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3877            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3878            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3879            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3880            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3881            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3882            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3883            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3884            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3885            Expression::RegrCount(e) => self.generate_regr_count(e),
3886            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3887            Expression::RegrR2(e) => self.generate_regr_r2(e),
3888            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3889            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3890            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3891            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3892            Expression::RegrValx(e) => self.generate_regr_valx(e),
3893            Expression::RegrValy(e) => self.generate_regr_valy(e),
3894            Expression::RemoteWithConnectionModelProperty(e) => {
3895                self.generate_remote_with_connection_model_property(e)
3896            }
3897            Expression::RenameColumn(e) => self.generate_rename_column(e),
3898            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3899            Expression::Returning(e) => self.generate_returning(e),
3900            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3901            Expression::Rollback(e) => self.generate_rollback(e),
3902            Expression::Rollup(e) => self.generate_rollup(e),
3903            Expression::RowFormatDelimitedProperty(e) => {
3904                self.generate_row_format_delimited_property(e)
3905            }
3906            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3907            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3908            Expression::SHA2(e) => self.generate_sha2(e),
3909            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3910            Expression::SafeAdd(e) => self.generate_safe_add(e),
3911            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3912            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3913            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3914            Expression::SampleProperty(e) => self.generate_sample_property(e),
3915            Expression::Schema(e) => self.generate_schema(e),
3916            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3917            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3918            Expression::Search(e) => self.generate_search(e),
3919            Expression::SearchIp(e) => self.generate_search_ip(e),
3920            Expression::SecurityProperty(e) => self.generate_security_property(e),
3921            Expression::SemanticView(e) => self.generate_semantic_view(e),
3922            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3923            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3924            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3925            Expression::Set(e) => self.generate_set(e),
3926            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3927            Expression::SetItem(e) => self.generate_set_item(e),
3928            Expression::SetOperation(e) => self.generate_set_operation(e),
3929            Expression::SetProperty(e) => self.generate_set_property(e),
3930            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3931            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3932            Expression::Slice(e) => self.generate_slice(e),
3933            Expression::SortArray(e) => self.generate_sort_array(e),
3934            Expression::SortBy(e) => self.generate_sort_by(e),
3935            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3936            Expression::SplitPart(e) => self.generate_split_part(e),
3937            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3938            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3939            Expression::StDistance(e) => self.generate_st_distance(e),
3940            Expression::StPoint(e) => self.generate_st_point(e),
3941            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3942            Expression::StandardHash(e) => self.generate_standard_hash(e),
3943            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3944            Expression::StrPosition(e) => self.generate_str_position(e),
3945            Expression::StrToDate(e) => self.generate_str_to_date(e),
3946            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3947            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3948            Expression::StrToMap(e) => self.generate_str_to_map(e),
3949            Expression::StrToTime(e) => self.generate_str_to_time(e),
3950            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3951            Expression::StringToArray(e) => self.generate_string_to_array(e),
3952            Expression::Struct(e) => self.generate_struct(e),
3953            Expression::Stuff(e) => self.generate_stuff(e),
3954            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3955            Expression::Summarize(e) => self.generate_summarize(e),
3956            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3957            Expression::TableAlias(e) => self.generate_table_alias(e),
3958            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3959            Expression::RowsFrom(e) => self.generate_rows_from(e),
3960            Expression::TableSample(e) => self.generate_table_sample(e),
3961            Expression::Tag(e) => self.generate_tag(e),
3962            Expression::Tags(e) => self.generate_tags(e),
3963            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3964            Expression::Time(e) => self.generate_time_func(e),
3965            Expression::TimeAdd(e) => self.generate_time_add(e),
3966            Expression::TimeDiff(e) => self.generate_time_diff(e),
3967            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3968            Expression::TimeSlice(e) => self.generate_time_slice(e),
3969            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3970            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3971            Expression::TimeSub(e) => self.generate_time_sub(e),
3972            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3973            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3974            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3975            Expression::TimeUnit(e) => self.generate_time_unit(e),
3976            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3977            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3978            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3979            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3980            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3981            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3982            Expression::ToBinary(e) => self.generate_to_binary(e),
3983            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3984            Expression::ToChar(e) => self.generate_to_char(e),
3985            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3986            Expression::ToDouble(e) => self.generate_to_double(e),
3987            Expression::ToFile(e) => self.generate_to_file(e),
3988            Expression::ToNumber(e) => self.generate_to_number(e),
3989            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3990            Expression::Transaction(e) => self.generate_transaction(e),
3991            Expression::Transform(e) => self.generate_transform(e),
3992            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3993            Expression::TransientProperty(e) => self.generate_transient_property(e),
3994            Expression::Translate(e) => self.generate_translate(e),
3995            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3996            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3997            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3998            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3999            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
4000            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
4001            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
4002            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
4003            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
4004            Expression::Unhex(e) => self.generate_unhex(e),
4005            Expression::UnicodeString(e) => self.generate_unicode_string(e),
4006            Expression::Uniform(e) => self.generate_uniform(e),
4007            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
4008            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
4009            Expression::RollupProperty(e) => self.generate_rollup_property(e),
4010            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
4011            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
4012            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
4013            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
4014            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
4015            Expression::UtcTime(e) => self.generate_utc_time(e),
4016            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
4017            Expression::Uuid(e) => self.generate_uuid(e),
4018            Expression::Var(v) => {
4019                if matches!(self.config.dialect, Some(DialectType::MySQL))
4020                    && v.this.len() > 2
4021                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
4022                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
4023                {
4024                    return self.generate_identifier(&Identifier {
4025                        name: v.this.clone(),
4026                        quoted: true,
4027                        trailing_comments: Vec::new(),
4028                        span: None,
4029                    });
4030                }
4031                self.write(&v.this);
4032                Ok(())
4033            }
4034            Expression::Variadic(e) => {
4035                self.write_keyword("VARIADIC");
4036                self.write_space();
4037                self.generate_expression(&e.this)?;
4038                Ok(())
4039            }
4040            Expression::VarMap(e) => self.generate_var_map(e),
4041            Expression::VectorSearch(e) => self.generate_vector_search(e),
4042            Expression::Version(e) => self.generate_version(e),
4043            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
4044            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
4045            Expression::WatermarkColumnConstraint(e) => {
4046                self.generate_watermark_column_constraint(e)
4047            }
4048            Expression::Week(e) => self.generate_week(e),
4049            Expression::When(e) => self.generate_when(e),
4050            Expression::Whens(e) => self.generate_whens(e),
4051            Expression::Where(e) => self.generate_where(e),
4052            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4053            Expression::Window(e) => self.generate_window(e),
4054            Expression::WindowSpec(e) => self.generate_window_spec(e),
4055            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4056            Expression::WithFill(e) => self.generate_with_fill(e),
4057            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4058            Expression::WithOperator(e) => self.generate_with_operator(e),
4059            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4060            Expression::WithSchemaBindingProperty(e) => {
4061                self.generate_with_schema_binding_property(e)
4062            }
4063            Expression::WithSystemVersioningProperty(e) => {
4064                self.generate_with_system_versioning_property(e)
4065            }
4066            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4067            Expression::XMLElement(e) => self.generate_xml_element(e),
4068            Expression::XMLGet(e) => self.generate_xml_get(e),
4069            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4070            Expression::XMLTable(e) => self.generate_xml_table(e),
4071            Expression::Xor(e) => self.generate_xor(e),
4072            Expression::Zipf(e) => self.generate_zipf(e),
4073            _ => self.write_unsupported_comment("unsupported expression"),
4074        }
4075    }
4076
4077    fn should_handle_tsql_distinct_null_ordering(&self, select: &Select) -> bool {
4078        matches!(
4079            self.config.dialect,
4080            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4081        ) && !self.config.null_ordering_supported
4082            && select.distinct
4083            && select.order_by.as_ref().is_some_and(|order_by| {
4084                order_by
4085                    .expressions
4086                    .iter()
4087                    .any(Self::ordered_requires_tsql_null_ordering_emulation)
4088            })
4089    }
4090
4091    fn ordered_requires_tsql_null_ordering_emulation(ordered: &Ordered) -> bool {
4092        let Some(nulls_first) = ordered.nulls_first else {
4093            return false;
4094        };
4095
4096        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
4097        let target_default_nulls_first = !ordered.desc;
4098
4099        nulls_first != target_default_nulls_first && !random_ordering
4100    }
4101
4102    fn projection_output_identifier(expression: &Expression) -> Option<Identifier> {
4103        match expression {
4104            Expression::Alias(alias) if !alias.alias.name.is_empty() => Some(alias.alias.clone()),
4105            Expression::Column(column) => Some(column.name.clone()),
4106            Expression::Identifier(identifier) => Some(identifier.clone()),
4107            _ => None,
4108        }
4109    }
4110
4111    fn identifier_names_match(left: &Identifier, right: &Identifier) -> bool {
4112        if left.quoted || right.quoted {
4113            left.name == right.name
4114        } else {
4115            left.name.eq_ignore_ascii_case(&right.name)
4116        }
4117    }
4118
4119    fn expression_matches_identifier(expression: &Expression, identifier: &Identifier) -> bool {
4120        match expression {
4121            Expression::Column(column) if column.table.is_none() => {
4122                Self::identifier_names_match(&column.name, identifier)
4123            }
4124            Expression::Identifier(other) => Self::identifier_names_match(other, identifier),
4125            _ => false,
4126        }
4127    }
4128
4129    fn column_expression(identifier: Identifier) -> Expression {
4130        Expression::Column(Box::new(Column {
4131            name: identifier,
4132            table: None,
4133            join_mark: false,
4134            trailing_comments: Vec::new(),
4135            span: None,
4136            inferred_type: None,
4137        }))
4138    }
4139
4140    fn positional_ordering_index(expression: &Expression) -> Option<usize> {
4141        match expression {
4142            Expression::Literal(lit) => match lit.as_ref() {
4143                Literal::Number(n) => {
4144                    let value = n.parse::<usize>().ok()?;
4145                    value.checked_sub(1)
4146                }
4147                _ => None,
4148            },
4149            _ => None,
4150        }
4151    }
4152
4153    fn projection_sort_expression(expression: &Expression) -> Option<Expression> {
4154        let expression = match expression {
4155            Expression::Alias(alias) => &alias.this,
4156            other => other,
4157        };
4158
4159        match expression {
4160            Expression::Star(_)
4161            | Expression::Literal(_)
4162            | Expression::Null(_)
4163            | Expression::Boolean(_) => None,
4164            _ => Some(expression.clone()),
4165        }
4166    }
4167
4168    fn resolve_positional_order_projection(
4169        projections: &[Expression],
4170        index: usize,
4171    ) -> Option<Expression> {
4172        Self::projection_sort_expression(projections.get(index)?)
4173    }
4174
4175    fn is_direct_grouping_expression(expression: &Expression) -> bool {
4176        match expression {
4177            Expression::Paren(paren) => Self::is_direct_grouping_expression(&paren.this),
4178            Expression::Grouping(_) | Expression::GroupingId(_) => true,
4179            Expression::Function(function) => {
4180                function.name.eq_ignore_ascii_case("GROUPING")
4181                    || function.name.eq_ignore_ascii_case("GROUPING_ID")
4182            }
4183            _ => false,
4184        }
4185    }
4186
4187    fn group_by_has_multiple_grouping_levels(group_by: &GroupBy) -> bool {
4188        group_by
4189            .expressions
4190            .iter()
4191            .any(|expression| match expression {
4192                Expression::Cube(_) | Expression::Rollup(_) => true,
4193                Expression::GroupingSets(grouping_sets) => grouping_sets.expressions.len() > 1,
4194                Expression::Function(function)
4195                    if function.name.eq_ignore_ascii_case("CUBE")
4196                        || function.name.eq_ignore_ascii_case("ROLLUP") =>
4197                {
4198                    !function.args.is_empty()
4199                }
4200                Expression::Function(function)
4201                    if function.name.eq_ignore_ascii_case("GROUPING SETS") =>
4202                {
4203                    function.args.len() > 1
4204                        || function.args.first().is_some_and(|expression| {
4205                            matches!(expression, Expression::Cube(_) | Expression::Rollup(_))
4206                                || matches!(expression,
4207                                    Expression::Function(nested)
4208                                        if nested.name.eq_ignore_ascii_case("CUBE")
4209                                            || nested.name.eq_ignore_ascii_case("ROLLUP")
4210                                )
4211                        })
4212                }
4213                _ => false,
4214            })
4215    }
4216
4217    fn set_output_identifier_at(expression: &Expression, index: usize) -> Option<Identifier> {
4218        match expression {
4219            Expression::Select(select) => {
4220                Self::projection_output_identifier(select.expressions.get(index)?)
4221            }
4222            Expression::Union(union) => Self::set_output_identifier_at(&union.left, index),
4223            Expression::Intersect(intersect) => {
4224                Self::set_output_identifier_at(&intersect.left, index)
4225            }
4226            Expression::Except(except) => Self::set_output_identifier_at(&except.left, index),
4227            Expression::Subquery(subquery) => subquery
4228                .column_aliases
4229                .get(index)
4230                .cloned()
4231                .or_else(|| Self::set_output_identifier_at(&subquery.this, index)),
4232            _ => None,
4233        }
4234    }
4235
4236    fn resolve_single_subquery_star_projection(
4237        select: &Select,
4238        index: usize,
4239    ) -> Option<Expression> {
4240        if select.expressions.len() != 1
4241            || !matches!(select.expressions.first(), Some(Expression::Star(_)))
4242        {
4243            return None;
4244        }
4245
4246        let from = select.from.as_ref()?;
4247        if from.expressions.len() != 1 {
4248            return None;
4249        }
4250
4251        let Expression::Subquery(subquery) = from.expressions.first()? else {
4252            return None;
4253        };
4254
4255        subquery
4256            .column_aliases
4257            .get(index)
4258            .cloned()
4259            .or_else(|| Self::set_output_identifier_at(&subquery.this, index))
4260            .map(Self::column_expression)
4261    }
4262
4263    fn sole_tsql_ordering_source_qualifier(select: &Select) -> Option<Identifier> {
4264        if !select.joins.is_empty() {
4265            return None;
4266        }
4267
4268        let from = select.from.as_ref()?;
4269        if from.expressions.len() != 1 {
4270            return None;
4271        }
4272
4273        match from.expressions.first()? {
4274            Expression::Table(table) => {
4275                Some(table.alias.clone().unwrap_or_else(|| table.name.clone()))
4276            }
4277            Expression::Subquery(subquery) => subquery.alias.clone(),
4278            _ => None,
4279        }
4280    }
4281
4282    fn resolve_duplicate_tsql_ordering_column(
4283        select: &Select,
4284        expression: &Expression,
4285    ) -> Option<Expression> {
4286        let Expression::Column(order_column) = expression else {
4287            return None;
4288        };
4289        if order_column.table.is_some() {
4290            return None;
4291        }
4292
4293        let matching_projections: Vec<_> = select
4294            .expressions
4295            .iter()
4296            .filter(|projection| {
4297                Self::projection_output_identifier(projection).is_some_and(|identifier| {
4298                    Self::identifier_names_match(&identifier, &order_column.name)
4299                })
4300            })
4301            .filter_map(Self::projection_sort_expression)
4302            .collect();
4303
4304        if matching_projections.len() < 2
4305            || matching_projections[1..]
4306                .iter()
4307                .any(|projection| projection != &matching_projections[0])
4308        {
4309            return None;
4310        }
4311
4312        let Expression::Column(projected_column) = &matching_projections[0] else {
4313            return None;
4314        };
4315        if !Self::identifier_names_match(&projected_column.name, &order_column.name) {
4316            return None;
4317        }
4318
4319        let qualifier = projected_column
4320            .table
4321            .clone()
4322            .or_else(|| Self::sole_tsql_ordering_source_qualifier(select))?;
4323        let mut resolved = order_column.as_ref().clone();
4324        resolved.table = Some(qualifier);
4325        Some(Expression::Column(Box::new(resolved)))
4326    }
4327
4328    fn resolve_tsql_null_ordering_for_select(&self, select: &Select) -> Option<Select> {
4329        if !matches!(
4330            self.config.dialect,
4331            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4332        ) || self.config.null_ordering_supported
4333        {
4334            return None;
4335        }
4336
4337        let order_by = select.order_by.as_ref()?;
4338        let grouping_order_is_constant = matches!(self.config.dialect, Some(DialectType::Fabric))
4339            && select
4340                .group_by
4341                .as_ref()
4342                .is_some_and(|group_by| !Self::group_by_has_multiple_grouping_levels(group_by));
4343        let mut resolved_expressions = Vec::with_capacity(order_by.expressions.len());
4344        let mut changed = false;
4345
4346        for mut ordered in order_by.expressions.iter().cloned() {
4347            if Self::ordered_requires_tsql_null_ordering_emulation(&ordered) {
4348                if let Some(index) = Self::positional_ordering_index(&ordered.this) {
4349                    if let Some(resolved) =
4350                        Self::resolve_positional_order_projection(&select.expressions, index)
4351                            .or_else(|| {
4352                                Self::resolve_single_subquery_star_projection(select, index)
4353                            })
4354                    {
4355                        if resolved != ordered.this {
4356                            ordered.this = resolved;
4357                            changed = true;
4358                        }
4359                    }
4360                }
4361
4362                if let Some(resolved) =
4363                    Self::resolve_duplicate_tsql_ordering_column(select, &ordered.this)
4364                {
4365                    ordered.this = resolved;
4366                    changed = true;
4367                }
4368            }
4369
4370            // Fabric rejects ORDER BY expressions that fold to constants (Msg 408).
4371            // With one effective grouping level, a direct GROUPING/GROUPING_ID
4372            // result is constant and contributes no ordering, so omit it.
4373            if grouping_order_is_constant && Self::is_direct_grouping_expression(&ordered.this) {
4374                changed = true;
4375                continue;
4376            }
4377
4378            resolved_expressions.push(ordered);
4379        }
4380
4381        if changed {
4382            let mut resolved_select = select.clone();
4383            resolved_select.order_by = if resolved_expressions.is_empty() {
4384                None
4385            } else {
4386                Some(OrderBy {
4387                    expressions: resolved_expressions,
4388                    siblings: order_by.siblings,
4389                    comments: order_by.comments.clone(),
4390                })
4391            };
4392            Some(resolved_select)
4393        } else {
4394            None
4395        }
4396    }
4397
4398    fn resolve_distinct_order_projection(
4399        projections: &[Expression],
4400        order_expression: &Expression,
4401    ) -> Option<Expression> {
4402        for projection in projections {
4403            if let Expression::Alias(alias) = projection {
4404                if Self::expression_matches_identifier(order_expression, &alias.alias)
4405                    || &alias.this == order_expression
4406                {
4407                    return Some(alias.this.clone());
4408                }
4409            } else if projection == order_expression {
4410                return Some(projection.clone());
4411            } else if let Some(identifier) = Self::projection_output_identifier(projection) {
4412                if Self::expression_matches_identifier(order_expression, &identifier) {
4413                    return Some(projection.clone());
4414                }
4415            }
4416        }
4417
4418        None
4419    }
4420
4421    fn fresh_polyglot_alias(base: &str, index: usize, used_names: &mut Vec<String>) -> String {
4422        let mut suffix = 0;
4423        loop {
4424            let candidate = if suffix == 0 {
4425                format!("{base}_{index}")
4426            } else {
4427                format!("{base}_{index}_{suffix}")
4428            };
4429
4430            if !used_names
4431                .iter()
4432                .any(|name| name.eq_ignore_ascii_case(&candidate))
4433            {
4434                used_names.push(candidate.clone());
4435                return candidate;
4436            }
4437
4438            suffix += 1;
4439        }
4440    }
4441
4442    fn tsql_null_ordering_case(expression: Expression) -> Expression {
4443        Expression::Case(Box::new(Case {
4444            operand: None,
4445            whens: vec![(
4446                Expression::IsNull(Box::new(IsNull {
4447                    this: expression,
4448                    not: false,
4449                    postfix_form: false,
4450                })),
4451                Expression::number(1),
4452            )],
4453            else_: Some(Expression::number(0)),
4454            comments: Vec::new(),
4455            inferred_type: None,
4456        }))
4457    }
4458
4459    fn try_build_tsql_distinct_null_ordering_wrapper(&self, select: &Select) -> Option<Select> {
4460        let order_by = select.order_by.as_ref()?;
4461
4462        if order_by.siblings
4463            || select.distinct_on.is_some()
4464            || select.distribute_by.is_some()
4465            || select.cluster_by.is_some()
4466            || select.sort_by.is_some()
4467            || select.limit.is_some()
4468            || select.offset.is_some()
4469            || select.limit_by.is_some()
4470            || select.fetch.is_some()
4471            || select.top.is_some()
4472            || select.settings.is_some()
4473            || select.format.is_some()
4474            || select.kind.is_some()
4475            || select.hint.is_some()
4476            || select.into.is_some()
4477            || !select.locks.is_empty()
4478            || !select.for_xml.is_empty()
4479            || !select.for_json.is_empty()
4480            || !select.operation_modifiers.is_empty()
4481            || select.option.is_some()
4482            || select.exclude.is_some()
4483        {
4484            return None;
4485        }
4486
4487        let projection_identifiers: Vec<_> = select
4488            .expressions
4489            .iter()
4490            .map(Self::projection_output_identifier)
4491            .collect::<Option<_>>()?;
4492        let mut used_names: Vec<_> = projection_identifiers
4493            .iter()
4494            .map(|identifier| identifier.name.clone())
4495            .collect();
4496
4497        let mut inner = select.clone();
4498        inner.order_by = None;
4499
4500        let outer_with = inner.with.take();
4501        let outer_leading_comments = std::mem::take(&mut inner.leading_comments);
4502        let outer_post_select_comments = std::mem::take(&mut inner.post_select_comments);
4503
4504        let mut outer_order_expressions = Vec::with_capacity(order_by.expressions.len() * 2);
4505        for (index, ordered) in order_by.expressions.iter().enumerate() {
4506            if ordered.with_fill.is_some() {
4507                return None;
4508            }
4509
4510            let sort_expression =
4511                Self::resolve_distinct_order_projection(&select.expressions, &ordered.this)?;
4512
4513            if Self::ordered_requires_tsql_null_ordering_emulation(ordered) {
4514                let null_alias =
4515                    Self::fresh_polyglot_alias("_polyglot_order_null", index, &mut used_names);
4516                inner.expressions.push(
4517                    Self::tsql_null_ordering_case(sort_expression.clone())
4518                        .alias(null_alias.clone()),
4519                );
4520                outer_order_expressions.push(Ordered {
4521                    this: Expression::column(null_alias),
4522                    desc: ordered.nulls_first == Some(true),
4523                    nulls_first: None,
4524                    explicit_asc: false,
4525                    with_fill: None,
4526                });
4527            }
4528
4529            let key_alias =
4530                Self::fresh_polyglot_alias("_polyglot_order_key", index, &mut used_names);
4531            inner
4532                .expressions
4533                .push(sort_expression.alias(key_alias.clone()));
4534            outer_order_expressions.push(Ordered {
4535                this: Expression::column(key_alias),
4536                desc: ordered.desc,
4537                nulls_first: None,
4538                explicit_asc: ordered.explicit_asc,
4539                with_fill: None,
4540            });
4541        }
4542
4543        let subquery = Subquery {
4544            this: Expression::Select(Box::new(inner)),
4545            alias: Some(Identifier::new("_polyglot_distinct_order")),
4546            column_aliases: Vec::new(),
4547            alias_explicit_as: true,
4548            alias_keyword: None,
4549            order_by: None,
4550            limit: None,
4551            offset: None,
4552            distribute_by: None,
4553            sort_by: None,
4554            cluster_by: None,
4555            lateral: false,
4556            modifiers_inside: false,
4557            trailing_comments: Vec::new(),
4558            inferred_type: None,
4559        };
4560
4561        let mut outer = Select::new();
4562        outer.with = outer_with;
4563        outer.leading_comments = outer_leading_comments;
4564        outer.post_select_comments = outer_post_select_comments;
4565        outer.expressions = projection_identifiers
4566            .into_iter()
4567            .map(Self::column_expression)
4568            .collect();
4569        outer.from = Some(From {
4570            expressions: vec![Expression::Subquery(Box::new(subquery))],
4571        });
4572        outer.order_by = Some(OrderBy {
4573            expressions: outer_order_expressions,
4574            siblings: false,
4575            comments: order_by.comments.clone(),
4576        });
4577
4578        Some(outer)
4579    }
4580
4581    fn generate_select(&mut self, select: &Select) -> Result<()> {
4582        use crate::dialects::DialectType;
4583
4584        if let Some(resolved_select) = self.resolve_tsql_null_ordering_for_select(select) {
4585            return self.generate_select(&resolved_select);
4586        }
4587
4588        if self.should_handle_tsql_distinct_null_ordering(select) {
4589            if let Some(wrapped_select) = self.try_build_tsql_distinct_null_ordering_wrapper(select)
4590            {
4591                return self.generate_select(&wrapped_select);
4592            }
4593
4594            self.unsupported(
4595                "SELECT DISTINCT with emulated NULL ordering is not supported for TSQL/Fabric",
4596            )?;
4597        }
4598
4599        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4600        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4601        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4602        if let Some(exclude) = &select.exclude {
4603            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4604                // Build the inner select (same as original but without exclude)
4605                let mut inner_select = select.clone();
4606                inner_select.exclude = None;
4607                let inner_expr = Expression::Select(Box::new(inner_select));
4608
4609                // Build the subquery
4610                let subquery = crate::expressions::Subquery {
4611                    this: inner_expr,
4612                    alias: None,
4613                    column_aliases: Vec::new(),
4614                    alias_explicit_as: false,
4615                    alias_keyword: None,
4616                    order_by: None,
4617                    limit: None,
4618                    offset: None,
4619                    distribute_by: None,
4620                    sort_by: None,
4621                    cluster_by: None,
4622                    lateral: false,
4623                    modifiers_inside: false,
4624                    trailing_comments: Vec::new(),
4625                    inferred_type: None,
4626                };
4627
4628                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4629                let star = Expression::Star(crate::expressions::Star {
4630                    table: None,
4631                    except: Some(
4632                        exclude
4633                            .iter()
4634                            .map(|e| match e {
4635                                Expression::Column(col) => col.name.clone(),
4636                                Expression::Identifier(id) => id.clone(),
4637                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4638                            })
4639                            .collect(),
4640                    ),
4641                    replace: None,
4642                    rename: None,
4643                    trailing_comments: Vec::new(),
4644                    span: None,
4645                });
4646
4647                let outer_select = Select {
4648                    expressions: vec![star],
4649                    from: Some(crate::expressions::From {
4650                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4651                    }),
4652                    ..Select::new()
4653                };
4654
4655                return self.generate_select(&outer_select);
4656            }
4657        }
4658
4659        // Output leading comments before SELECT
4660        for comment in &select.leading_comments {
4661            self.write_formatted_comment(comment);
4662            self.write(" ");
4663        }
4664
4665        // WITH clause
4666        if let Some(with) = &select.with {
4667            self.generate_with(with)?;
4668            if self.config.pretty {
4669                self.write_newline();
4670                self.write_indent();
4671            } else {
4672                self.write_space();
4673            }
4674        }
4675
4676        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4677        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4678        for comment in &select.post_select_comments {
4679            self.write_formatted_comment(comment);
4680            self.write(" ");
4681        }
4682
4683        self.write_keyword("SELECT");
4684
4685        // Generate query hint if present /*+ ... */
4686        if let Some(hint) = &select.hint {
4687            self.generate_hint(hint)?;
4688        }
4689
4690        // For SQL Server, convert LIMIT to TOP (structural transformation)
4691        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4692        // TOP clause (SQL Server style - before DISTINCT)
4693        let use_top_from_limit = matches!(
4694            self.config.dialect,
4695            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4696        ) && select.top.is_none()
4697            && select
4698                .limit
4699                .as_ref()
4700                .is_some_and(|limit| !Self::is_noop_limit_expr(&limit.this))
4701            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4702
4703        // For TOP-supporting dialects: DISTINCT before TOP
4704        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4705        let is_top_dialect = matches!(
4706            self.config.dialect,
4707            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4708        );
4709        let keep_top_verbatim = !is_top_dialect
4710            && select.limit.is_none()
4711            && select
4712                .top
4713                .as_ref()
4714                .map_or(false, |top| top.percent || top.with_ties);
4715
4716        if select.distinct && (is_top_dialect || select.top.is_some()) {
4717            self.write_space();
4718            self.write_keyword("DISTINCT");
4719        }
4720
4721        if is_top_dialect || keep_top_verbatim {
4722            if let Some(top) = &select.top {
4723                self.write_space();
4724                self.write_keyword("TOP");
4725                if top.parenthesized {
4726                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4727                        self.write_space();
4728                        self.generate_expression(&top.this)?;
4729                    } else {
4730                        self.write(" (");
4731                        self.generate_expression(&top.this)?;
4732                        self.write(")");
4733                    }
4734                } else {
4735                    self.write_space();
4736                    self.generate_expression(&top.this)?;
4737                }
4738                if top.percent {
4739                    self.write_space();
4740                    self.write_keyword("PERCENT");
4741                }
4742                if top.with_ties {
4743                    self.write_space();
4744                    self.write_keyword("WITH TIES");
4745                }
4746            } else if use_top_from_limit {
4747                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4748                if let Some(limit) = &select.limit {
4749                    self.write_space();
4750                    self.write_keyword("TOP");
4751                    // Use parentheses for complex expressions, but not for simple literals
4752                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4753                    if is_simple_literal {
4754                        self.write_space();
4755                        self.generate_expression(&limit.this)?;
4756                    } else {
4757                        self.write(" (");
4758                        self.generate_expression(&limit.this)?;
4759                        self.write(")");
4760                    }
4761                }
4762            }
4763        }
4764
4765        if select.distinct && !is_top_dialect && select.top.is_none() {
4766            self.write_space();
4767            self.write_keyword("DISTINCT");
4768        }
4769
4770        // DISTINCT ON clause (PostgreSQL)
4771        if let Some(distinct_on) = &select.distinct_on {
4772            self.write_space();
4773            self.write_keyword("ON");
4774            self.write(" (");
4775            for (i, expr) in distinct_on.iter().enumerate() {
4776                if i > 0 {
4777                    self.write(", ");
4778                }
4779                self.generate_expression(expr)?;
4780            }
4781            self.write(")");
4782        }
4783
4784        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4785        for modifier in &select.operation_modifiers {
4786            self.write_space();
4787            self.write_keyword(modifier);
4788        }
4789
4790        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4791        if let Some(kind) = &select.kind {
4792            self.write_space();
4793            self.write_keyword("AS");
4794            self.write_space();
4795            self.write_keyword(kind);
4796        }
4797
4798        // Expressions (only if there are any)
4799        if !select.expressions.is_empty() {
4800            if self.config.pretty {
4801                self.write_newline();
4802                self.indent_level += 1;
4803            } else {
4804                self.write_space();
4805            }
4806        }
4807
4808        for (i, expr) in select.expressions.iter().enumerate() {
4809            if i > 0 {
4810                self.write(",");
4811                if self.config.pretty {
4812                    self.write_newline();
4813                } else {
4814                    self.write_space();
4815                }
4816            }
4817            if self.config.pretty {
4818                self.write_indent();
4819            }
4820            self.generate_expression(expr)?;
4821        }
4822
4823        if self.config.pretty && !select.expressions.is_empty() {
4824            self.indent_level -= 1;
4825        }
4826
4827        // Redshift-style EXCLUDE clause at the end of the projection list
4828        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4829        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4830        // (done after the full select is generated below)
4831        if let Some(exclude) = &select.exclude {
4832            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4833                self.write_space();
4834                self.write_keyword("EXCLUDE");
4835                self.write(" (");
4836                for (i, col) in exclude.iter().enumerate() {
4837                    if i > 0 {
4838                        self.write(", ");
4839                    }
4840                    self.generate_expression(col)?;
4841                }
4842                self.write(")");
4843            }
4844        }
4845
4846        // INTO clause (SELECT ... INTO table_name)
4847        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4848        if let Some(into) = &select.into {
4849            if self.config.pretty {
4850                self.write_newline();
4851                self.write_indent();
4852            } else {
4853                self.write_space();
4854            }
4855            if into.bulk_collect {
4856                self.write_keyword("BULK COLLECT INTO");
4857            } else {
4858                self.write_keyword("INTO");
4859            }
4860            if into.temporary {
4861                self.write_space();
4862                self.write_keyword("TEMPORARY");
4863            }
4864            if into.unlogged {
4865                self.write_space();
4866                self.write_keyword("UNLOGGED");
4867            }
4868            self.write_space();
4869            // If we have multiple expressions, output them comma-separated
4870            if !into.expressions.is_empty() {
4871                for (i, expr) in into.expressions.iter().enumerate() {
4872                    if i > 0 {
4873                        self.write(", ");
4874                    }
4875                    self.generate_expression(expr)?;
4876                }
4877            } else {
4878                self.generate_expression(&into.this)?;
4879            }
4880        }
4881
4882        // FROM clause
4883        if let Some(from) = &select.from {
4884            if self.config.pretty {
4885                self.write_newline();
4886                self.write_indent();
4887            } else {
4888                self.write_space();
4889            }
4890            self.write_keyword("FROM");
4891            self.write_space();
4892
4893            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4894            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4895            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4896            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4897            let has_tablesample = from
4898                .expressions
4899                .iter()
4900                .any(|e| matches!(e, Expression::TableSample(_)));
4901            let is_cross_join_dialect = matches!(
4902                self.config.dialect,
4903                Some(DialectType::BigQuery)
4904                    | Some(DialectType::Hive)
4905                    | Some(DialectType::Spark)
4906                    | Some(DialectType::Databricks)
4907                    | Some(DialectType::SQLite)
4908                    | Some(DialectType::ClickHouse)
4909            );
4910            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4911            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4912            let source_is_same_as_target = self.config.source_dialect.is_some()
4913                && self.config.source_dialect == self.config.dialect;
4914            let source_is_cross_join_dialect = matches!(
4915                self.config.source_dialect,
4916                Some(DialectType::BigQuery)
4917                    | Some(DialectType::Hive)
4918                    | Some(DialectType::Spark)
4919                    | Some(DialectType::Databricks)
4920                    | Some(DialectType::SQLite)
4921                    | Some(DialectType::ClickHouse)
4922            );
4923            let use_cross_join = !has_tablesample
4924                && is_cross_join_dialect
4925                && (source_is_same_as_target
4926                    || source_is_cross_join_dialect
4927                    || self.config.source_dialect.is_none());
4928
4929            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4930            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4931
4932            for (i, expr) in from.expressions.iter().enumerate() {
4933                if i > 0 {
4934                    if use_cross_join {
4935                        self.write(" CROSS JOIN ");
4936                    } else {
4937                        self.write(", ");
4938                    }
4939                }
4940                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4941                    self.write("(");
4942                    self.generate_expression(expr)?;
4943                    self.write(")");
4944                } else {
4945                    self.generate_expression(expr)?;
4946                }
4947                // Output leading comments that were on the table name before FROM
4948                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4949                let leading = Self::extract_table_leading_comments(expr);
4950                for comment in &leading {
4951                    self.write_space();
4952                    self.write_formatted_comment(comment);
4953                }
4954            }
4955        }
4956
4957        // JOINs - handle nested join structure for pretty printing
4958        // Deferred-condition joins "own" the non-deferred joins that follow them
4959        // until the next deferred join or end of list
4960        if self.config.pretty {
4961            self.generate_joins_with_nesting(&select.joins)?;
4962        } else {
4963            for join in &select.joins {
4964                self.generate_join(join)?;
4965            }
4966            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4967            for join in select.joins.iter().rev() {
4968                if join.deferred_condition {
4969                    self.generate_join_condition(join)?;
4970                }
4971            }
4972        }
4973
4974        // LATERAL VIEW clauses (Hive/Spark)
4975        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4976            self.generate_lateral_view(lateral_view, lv_idx)?;
4977        }
4978
4979        // PREWHERE (ClickHouse)
4980        if let Some(prewhere) = &select.prewhere {
4981            self.write_clause_condition("PREWHERE", prewhere)?;
4982        }
4983
4984        // WHERE
4985        if let Some(where_clause) = &select.where_clause {
4986            self.write_clause_condition("WHERE", &where_clause.this)?;
4987        }
4988
4989        // CONNECT BY (Oracle hierarchical queries)
4990        if let Some(connect) = &select.connect {
4991            self.generate_connect(connect)?;
4992        }
4993
4994        // GROUP BY
4995        if let Some(group_by) = &select.group_by {
4996            if self.config.pretty {
4997                // Output leading comments on their own lines before GROUP BY
4998                for comment in &group_by.comments {
4999                    self.write_newline();
5000                    self.write_indent();
5001                    self.write_formatted_comment(comment);
5002                }
5003                self.write_newline();
5004                self.write_indent();
5005            } else {
5006                self.write_space();
5007                // In non-pretty mode, output comments inline
5008                for comment in &group_by.comments {
5009                    self.write_formatted_comment(comment);
5010                    self.write_space();
5011                }
5012            }
5013            let clickhouse_bare_modifiers =
5014                matches!(self.config.dialect, Some(DialectType::ClickHouse))
5015                    && group_by.all.is_none()
5016                    && (group_by.totals || !group_by.expressions.is_empty())
5017                    && group_by.expressions.iter().all(|expr| match expr {
5018                        Expression::Cube(c) => c.expressions.is_empty(),
5019                        Expression::Rollup(r) => r.expressions.is_empty(),
5020                        _ => false,
5021                    });
5022
5023            if clickhouse_bare_modifiers {
5024                let trailing_cube = group_by
5025                    .expressions
5026                    .iter()
5027                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
5028                let trailing_rollup = group_by
5029                    .expressions
5030                    .iter()
5031                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
5032
5033                if trailing_cube {
5034                    self.write_keyword("WITH CUBE");
5035                } else if trailing_rollup {
5036                    self.write_keyword("WITH ROLLUP");
5037                }
5038
5039                if group_by.totals {
5040                    if trailing_cube || trailing_rollup {
5041                        self.write_space();
5042                    }
5043                    self.write_keyword("WITH TOTALS");
5044                }
5045            } else {
5046                self.write_keyword("GROUP BY");
5047                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
5048                match group_by.all {
5049                    Some(true) => {
5050                        self.write_space();
5051                        self.write_keyword("ALL");
5052                    }
5053                    Some(false) => {
5054                        self.write_space();
5055                        self.write_keyword("DISTINCT");
5056                    }
5057                    None => {}
5058                }
5059                if !group_by.expressions.is_empty() {
5060                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
5061                    // These are represented as Cube/Rollup expressions with empty expressions at the end
5062                    let mut trailing_cube = false;
5063                    let mut trailing_rollup = false;
5064                    let mut plain_expressions: Vec<&Expression> = Vec::new();
5065                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
5066                    let mut cube_expressions: Vec<&Expression> = Vec::new();
5067                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
5068
5069                    for expr in &group_by.expressions {
5070                        match expr {
5071                            Expression::Cube(c) if c.expressions.is_empty() => {
5072                                trailing_cube = true;
5073                            }
5074                            Expression::Rollup(r) if r.expressions.is_empty() => {
5075                                trailing_rollup = true;
5076                            }
5077                            Expression::Function(f) if f.name == "CUBE" => {
5078                                cube_expressions.push(expr);
5079                            }
5080                            Expression::Function(f) if f.name == "ROLLUP" => {
5081                                rollup_expressions.push(expr);
5082                            }
5083                            Expression::Function(f) if f.name == "GROUPING SETS" => {
5084                                grouping_sets_expressions.push(expr);
5085                            }
5086                            _ => {
5087                                plain_expressions.push(expr);
5088                            }
5089                        }
5090                    }
5091
5092                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
5093                    let mut regular_expressions: Vec<&Expression> = Vec::new();
5094                    regular_expressions.extend(plain_expressions);
5095                    regular_expressions.extend(grouping_sets_expressions);
5096                    regular_expressions.extend(cube_expressions);
5097                    regular_expressions.extend(rollup_expressions);
5098
5099                    if self.config.pretty {
5100                        self.write_newline();
5101                        self.indent_level += 1;
5102                        self.write_indent();
5103                    } else {
5104                        self.write_space();
5105                    }
5106
5107                    for (i, expr) in regular_expressions.iter().enumerate() {
5108                        if i > 0 {
5109                            if self.config.pretty {
5110                                self.write(",");
5111                                self.write_newline();
5112                                self.write_indent();
5113                            } else {
5114                                self.write(", ");
5115                            }
5116                        }
5117                        self.generate_expression(expr)?;
5118                    }
5119
5120                    if self.config.pretty {
5121                        self.indent_level -= 1;
5122                    }
5123
5124                    // Output trailing WITH CUBE or WITH ROLLUP
5125                    if trailing_cube {
5126                        self.write_space();
5127                        self.write_keyword("WITH CUBE");
5128                    } else if trailing_rollup {
5129                        self.write_space();
5130                        self.write_keyword("WITH ROLLUP");
5131                    }
5132                }
5133
5134                // ClickHouse: WITH TOTALS
5135                if group_by.totals {
5136                    self.write_space();
5137                    self.write_keyword("WITH TOTALS");
5138                }
5139            }
5140        }
5141
5142        // HAVING
5143        if let Some(having) = &select.having {
5144            if self.config.pretty {
5145                // Output leading comments on their own lines before HAVING
5146                for comment in &having.comments {
5147                    self.write_newline();
5148                    self.write_indent();
5149                    self.write_formatted_comment(comment);
5150                }
5151            } else {
5152                for comment in &having.comments {
5153                    self.write_space();
5154                    self.write_formatted_comment(comment);
5155                }
5156            }
5157            self.write_clause_condition("HAVING", &having.this)?;
5158        }
5159
5160        // QUALIFY and WINDOW clause ordering depends on input SQL
5161        if select.qualify_after_window {
5162            // WINDOW before QUALIFY (DuckDB style)
5163            if let Some(windows) = &select.windows {
5164                self.write_window_clause(windows)?;
5165            }
5166            if let Some(qualify) = &select.qualify {
5167                self.write_clause_condition("QUALIFY", &qualify.this)?;
5168            }
5169        } else {
5170            // QUALIFY before WINDOW (Snowflake/BigQuery default)
5171            if let Some(qualify) = &select.qualify {
5172                self.write_clause_condition("QUALIFY", &qualify.this)?;
5173            }
5174            if let Some(windows) = &select.windows {
5175                self.write_window_clause(windows)?;
5176            }
5177        }
5178
5179        // DISTRIBUTE BY (Hive/Spark)
5180        if let Some(distribute_by) = &select.distribute_by {
5181            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
5182        }
5183
5184        // CLUSTER BY (Hive/Spark)
5185        if let Some(cluster_by) = &select.cluster_by {
5186            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
5187        }
5188
5189        // SORT BY (Hive/Spark - comes before ORDER BY)
5190        if let Some(sort_by) = &select.sort_by {
5191            self.write_order_clause("SORT BY", &sort_by.expressions)?;
5192        }
5193
5194        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
5195        if let Some(order_by) = &select.order_by {
5196            if self.config.pretty {
5197                // Output leading comments on their own lines before ORDER BY
5198                for comment in &order_by.comments {
5199                    self.write_newline();
5200                    self.write_indent();
5201                    self.write_formatted_comment(comment);
5202                }
5203            } else {
5204                for comment in &order_by.comments {
5205                    self.write_space();
5206                    self.write_formatted_comment(comment);
5207                }
5208            }
5209            let keyword = if order_by.siblings {
5210                "ORDER SIBLINGS BY"
5211            } else {
5212                "ORDER BY"
5213            };
5214            self.write_order_clause(keyword, &order_by.expressions)?;
5215        }
5216
5217        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
5218        if select.order_by.is_none()
5219            && select.fetch.is_some()
5220            && matches!(
5221                self.config.dialect,
5222                Some(DialectType::TSQL) | Some(DialectType::Fabric)
5223            )
5224        {
5225            if self.config.pretty {
5226                self.write_newline();
5227                self.write_indent();
5228            } else {
5229                self.write_space();
5230            }
5231            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
5232        }
5233
5234        // LIMIT and OFFSET
5235        // PostgreSQL and others use: LIMIT count OFFSET offset
5236        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
5237        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
5238        let is_presto_like = matches!(
5239            self.config.dialect,
5240            Some(DialectType::Presto) | Some(DialectType::Trino)
5241        );
5242
5243        if is_presto_like && select.offset.is_some() {
5244            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
5245            if let Some(offset) = &select.offset {
5246                if self.config.pretty {
5247                    self.write_newline();
5248                    self.write_indent();
5249                } else {
5250                    self.write_space();
5251                }
5252                self.write_keyword("OFFSET");
5253                self.write_space();
5254                self.write_limit_expr(&offset.this)?;
5255                if offset.rows == Some(true) {
5256                    self.write_space();
5257                    self.write_keyword("ROWS");
5258                }
5259            }
5260            if let Some(limit) = &select.limit {
5261                if self.config.pretty {
5262                    self.write_newline();
5263                    self.write_indent();
5264                } else {
5265                    self.write_space();
5266                }
5267                self.write_keyword("LIMIT");
5268                self.write_space();
5269                self.write_limit_expr(&limit.this)?;
5270                if limit.percent {
5271                    self.write_space();
5272                    self.write_keyword("PERCENT");
5273                }
5274                // Emit any comments that were captured from before the LIMIT keyword
5275                for comment in &limit.comments {
5276                    self.write(" ");
5277                    self.write_formatted_comment(comment);
5278                }
5279            }
5280        } else {
5281            // Check if FETCH will be converted to LIMIT (used for ordering)
5282            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
5283                !fetch.percent
5284                    && !fetch.with_ties
5285                    && fetch.count.is_some()
5286                    && matches!(
5287                        self.config.dialect,
5288                        Some(DialectType::Spark)
5289                            | Some(DialectType::Hive)
5290                            | Some(DialectType::DuckDB)
5291                            | Some(DialectType::SQLite)
5292                            | Some(DialectType::MySQL)
5293                            | Some(DialectType::BigQuery)
5294                            | Some(DialectType::Databricks)
5295                            | Some(DialectType::StarRocks)
5296                            | Some(DialectType::Doris)
5297                            | Some(DialectType::Athena)
5298                            | Some(DialectType::ClickHouse)
5299                            | Some(DialectType::Redshift)
5300                    )
5301            });
5302
5303            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
5304            if let Some(limit) = &select.limit {
5305                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
5306                if !matches!(
5307                    self.config.dialect,
5308                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5309                ) {
5310                    if self.config.pretty {
5311                        self.write_newline();
5312                        self.write_indent();
5313                    } else {
5314                        self.write_space();
5315                    }
5316                    self.write_keyword("LIMIT");
5317                    self.write_space();
5318                    self.write_limit_expr(&limit.this)?;
5319                    if limit.percent {
5320                        self.write_space();
5321                        self.write_keyword("PERCENT");
5322                    }
5323                    // Emit any comments that were captured from before the LIMIT keyword
5324                    for comment in &limit.comments {
5325                        self.write(" ");
5326                        self.write_formatted_comment(comment);
5327                    }
5328                }
5329            }
5330
5331            // Convert TOP to LIMIT for non-TOP dialects
5332            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
5333                if let Some(top) = &select.top {
5334                    if !top.percent && !top.with_ties {
5335                        if self.config.pretty {
5336                            self.write_newline();
5337                            self.write_indent();
5338                        } else {
5339                            self.write_space();
5340                        }
5341                        self.write_keyword("LIMIT");
5342                        self.write_space();
5343                        self.generate_expression(&top.this)?;
5344                    }
5345                }
5346            }
5347
5348            // If FETCH will be converted to LIMIT and there's also OFFSET,
5349            // emit LIMIT from FETCH BEFORE the OFFSET
5350            if fetch_as_limit && select.offset.is_some() {
5351                if let Some(fetch) = &select.fetch {
5352                    if self.config.pretty {
5353                        self.write_newline();
5354                        self.write_indent();
5355                    } else {
5356                        self.write_space();
5357                    }
5358                    self.write_keyword("LIMIT");
5359                    self.write_space();
5360                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5361                }
5362            }
5363
5364            // OFFSET
5365            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
5366            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
5367            if let Some(offset) = &select.offset {
5368                if self.config.pretty {
5369                    self.write_newline();
5370                    self.write_indent();
5371                } else {
5372                    self.write_space();
5373                }
5374                if matches!(
5375                    self.config.dialect,
5376                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5377                ) {
5378                    // SQL Server 2012+ OFFSET ... FETCH syntax
5379                    self.write_keyword("OFFSET");
5380                    self.write_space();
5381                    self.write_limit_expr(&offset.this)?;
5382                    self.write_space();
5383                    self.write_keyword("ROWS");
5384                    // If there was a real LIMIT, use FETCH NEXT ... ROWS ONLY.
5385                    // PostgreSQL LIMIT NULL / LIMIT ALL mean "no limit", so
5386                    // T-SQL/Fabric should keep the OFFSET without FETCH.
5387                    if let Some(limit) = &select.limit {
5388                        if !Self::is_noop_limit_expr(&limit.this) {
5389                            self.write_space();
5390                            self.write_keyword("FETCH NEXT");
5391                            self.write_space();
5392                            self.write_limit_expr(&limit.this)?;
5393                            self.write_space();
5394                            self.write_keyword("ROWS ONLY");
5395                        }
5396                    }
5397                } else {
5398                    self.write_keyword("OFFSET");
5399                    self.write_space();
5400                    self.write_limit_expr(&offset.this)?;
5401                    // Output ROWS keyword if it was in the original SQL
5402                    if offset.rows == Some(true) {
5403                        self.write_space();
5404                        self.write_keyword("ROWS");
5405                    }
5406                }
5407            }
5408        }
5409
5410        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
5411        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5412            if let Some(limit_by) = &select.limit_by {
5413                if !limit_by.is_empty() {
5414                    self.write_space();
5415                    self.write_keyword("BY");
5416                    self.write_space();
5417                    for (i, expr) in limit_by.iter().enumerate() {
5418                        if i > 0 {
5419                            self.write(", ");
5420                        }
5421                        self.generate_expression(expr)?;
5422                    }
5423                }
5424            }
5425        }
5426
5427        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
5428        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5429            if let Some(settings) = &select.settings {
5430                if self.config.pretty {
5431                    self.write_newline();
5432                    self.write_indent();
5433                } else {
5434                    self.write_space();
5435                }
5436                self.write_keyword("SETTINGS");
5437                self.write_space();
5438                for (i, expr) in settings.iter().enumerate() {
5439                    if i > 0 {
5440                        self.write(", ");
5441                    }
5442                    self.generate_expression(expr)?;
5443                }
5444            }
5445
5446            if let Some(format_expr) = &select.format {
5447                if self.config.pretty {
5448                    self.write_newline();
5449                    self.write_indent();
5450                } else {
5451                    self.write_space();
5452                }
5453                self.write_keyword("FORMAT");
5454                self.write_space();
5455                self.generate_expression(format_expr)?;
5456            }
5457        }
5458
5459        // FETCH FIRST/NEXT
5460        if let Some(fetch) = &select.fetch {
5461            // Check if we already emitted LIMIT from FETCH before OFFSET
5462            let fetch_already_as_limit = select.offset.is_some()
5463                && !fetch.percent
5464                && !fetch.with_ties
5465                && fetch.count.is_some()
5466                && matches!(
5467                    self.config.dialect,
5468                    Some(DialectType::Spark)
5469                        | Some(DialectType::Hive)
5470                        | Some(DialectType::DuckDB)
5471                        | Some(DialectType::SQLite)
5472                        | Some(DialectType::MySQL)
5473                        | Some(DialectType::BigQuery)
5474                        | Some(DialectType::Databricks)
5475                        | Some(DialectType::StarRocks)
5476                        | Some(DialectType::Doris)
5477                        | Some(DialectType::Athena)
5478                        | Some(DialectType::ClickHouse)
5479                        | Some(DialectType::Redshift)
5480                );
5481
5482            if fetch_already_as_limit {
5483                // Already emitted as LIMIT before OFFSET, skip
5484            } else {
5485                if self.config.pretty {
5486                    self.write_newline();
5487                    self.write_indent();
5488                } else {
5489                    self.write_space();
5490                }
5491
5492                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
5493                let use_limit = !fetch.percent
5494                    && !fetch.with_ties
5495                    && fetch.count.is_some()
5496                    && matches!(
5497                        self.config.dialect,
5498                        Some(DialectType::Spark)
5499                            | Some(DialectType::Hive)
5500                            | Some(DialectType::DuckDB)
5501                            | Some(DialectType::SQLite)
5502                            | Some(DialectType::MySQL)
5503                            | Some(DialectType::BigQuery)
5504                            | Some(DialectType::Databricks)
5505                            | Some(DialectType::StarRocks)
5506                            | Some(DialectType::Doris)
5507                            | Some(DialectType::Athena)
5508                            | Some(DialectType::ClickHouse)
5509                            | Some(DialectType::Redshift)
5510                    );
5511
5512                if use_limit {
5513                    self.write_keyword("LIMIT");
5514                    self.write_space();
5515                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5516                } else {
5517                    self.write_keyword("FETCH");
5518                    self.write_space();
5519                    self.write_keyword(&fetch.direction);
5520                    if let Some(ref count) = fetch.count {
5521                        self.write_space();
5522                        self.generate_expression(count)?;
5523                    }
5524                    if fetch.percent {
5525                        self.write_space();
5526                        self.write_keyword("PERCENT");
5527                    }
5528                    if fetch.rows {
5529                        self.write_space();
5530                        self.write_keyword("ROWS");
5531                    }
5532                    if fetch.with_ties {
5533                        self.write_space();
5534                        self.write_keyword("WITH TIES");
5535                    } else {
5536                        self.write_space();
5537                        self.write_keyword("ONLY");
5538                    }
5539                }
5540            } // close fetch_already_as_limit else
5541        }
5542
5543        // SAMPLE / TABLESAMPLE
5544        if let Some(sample) = &select.sample {
5545            use crate::dialects::DialectType;
5546            if self.config.pretty {
5547                self.write_newline();
5548            } else {
5549                self.write_space();
5550            }
5551
5552            if sample.is_using_sample {
5553                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5554                self.write_keyword("USING SAMPLE");
5555                self.generate_sample_body(sample)?;
5556            } else {
5557                self.write_keyword("TABLESAMPLE");
5558
5559                // Snowflake defaults to BERNOULLI when no explicit method is given
5560                let snowflake_bernoulli =
5561                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5562                        && !sample.explicit_method;
5563                if snowflake_bernoulli {
5564                    self.write_space();
5565                    self.write_keyword("BERNOULLI");
5566                }
5567
5568                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5569                if matches!(sample.method, SampleMethod::Bucket) {
5570                    self.write_space();
5571                    self.write("(");
5572                    self.write_keyword("BUCKET");
5573                    self.write_space();
5574                    if let Some(ref num) = sample.bucket_numerator {
5575                        self.generate_expression(num)?;
5576                    }
5577                    self.write_space();
5578                    self.write_keyword("OUT OF");
5579                    self.write_space();
5580                    if let Some(ref denom) = sample.bucket_denominator {
5581                        self.generate_expression(denom)?;
5582                    }
5583                    if let Some(ref field) = sample.bucket_field {
5584                        self.write_space();
5585                        self.write_keyword("ON");
5586                        self.write_space();
5587                        self.generate_expression(field)?;
5588                    }
5589                    self.write(")");
5590                } else if sample.unit_after_size {
5591                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5592                    if sample.explicit_method && sample.method_before_size {
5593                        self.write_space();
5594                        match sample.method {
5595                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5596                            SampleMethod::System => self.write_keyword("SYSTEM"),
5597                            SampleMethod::Block => self.write_keyword("BLOCK"),
5598                            SampleMethod::Row => self.write_keyword("ROW"),
5599                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5600                            _ => {}
5601                        }
5602                    }
5603                    self.write(" (");
5604                    self.generate_expression(&sample.size)?;
5605                    self.write_space();
5606                    match sample.method {
5607                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5608                        SampleMethod::Row => self.write_keyword("ROWS"),
5609                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5610                        _ => {
5611                            self.write_keyword("PERCENT");
5612                        }
5613                    }
5614                    self.write(")");
5615                } else {
5616                    // Syntax: TABLESAMPLE METHOD (size)
5617                    self.write_space();
5618                    match sample.method {
5619                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5620                        SampleMethod::System => self.write_keyword("SYSTEM"),
5621                        SampleMethod::Block => self.write_keyword("BLOCK"),
5622                        SampleMethod::Row => self.write_keyword("ROW"),
5623                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5624                        SampleMethod::Bucket => {}
5625                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5626                    }
5627                    self.write(" (");
5628                    self.generate_expression(&sample.size)?;
5629                    if matches!(sample.method, SampleMethod::Percent) {
5630                        self.write_space();
5631                        self.write_keyword("PERCENT");
5632                    }
5633                    self.write(")");
5634                }
5635            }
5636
5637            if let Some(seed) = &sample.seed {
5638                self.write_space();
5639                // Databricks/Spark use REPEATABLE, not SEED
5640                let use_seed = sample.use_seed_keyword
5641                    && !matches!(
5642                        self.config.dialect,
5643                        Some(crate::dialects::DialectType::Databricks)
5644                            | Some(crate::dialects::DialectType::Spark)
5645                    );
5646                if use_seed {
5647                    self.write_keyword("SEED");
5648                } else {
5649                    self.write_keyword("REPEATABLE");
5650                }
5651                self.write(" (");
5652                self.generate_expression(seed)?;
5653                self.write(")");
5654            }
5655        }
5656
5657        // FOR UPDATE/SHARE locks
5658        // Skip locking clauses for dialects that don't support them
5659        if self.config.locking_reads_supported {
5660            for lock in &select.locks {
5661                if self.config.pretty {
5662                    self.write_newline();
5663                    self.write_indent();
5664                } else {
5665                    self.write_space();
5666                }
5667                self.generate_lock(lock)?;
5668            }
5669        }
5670
5671        // FOR XML clause (T-SQL)
5672        if !select.for_xml.is_empty() {
5673            if self.config.pretty {
5674                self.write_newline();
5675                self.write_indent();
5676            } else {
5677                self.write_space();
5678            }
5679            self.write_keyword("FOR XML");
5680            for (i, opt) in select.for_xml.iter().enumerate() {
5681                if self.config.pretty {
5682                    if i > 0 {
5683                        self.write(",");
5684                    }
5685                    self.write_newline();
5686                    self.write_indent();
5687                    self.write("  "); // extra indent for options
5688                } else {
5689                    if i > 0 {
5690                        self.write(",");
5691                    }
5692                    self.write_space();
5693                }
5694                self.generate_for_xml_option(opt)?;
5695            }
5696        }
5697
5698        // FOR JSON clause (T-SQL)
5699        if !select.for_json.is_empty()
5700            && matches!(
5701                self.config.dialect,
5702                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5703            )
5704        {
5705            if self.config.pretty {
5706                self.write_newline();
5707                self.write_indent();
5708            } else {
5709                self.write_space();
5710            }
5711            self.write_keyword("FOR JSON");
5712            for (i, opt) in select.for_json.iter().enumerate() {
5713                if self.config.pretty {
5714                    if i > 0 {
5715                        self.write(",");
5716                    }
5717                    self.write_newline();
5718                    self.write_indent();
5719                    self.write("  "); // extra indent for options
5720                } else {
5721                    if i > 0 {
5722                        self.write(",");
5723                    }
5724                    self.write_space();
5725                }
5726                self.generate_for_xml_option(opt)?;
5727            }
5728        }
5729
5730        // TSQL: OPTION clause
5731        if let Some(ref option) = select.option {
5732            if matches!(
5733                self.config.dialect,
5734                Some(crate::dialects::DialectType::TSQL)
5735                    | Some(crate::dialects::DialectType::Fabric)
5736            ) {
5737                self.write_space();
5738                self.write(option);
5739            }
5740        }
5741
5742        Ok(())
5743    }
5744
5745    /// Generate a single FOR XML option
5746    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5747        match opt {
5748            Expression::QueryOption(qo) => {
5749                // Extract the option name from Var
5750                if let Expression::Var(var) = &*qo.this {
5751                    self.write(&var.this);
5752                } else {
5753                    self.generate_expression(&qo.this)?;
5754                }
5755                // If there's an expression (like PATH('element')), output it in parens
5756                if let Some(expr) = &qo.expression {
5757                    self.write("(");
5758                    self.generate_expression(expr)?;
5759                    self.write(")");
5760                }
5761            }
5762            _ => {
5763                self.generate_expression(opt)?;
5764            }
5765        }
5766        Ok(())
5767    }
5768
5769    fn generate_with(&mut self, with: &With) -> Result<()> {
5770        use crate::dialects::DialectType;
5771
5772        // Output leading comments before WITH
5773        for comment in &with.leading_comments {
5774            self.write_formatted_comment(comment);
5775            self.write(" ");
5776        }
5777        self.write_keyword("WITH");
5778        if with.recursive && self.config.cte_recursive_keyword_required {
5779            self.write_space();
5780            self.write_keyword("RECURSIVE");
5781        }
5782        self.write_space();
5783
5784        // BigQuery doesn't support column aliases in CTE definitions
5785        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5786
5787        for (i, cte) in with.ctes.iter().enumerate() {
5788            if i > 0 {
5789                self.write(",");
5790                if self.config.pretty {
5791                    self.write_space();
5792                } else {
5793                    self.write(" ");
5794                }
5795            }
5796            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5797                self.generate_expression(&cte.this)?;
5798                self.write_space();
5799                self.write_keyword("AS");
5800                self.write_space();
5801                self.generate_identifier(&cte.alias)?;
5802                continue;
5803            }
5804            self.generate_identifier(&cte.alias)?;
5805            // Output CTE comments after alias name, before AS
5806            for comment in &cte.comments {
5807                self.write_space();
5808                self.write_formatted_comment(comment);
5809            }
5810            if !cte.columns.is_empty() && !skip_cte_columns {
5811                self.write("(");
5812                for (j, col) in cte.columns.iter().enumerate() {
5813                    if j > 0 {
5814                        self.write(", ");
5815                    }
5816                    self.generate_identifier(col)?;
5817                }
5818                self.write(")");
5819            }
5820            // USING KEY (columns) for DuckDB recursive CTEs
5821            if !cte.key_expressions.is_empty() {
5822                self.write_space();
5823                self.write_keyword("USING KEY");
5824                self.write(" (");
5825                for (i, key) in cte.key_expressions.iter().enumerate() {
5826                    if i > 0 {
5827                        self.write(", ");
5828                    }
5829                    self.generate_identifier(key)?;
5830                }
5831                self.write(")");
5832            }
5833            self.write_space();
5834            self.write_keyword("AS");
5835            // MATERIALIZED / NOT MATERIALIZED
5836            if let Some(materialized) = cte.materialized {
5837                self.write_space();
5838                if materialized {
5839                    self.write_keyword("MATERIALIZED");
5840                } else {
5841                    self.write_keyword("NOT MATERIALIZED");
5842                }
5843            }
5844            self.write(" (");
5845            if self.config.pretty {
5846                self.write_newline();
5847                self.indent_level += 1;
5848                self.write_indent();
5849            }
5850            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5851            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5852            let wrap_values_in_select = matches!(
5853                self.config.dialect,
5854                Some(DialectType::Spark) | Some(DialectType::Databricks)
5855            ) && matches!(&cte.this, Expression::Values(_));
5856
5857            if wrap_values_in_select {
5858                self.write_keyword("SELECT");
5859                self.write(" * ");
5860                self.write_keyword("FROM");
5861                self.write_space();
5862            }
5863            self.generate_expression(&cte.this)?;
5864            if self.config.pretty {
5865                self.write_newline();
5866                self.indent_level -= 1;
5867                self.write_indent();
5868            }
5869            self.write(")");
5870        }
5871
5872        // Generate SEARCH/CYCLE clause if present
5873        if let Some(search) = &with.search {
5874            self.write_space();
5875            self.generate_expression(search)?;
5876        }
5877
5878        Ok(())
5879    }
5880
5881    /// Generate joins with proper nesting structure for pretty printing.
5882    /// Deferred-condition joins "own" the non-deferred joins that follow them
5883    /// within the same nesting_group.
5884    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5885        let mut i = 0;
5886        while i < joins.len() {
5887            if joins[i].deferred_condition {
5888                let parent_group = joins[i].nesting_group;
5889
5890                // This join owns the following non-deferred joins in the same nesting_group
5891                // First output the join keyword and table (without condition)
5892                self.generate_join_without_condition(&joins[i])?;
5893
5894                // Find the range of child joins: same nesting_group and not deferred
5895                let child_start = i + 1;
5896                let mut child_end = child_start;
5897                while child_end < joins.len()
5898                    && !joins[child_end].deferred_condition
5899                    && joins[child_end].nesting_group == parent_group
5900                {
5901                    child_end += 1;
5902                }
5903
5904                // Output child joins with extra indentation
5905                if child_start < child_end {
5906                    self.indent_level += 1;
5907                    for j in child_start..child_end {
5908                        self.generate_join(&joins[j])?;
5909                    }
5910                    self.indent_level -= 1;
5911                }
5912
5913                // Output the deferred condition at the parent level
5914                self.generate_join_condition(&joins[i])?;
5915
5916                i = child_end;
5917            } else {
5918                // Regular join (no nesting)
5919                self.generate_join(&joins[i])?;
5920                i += 1;
5921            }
5922        }
5923        Ok(())
5924    }
5925
5926    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5927    /// Used for deferred-condition joins where the condition is output after child joins.
5928    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5929        // Save and temporarily clear the condition to prevent generate_join from outputting it
5930        // We achieve this by creating a modified copy
5931        let mut join_copy = join.clone();
5932        join_copy.on = None;
5933        join_copy.using = Vec::new();
5934        join_copy.deferred_condition = false;
5935        self.generate_join(&join_copy)
5936    }
5937
5938    fn generate_join(&mut self, join: &Join) -> Result<()> {
5939        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5940        if join.kind == JoinKind::Implicit {
5941            self.write(",");
5942            if self.config.pretty {
5943                self.write_newline();
5944                self.write_indent();
5945            } else {
5946                self.write_space();
5947            }
5948            self.generate_expression(&join.this)?;
5949            return Ok(());
5950        }
5951
5952        if self.config.pretty {
5953            self.write_newline();
5954            self.write_indent();
5955        } else {
5956            self.write_space();
5957        }
5958
5959        // Helper: format hint suffix (e.g., " LOOP" or "")
5960        // Only include join hints for dialects that support them
5961        let hint_str = if self.config.join_hints {
5962            join.join_hint
5963                .as_ref()
5964                .map(|h| format!(" {}", h))
5965                .unwrap_or_default()
5966        } else {
5967            String::new()
5968        };
5969
5970        let clickhouse_join_keyword =
5971            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5972                if let Some(hint) = &join.join_hint {
5973                    let mut global = false;
5974                    let mut strictness: Option<&'static str> = None;
5975                    for part in hint.split_whitespace() {
5976                        if part.eq_ignore_ascii_case("GLOBAL") {
5977                            global = true;
5978                        } else if part.eq_ignore_ascii_case("ALL") {
5979                            strictness = Some("ALL");
5980                        } else if part.eq_ignore_ascii_case("ANY") {
5981                            strictness = Some("ANY");
5982                        } else if part.eq_ignore_ascii_case("ASOF") {
5983                            strictness = Some("ASOF");
5984                        } else if part.eq_ignore_ascii_case("SEMI") {
5985                            strictness = Some("SEMI");
5986                        } else if part.eq_ignore_ascii_case("ANTI") {
5987                            strictness = Some("ANTI");
5988                        }
5989                    }
5990
5991                    if global || strictness.is_some() {
5992                        let join_type = match join.kind {
5993                            JoinKind::Left => {
5994                                if join.use_outer_keyword {
5995                                    "LEFT OUTER"
5996                                } else if join.use_inner_keyword {
5997                                    "LEFT INNER"
5998                                } else {
5999                                    "LEFT"
6000                                }
6001                            }
6002                            JoinKind::Right => {
6003                                if join.use_outer_keyword {
6004                                    "RIGHT OUTER"
6005                                } else if join.use_inner_keyword {
6006                                    "RIGHT INNER"
6007                                } else {
6008                                    "RIGHT"
6009                                }
6010                            }
6011                            JoinKind::Full => {
6012                                if join.use_outer_keyword {
6013                                    "FULL OUTER"
6014                                } else {
6015                                    "FULL"
6016                                }
6017                            }
6018                            JoinKind::Inner => {
6019                                if join.use_inner_keyword {
6020                                    "INNER"
6021                                } else {
6022                                    ""
6023                                }
6024                            }
6025                            _ => "",
6026                        };
6027
6028                        let mut parts = Vec::new();
6029                        if global {
6030                            parts.push("GLOBAL");
6031                        }
6032                        if !join_type.is_empty() {
6033                            parts.push(join_type);
6034                        }
6035                        if let Some(strict) = strictness {
6036                            parts.push(strict);
6037                        }
6038                        parts.push("JOIN");
6039                        Some(parts.join(" "))
6040                    } else {
6041                        None
6042                    }
6043                } else {
6044                    None
6045                }
6046            } else {
6047                None
6048            };
6049
6050        // Output any comments associated with this join
6051        // In pretty mode, comments go on their own line before the join keyword
6052        // In non-pretty mode, comments go inline before the join keyword
6053        if !join.comments.is_empty() {
6054            if self.config.pretty {
6055                // In pretty mode, go back before the newline+indent we just wrote
6056                // and output comments on their own lines
6057                // We need to output comments BEFORE the join keyword on separate lines
6058                // Trim the trailing newline+indent we already wrote
6059                let trimmed = self.output.trim_end().len();
6060                self.output.truncate(trimmed);
6061                for comment in &join.comments {
6062                    self.write_newline();
6063                    self.write_indent();
6064                    self.write_formatted_comment(comment);
6065                }
6066                self.write_newline();
6067                self.write_indent();
6068            } else {
6069                for comment in &join.comments {
6070                    self.write_formatted_comment(comment);
6071                    self.write_space();
6072                }
6073            }
6074        }
6075
6076        let directed_str = if join.directed { " DIRECTED" } else { "" };
6077
6078        if let Some(keyword) = clickhouse_join_keyword {
6079            self.write_keyword(&keyword);
6080        } else {
6081            match join.kind {
6082                JoinKind::Inner => {
6083                    if join.use_inner_keyword {
6084                        if hint_str.is_empty() && directed_str.is_empty() {
6085                            self.write_keyword("INNER JOIN");
6086                        } else {
6087                            self.write_keyword("INNER");
6088                            if !hint_str.is_empty() {
6089                                self.write_keyword(&hint_str);
6090                            }
6091                            if !directed_str.is_empty() {
6092                                self.write_keyword(directed_str);
6093                            }
6094                            self.write_keyword(" JOIN");
6095                        }
6096                    } else {
6097                        if !hint_str.is_empty() {
6098                            self.write_keyword(hint_str.trim());
6099                            self.write_keyword(" ");
6100                        }
6101                        if !directed_str.is_empty() {
6102                            self.write_keyword("DIRECTED ");
6103                        }
6104                        self.write_keyword("JOIN");
6105                    }
6106                }
6107                JoinKind::Left => {
6108                    if join.use_outer_keyword {
6109                        if hint_str.is_empty() && directed_str.is_empty() {
6110                            self.write_keyword("LEFT OUTER JOIN");
6111                        } else {
6112                            self.write_keyword("LEFT OUTER");
6113                            if !hint_str.is_empty() {
6114                                self.write_keyword(&hint_str);
6115                            }
6116                            if !directed_str.is_empty() {
6117                                self.write_keyword(directed_str);
6118                            }
6119                            self.write_keyword(" JOIN");
6120                        }
6121                    } else if join.use_inner_keyword {
6122                        if hint_str.is_empty() && directed_str.is_empty() {
6123                            self.write_keyword("LEFT INNER JOIN");
6124                        } else {
6125                            self.write_keyword("LEFT INNER");
6126                            if !hint_str.is_empty() {
6127                                self.write_keyword(&hint_str);
6128                            }
6129                            if !directed_str.is_empty() {
6130                                self.write_keyword(directed_str);
6131                            }
6132                            self.write_keyword(" JOIN");
6133                        }
6134                    } else {
6135                        if hint_str.is_empty() && directed_str.is_empty() {
6136                            self.write_keyword("LEFT JOIN");
6137                        } else {
6138                            self.write_keyword("LEFT");
6139                            if !hint_str.is_empty() {
6140                                self.write_keyword(&hint_str);
6141                            }
6142                            if !directed_str.is_empty() {
6143                                self.write_keyword(directed_str);
6144                            }
6145                            self.write_keyword(" JOIN");
6146                        }
6147                    }
6148                }
6149                JoinKind::Right => {
6150                    if join.use_outer_keyword {
6151                        if hint_str.is_empty() && directed_str.is_empty() {
6152                            self.write_keyword("RIGHT OUTER JOIN");
6153                        } else {
6154                            self.write_keyword("RIGHT OUTER");
6155                            if !hint_str.is_empty() {
6156                                self.write_keyword(&hint_str);
6157                            }
6158                            if !directed_str.is_empty() {
6159                                self.write_keyword(directed_str);
6160                            }
6161                            self.write_keyword(" JOIN");
6162                        }
6163                    } else if join.use_inner_keyword {
6164                        if hint_str.is_empty() && directed_str.is_empty() {
6165                            self.write_keyword("RIGHT INNER JOIN");
6166                        } else {
6167                            self.write_keyword("RIGHT INNER");
6168                            if !hint_str.is_empty() {
6169                                self.write_keyword(&hint_str);
6170                            }
6171                            if !directed_str.is_empty() {
6172                                self.write_keyword(directed_str);
6173                            }
6174                            self.write_keyword(" JOIN");
6175                        }
6176                    } else {
6177                        if hint_str.is_empty() && directed_str.is_empty() {
6178                            self.write_keyword("RIGHT JOIN");
6179                        } else {
6180                            self.write_keyword("RIGHT");
6181                            if !hint_str.is_empty() {
6182                                self.write_keyword(&hint_str);
6183                            }
6184                            if !directed_str.is_empty() {
6185                                self.write_keyword(directed_str);
6186                            }
6187                            self.write_keyword(" JOIN");
6188                        }
6189                    }
6190                }
6191                JoinKind::Full => {
6192                    if join.use_outer_keyword {
6193                        if hint_str.is_empty() && directed_str.is_empty() {
6194                            self.write_keyword("FULL OUTER JOIN");
6195                        } else {
6196                            self.write_keyword("FULL OUTER");
6197                            if !hint_str.is_empty() {
6198                                self.write_keyword(&hint_str);
6199                            }
6200                            if !directed_str.is_empty() {
6201                                self.write_keyword(directed_str);
6202                            }
6203                            self.write_keyword(" JOIN");
6204                        }
6205                    } else {
6206                        if hint_str.is_empty() && directed_str.is_empty() {
6207                            self.write_keyword("FULL JOIN");
6208                        } else {
6209                            self.write_keyword("FULL");
6210                            if !hint_str.is_empty() {
6211                                self.write_keyword(&hint_str);
6212                            }
6213                            if !directed_str.is_empty() {
6214                                self.write_keyword(directed_str);
6215                            }
6216                            self.write_keyword(" JOIN");
6217                        }
6218                    }
6219                }
6220                JoinKind::Outer => {
6221                    if directed_str.is_empty() {
6222                        self.write_keyword("OUTER JOIN");
6223                    } else {
6224                        self.write_keyword("OUTER");
6225                        self.write_keyword(directed_str);
6226                        self.write_keyword(" JOIN");
6227                    }
6228                }
6229                JoinKind::Cross => {
6230                    if directed_str.is_empty() {
6231                        self.write_keyword("CROSS JOIN");
6232                    } else {
6233                        self.write_keyword("CROSS");
6234                        self.write_keyword(directed_str);
6235                        self.write_keyword(" JOIN");
6236                    }
6237                }
6238                JoinKind::Natural => {
6239                    if join.use_inner_keyword {
6240                        if directed_str.is_empty() {
6241                            self.write_keyword("NATURAL INNER JOIN");
6242                        } else {
6243                            self.write_keyword("NATURAL INNER");
6244                            self.write_keyword(directed_str);
6245                            self.write_keyword(" JOIN");
6246                        }
6247                    } else {
6248                        if directed_str.is_empty() {
6249                            self.write_keyword("NATURAL JOIN");
6250                        } else {
6251                            self.write_keyword("NATURAL");
6252                            self.write_keyword(directed_str);
6253                            self.write_keyword(" JOIN");
6254                        }
6255                    }
6256                }
6257                JoinKind::NaturalLeft => {
6258                    if join.use_outer_keyword {
6259                        if directed_str.is_empty() {
6260                            self.write_keyword("NATURAL LEFT OUTER JOIN");
6261                        } else {
6262                            self.write_keyword("NATURAL LEFT OUTER");
6263                            self.write_keyword(directed_str);
6264                            self.write_keyword(" JOIN");
6265                        }
6266                    } else {
6267                        if directed_str.is_empty() {
6268                            self.write_keyword("NATURAL LEFT JOIN");
6269                        } else {
6270                            self.write_keyword("NATURAL LEFT");
6271                            self.write_keyword(directed_str);
6272                            self.write_keyword(" JOIN");
6273                        }
6274                    }
6275                }
6276                JoinKind::NaturalRight => {
6277                    if join.use_outer_keyword {
6278                        if directed_str.is_empty() {
6279                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
6280                        } else {
6281                            self.write_keyword("NATURAL RIGHT OUTER");
6282                            self.write_keyword(directed_str);
6283                            self.write_keyword(" JOIN");
6284                        }
6285                    } else {
6286                        if directed_str.is_empty() {
6287                            self.write_keyword("NATURAL RIGHT JOIN");
6288                        } else {
6289                            self.write_keyword("NATURAL RIGHT");
6290                            self.write_keyword(directed_str);
6291                            self.write_keyword(" JOIN");
6292                        }
6293                    }
6294                }
6295                JoinKind::NaturalFull => {
6296                    if join.use_outer_keyword {
6297                        if directed_str.is_empty() {
6298                            self.write_keyword("NATURAL FULL OUTER JOIN");
6299                        } else {
6300                            self.write_keyword("NATURAL FULL OUTER");
6301                            self.write_keyword(directed_str);
6302                            self.write_keyword(" JOIN");
6303                        }
6304                    } else {
6305                        if directed_str.is_empty() {
6306                            self.write_keyword("NATURAL FULL JOIN");
6307                        } else {
6308                            self.write_keyword("NATURAL FULL");
6309                            self.write_keyword(directed_str);
6310                            self.write_keyword(" JOIN");
6311                        }
6312                    }
6313                }
6314                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
6315                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
6316                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
6317                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
6318                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
6319                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
6320                JoinKind::CrossApply => {
6321                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
6322                    if matches!(
6323                        self.config.dialect,
6324                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6325                    ) {
6326                        self.write_keyword("CROSS APPLY");
6327                    } else {
6328                        self.write_keyword("INNER JOIN LATERAL");
6329                    }
6330                }
6331                JoinKind::OuterApply => {
6332                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL-like dialects
6333                    if matches!(
6334                        self.config.dialect,
6335                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6336                    ) {
6337                        self.write_keyword("OUTER APPLY");
6338                    } else {
6339                        self.write_keyword("LEFT JOIN LATERAL");
6340                    }
6341                }
6342                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
6343                JoinKind::AsOfLeft => {
6344                    if join.use_outer_keyword {
6345                        self.write_keyword("ASOF LEFT OUTER JOIN");
6346                    } else {
6347                        self.write_keyword("ASOF LEFT JOIN");
6348                    }
6349                }
6350                JoinKind::AsOfRight => {
6351                    if join.use_outer_keyword {
6352                        self.write_keyword("ASOF RIGHT OUTER JOIN");
6353                    } else {
6354                        self.write_keyword("ASOF RIGHT JOIN");
6355                    }
6356                }
6357                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
6358                JoinKind::LeftLateral => {
6359                    if join.use_outer_keyword {
6360                        self.write_keyword("LEFT OUTER LATERAL JOIN");
6361                    } else {
6362                        self.write_keyword("LEFT LATERAL JOIN");
6363                    }
6364                }
6365                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
6366                JoinKind::Implicit => {
6367                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
6368                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
6369                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
6370                    use crate::dialects::DialectType;
6371                    let is_cj_dialect = matches!(
6372                        self.config.dialect,
6373                        Some(DialectType::BigQuery)
6374                            | Some(DialectType::Hive)
6375                            | Some(DialectType::Spark)
6376                            | Some(DialectType::Databricks)
6377                    );
6378                    let source_is_same = self.config.source_dialect.is_some()
6379                        && self.config.source_dialect == self.config.dialect;
6380                    let source_is_cj = matches!(
6381                        self.config.source_dialect,
6382                        Some(DialectType::BigQuery)
6383                            | Some(DialectType::Hive)
6384                            | Some(DialectType::Spark)
6385                            | Some(DialectType::Databricks)
6386                    );
6387                    if is_cj_dialect
6388                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
6389                    {
6390                        self.write_keyword("CROSS JOIN");
6391                    } else {
6392                        // Implicit join uses comma: FROM a, b
6393                        // We already wrote a space before the match, so replace with comma
6394                        // by removing trailing space and writing ", "
6395                        self.output.truncate(self.output.trim_end().len());
6396                        self.write(",");
6397                    }
6398                }
6399                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
6400                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
6401                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
6402                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
6403            }
6404        }
6405
6406        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
6407        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
6408            match &join.this {
6409                Expression::Tuple(t) if t.expressions.is_empty() => {}
6410                Expression::Tuple(t) => {
6411                    self.write_space();
6412                    for (i, item) in t.expressions.iter().enumerate() {
6413                        if i > 0 {
6414                            self.write(", ");
6415                        }
6416                        self.generate_expression(item)?;
6417                    }
6418                }
6419                other => {
6420                    self.write_space();
6421                    self.generate_expression(other)?;
6422                }
6423            }
6424        } else {
6425            self.write_space();
6426            self.generate_expression(&join.this)?;
6427        }
6428
6429        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
6430        if !join.deferred_condition {
6431            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
6432            if let Some(match_cond) = &join.match_condition {
6433                self.write_space();
6434                self.write_keyword("MATCH_CONDITION");
6435                self.write(" (");
6436                self.generate_expression(match_cond)?;
6437                self.write(")");
6438            }
6439
6440            if let Some(on) = &join.on {
6441                if self.config.pretty {
6442                    self.write_newline();
6443                    self.indent_level += 1;
6444                    self.write_indent();
6445                    self.write_keyword("ON");
6446                    self.write_space();
6447                    self.generate_join_on_condition(on)?;
6448                    self.indent_level -= 1;
6449                } else {
6450                    self.write_space();
6451                    self.write_keyword("ON");
6452                    self.write_space();
6453                    self.generate_expression(on)?;
6454                }
6455            }
6456
6457            if !join.using.is_empty() {
6458                if self.config.pretty {
6459                    self.write_newline();
6460                    self.indent_level += 1;
6461                    self.write_indent();
6462                    self.write_keyword("USING");
6463                    self.write(" (");
6464                    for (i, col) in join.using.iter().enumerate() {
6465                        if i > 0 {
6466                            self.write(", ");
6467                        }
6468                        self.generate_identifier(col)?;
6469                    }
6470                    self.write(")");
6471                    self.indent_level -= 1;
6472                } else {
6473                    self.write_space();
6474                    self.write_keyword("USING");
6475                    self.write(" (");
6476                    for (i, col) in join.using.iter().enumerate() {
6477                        if i > 0 {
6478                            self.write(", ");
6479                        }
6480                        self.generate_identifier(col)?;
6481                    }
6482                    self.write(")");
6483                }
6484            }
6485        }
6486
6487        // Generate PIVOT/UNPIVOT expressions that follow this join
6488        for pivot in &join.pivots {
6489            self.write_space();
6490            self.generate_expression(pivot)?;
6491        }
6492
6493        Ok(())
6494    }
6495
6496    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
6497    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
6498        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
6499        if let Some(match_cond) = &join.match_condition {
6500            self.write_space();
6501            self.write_keyword("MATCH_CONDITION");
6502            self.write(" (");
6503            self.generate_expression(match_cond)?;
6504            self.write(")");
6505        }
6506
6507        if let Some(on) = &join.on {
6508            if self.config.pretty {
6509                self.write_newline();
6510                self.indent_level += 1;
6511                self.write_indent();
6512                self.write_keyword("ON");
6513                self.write_space();
6514                // In pretty mode, split AND conditions onto separate lines
6515                self.generate_join_on_condition(on)?;
6516                self.indent_level -= 1;
6517            } else {
6518                self.write_space();
6519                self.write_keyword("ON");
6520                self.write_space();
6521                self.generate_expression(on)?;
6522            }
6523        }
6524
6525        if !join.using.is_empty() {
6526            if self.config.pretty {
6527                self.write_newline();
6528                self.indent_level += 1;
6529                self.write_indent();
6530                self.write_keyword("USING");
6531                self.write(" (");
6532                for (i, col) in join.using.iter().enumerate() {
6533                    if i > 0 {
6534                        self.write(", ");
6535                    }
6536                    self.generate_identifier(col)?;
6537                }
6538                self.write(")");
6539                self.indent_level -= 1;
6540            } else {
6541                self.write_space();
6542                self.write_keyword("USING");
6543                self.write(" (");
6544                for (i, col) in join.using.iter().enumerate() {
6545                    if i > 0 {
6546                        self.write(", ");
6547                    }
6548                    self.generate_identifier(col)?;
6549                }
6550                self.write(")");
6551            }
6552        }
6553
6554        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6555        for pivot in &join.pivots {
6556            self.write_space();
6557            self.generate_expression(pivot)?;
6558        }
6559
6560        Ok(())
6561    }
6562
6563    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6564    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6565        if let Expression::And(and_op) = expr {
6566            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6567                self.generate_expression(conditions[0])?;
6568                for condition in conditions.iter().skip(1) {
6569                    self.write_newline();
6570                    self.write_indent();
6571                    self.write_keyword("AND");
6572                    self.write_space();
6573                    self.generate_expression(condition)?;
6574                }
6575                return Ok(());
6576            }
6577        }
6578
6579        self.generate_expression(expr)
6580    }
6581
6582    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6583        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6584        self.write("(");
6585        self.generate_expression(&jt.left)?;
6586
6587        // Generate all joins
6588        for join in &jt.joins {
6589            self.generate_join(join)?;
6590        }
6591
6592        // Generate LATERAL VIEW clauses (Hive/Spark)
6593        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6594            self.generate_lateral_view(lv, lv_idx)?;
6595        }
6596
6597        self.write(")");
6598
6599        // Alias
6600        if let Some(alias) = &jt.alias {
6601            self.write_space();
6602            self.write_keyword("AS");
6603            self.write_space();
6604            self.generate_identifier(alias)?;
6605        }
6606
6607        Ok(())
6608    }
6609
6610    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6611        use crate::dialects::DialectType;
6612
6613        if self.config.pretty {
6614            self.write_newline();
6615            self.write_indent();
6616        } else {
6617            self.write_space();
6618        }
6619
6620        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6621        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6622        let use_lateral_join = matches!(
6623            self.config.dialect,
6624            Some(DialectType::PostgreSQL)
6625                | Some(DialectType::DuckDB)
6626                | Some(DialectType::Snowflake)
6627                | Some(DialectType::TSQL)
6628                | Some(DialectType::Presto)
6629                | Some(DialectType::Trino)
6630                | Some(DialectType::Athena)
6631        );
6632
6633        // Check if target dialect should use UNNEST instead of EXPLODE
6634        let use_unnest = matches!(
6635            self.config.dialect,
6636            Some(DialectType::DuckDB)
6637                | Some(DialectType::Presto)
6638                | Some(DialectType::Trino)
6639                | Some(DialectType::Athena)
6640        );
6641
6642        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6643        let (is_posexplode, is_inline, func_args) = match &lv.this {
6644            Expression::Explode(uf) => {
6645                // Expression::Explode is the dedicated EXPLODE expression type
6646                (false, false, vec![uf.this.clone()])
6647            }
6648            Expression::Unnest(uf) => {
6649                let mut args = vec![uf.this.clone()];
6650                args.extend(uf.expressions.clone());
6651                (false, false, args)
6652            }
6653            Expression::Function(func) => {
6654                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6655                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6656                {
6657                    (true, false, func.args.clone())
6658                } else if func.name.eq_ignore_ascii_case("INLINE") {
6659                    (false, true, func.args.clone())
6660                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6661                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6662                {
6663                    (false, false, func.args.clone())
6664                } else {
6665                    (false, false, vec![])
6666                }
6667            }
6668            _ => (false, false, vec![]),
6669        };
6670
6671        if use_lateral_join {
6672            // Convert to CROSS JOIN for PostgreSQL-like dialects
6673            if lv.outer {
6674                self.write_keyword("LEFT JOIN LATERAL");
6675            } else {
6676                self.write_keyword("CROSS JOIN");
6677            }
6678            self.write_space();
6679
6680            if use_unnest && !func_args.is_empty() {
6681                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6682                // For DuckDB, also convert ARRAY(y) -> [y]
6683                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6684                    // DuckDB: ARRAY(y) -> [y]
6685                    func_args
6686                        .iter()
6687                        .map(|a| {
6688                            if let Expression::Function(ref f) = a {
6689                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6690                                    return Expression::ArrayFunc(Box::new(
6691                                        crate::expressions::ArrayConstructor {
6692                                            expressions: f.args.clone(),
6693                                            bracket_notation: true,
6694                                            use_list_keyword: false,
6695                                        },
6696                                    ));
6697                                }
6698                            }
6699                            a.clone()
6700                        })
6701                        .collect::<Vec<_>>()
6702                } else if matches!(
6703                    self.config.dialect,
6704                    Some(DialectType::Presto)
6705                        | Some(DialectType::Trino)
6706                        | Some(DialectType::Athena)
6707                ) {
6708                    // Presto: ARRAY(y) -> ARRAY[y]
6709                    func_args
6710                        .iter()
6711                        .map(|a| {
6712                            if let Expression::Function(ref f) = a {
6713                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6714                                    return Expression::ArrayFunc(Box::new(
6715                                        crate::expressions::ArrayConstructor {
6716                                            expressions: f.args.clone(),
6717                                            bracket_notation: true,
6718                                            use_list_keyword: false,
6719                                        },
6720                                    ));
6721                                }
6722                            }
6723                            a.clone()
6724                        })
6725                        .collect::<Vec<_>>()
6726                } else {
6727                    func_args
6728                };
6729
6730                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6731                if is_posexplode {
6732                    self.write_keyword("LATERAL");
6733                    self.write(" (");
6734                    self.write_keyword("SELECT");
6735                    self.write_space();
6736
6737                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6738                    // column_aliases[0] is the position column, rest are data columns
6739                    let pos_alias = if !lv.column_aliases.is_empty() {
6740                        lv.column_aliases[0].clone()
6741                    } else {
6742                        Identifier::new("pos")
6743                    };
6744                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6745                        lv.column_aliases[1..].to_vec()
6746                    } else {
6747                        vec![Identifier::new("col")]
6748                    };
6749
6750                    // pos - 1 AS pos
6751                    self.generate_identifier(&pos_alias)?;
6752                    self.write(" - 1");
6753                    self.write_space();
6754                    self.write_keyword("AS");
6755                    self.write_space();
6756                    self.generate_identifier(&pos_alias)?;
6757
6758                    // , col [, key, value ...]
6759                    for data_col in &data_aliases {
6760                        self.write(", ");
6761                        self.generate_identifier(data_col)?;
6762                    }
6763
6764                    self.write_space();
6765                    self.write_keyword("FROM");
6766                    self.write_space();
6767                    self.write_keyword("UNNEST");
6768                    self.write("(");
6769                    for (i, arg) in unnest_args.iter().enumerate() {
6770                        if i > 0 {
6771                            self.write(", ");
6772                        }
6773                        self.generate_expression(arg)?;
6774                    }
6775                    self.write(")");
6776                    self.write_space();
6777                    self.write_keyword("WITH ORDINALITY");
6778                    self.write_space();
6779                    self.write_keyword("AS");
6780                    self.write_space();
6781
6782                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6783                    let table_alias_ident = lv
6784                        .table_alias
6785                        .clone()
6786                        .unwrap_or_else(|| Identifier::new("t"));
6787                    self.generate_identifier(&table_alias_ident)?;
6788                    self.write("(");
6789                    for (i, data_col) in data_aliases.iter().enumerate() {
6790                        if i > 0 {
6791                            self.write(", ");
6792                        }
6793                        self.generate_identifier(data_col)?;
6794                    }
6795                    self.write(", ");
6796                    self.generate_identifier(&pos_alias)?;
6797                    self.write("))");
6798                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6799                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6800                    self.write_keyword("LATERAL");
6801                    self.write(" (");
6802                    self.write_keyword("SELECT");
6803                    self.write_space();
6804                    self.write_keyword("UNNEST");
6805                    self.write("(");
6806                    for (i, arg) in unnest_args.iter().enumerate() {
6807                        if i > 0 {
6808                            self.write(", ");
6809                        }
6810                        self.generate_expression(arg)?;
6811                    }
6812                    self.write(", ");
6813                    self.write_keyword("max_depth");
6814                    self.write(" => 2))");
6815
6816                    // Add table and column aliases
6817                    if let Some(alias) = &lv.table_alias {
6818                        self.write_space();
6819                        self.write_keyword("AS");
6820                        self.write_space();
6821                        self.generate_identifier(alias)?;
6822                        if !lv.column_aliases.is_empty() {
6823                            self.write("(");
6824                            for (i, col) in lv.column_aliases.iter().enumerate() {
6825                                if i > 0 {
6826                                    self.write(", ");
6827                                }
6828                                self.generate_identifier(col)?;
6829                            }
6830                            self.write(")");
6831                        }
6832                    } else if !lv.column_aliases.is_empty() {
6833                        // Auto-generate alias like _u_N
6834                        self.write_space();
6835                        self.write_keyword("AS");
6836                        self.write_space();
6837                        self.write(&format!("_u_{}", lv_index));
6838                        self.write("(");
6839                        for (i, col) in lv.column_aliases.iter().enumerate() {
6840                            if i > 0 {
6841                                self.write(", ");
6842                            }
6843                            self.generate_identifier(col)?;
6844                        }
6845                        self.write(")");
6846                    }
6847                } else {
6848                    self.write_keyword("UNNEST");
6849                    self.write("(");
6850                    for (i, arg) in unnest_args.iter().enumerate() {
6851                        if i > 0 {
6852                            self.write(", ");
6853                        }
6854                        self.generate_expression(arg)?;
6855                    }
6856                    self.write(")");
6857
6858                    // Add table and column aliases for non-POSEXPLODE
6859                    if let Some(alias) = &lv.table_alias {
6860                        self.write_space();
6861                        self.write_keyword("AS");
6862                        self.write_space();
6863                        self.generate_identifier(alias)?;
6864                        if !lv.column_aliases.is_empty() {
6865                            self.write("(");
6866                            for (i, col) in lv.column_aliases.iter().enumerate() {
6867                                if i > 0 {
6868                                    self.write(", ");
6869                                }
6870                                self.generate_identifier(col)?;
6871                            }
6872                            self.write(")");
6873                        }
6874                    } else if !lv.column_aliases.is_empty() {
6875                        self.write_space();
6876                        self.write_keyword("AS");
6877                        self.write(" t(");
6878                        for (i, col) in lv.column_aliases.iter().enumerate() {
6879                            if i > 0 {
6880                                self.write(", ");
6881                            }
6882                            self.generate_identifier(col)?;
6883                        }
6884                        self.write(")");
6885                    }
6886                }
6887            } else {
6888                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6889                if !lv.outer {
6890                    self.write_keyword("LATERAL");
6891                    self.write_space();
6892                }
6893                self.generate_expression(&lv.this)?;
6894
6895                // Add table and column aliases
6896                if let Some(alias) = &lv.table_alias {
6897                    self.write_space();
6898                    self.write_keyword("AS");
6899                    self.write_space();
6900                    self.generate_identifier(alias)?;
6901                    if !lv.column_aliases.is_empty() {
6902                        self.write("(");
6903                        for (i, col) in lv.column_aliases.iter().enumerate() {
6904                            if i > 0 {
6905                                self.write(", ");
6906                            }
6907                            self.generate_identifier(col)?;
6908                        }
6909                        self.write(")");
6910                    }
6911                } else if !lv.column_aliases.is_empty() {
6912                    self.write_space();
6913                    self.write_keyword("AS");
6914                    self.write(" t(");
6915                    for (i, col) in lv.column_aliases.iter().enumerate() {
6916                        if i > 0 {
6917                            self.write(", ");
6918                        }
6919                        self.generate_identifier(col)?;
6920                    }
6921                    self.write(")");
6922                }
6923            }
6924
6925            // For LEFT JOIN LATERAL, need ON TRUE
6926            if lv.outer {
6927                self.write_space();
6928                self.write_keyword("ON TRUE");
6929            }
6930        } else {
6931            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6932            self.write_keyword("LATERAL VIEW");
6933            if lv.outer {
6934                self.write_space();
6935                self.write_keyword("OUTER");
6936            }
6937            if self.config.pretty {
6938                self.write_newline();
6939                self.write_indent();
6940            } else {
6941                self.write_space();
6942            }
6943            self.generate_expression(&lv.this)?;
6944
6945            // Table alias
6946            if let Some(alias) = &lv.table_alias {
6947                self.write_space();
6948                self.generate_identifier(alias)?;
6949            }
6950
6951            // Column aliases
6952            if !lv.column_aliases.is_empty() {
6953                self.write_space();
6954                self.write_keyword("AS");
6955                self.write_space();
6956                for (i, col) in lv.column_aliases.iter().enumerate() {
6957                    if i > 0 {
6958                        self.write(", ");
6959                    }
6960                    self.generate_identifier(col)?;
6961                }
6962            }
6963        }
6964
6965        Ok(())
6966    }
6967
6968    fn should_wrap_set_operation_modifiers(
6969        &self,
6970        order_by: &Option<OrderBy>,
6971        limit: &Option<Box<Expression>>,
6972        offset: &Option<Box<Expression>>,
6973    ) -> bool {
6974        let has_row_limit = limit.is_some() || offset.is_some();
6975        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6976            order_by
6977                .expressions
6978                .iter()
6979                .any(|ordered| ordered.nulls_first.is_some())
6980        });
6981
6982        (has_row_limit || has_emulated_null_ordering)
6983            && matches!(
6984                self.config.dialect,
6985                Some(DialectType::TSQL) | Some(DialectType::Fabric)
6986            )
6987    }
6988
6989    fn generate_tsql_wrapped_set_operation(
6990        &mut self,
6991        inner: Expression,
6992        with: Option<With>,
6993        order_by: Option<OrderBy>,
6994        limit: Option<Box<Expression>>,
6995        offset: Option<Box<Expression>>,
6996    ) -> Result<()> {
6997        let subquery = Subquery {
6998            this: inner,
6999            alias: Some(Identifier::new("_l_0".to_string())),
7000            column_aliases: Vec::new(),
7001            alias_explicit_as: true,
7002            alias_keyword: None,
7003            order_by: None,
7004            limit: None,
7005            offset: None,
7006            lateral: false,
7007            modifiers_inside: false,
7008            trailing_comments: Vec::new(),
7009            distribute_by: None,
7010            sort_by: None,
7011            cluster_by: None,
7012            inferred_type: None,
7013        };
7014
7015        let mut outer_select = Select {
7016            expressions: vec![Expression::Star(Star {
7017                table: None,
7018                except: None,
7019                replace: None,
7020                rename: None,
7021                trailing_comments: Vec::new(),
7022                span: None,
7023            })],
7024            from: Some(From {
7025                expressions: vec![Expression::Subquery(Box::new(subquery))],
7026            }),
7027            with,
7028            order_by,
7029            limit: limit.map(|limit| Limit {
7030                this: *limit,
7031                percent: false,
7032                comments: Vec::new(),
7033            }),
7034            offset: offset.map(|offset| Offset {
7035                this: *offset,
7036                rows: Some(true),
7037            }),
7038            ..Select::new()
7039        };
7040
7041        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
7042            outer_select.order_by = Some(Self::dummy_tsql_order_by());
7043        }
7044
7045        self.generate_select(&outer_select)
7046    }
7047
7048    pub(crate) fn dummy_tsql_order_by() -> OrderBy {
7049        let null_select = Expression::Select(Box::new(Select {
7050            expressions: vec![Expression::Null(Null)],
7051            ..Select::new()
7052        }));
7053
7054        OrderBy {
7055            expressions: vec![Ordered {
7056                this: Expression::Subquery(Box::new(Subquery {
7057                    this: null_select,
7058                    alias: None,
7059                    column_aliases: Vec::new(),
7060                    alias_explicit_as: false,
7061                    alias_keyword: None,
7062                    order_by: None,
7063                    limit: None,
7064                    offset: None,
7065                    lateral: false,
7066                    modifiers_inside: false,
7067                    trailing_comments: Vec::new(),
7068                    distribute_by: None,
7069                    sort_by: None,
7070                    cluster_by: None,
7071                    inferred_type: None,
7072                })),
7073                desc: false,
7074                nulls_first: None,
7075                explicit_asc: false,
7076                with_fill: None,
7077            }],
7078            siblings: false,
7079            comments: Vec::new(),
7080        }
7081    }
7082
7083    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
7084        if self.should_wrap_set_operation_modifiers(
7085            &outermost.order_by,
7086            &outermost.limit,
7087            &outermost.offset,
7088        ) {
7089            let mut inner = outermost.clone();
7090            let with = inner.with.take();
7091            let order_by = inner.order_by.take();
7092            let limit = inner.limit.take();
7093            let offset = inner.offset.take();
7094
7095            return self.generate_tsql_wrapped_set_operation(
7096                Expression::Union(Box::new(inner)),
7097                with,
7098                order_by,
7099                limit,
7100                offset,
7101            );
7102        }
7103
7104        // Collect the left-recursive chain of Union nodes iteratively.
7105        // This avoids stack overflow for deeply nested chains like
7106        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
7107        // where the parser builds: Union(Union(Union(A, B), C), D)
7108        let mut chain: Vec<&Union> = vec![outermost];
7109        let mut leftmost: &Expression = &outermost.left;
7110        while let Expression::Union(inner) = leftmost {
7111            chain.push(inner);
7112            leftmost = &inner.left;
7113        }
7114        // chain[0] = outermost, chain[last] = innermost
7115        // leftmost = innermost.left (a non-Union expression, typically Select)
7116
7117        // WITH clause (only on outermost)
7118        if let Some(with) = &outermost.with {
7119            self.generate_with(with)?;
7120            self.write_space();
7121        }
7122
7123        // Generate the base (leftmost) expression
7124        self.generate_expression(leftmost)?;
7125
7126        // Generate each union step from innermost to outermost
7127        for union in chain.iter().rev() {
7128            self.generate_union_step(union)?;
7129        }
7130        Ok(())
7131    }
7132
7133    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
7134    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
7135        if self.config.pretty {
7136            self.write_newline();
7137            self.write_indent();
7138        } else {
7139            self.write_space();
7140        }
7141
7142        // BigQuery set operation modifiers: [side] [kind] UNION
7143        if let Some(side) = &union.side {
7144            self.write_keyword(side);
7145            self.write_space();
7146        }
7147        if let Some(kind) = &union.kind {
7148            self.write_keyword(kind);
7149            self.write_space();
7150        }
7151
7152        self.write_keyword("UNION");
7153        if union.all {
7154            self.write_space();
7155            self.write_keyword("ALL");
7156        } else if union.distinct {
7157            self.write_space();
7158            self.write_keyword("DISTINCT");
7159        }
7160
7161        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7162        // DuckDB: BY NAME
7163        if union.corresponding || union.by_name {
7164            self.write_space();
7165            self.write_keyword("BY NAME");
7166        }
7167        if !union.on_columns.is_empty() {
7168            self.write_space();
7169            self.write_keyword("ON");
7170            self.write(" (");
7171            for (i, col) in union.on_columns.iter().enumerate() {
7172                if i > 0 {
7173                    self.write(", ");
7174                }
7175                self.generate_expression(col)?;
7176            }
7177            self.write(")");
7178        }
7179
7180        if self.config.pretty {
7181            self.write_newline();
7182            self.write_indent();
7183        } else {
7184            self.write_space();
7185        }
7186        self.generate_expression(&union.right)?;
7187        // ORDER BY, LIMIT, OFFSET for the set operation
7188        if let Some(order_by) = &union.order_by {
7189            if self.config.pretty {
7190                self.write_newline();
7191            } else {
7192                self.write_space();
7193            }
7194            self.write_keyword("ORDER BY");
7195            self.write_space();
7196            for (i, ordered) in order_by.expressions.iter().enumerate() {
7197                if i > 0 {
7198                    self.write(", ");
7199                }
7200                self.generate_ordered(ordered)?;
7201            }
7202        }
7203        if let Some(limit) = &union.limit {
7204            if self.config.pretty {
7205                self.write_newline();
7206            } else {
7207                self.write_space();
7208            }
7209            self.write_keyword("LIMIT");
7210            self.write_space();
7211            self.generate_expression(limit)?;
7212        }
7213        if let Some(offset) = &union.offset {
7214            if self.config.pretty {
7215                self.write_newline();
7216            } else {
7217                self.write_space();
7218            }
7219            self.write_keyword("OFFSET");
7220            self.write_space();
7221            self.generate_expression(offset)?;
7222        }
7223        // DISTRIBUTE BY (Hive/Spark)
7224        if let Some(distribute_by) = &union.distribute_by {
7225            self.write_space();
7226            self.write_keyword("DISTRIBUTE BY");
7227            self.write_space();
7228            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7229                if i > 0 {
7230                    self.write(", ");
7231                }
7232                self.generate_expression(expr)?;
7233            }
7234        }
7235        // SORT BY (Hive/Spark)
7236        if let Some(sort_by) = &union.sort_by {
7237            self.write_space();
7238            self.write_keyword("SORT BY");
7239            self.write_space();
7240            for (i, ord) in sort_by.expressions.iter().enumerate() {
7241                if i > 0 {
7242                    self.write(", ");
7243                }
7244                self.generate_ordered(ord)?;
7245            }
7246        }
7247        // CLUSTER BY (Hive/Spark)
7248        if let Some(cluster_by) = &union.cluster_by {
7249            self.write_space();
7250            self.write_keyword("CLUSTER BY");
7251            self.write_space();
7252            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7253                if i > 0 {
7254                    self.write(", ");
7255                }
7256                self.generate_ordered(ord)?;
7257            }
7258        }
7259        Ok(())
7260    }
7261
7262    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
7263        if self.should_wrap_set_operation_modifiers(
7264            &outermost.order_by,
7265            &outermost.limit,
7266            &outermost.offset,
7267        ) {
7268            let mut inner = outermost.clone();
7269            let with = inner.with.take();
7270            let order_by = inner.order_by.take();
7271            let limit = inner.limit.take();
7272            let offset = inner.offset.take();
7273
7274            return self.generate_tsql_wrapped_set_operation(
7275                Expression::Intersect(Box::new(inner)),
7276                with,
7277                order_by,
7278                limit,
7279                offset,
7280            );
7281        }
7282
7283        // Collect the left-recursive chain iteratively to avoid stack overflow
7284        let mut chain: Vec<&Intersect> = vec![outermost];
7285        let mut leftmost: &Expression = &outermost.left;
7286        while let Expression::Intersect(inner) = leftmost {
7287            chain.push(inner);
7288            leftmost = &inner.left;
7289        }
7290
7291        if let Some(with) = &outermost.with {
7292            self.generate_with(with)?;
7293            self.write_space();
7294        }
7295
7296        self.generate_expression(leftmost)?;
7297
7298        for intersect in chain.iter().rev() {
7299            self.generate_intersect_step(intersect)?;
7300        }
7301        Ok(())
7302    }
7303
7304    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
7305    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
7306        if self.config.pretty {
7307            self.write_newline();
7308            self.write_indent();
7309        } else {
7310            self.write_space();
7311        }
7312
7313        // BigQuery set operation modifiers: [side] [kind] INTERSECT
7314        if let Some(side) = &intersect.side {
7315            self.write_keyword(side);
7316            self.write_space();
7317        }
7318        if let Some(kind) = &intersect.kind {
7319            self.write_keyword(kind);
7320            self.write_space();
7321        }
7322
7323        self.write_keyword("INTERSECT");
7324        if intersect.all {
7325            if !self.config.except_intersect_support_all_clause {
7326                self.unsupported("INTERSECT ALL is not supported")?;
7327            }
7328            self.write_space();
7329            self.write_keyword("ALL");
7330        } else if intersect.distinct {
7331            self.write_space();
7332            self.write_keyword("DISTINCT");
7333        }
7334
7335        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7336        // DuckDB: BY NAME
7337        if intersect.corresponding || intersect.by_name {
7338            self.write_space();
7339            self.write_keyword("BY NAME");
7340        }
7341        if !intersect.on_columns.is_empty() {
7342            self.write_space();
7343            self.write_keyword("ON");
7344            self.write(" (");
7345            for (i, col) in intersect.on_columns.iter().enumerate() {
7346                if i > 0 {
7347                    self.write(", ");
7348                }
7349                self.generate_expression(col)?;
7350            }
7351            self.write(")");
7352        }
7353
7354        if self.config.pretty {
7355            self.write_newline();
7356            self.write_indent();
7357        } else {
7358            self.write_space();
7359        }
7360        self.generate_expression(&intersect.right)?;
7361        // ORDER BY, LIMIT, OFFSET for the set operation
7362        if let Some(order_by) = &intersect.order_by {
7363            if self.config.pretty {
7364                self.write_newline();
7365            } else {
7366                self.write_space();
7367            }
7368            self.write_keyword("ORDER BY");
7369            self.write_space();
7370            for (i, ordered) in order_by.expressions.iter().enumerate() {
7371                if i > 0 {
7372                    self.write(", ");
7373                }
7374                self.generate_ordered(ordered)?;
7375            }
7376        }
7377        if let Some(limit) = &intersect.limit {
7378            if self.config.pretty {
7379                self.write_newline();
7380            } else {
7381                self.write_space();
7382            }
7383            self.write_keyword("LIMIT");
7384            self.write_space();
7385            self.generate_expression(limit)?;
7386        }
7387        if let Some(offset) = &intersect.offset {
7388            if self.config.pretty {
7389                self.write_newline();
7390            } else {
7391                self.write_space();
7392            }
7393            self.write_keyword("OFFSET");
7394            self.write_space();
7395            self.generate_expression(offset)?;
7396        }
7397        // DISTRIBUTE BY (Hive/Spark)
7398        if let Some(distribute_by) = &intersect.distribute_by {
7399            self.write_space();
7400            self.write_keyword("DISTRIBUTE BY");
7401            self.write_space();
7402            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7403                if i > 0 {
7404                    self.write(", ");
7405                }
7406                self.generate_expression(expr)?;
7407            }
7408        }
7409        // SORT BY (Hive/Spark)
7410        if let Some(sort_by) = &intersect.sort_by {
7411            self.write_space();
7412            self.write_keyword("SORT BY");
7413            self.write_space();
7414            for (i, ord) in sort_by.expressions.iter().enumerate() {
7415                if i > 0 {
7416                    self.write(", ");
7417                }
7418                self.generate_ordered(ord)?;
7419            }
7420        }
7421        // CLUSTER BY (Hive/Spark)
7422        if let Some(cluster_by) = &intersect.cluster_by {
7423            self.write_space();
7424            self.write_keyword("CLUSTER BY");
7425            self.write_space();
7426            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7427                if i > 0 {
7428                    self.write(", ");
7429                }
7430                self.generate_ordered(ord)?;
7431            }
7432        }
7433        Ok(())
7434    }
7435
7436    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
7437        if self.should_wrap_set_operation_modifiers(
7438            &outermost.order_by,
7439            &outermost.limit,
7440            &outermost.offset,
7441        ) {
7442            let mut inner = outermost.clone();
7443            let with = inner.with.take();
7444            let order_by = inner.order_by.take();
7445            let limit = inner.limit.take();
7446            let offset = inner.offset.take();
7447
7448            return self.generate_tsql_wrapped_set_operation(
7449                Expression::Except(Box::new(inner)),
7450                with,
7451                order_by,
7452                limit,
7453                offset,
7454            );
7455        }
7456
7457        // Collect the left-recursive chain iteratively to avoid stack overflow
7458        let mut chain: Vec<&Except> = vec![outermost];
7459        let mut leftmost: &Expression = &outermost.left;
7460        while let Expression::Except(inner) = leftmost {
7461            chain.push(inner);
7462            leftmost = &inner.left;
7463        }
7464
7465        if let Some(with) = &outermost.with {
7466            self.generate_with(with)?;
7467            self.write_space();
7468        }
7469
7470        self.generate_expression(leftmost)?;
7471
7472        for except in chain.iter().rev() {
7473            self.generate_except_step(except)?;
7474        }
7475        Ok(())
7476    }
7477
7478    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
7479    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
7480        use crate::dialects::DialectType;
7481
7482        if self.config.pretty {
7483            self.write_newline();
7484            self.write_indent();
7485        } else {
7486            self.write_space();
7487        }
7488
7489        // BigQuery set operation modifiers: [side] [kind] EXCEPT
7490        if let Some(side) = &except.side {
7491            self.write_keyword(side);
7492            self.write_space();
7493        }
7494        if let Some(kind) = &except.kind {
7495            self.write_keyword(kind);
7496            self.write_space();
7497        }
7498
7499        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
7500        match self.config.dialect {
7501            Some(DialectType::Oracle) if !except.all => {
7502                self.write_keyword("MINUS");
7503            }
7504            Some(DialectType::ClickHouse) => {
7505                self.write_keyword("EXCEPT");
7506                let preserve_all = self.config.source_dialect.is_none()
7507                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
7508                if except.all && preserve_all {
7509                    self.write_space();
7510                    self.write_keyword("ALL");
7511                }
7512                if except.distinct {
7513                    self.write_space();
7514                    self.write_keyword("DISTINCT");
7515                }
7516            }
7517            Some(DialectType::BigQuery) => {
7518                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
7519                self.write_keyword("EXCEPT");
7520                if except.all {
7521                    self.write_space();
7522                    self.write_keyword("ALL");
7523                } else {
7524                    self.write_space();
7525                    self.write_keyword("DISTINCT");
7526                }
7527            }
7528            _ => {
7529                self.write_keyword("EXCEPT");
7530                if except.all {
7531                    if !self.config.except_intersect_support_all_clause {
7532                        self.unsupported("EXCEPT ALL is not supported")?;
7533                    }
7534                    self.write_space();
7535                    self.write_keyword("ALL");
7536                } else if except.distinct {
7537                    self.write_space();
7538                    self.write_keyword("DISTINCT");
7539                }
7540            }
7541        }
7542
7543        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7544        // DuckDB: BY NAME
7545        if except.corresponding || except.by_name {
7546            self.write_space();
7547            self.write_keyword("BY NAME");
7548        }
7549        if !except.on_columns.is_empty() {
7550            self.write_space();
7551            self.write_keyword("ON");
7552            self.write(" (");
7553            for (i, col) in except.on_columns.iter().enumerate() {
7554                if i > 0 {
7555                    self.write(", ");
7556                }
7557                self.generate_expression(col)?;
7558            }
7559            self.write(")");
7560        }
7561
7562        if self.config.pretty {
7563            self.write_newline();
7564            self.write_indent();
7565        } else {
7566            self.write_space();
7567        }
7568        self.generate_expression(&except.right)?;
7569        // ORDER BY, LIMIT, OFFSET for the set operation
7570        if let Some(order_by) = &except.order_by {
7571            if self.config.pretty {
7572                self.write_newline();
7573            } else {
7574                self.write_space();
7575            }
7576            self.write_keyword("ORDER BY");
7577            self.write_space();
7578            for (i, ordered) in order_by.expressions.iter().enumerate() {
7579                if i > 0 {
7580                    self.write(", ");
7581                }
7582                self.generate_ordered(ordered)?;
7583            }
7584        }
7585        if let Some(limit) = &except.limit {
7586            if self.config.pretty {
7587                self.write_newline();
7588            } else {
7589                self.write_space();
7590            }
7591            self.write_keyword("LIMIT");
7592            self.write_space();
7593            self.generate_expression(limit)?;
7594        }
7595        if let Some(offset) = &except.offset {
7596            if self.config.pretty {
7597                self.write_newline();
7598            } else {
7599                self.write_space();
7600            }
7601            self.write_keyword("OFFSET");
7602            self.write_space();
7603            self.generate_expression(offset)?;
7604        }
7605        // DISTRIBUTE BY (Hive/Spark)
7606        if let Some(distribute_by) = &except.distribute_by {
7607            self.write_space();
7608            self.write_keyword("DISTRIBUTE BY");
7609            self.write_space();
7610            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7611                if i > 0 {
7612                    self.write(", ");
7613                }
7614                self.generate_expression(expr)?;
7615            }
7616        }
7617        // SORT BY (Hive/Spark)
7618        if let Some(sort_by) = &except.sort_by {
7619            self.write_space();
7620            self.write_keyword("SORT BY");
7621            self.write_space();
7622            for (i, ord) in sort_by.expressions.iter().enumerate() {
7623                if i > 0 {
7624                    self.write(", ");
7625                }
7626                self.generate_ordered(ord)?;
7627            }
7628        }
7629        // CLUSTER BY (Hive/Spark)
7630        if let Some(cluster_by) = &except.cluster_by {
7631            self.write_space();
7632            self.write_keyword("CLUSTER BY");
7633            self.write_space();
7634            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7635                if i > 0 {
7636                    self.write(", ");
7637                }
7638                self.generate_ordered(ord)?;
7639            }
7640        }
7641        Ok(())
7642    }
7643
7644    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7645        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7646        let prepend_query_cte = if insert.with.is_none() {
7647            use crate::dialects::DialectType;
7648            let should_prepend = matches!(
7649                self.config.dialect,
7650                Some(DialectType::TSQL)
7651                    | Some(DialectType::Fabric)
7652                    | Some(DialectType::Spark)
7653                    | Some(DialectType::Databricks)
7654                    | Some(DialectType::Hive)
7655            );
7656            if should_prepend {
7657                if let Some(Expression::Select(select)) = &insert.query {
7658                    select.with.clone()
7659                } else {
7660                    None
7661                }
7662            } else {
7663                None
7664            }
7665        } else {
7666            None
7667        };
7668
7669        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7670        if let Some(with) = &insert.with {
7671            self.generate_with(with)?;
7672            self.write_space();
7673        } else if let Some(with) = &prepend_query_cte {
7674            self.generate_with(with)?;
7675            self.write_space();
7676        }
7677
7678        // Output leading comments before INSERT
7679        for comment in &insert.leading_comments {
7680            self.write_formatted_comment(comment);
7681            self.write(" ");
7682        }
7683
7684        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7685        if let Some(dir) = &insert.directory {
7686            self.write_keyword("INSERT OVERWRITE");
7687            if dir.local {
7688                self.write_space();
7689                self.write_keyword("LOCAL");
7690            }
7691            self.write_space();
7692            self.write_keyword("DIRECTORY");
7693            self.write_space();
7694            self.write("'");
7695            self.write(&dir.path);
7696            self.write("'");
7697
7698            // ROW FORMAT clause
7699            if let Some(row_format) = &dir.row_format {
7700                self.write_space();
7701                self.write_keyword("ROW FORMAT");
7702                if row_format.delimited {
7703                    self.write_space();
7704                    self.write_keyword("DELIMITED");
7705                }
7706                if let Some(val) = &row_format.fields_terminated_by {
7707                    self.write_space();
7708                    self.write_keyword("FIELDS TERMINATED BY");
7709                    self.write_space();
7710                    self.generate_string_literal(val)?;
7711                }
7712                if let Some(val) = &row_format.collection_items_terminated_by {
7713                    self.write_space();
7714                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7715                    self.write_space();
7716                    self.write("'");
7717                    self.write(val);
7718                    self.write("'");
7719                }
7720                if let Some(val) = &row_format.map_keys_terminated_by {
7721                    self.write_space();
7722                    self.write_keyword("MAP KEYS TERMINATED BY");
7723                    self.write_space();
7724                    self.write("'");
7725                    self.write(val);
7726                    self.write("'");
7727                }
7728                if let Some(val) = &row_format.lines_terminated_by {
7729                    self.write_space();
7730                    self.write_keyword("LINES TERMINATED BY");
7731                    self.write_space();
7732                    self.write("'");
7733                    self.write(val);
7734                    self.write("'");
7735                }
7736                if let Some(val) = &row_format.null_defined_as {
7737                    self.write_space();
7738                    self.write_keyword("NULL DEFINED AS");
7739                    self.write_space();
7740                    self.write("'");
7741                    self.write(val);
7742                    self.write("'");
7743                }
7744            }
7745
7746            // STORED AS clause
7747            if let Some(format) = &dir.stored_as {
7748                self.write_space();
7749                self.write_keyword("STORED AS");
7750                self.write_space();
7751                self.write_keyword(format);
7752            }
7753
7754            // Query (SELECT statement)
7755            if let Some(query) = &insert.query {
7756                self.write_space();
7757                self.generate_expression(query)?;
7758            }
7759
7760            return Ok(());
7761        }
7762
7763        if insert.is_replace {
7764            // MySQL/SQLite REPLACE INTO statement
7765            self.write_keyword("REPLACE INTO");
7766        } else if insert.overwrite {
7767            // Use dialect-specific INSERT OVERWRITE format
7768            self.write_keyword("INSERT");
7769            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7770            if let Some(ref hint) = insert.hint {
7771                self.generate_hint(hint)?;
7772            }
7773            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7774        } else if let Some(ref action) = insert.conflict_action {
7775            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7776            self.write_keyword("INSERT OR");
7777            self.write_space();
7778            self.write_keyword(action);
7779            self.write_space();
7780            self.write_keyword("INTO");
7781        } else if insert.ignore {
7782            // MySQL INSERT IGNORE syntax
7783            self.write_keyword("INSERT IGNORE INTO");
7784        } else {
7785            self.write_keyword("INSERT");
7786            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7787            if let Some(ref hint) = insert.hint {
7788                self.generate_hint(hint)?;
7789            }
7790            self.write_space();
7791            self.write_keyword("INTO");
7792        }
7793        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7794        if let Some(ref func) = insert.function_target {
7795            self.write_space();
7796            self.write_keyword("FUNCTION");
7797            self.write_space();
7798            self.generate_expression(func)?;
7799        } else {
7800            self.write_space();
7801            self.generate_table(&insert.table)?;
7802        }
7803
7804        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7805        if let Some(ref alias) = insert.alias {
7806            self.write_space();
7807            if insert.alias_explicit_as {
7808                self.write_keyword("AS");
7809                self.write_space();
7810            }
7811            self.generate_identifier(alias)?;
7812        }
7813
7814        // IF EXISTS clause (Hive)
7815        if insert.if_exists {
7816            self.write_space();
7817            self.write_keyword("IF EXISTS");
7818        }
7819
7820        // REPLACE WHERE clause (Databricks)
7821        if let Some(ref replace_where) = insert.replace_where {
7822            if self.config.pretty {
7823                self.write_newline();
7824                self.write_indent();
7825            } else {
7826                self.write_space();
7827            }
7828            self.write_keyword("REPLACE WHERE");
7829            self.write_space();
7830            self.generate_expression(replace_where)?;
7831        }
7832
7833        // Generate PARTITION clause if present
7834        if !insert.partition.is_empty() {
7835            self.write_space();
7836            self.write_keyword("PARTITION");
7837            self.write("(");
7838            for (i, (col, val)) in insert.partition.iter().enumerate() {
7839                if i > 0 {
7840                    self.write(", ");
7841                }
7842                self.generate_identifier(col)?;
7843                if let Some(v) = val {
7844                    self.write(" = ");
7845                    self.generate_expression(v)?;
7846                }
7847            }
7848            self.write(")");
7849        }
7850
7851        // ClickHouse: PARTITION BY expr
7852        if let Some(ref partition_by) = insert.partition_by {
7853            self.write_space();
7854            self.write_keyword("PARTITION BY");
7855            self.write_space();
7856            self.generate_expression(partition_by)?;
7857        }
7858
7859        // ClickHouse: SETTINGS key = val, ...
7860        if !insert.settings.is_empty() {
7861            self.write_space();
7862            self.write_keyword("SETTINGS");
7863            self.write_space();
7864            for (i, setting) in insert.settings.iter().enumerate() {
7865                if i > 0 {
7866                    self.write(", ");
7867                }
7868                self.generate_expression(setting)?;
7869            }
7870        }
7871
7872        if !insert.columns.is_empty() {
7873            if insert.alias.is_some() && insert.alias_explicit_as {
7874                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7875                self.write("(");
7876            } else {
7877                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7878                self.write(" (");
7879            }
7880            for (i, col) in insert.columns.iter().enumerate() {
7881                if i > 0 {
7882                    self.write(", ");
7883                }
7884                self.generate_identifier(col)?;
7885            }
7886            self.write(")");
7887        }
7888
7889        // OUTPUT clause (TSQL)
7890        if let Some(ref output) = insert.output {
7891            self.generate_output_clause(output)?;
7892        }
7893
7894        // BY NAME modifier (DuckDB)
7895        if insert.by_name {
7896            self.write_space();
7897            self.write_keyword("BY NAME");
7898        }
7899
7900        if insert.default_values {
7901            self.write_space();
7902            self.write_keyword("DEFAULT VALUES");
7903        } else if let Some(query) = &insert.query {
7904            if self.config.pretty {
7905                self.write_newline();
7906            } else {
7907                self.write_space();
7908            }
7909            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7910            if prepend_query_cte.is_some() {
7911                if let Expression::Select(select) = query {
7912                    let mut select_no_with = select.clone();
7913                    select_no_with.with = None;
7914                    self.generate_select(&select_no_with)?;
7915                } else {
7916                    self.generate_expression(query)?;
7917                }
7918            } else {
7919                self.generate_expression(query)?;
7920            }
7921        } else if !insert.values.is_empty() {
7922            if self.config.pretty {
7923                // Pretty printing: VALUES on new line, each tuple indented
7924                self.write_newline();
7925                self.write_keyword("VALUES");
7926                self.write_newline();
7927                self.indent_level += 1;
7928                for (i, row) in insert.values.iter().enumerate() {
7929                    if i > 0 {
7930                        self.write(",");
7931                        self.write_newline();
7932                    }
7933                    self.write_indent();
7934                    self.write("(");
7935                    for (j, val) in row.iter().enumerate() {
7936                        if j > 0 {
7937                            self.write(", ");
7938                        }
7939                        self.generate_expression(val)?;
7940                    }
7941                    self.write(")");
7942                }
7943                self.indent_level -= 1;
7944            } else {
7945                // Non-pretty: single line
7946                self.write_space();
7947                self.write_keyword("VALUES");
7948                for (i, row) in insert.values.iter().enumerate() {
7949                    if i > 0 {
7950                        self.write(",");
7951                    }
7952                    self.write(" (");
7953                    for (j, val) in row.iter().enumerate() {
7954                        if j > 0 {
7955                            self.write(", ");
7956                        }
7957                        self.generate_expression(val)?;
7958                    }
7959                    self.write(")");
7960                }
7961            }
7962        }
7963
7964        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7965        if let Some(ref source) = insert.source {
7966            self.write_space();
7967            self.write_keyword("TABLE");
7968            self.write_space();
7969            self.generate_expression(source)?;
7970        }
7971
7972        // Source alias (MySQL: VALUES (...) AS new_data)
7973        if let Some(alias) = &insert.source_alias {
7974            self.write_space();
7975            self.write_keyword("AS");
7976            self.write_space();
7977            self.generate_identifier(alias)?;
7978        }
7979
7980        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7981        if let Some(on_conflict) = &insert.on_conflict {
7982            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7983                self.write_space();
7984                self.generate_expression(on_conflict)?;
7985            }
7986        }
7987
7988        // RETURNING clause
7989        if !insert.returning.is_empty() {
7990            self.write_space();
7991            self.write_keyword("RETURNING");
7992            self.write_space();
7993            for (i, expr) in insert.returning.iter().enumerate() {
7994                if i > 0 {
7995                    self.write(", ");
7996                }
7997                self.generate_expression(expr)?;
7998            }
7999        }
8000
8001        Ok(())
8002    }
8003
8004    fn generate_update(&mut self, update: &Update) -> Result<()> {
8005        // Output leading comments before UPDATE
8006        for comment in &update.leading_comments {
8007            self.write_formatted_comment(comment);
8008            self.write(" ");
8009        }
8010
8011        // WITH clause (CTEs)
8012        if let Some(ref with) = update.with {
8013            self.generate_with(with)?;
8014            self.write_space();
8015        }
8016
8017        self.write_keyword("UPDATE");
8018        if let Some(hint) = &update.hint {
8019            self.generate_hint(hint)?;
8020        }
8021        self.write_space();
8022        self.generate_table(&update.table)?;
8023
8024        let mysql_like_update_from = matches!(
8025            self.config.dialect,
8026            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
8027        ) && update.from_clause.is_some();
8028
8029        let mut set_pairs = update.set.clone();
8030
8031        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
8032        let mut pre_set_joins = update.table_joins.clone();
8033        if mysql_like_update_from {
8034            let target_name = update
8035                .table
8036                .alias
8037                .as_ref()
8038                .map(|a| a.name.clone())
8039                .unwrap_or_else(|| update.table.name.name.clone());
8040
8041            for (col, _) in &mut set_pairs {
8042                if !col.name.contains('.') {
8043                    col.name = format!("{}.{}", target_name, col.name);
8044                }
8045            }
8046
8047            if let Some(from_clause) = &update.from_clause {
8048                for table_expr in &from_clause.expressions {
8049                    pre_set_joins.push(crate::expressions::Join {
8050                        this: table_expr.clone(),
8051                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8052                            value: true,
8053                        })),
8054                        using: Vec::new(),
8055                        kind: crate::expressions::JoinKind::Inner,
8056                        use_inner_keyword: false,
8057                        use_outer_keyword: false,
8058                        deferred_condition: false,
8059                        join_hint: None,
8060                        match_condition: None,
8061                        pivots: Vec::new(),
8062                        comments: Vec::new(),
8063                        nesting_group: 0,
8064                        directed: false,
8065                    });
8066                }
8067            }
8068            for join in &update.from_joins {
8069                let mut join = join.clone();
8070                if join.on.is_none() && join.using.is_empty() {
8071                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8072                        value: true,
8073                    }));
8074                }
8075                pre_set_joins.push(join);
8076            }
8077        }
8078
8079        // Extra tables for multi-table UPDATE (MySQL syntax)
8080        for extra_table in &update.extra_tables {
8081            self.write(", ");
8082            self.generate_table(extra_table)?;
8083        }
8084
8085        // JOINs attached to the table list (MySQL multi-table syntax)
8086        for join in &pre_set_joins {
8087            // generate_join already adds a leading space
8088            self.generate_join(join)?;
8089        }
8090
8091        // Teradata: FROM clause comes before SET
8092        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
8093        if teradata_from_before_set && !mysql_like_update_from {
8094            if let Some(ref from_clause) = update.from_clause {
8095                self.write_space();
8096                self.write_keyword("FROM");
8097                self.write_space();
8098                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8099                    if i > 0 {
8100                        self.write(", ");
8101                    }
8102                    self.generate_expression(table_expr)?;
8103                }
8104            }
8105            for join in &update.from_joins {
8106                self.generate_join(join)?;
8107            }
8108        }
8109
8110        self.write_space();
8111        self.write_keyword("SET");
8112        self.write_space();
8113
8114        for (i, (col, val)) in set_pairs.iter().enumerate() {
8115            if i > 0 {
8116                self.write(", ");
8117            }
8118            self.generate_identifier(col)?;
8119            self.write(" = ");
8120            self.generate_expression(val)?;
8121        }
8122
8123        // OUTPUT clause (TSQL)
8124        if let Some(ref output) = update.output {
8125            self.generate_output_clause(output)?;
8126        }
8127
8128        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
8129        if !mysql_like_update_from && !teradata_from_before_set {
8130            if let Some(ref from_clause) = update.from_clause {
8131                self.write_space();
8132                self.write_keyword("FROM");
8133                self.write_space();
8134                // Generate each table in the FROM clause
8135                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8136                    if i > 0 {
8137                        self.write(", ");
8138                    }
8139                    self.generate_expression(table_expr)?;
8140                }
8141            }
8142        }
8143
8144        if !mysql_like_update_from && !teradata_from_before_set {
8145            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
8146            for join in &update.from_joins {
8147                self.generate_join(join)?;
8148            }
8149        }
8150
8151        if let Some(where_clause) = &update.where_clause {
8152            self.write_space();
8153            self.write_keyword("WHERE");
8154            self.write_space();
8155            self.generate_expression(&where_clause.this)?;
8156        }
8157
8158        // RETURNING clause
8159        if !update.returning.is_empty() {
8160            self.write_space();
8161            self.write_keyword("RETURNING");
8162            self.write_space();
8163            for (i, expr) in update.returning.iter().enumerate() {
8164                if i > 0 {
8165                    self.write(", ");
8166                }
8167                self.generate_expression(expr)?;
8168            }
8169        }
8170
8171        // ORDER BY clause (MySQL)
8172        if let Some(ref order_by) = update.order_by {
8173            self.write_space();
8174            self.generate_order_by(order_by)?;
8175        }
8176
8177        // LIMIT clause (MySQL)
8178        if let Some(ref limit) = update.limit {
8179            self.write_space();
8180            self.write_keyword("LIMIT");
8181            self.write_space();
8182            self.generate_expression(limit)?;
8183        }
8184
8185        Ok(())
8186    }
8187
8188    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
8189        // Output WITH clause if present
8190        if let Some(with) = &delete.with {
8191            self.generate_with(with)?;
8192            self.write_space();
8193        }
8194
8195        // Output leading comments before DELETE
8196        for comment in &delete.leading_comments {
8197            self.write_formatted_comment(comment);
8198            self.write(" ");
8199        }
8200
8201        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
8202        if !delete.tables.is_empty() && !delete.tables_from_using {
8203            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
8204            self.write_keyword("DELETE");
8205            if let Some(hint) = &delete.hint {
8206                self.generate_hint(hint)?;
8207            }
8208            self.write_space();
8209            for (i, tbl) in delete.tables.iter().enumerate() {
8210                if i > 0 {
8211                    self.write(", ");
8212                }
8213                self.generate_table(tbl)?;
8214            }
8215            // TSQL: OUTPUT clause between target table and FROM
8216            if let Some(ref output) = delete.output {
8217                self.generate_output_clause(output)?;
8218            }
8219            self.write_space();
8220            self.write_keyword("FROM");
8221            self.write_space();
8222            self.generate_table(&delete.table)?;
8223        } else if !delete.tables.is_empty() && delete.tables_from_using {
8224            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
8225            self.write_keyword("DELETE");
8226            if let Some(hint) = &delete.hint {
8227                self.generate_hint(hint)?;
8228            }
8229            self.write_space();
8230            self.write_keyword("FROM");
8231            self.write_space();
8232            for (i, tbl) in delete.tables.iter().enumerate() {
8233                if i > 0 {
8234                    self.write(", ");
8235                }
8236                self.generate_table(tbl)?;
8237            }
8238        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
8239            // BigQuery-style DELETE without FROM keyword
8240            self.write_keyword("DELETE");
8241            if let Some(hint) = &delete.hint {
8242                self.generate_hint(hint)?;
8243            }
8244            self.write_space();
8245            self.generate_table(&delete.table)?;
8246        } else {
8247            self.write_keyword("DELETE");
8248            if let Some(hint) = &delete.hint {
8249                self.generate_hint(hint)?;
8250            }
8251            self.write_space();
8252            self.write_keyword("FROM");
8253            self.write_space();
8254            self.generate_table(&delete.table)?;
8255        }
8256
8257        // ClickHouse: ON CLUSTER clause
8258        if let Some(ref on_cluster) = delete.on_cluster {
8259            self.write_space();
8260            self.generate_on_cluster(on_cluster)?;
8261        }
8262
8263        // FORCE INDEX hint (MySQL)
8264        if let Some(ref idx) = delete.force_index {
8265            self.write_space();
8266            self.write_keyword("FORCE INDEX");
8267            self.write(" (");
8268            self.write(idx);
8269            self.write(")");
8270        }
8271
8272        // Optional alias
8273        if let Some(ref alias) = delete.alias {
8274            self.write_space();
8275            if delete.alias_explicit_as
8276                || matches!(self.config.dialect, Some(DialectType::BigQuery))
8277            {
8278                self.write_keyword("AS");
8279                self.write_space();
8280            }
8281            self.generate_identifier(alias)?;
8282        }
8283
8284        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
8285        if !delete.tables_from_using {
8286            for join in &delete.joins {
8287                self.generate_join(join)?;
8288            }
8289        }
8290
8291        // USING clause (PostgreSQL/DuckDB/MySQL)
8292        if !delete.using.is_empty() {
8293            self.write_space();
8294            self.write_keyword("USING");
8295            for (i, table) in delete.using.iter().enumerate() {
8296                if i > 0 {
8297                    self.write(",");
8298                }
8299                self.write_space();
8300                // Check if the table has subquery hints (DuckDB USING with subquery)
8301                if !table.hints.is_empty() && table.name.is_empty() {
8302                    // Subquery in USING: (VALUES ...) AS alias(cols)
8303                    self.generate_expression(&table.hints[0])?;
8304                    if let Some(ref alias) = table.alias {
8305                        self.write_space();
8306                        if table.alias_explicit_as {
8307                            self.write_keyword("AS");
8308                            self.write_space();
8309                        }
8310                        self.generate_identifier(alias)?;
8311                        if !table.column_aliases.is_empty() {
8312                            self.write("(");
8313                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
8314                                if j > 0 {
8315                                    self.write(", ");
8316                                }
8317                                self.generate_identifier(col_alias)?;
8318                            }
8319                            self.write(")");
8320                        }
8321                    }
8322                } else {
8323                    self.generate_table(table)?;
8324                }
8325            }
8326        }
8327
8328        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
8329        if delete.tables_from_using {
8330            for join in &delete.joins {
8331                self.generate_join(join)?;
8332            }
8333        }
8334
8335        // OUTPUT clause (TSQL) - only if not already emitted in the early position
8336        let output_already_emitted =
8337            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
8338        if !output_already_emitted {
8339            if let Some(ref output) = delete.output {
8340                self.generate_output_clause(output)?;
8341            }
8342        }
8343
8344        if let Some(where_clause) = &delete.where_clause {
8345            self.write_space();
8346            self.write_keyword("WHERE");
8347            self.write_space();
8348            self.generate_expression(&where_clause.this)?;
8349        }
8350
8351        // ORDER BY clause (MySQL)
8352        if let Some(ref order_by) = delete.order_by {
8353            self.write_space();
8354            self.generate_order_by(order_by)?;
8355        }
8356
8357        // LIMIT clause (MySQL)
8358        if let Some(ref limit) = delete.limit {
8359            self.write_space();
8360            self.write_keyword("LIMIT");
8361            self.write_space();
8362            self.generate_expression(limit)?;
8363        }
8364
8365        // RETURNING clause (PostgreSQL)
8366        if !delete.returning.is_empty() {
8367            self.write_space();
8368            self.write_keyword("RETURNING");
8369            self.write_space();
8370            for (i, expr) in delete.returning.iter().enumerate() {
8371                if i > 0 {
8372                    self.write(", ");
8373                }
8374                self.generate_expression(expr)?;
8375            }
8376        }
8377
8378        Ok(())
8379    }
8380
8381    // ==================== DDL Generation ====================
8382
8383    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
8384        // Athena: Determine if this is Hive-style DDL or Trino-style DML
8385        // CREATE TABLE AS SELECT uses Trino (double quotes)
8386        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
8387        let saved_athena_hive_context = self.athena_hive_context;
8388        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
8389        if matches!(
8390            self.config.dialect,
8391            Some(crate::dialects::DialectType::Athena)
8392        ) {
8393            // Use Hive context if:
8394            // 1. It's an EXTERNAL table, OR
8395            // 2. There's no AS SELECT clause
8396            let is_external = ct
8397                .table_modifier
8398                .as_ref()
8399                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
8400                .unwrap_or(false);
8401            let has_as_select = ct.as_select.is_some();
8402            self.athena_hive_context = is_external || !has_as_select;
8403        }
8404
8405        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
8406        if matches!(
8407            self.config.dialect,
8408            Some(crate::dialects::DialectType::TSQL)
8409        ) {
8410            if let Some(ref query) = ct.as_select {
8411                // Output WITH CTE clause if present
8412                if let Some(with_cte) = &ct.with_cte {
8413                    self.generate_with(with_cte)?;
8414                    self.write_space();
8415                }
8416
8417                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
8418                self.write_keyword("SELECT");
8419                self.write(" * ");
8420                self.write_keyword("INTO");
8421                self.write_space();
8422
8423                // If temporary, prefix with # for TSQL temp table
8424                if ct.temporary {
8425                    self.write("#");
8426                }
8427                self.generate_table(&ct.name)?;
8428
8429                self.write_space();
8430                self.write_keyword("FROM");
8431                self.write(" (");
8432                // For TSQL, add aliases to select columns to preserve column names
8433                let aliased_query = Self::add_column_aliases_to_query(query.clone());
8434                self.generate_expression(&aliased_query)?;
8435                self.write(") ");
8436                self.write_keyword("AS");
8437                self.write(" temp");
8438                return Ok(());
8439            }
8440        }
8441
8442        // Output WITH CTE clause if present
8443        if let Some(with_cte) = &ct.with_cte {
8444            self.generate_with(with_cte)?;
8445            self.write_space();
8446        }
8447
8448        // Output leading comments before CREATE
8449        for comment in &ct.leading_comments {
8450            self.write_formatted_comment(comment);
8451            self.write(" ");
8452        }
8453        self.write_keyword("CREATE");
8454
8455        if ct.or_replace {
8456            self.write_space();
8457            self.write_keyword("OR REPLACE");
8458        }
8459
8460        if ct.temporary {
8461            self.write_space();
8462            // Oracle uses GLOBAL TEMPORARY TABLE syntax
8463            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
8464                self.write_keyword("GLOBAL TEMPORARY");
8465            } else {
8466                self.write_keyword("TEMPORARY");
8467            }
8468        }
8469
8470        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
8471        let is_dictionary = ct
8472            .table_modifier
8473            .as_ref()
8474            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
8475            .unwrap_or(false);
8476        if let Some(ref modifier) = ct.table_modifier {
8477            // TRANSIENT is Snowflake-specific - skip for other dialects
8478            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
8479                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
8480            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
8481            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
8482                || modifier.eq_ignore_ascii_case("SET")
8483                || modifier.eq_ignore_ascii_case("MULTISET")
8484                || modifier.to_ascii_uppercase().contains("VOLATILE")
8485                || modifier.to_ascii_uppercase().starts_with("SET ")
8486                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
8487            let skip_teradata =
8488                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
8489            if !skip_transient && !skip_teradata {
8490                self.write_space();
8491                self.write_keyword(modifier);
8492            }
8493        }
8494
8495        if !is_dictionary {
8496            self.write_space();
8497            self.write_keyword("TABLE");
8498        }
8499
8500        if ct.if_not_exists {
8501            self.write_space();
8502            self.write_keyword("IF NOT EXISTS");
8503        }
8504
8505        self.write_space();
8506        self.generate_table(&ct.name)?;
8507
8508        // ClickHouse: UUID 'xxx' clause after table name
8509        if let Some(ref uuid) = ct.uuid {
8510            self.write_space();
8511            self.write_keyword("UUID");
8512            self.write(" '");
8513            self.write(uuid);
8514            self.write("'");
8515        }
8516
8517        // ClickHouse: ON CLUSTER clause
8518        if let Some(ref on_cluster) = ct.on_cluster {
8519            self.write_space();
8520            self.generate_on_cluster(on_cluster)?;
8521        }
8522
8523        // Teradata: options after table name before column list (comma-separated)
8524        if matches!(
8525            self.config.dialect,
8526            Some(crate::dialects::DialectType::Teradata)
8527        ) && !ct.teradata_post_name_options.is_empty()
8528        {
8529            for opt in &ct.teradata_post_name_options {
8530                self.write(", ");
8531                self.write(opt);
8532            }
8533        }
8534
8535        // Snowflake: COPY GRANTS clause
8536        if ct.copy_grants {
8537            self.write_space();
8538            self.write_keyword("COPY GRANTS");
8539        }
8540
8541        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
8542        if let Some(ref using_template) = ct.using_template {
8543            self.write_space();
8544            self.write_keyword("USING TEMPLATE");
8545            self.write_space();
8546            self.generate_expression(using_template)?;
8547            return Ok(());
8548        }
8549
8550        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
8551        // When explicit columns or constraints are present, the source must be emitted
8552        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
8553        if is_clickhouse {
8554            if let Some(ref clone_source) = ct.clone_source {
8555                if ct.columns.is_empty() && ct.constraints.is_empty() {
8556                    self.write_space();
8557                    self.write_keyword("AS");
8558                    self.write_space();
8559                    self.generate_table(clone_source)?;
8560                }
8561            }
8562        }
8563
8564        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8565        if !is_clickhouse {
8566            if let Some(ref clone_source) = ct.clone_source {
8567                self.write_space();
8568                if ct.is_copy && self.config.supports_table_copy {
8569                    // BigQuery uses COPY
8570                    self.write_keyword("COPY");
8571                } else if ct.shallow_clone {
8572                    self.write_keyword("SHALLOW CLONE");
8573                } else if ct.deep_clone {
8574                    self.write_keyword("DEEP CLONE");
8575                } else {
8576                    self.write_keyword("CLONE");
8577                }
8578                self.write_space();
8579                self.generate_table(clone_source)?;
8580                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8581                if let Some(ref at_clause) = ct.clone_at_clause {
8582                    self.write_space();
8583                    self.generate_expression(at_clause)?;
8584                }
8585                return Ok(());
8586            }
8587        }
8588
8589        // Handle PARTITION OF property
8590        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8591        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8592        if let Some(ref partition_of) = ct.partition_of {
8593            self.write_space();
8594
8595            // Extract the PartitionedOfProperty parts to generate them separately
8596            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8597                // Output: PARTITION OF <table>
8598                self.write_keyword("PARTITION OF");
8599                self.write_space();
8600                self.generate_expression(&pop.this)?;
8601
8602                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8603                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8604                    self.write(" (");
8605                    let mut first = true;
8606                    for col in &ct.columns {
8607                        if !first {
8608                            self.write(", ");
8609                        }
8610                        first = false;
8611                        self.generate_column_def(col)?;
8612                    }
8613                    for constraint in &ct.constraints {
8614                        if !first {
8615                            self.write(", ");
8616                        }
8617                        first = false;
8618                        self.generate_table_constraint(constraint)?;
8619                    }
8620                    self.write(")");
8621                }
8622
8623                // Output partition bound spec: FOR VALUES ... or DEFAULT
8624                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8625                    self.write_space();
8626                    self.write_keyword("FOR VALUES");
8627                    self.write_space();
8628                    self.generate_expression(&pop.expression)?;
8629                } else {
8630                    self.write_space();
8631                    self.write_keyword("DEFAULT");
8632                }
8633            } else {
8634                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8635                self.generate_expression(partition_of)?;
8636
8637                // Output columns/constraints if present
8638                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8639                    self.write(" (");
8640                    let mut first = true;
8641                    for col in &ct.columns {
8642                        if !first {
8643                            self.write(", ");
8644                        }
8645                        first = false;
8646                        self.generate_column_def(col)?;
8647                    }
8648                    for constraint in &ct.constraints {
8649                        if !first {
8650                            self.write(", ");
8651                        }
8652                        first = false;
8653                        self.generate_table_constraint(constraint)?;
8654                    }
8655                    self.write(")");
8656                }
8657            }
8658
8659            // Output table properties (e.g., PARTITION BY RANGE(population))
8660            for prop in &ct.properties {
8661                self.write_space();
8662                self.generate_expression(prop)?;
8663            }
8664
8665            return Ok(());
8666        }
8667
8668        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8669        // This matches Python sqlglot's behavior for SQLite dialect
8670        self.sqlite_inline_pk_columns.clear();
8671        if matches!(
8672            self.config.dialect,
8673            Some(crate::dialects::DialectType::SQLite)
8674        ) {
8675            for constraint in &ct.constraints {
8676                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8677                    // Only inline if: single column, no constraint name, and column exists in table
8678                    if columns.len() == 1 && name.is_none() {
8679                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8680                        // Check if this column exists in the table
8681                        if ct
8682                            .columns
8683                            .iter()
8684                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8685                        {
8686                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8687                        }
8688                    }
8689                }
8690            }
8691        }
8692
8693        // Output columns if present (even for CTAS with columns)
8694        if !ct.columns.is_empty() {
8695            if self.config.pretty {
8696                // Pretty print: each column on new line
8697                self.write(" (");
8698                self.write_newline();
8699                self.indent_level += 1;
8700                for (i, col) in ct.columns.iter().enumerate() {
8701                    if i > 0 {
8702                        self.write(",");
8703                        self.write_newline();
8704                    }
8705                    self.write_indent();
8706                    self.generate_column_def(col)?;
8707                }
8708                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8709                for constraint in &ct.constraints {
8710                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8711                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8712                        if columns.len() == 1
8713                            && name.is_none()
8714                            && self
8715                                .sqlite_inline_pk_columns
8716                                .contains(&columns[0].name.to_ascii_lowercase())
8717                        {
8718                            continue;
8719                        }
8720                    }
8721                    self.write(",");
8722                    self.write_newline();
8723                    self.write_indent();
8724                    self.generate_table_constraint(constraint)?;
8725                }
8726                self.indent_level -= 1;
8727                self.write_newline();
8728                self.write(")");
8729            } else {
8730                self.write(" (");
8731                for (i, col) in ct.columns.iter().enumerate() {
8732                    if i > 0 {
8733                        self.write(", ");
8734                    }
8735                    self.generate_column_def(col)?;
8736                }
8737                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8738                let mut first_constraint = true;
8739                for constraint in &ct.constraints {
8740                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8741                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8742                        if columns.len() == 1
8743                            && name.is_none()
8744                            && self
8745                                .sqlite_inline_pk_columns
8746                                .contains(&columns[0].name.to_ascii_lowercase())
8747                        {
8748                            continue;
8749                        }
8750                    }
8751                    if first_constraint {
8752                        self.write(", ");
8753                        first_constraint = false;
8754                    } else {
8755                        self.write(", ");
8756                    }
8757                    self.generate_table_constraint(constraint)?;
8758                }
8759                self.write(")");
8760            }
8761        } else if !ct.constraints.is_empty() {
8762            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8763            let has_like_only = ct
8764                .constraints
8765                .iter()
8766                .all(|c| matches!(c, TableConstraint::Like { .. }));
8767            let has_tags_only = ct
8768                .constraints
8769                .iter()
8770                .all(|c| matches!(c, TableConstraint::Tags(_)));
8771            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8772            // Most dialects: CREATE TABLE A LIKE B (no parens)
8773            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8774            let is_pg_like = matches!(
8775                self.config.dialect,
8776                Some(crate::dialects::DialectType::PostgreSQL)
8777                    | Some(crate::dialects::DialectType::CockroachDB)
8778                    | Some(crate::dialects::DialectType::Materialize)
8779                    | Some(crate::dialects::DialectType::RisingWave)
8780                    | Some(crate::dialects::DialectType::Redshift)
8781                    | Some(crate::dialects::DialectType::Presto)
8782                    | Some(crate::dialects::DialectType::Trino)
8783                    | Some(crate::dialects::DialectType::Athena)
8784            );
8785            let use_parens = if has_like_only {
8786                is_pg_like
8787            } else {
8788                !has_tags_only
8789            };
8790            if self.config.pretty && use_parens {
8791                self.write(" (");
8792                self.write_newline();
8793                self.indent_level += 1;
8794                for (i, constraint) in ct.constraints.iter().enumerate() {
8795                    if i > 0 {
8796                        self.write(",");
8797                        self.write_newline();
8798                    }
8799                    self.write_indent();
8800                    self.generate_table_constraint(constraint)?;
8801                }
8802                self.indent_level -= 1;
8803                self.write_newline();
8804                self.write(")");
8805            } else {
8806                if use_parens {
8807                    self.write(" (");
8808                } else {
8809                    self.write_space();
8810                }
8811                for (i, constraint) in ct.constraints.iter().enumerate() {
8812                    if i > 0 {
8813                        self.write(", ");
8814                    }
8815                    self.generate_table_constraint(constraint)?;
8816                }
8817                if use_parens {
8818                    self.write(")");
8819                }
8820            }
8821        }
8822
8823        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8824            if let Some(ref clone_source) = ct.clone_source {
8825                self.write_space();
8826                self.write_keyword("AS");
8827                self.write_space();
8828                self.generate_table(clone_source)?;
8829            }
8830        }
8831
8832        // TSQL ON filegroup or ON filegroup (partition_column) clause
8833        if let Some(ref on_prop) = ct.on_property {
8834            self.write(" ");
8835            self.write_keyword("ON");
8836            self.write(" ");
8837            self.generate_expression(&on_prop.this)?;
8838        }
8839
8840        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8841        if !ct.with_partition_columns.is_empty() {
8842            if self.config.pretty {
8843                self.write_newline();
8844            } else {
8845                self.write_space();
8846            }
8847            self.write_keyword("WITH PARTITION COLUMNS");
8848            self.write(" (");
8849            if self.config.pretty {
8850                self.write_newline();
8851                self.indent_level += 1;
8852                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8853                    if i > 0 {
8854                        self.write(",");
8855                        self.write_newline();
8856                    }
8857                    self.write_indent();
8858                    self.generate_column_def(col)?;
8859                }
8860                self.indent_level -= 1;
8861                self.write_newline();
8862            } else {
8863                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8864                    if i > 0 {
8865                        self.write(", ");
8866                    }
8867                    self.generate_column_def(col)?;
8868                }
8869            }
8870            self.write(")");
8871        }
8872
8873        // BigQuery: WITH CONNECTION `project.region.connection`
8874        if let Some(ref conn) = ct.with_connection {
8875            if self.config.pretty {
8876                self.write_newline();
8877            } else {
8878                self.write_space();
8879            }
8880            self.write_keyword("WITH CONNECTION");
8881            self.write_space();
8882            self.generate_table(conn)?;
8883        }
8884
8885        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8886        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8887        if !is_clickhouse {
8888            for prop in &ct.properties {
8889                if let Expression::SchemaCommentProperty(_) = prop {
8890                    if self.config.pretty {
8891                        self.write_newline();
8892                    } else {
8893                        self.write_space();
8894                    }
8895                    self.generate_expression(prop)?;
8896                }
8897            }
8898        }
8899
8900        // WITH properties (output after columns if columns exist, otherwise before AS)
8901        if !ct.with_properties.is_empty() {
8902            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8903            let is_snowflake_special_table = matches!(
8904                self.config.dialect,
8905                Some(crate::dialects::DialectType::Snowflake)
8906            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8907                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8908            if is_snowflake_special_table {
8909                for (key, value) in &ct.with_properties {
8910                    self.write_space();
8911                    self.write(key);
8912                    self.write("=");
8913                    self.write(value);
8914                }
8915            } else if self.config.pretty {
8916                self.write_newline();
8917                self.write_keyword("WITH");
8918                self.write(" (");
8919                self.write_newline();
8920                self.indent_level += 1;
8921                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8922                    if i > 0 {
8923                        self.write(",");
8924                        self.write_newline();
8925                    }
8926                    self.write_indent();
8927                    self.write(key);
8928                    self.write("=");
8929                    self.write(value);
8930                }
8931                self.indent_level -= 1;
8932                self.write_newline();
8933                self.write(")");
8934            } else {
8935                self.write_space();
8936                self.write_keyword("WITH");
8937                self.write(" (");
8938                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8939                    if i > 0 {
8940                        self.write(", ");
8941                    }
8942                    self.write(key);
8943                    self.write("=");
8944                    self.write(value);
8945                }
8946                self.write(")");
8947            }
8948        }
8949
8950        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8951            if is_clickhouse && ct.as_select.is_some() {
8952                let mut pre = Vec::new();
8953                let mut post = Vec::new();
8954                for prop in &ct.properties {
8955                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8956                        post.push(prop);
8957                    } else {
8958                        pre.push(prop);
8959                    }
8960                }
8961                (pre, post)
8962            } else {
8963                (ct.properties.iter().collect(), Vec::new())
8964            };
8965
8966        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8967        for prop in pre_as_properties {
8968            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8969            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8970                continue;
8971            }
8972            if self.config.pretty {
8973                self.write_newline();
8974            } else {
8975                self.write_space();
8976            }
8977            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8978            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8979            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8980            if let Expression::Properties(props) = prop {
8981                let is_hive_dialect = matches!(
8982                    self.config.dialect,
8983                    Some(crate::dialects::DialectType::Hive)
8984                        | Some(crate::dialects::DialectType::Spark)
8985                        | Some(crate::dialects::DialectType::Databricks)
8986                        | Some(crate::dialects::DialectType::Athena)
8987                );
8988                let is_doris_starrocks = matches!(
8989                    self.config.dialect,
8990                    Some(crate::dialects::DialectType::Doris)
8991                        | Some(crate::dialects::DialectType::StarRocks)
8992                );
8993                if is_hive_dialect {
8994                    self.generate_tblproperties_clause(&props.expressions)?;
8995                } else if is_doris_starrocks {
8996                    self.generate_properties_clause(&props.expressions)?;
8997                } else {
8998                    self.generate_options_clause(&props.expressions)?;
8999                }
9000            } else {
9001                self.generate_expression(prop)?;
9002            }
9003        }
9004
9005        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
9006        for prop in &ct.post_table_properties {
9007            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
9008                self.write(" WITH(");
9009                self.generate_system_versioning_content(svp)?;
9010                self.write(")");
9011            } else if let Expression::Properties(props) = prop {
9012                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
9013                let is_doris_starrocks = matches!(
9014                    self.config.dialect,
9015                    Some(crate::dialects::DialectType::Doris)
9016                        | Some(crate::dialects::DialectType::StarRocks)
9017                );
9018                self.write_space();
9019                if is_doris_starrocks {
9020                    self.generate_properties_clause(&props.expressions)?;
9021                } else {
9022                    self.generate_options_clause(&props.expressions)?;
9023                }
9024            } else {
9025                self.write_space();
9026                self.generate_expression(prop)?;
9027            }
9028        }
9029
9030        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
9031        // Only output for StarRocks target
9032        if let Some(ref rollup) = ct.rollup {
9033            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
9034                self.write_space();
9035                self.generate_rollup_property(rollup)?;
9036            }
9037        }
9038
9039        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
9040        // Only output for MySQL-compatible dialects; strip for others during transpilation
9041        // COMMENT is also used by Hive/Spark so we selectively preserve it
9042        let is_mysql_compatible = matches!(
9043            self.config.dialect,
9044            Some(DialectType::MySQL)
9045                | Some(DialectType::TiDB)
9046                | Some(DialectType::SingleStore)
9047                | Some(DialectType::Doris)
9048                | Some(DialectType::StarRocks)
9049                | None
9050        );
9051        let is_hive_compatible = matches!(
9052            self.config.dialect,
9053            Some(DialectType::Hive)
9054                | Some(DialectType::Spark)
9055                | Some(DialectType::Databricks)
9056                | Some(DialectType::Athena)
9057        );
9058        let mysql_pretty_options =
9059            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
9060        for (key, value) in &ct.mysql_table_options {
9061            // Skip non-MySQL-specific options for non-MySQL targets
9062            let should_output = if is_mysql_compatible {
9063                true
9064            } else if is_hive_compatible && key == "COMMENT" {
9065                true // COMMENT is valid in Hive/Spark table definitions
9066            } else {
9067                false
9068            };
9069            if should_output {
9070                if mysql_pretty_options {
9071                    self.write_newline();
9072                    self.write_indent();
9073                } else {
9074                    self.write_space();
9075                }
9076                self.write_keyword(key);
9077                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
9078                if key == "COMMENT" && !self.config.schema_comment_with_eq {
9079                    self.write_space();
9080                } else {
9081                    self.write("=");
9082                }
9083                self.write(value);
9084            }
9085        }
9086
9087        for option in &ct.tidb_table_options {
9088            if self.config.pretty {
9089                self.write_newline();
9090                self.write_indent();
9091            } else {
9092                self.write_space();
9093            }
9094            self.generate_tidb_table_option(option, false)?;
9095        }
9096
9097        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
9098        if ct.temporary
9099            && matches!(
9100                self.config.dialect,
9101                Some(DialectType::Spark) | Some(DialectType::Databricks)
9102            )
9103            && ct.as_select.is_none()
9104        {
9105            self.write_space();
9106            self.write_keyword("USING PARQUET");
9107        }
9108
9109        // PostgreSQL INHERITS clause
9110        if !ct.inherits.is_empty() {
9111            self.write_space();
9112            self.write_keyword("INHERITS");
9113            self.write(" (");
9114            for (i, parent) in ct.inherits.iter().enumerate() {
9115                if i > 0 {
9116                    self.write(", ");
9117                }
9118                self.generate_table(parent)?;
9119            }
9120            self.write(")");
9121        }
9122
9123        // CREATE TABLE AS SELECT
9124        if let Some(ref query) = ct.as_select {
9125            self.write_space();
9126            self.write_keyword("AS");
9127            self.write_space();
9128            let source_is_clickhouse =
9129                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
9130            let wrap_as_select =
9131                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
9132            if wrap_as_select {
9133                self.write("(");
9134            }
9135            self.generate_expression(query)?;
9136            if wrap_as_select {
9137                self.write(")");
9138            }
9139
9140            // Teradata: WITH DATA / WITH NO DATA
9141            if let Some(with_data) = ct.with_data {
9142                self.write_space();
9143                self.write_keyword("WITH");
9144                if !with_data {
9145                    self.write_space();
9146                    self.write_keyword("NO");
9147                }
9148                self.write_space();
9149                self.write_keyword("DATA");
9150            }
9151
9152            // Teradata: AND STATISTICS / AND NO STATISTICS
9153            if let Some(with_statistics) = ct.with_statistics {
9154                self.write_space();
9155                self.write_keyword("AND");
9156                if !with_statistics {
9157                    self.write_space();
9158                    self.write_keyword("NO");
9159                }
9160                self.write_space();
9161                self.write_keyword("STATISTICS");
9162            }
9163
9164            // Teradata: Index specifications
9165            for index in &ct.teradata_indexes {
9166                self.write_space();
9167                match index.kind {
9168                    TeradataIndexKind::NoPrimary => {
9169                        self.write_keyword("NO PRIMARY INDEX");
9170                    }
9171                    TeradataIndexKind::Primary => {
9172                        self.write_keyword("PRIMARY INDEX");
9173                    }
9174                    TeradataIndexKind::PrimaryAmp => {
9175                        self.write_keyword("PRIMARY AMP INDEX");
9176                    }
9177                    TeradataIndexKind::Unique => {
9178                        self.write_keyword("UNIQUE INDEX");
9179                    }
9180                    TeradataIndexKind::UniquePrimary => {
9181                        self.write_keyword("UNIQUE PRIMARY INDEX");
9182                    }
9183                    TeradataIndexKind::Secondary => {
9184                        self.write_keyword("INDEX");
9185                    }
9186                }
9187                // Output index name if present
9188                if let Some(ref name) = index.name {
9189                    self.write_space();
9190                    self.write(name);
9191                }
9192                // Output columns if present
9193                if !index.columns.is_empty() {
9194                    self.write(" (");
9195                    for (i, col) in index.columns.iter().enumerate() {
9196                        if i > 0 {
9197                            self.write(", ");
9198                        }
9199                        self.write(col);
9200                    }
9201                    self.write(")");
9202                }
9203            }
9204
9205            // Teradata: ON COMMIT behavior for volatile tables
9206            if let Some(ref on_commit) = ct.on_commit {
9207                self.write_space();
9208                self.write_keyword("ON COMMIT");
9209                self.write_space();
9210                match on_commit {
9211                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9212                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9213                }
9214            }
9215
9216            if !post_as_properties.is_empty() {
9217                for prop in post_as_properties {
9218                    self.write_space();
9219                    self.generate_expression(prop)?;
9220                }
9221            }
9222
9223            // Restore Athena Hive context before early return
9224            self.athena_hive_context = saved_athena_hive_context;
9225            return Ok(());
9226        }
9227
9228        // ON COMMIT behavior (for non-CTAS tables)
9229        if let Some(ref on_commit) = ct.on_commit {
9230            self.write_space();
9231            self.write_keyword("ON COMMIT");
9232            self.write_space();
9233            match on_commit {
9234                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9235                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9236            }
9237        }
9238
9239        // Restore Athena Hive context
9240        self.athena_hive_context = saved_athena_hive_context;
9241
9242        Ok(())
9243    }
9244
9245    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
9246    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
9247    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
9248        // Output column name
9249        self.generate_identifier(&col.name)?;
9250        // Output data type if known
9251        if !matches!(col.data_type, DataType::Unknown) {
9252            self.write_space();
9253            self.generate_data_type(&col.data_type)?;
9254        }
9255        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
9256        for constraint in &col.constraints {
9257            if let ColumnConstraint::Path(path_expr) = constraint {
9258                self.write_space();
9259                self.write_keyword("PATH");
9260                self.write_space();
9261                self.generate_expression(path_expr)?;
9262            }
9263        }
9264        Ok(())
9265    }
9266
9267    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
9268        // Check if this is a TSQL computed column (no data type)
9269        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9270            && col
9271                .constraints
9272                .iter()
9273                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9274        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
9275        let omit_computed_type = !self.config.computed_column_with_type
9276            && col
9277                .constraints
9278                .iter()
9279                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9280
9281        // Check if this is a partition column spec (no data type, type is Unknown)
9282        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
9283        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
9284
9285        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
9286        // Also check the no_type flag for SQLite columns without types
9287        let has_no_type = col.no_type
9288            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9289                && col.constraints.is_empty());
9290
9291        self.generate_identifier(&col.name)?;
9292
9293        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
9294        let serial_expansion = if matches!(
9295            self.config.dialect,
9296            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
9297        ) {
9298            if let DataType::Custom { ref name } = col.data_type {
9299                if name.eq_ignore_ascii_case("SERIAL") {
9300                    Some("INT")
9301                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
9302                    Some("BIGINT")
9303                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
9304                    Some("SMALLINT")
9305                } else {
9306                    None
9307                }
9308            } else {
9309                None
9310            }
9311        } else {
9312            None
9313        };
9314
9315        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
9316        {
9317            self.write_space();
9318            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
9319            // since ClickHouse uses explicit Nullable() in its type system.
9320            let saved_nullable_depth = self.clickhouse_nullable_depth;
9321            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9322                self.clickhouse_nullable_depth = -1;
9323            }
9324            if let Some(int_type) = serial_expansion {
9325                // SERIAL -> INT (+ constraints added below)
9326                self.write_keyword(int_type);
9327            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9328                // For DuckDB: convert unsigned integer types to their unsigned equivalents
9329                let unsigned_type = match &col.data_type {
9330                    DataType::Int { .. } => Some("UINTEGER"),
9331                    DataType::BigInt { .. } => Some("UBIGINT"),
9332                    DataType::SmallInt { .. } => Some("USMALLINT"),
9333                    DataType::TinyInt { .. } => Some("UTINYINT"),
9334                    _ => None,
9335                };
9336                if let Some(utype) = unsigned_type {
9337                    self.write_keyword(utype);
9338                } else {
9339                    self.generate_data_type(&col.data_type)?;
9340                }
9341            } else {
9342                self.generate_data_type(&col.data_type)?;
9343            }
9344            self.clickhouse_nullable_depth = saved_nullable_depth;
9345        }
9346
9347        // MySQL type modifiers (must come right after data type)
9348        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
9349        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9350            self.write_space();
9351            self.write_keyword("UNSIGNED");
9352        }
9353        if col.zerofill {
9354            self.write_space();
9355            self.write_keyword("ZEROFILL");
9356        }
9357
9358        // Teradata column attributes (must come right after data type, in specific order)
9359        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
9360
9361        if let Some(ref charset) = col.character_set {
9362            self.write_space();
9363            self.write_keyword("CHARACTER SET");
9364            self.write_space();
9365            self.write(charset);
9366        }
9367
9368        if col.uppercase {
9369            self.write_space();
9370            self.write_keyword("UPPERCASE");
9371        }
9372
9373        if let Some(casespecific) = col.casespecific {
9374            self.write_space();
9375            if casespecific {
9376                self.write_keyword("CASESPECIFIC");
9377            } else {
9378                self.write_keyword("NOT CASESPECIFIC");
9379            }
9380        }
9381
9382        if let Some(ref format) = col.format {
9383            self.write_space();
9384            self.write_keyword("FORMAT");
9385            self.write(" '");
9386            self.write(format);
9387            self.write("'");
9388        }
9389
9390        if let Some(ref title) = col.title {
9391            self.write_space();
9392            self.write_keyword("TITLE");
9393            self.write(" '");
9394            self.write(title);
9395            self.write("'");
9396        }
9397
9398        if let Some(length) = col.inline_length {
9399            self.write_space();
9400            self.write_keyword("INLINE LENGTH");
9401            self.write(" ");
9402            self.write(&length.to_string());
9403        }
9404
9405        if let Some(ref compress) = col.compress {
9406            self.write_space();
9407            self.write_keyword("COMPRESS");
9408            if !compress.is_empty() {
9409                // Single string literal: output without parentheses (Teradata syntax)
9410                if compress.len() == 1 {
9411                    if let Expression::Literal(lit) = &compress[0] {
9412                        if let Literal::String(_) = lit.as_ref() {
9413                            self.write_space();
9414                            self.generate_expression(&compress[0])?;
9415                        }
9416                    } else {
9417                        self.write(" (");
9418                        self.generate_expression(&compress[0])?;
9419                        self.write(")");
9420                    }
9421                } else {
9422                    self.write(" (");
9423                    for (i, val) in compress.iter().enumerate() {
9424                        if i > 0 {
9425                            self.write(", ");
9426                        }
9427                        self.generate_expression(val)?;
9428                    }
9429                    self.write(")");
9430                }
9431            }
9432        }
9433
9434        // Column constraints - output in original order if constraint_order is populated
9435        // Otherwise fall back to legacy fixed order for backward compatibility
9436        if !col.constraint_order.is_empty() {
9437            // Use constraint_order for original ordering
9438            // Track indices for constraints stored in the constraints Vec
9439            let mut references_idx = 0;
9440            let mut check_idx = 0;
9441            let mut generated_idx = 0;
9442            let mut collate_idx = 0;
9443            let mut comment_idx = 0;
9444            // The preprocessing in dialects/mod.rs now handles the correct ordering of
9445            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
9446            let defer_not_null_after_identity = false;
9447            let mut pending_not_null_after_identity = false;
9448
9449            for constraint_type in &col.constraint_order {
9450                match constraint_type {
9451                    ConstraintType::PrimaryKey => {
9452                        // Materialize doesn't support PRIMARY KEY column constraints
9453                        if col.primary_key
9454                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
9455                        {
9456                            if let Some(ref cname) = col.primary_key_constraint_name {
9457                                self.write_space();
9458                                self.write_keyword("CONSTRAINT");
9459                                self.write_space();
9460                                self.write(cname);
9461                            }
9462                            self.write_space();
9463                            self.write_keyword("PRIMARY KEY");
9464                            if let Some(ref order) = col.primary_key_order {
9465                                self.write_space();
9466                                match order {
9467                                    SortOrder::Asc => self.write_keyword("ASC"),
9468                                    SortOrder::Desc => self.write_keyword("DESC"),
9469                                }
9470                            }
9471                        }
9472                    }
9473                    ConstraintType::Unique => {
9474                        if col.unique {
9475                            if let Some(ref cname) = col.unique_constraint_name {
9476                                self.write_space();
9477                                self.write_keyword("CONSTRAINT");
9478                                self.write_space();
9479                                self.write(cname);
9480                            }
9481                            self.write_space();
9482                            self.write_keyword("UNIQUE");
9483                            // PostgreSQL 15+: NULLS NOT DISTINCT
9484                            if col.unique_nulls_not_distinct {
9485                                self.write(" NULLS NOT DISTINCT");
9486                            }
9487                        }
9488                    }
9489                    ConstraintType::NotNull => {
9490                        if col.nullable == Some(false) {
9491                            if defer_not_null_after_identity {
9492                                pending_not_null_after_identity = true;
9493                                continue;
9494                            }
9495                            if let Some(ref cname) = col.not_null_constraint_name {
9496                                self.write_space();
9497                                self.write_keyword("CONSTRAINT");
9498                                self.write_space();
9499                                self.write(cname);
9500                            }
9501                            self.write_space();
9502                            self.write_keyword("NOT NULL");
9503                        }
9504                    }
9505                    ConstraintType::Null => {
9506                        if col.nullable == Some(true) {
9507                            self.write_space();
9508                            self.write_keyword("NULL");
9509                        }
9510                    }
9511                    ConstraintType::Default => {
9512                        if let Some(ref default) = col.default {
9513                            self.write_space();
9514                            self.write_keyword("DEFAULT");
9515                            self.write_space();
9516                            self.generate_expression(default)?;
9517                        }
9518                    }
9519                    ConstraintType::AutoIncrement => {
9520                        if col.auto_increment {
9521                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
9522                            if matches!(
9523                                self.config.dialect,
9524                                Some(crate::dialects::DialectType::DuckDB)
9525                            ) {
9526                                // Skip - DuckDB uses sequences or rowid instead
9527                            } else if matches!(
9528                                self.config.dialect,
9529                                Some(crate::dialects::DialectType::Materialize)
9530                            ) {
9531                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
9532                                if !matches!(col.nullable, Some(false)) {
9533                                    self.write_space();
9534                                    self.write_keyword("NOT NULL");
9535                                }
9536                            } else if matches!(
9537                                self.config.dialect,
9538                                Some(crate::dialects::DialectType::PostgreSQL)
9539                            ) {
9540                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
9541                                self.write_space();
9542                                self.generate_auto_increment_keyword(col)?;
9543                            } else if matches!(
9544                                self.config.dialect,
9545                                Some(crate::dialects::DialectType::SQLite)
9546                            ) && !col.primary_key
9547                                && self
9548                                    .sqlite_inline_pk_columns
9549                                    .contains(&col.name.name.to_ascii_lowercase())
9550                            {
9551                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
9552                                // The table-level primary key is emitted later inline.
9553                            } else {
9554                                self.write_space();
9555                                self.generate_auto_increment_keyword(col)?;
9556                                if pending_not_null_after_identity {
9557                                    self.write_space();
9558                                    self.write_keyword("NOT NULL");
9559                                    pending_not_null_after_identity = false;
9560                                }
9561                            }
9562                        } // close else for DuckDB skip
9563                    }
9564                    ConstraintType::AutoRandom => {
9565                        if let Some(ref auto_random) = col.auto_random {
9566                            self.write_space();
9567                            self.generate_tidb_auto_random(auto_random)?;
9568                        }
9569                    }
9570                    ConstraintType::References => {
9571                        // Find next References constraint
9572                        while references_idx < col.constraints.len() {
9573                            if let ColumnConstraint::References(fk_ref) =
9574                                &col.constraints[references_idx]
9575                            {
9576                                // CONSTRAINT name if present
9577                                if let Some(ref name) = fk_ref.constraint_name {
9578                                    self.write_space();
9579                                    self.write_keyword("CONSTRAINT");
9580                                    self.write_space();
9581                                    self.write(name);
9582                                }
9583                                self.write_space();
9584                                if fk_ref.has_foreign_key_keywords {
9585                                    self.write_keyword("FOREIGN KEY");
9586                                    self.write_space();
9587                                }
9588                                self.write_keyword("REFERENCES");
9589                                self.write_space();
9590                                self.generate_table(&fk_ref.table)?;
9591                                if !fk_ref.columns.is_empty() {
9592                                    self.write(" (");
9593                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9594                                        if i > 0 {
9595                                            self.write(", ");
9596                                        }
9597                                        self.generate_identifier(c)?;
9598                                    }
9599                                    self.write(")");
9600                                }
9601                                self.generate_referential_actions(fk_ref)?;
9602                                references_idx += 1;
9603                                break;
9604                            }
9605                            references_idx += 1;
9606                        }
9607                    }
9608                    ConstraintType::Check => {
9609                        // Find next Check constraint
9610                        while check_idx < col.constraints.len() {
9611                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9612                                // Output CONSTRAINT name if present (only for first CHECK)
9613                                if check_idx == 0 {
9614                                    if let Some(ref cname) = col.check_constraint_name {
9615                                        self.write_space();
9616                                        self.write_keyword("CONSTRAINT");
9617                                        self.write_space();
9618                                        self.write(cname);
9619                                    }
9620                                }
9621                                self.write_space();
9622                                self.write_keyword("CHECK");
9623                                self.write(" (");
9624                                self.generate_expression(expr)?;
9625                                self.write(")");
9626                                check_idx += 1;
9627                                break;
9628                            }
9629                            check_idx += 1;
9630                        }
9631                    }
9632                    ConstraintType::GeneratedAsIdentity => {
9633                        // Find next GeneratedAsIdentity constraint
9634                        while generated_idx < col.constraints.len() {
9635                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9636                                &col.constraints[generated_idx]
9637                            {
9638                                self.write_space();
9639                                // Redshift uses IDENTITY(start, increment) syntax
9640                                if matches!(
9641                                    self.config.dialect,
9642                                    Some(crate::dialects::DialectType::Redshift)
9643                                ) {
9644                                    self.write_keyword("IDENTITY");
9645                                    self.write("(");
9646                                    if let Some(ref start) = gen.start {
9647                                        self.generate_expression(start)?;
9648                                    } else {
9649                                        self.write("0");
9650                                    }
9651                                    self.write(", ");
9652                                    if let Some(ref incr) = gen.increment {
9653                                        self.generate_expression(incr)?;
9654                                    } else {
9655                                        self.write("1");
9656                                    }
9657                                    self.write(")");
9658                                } else {
9659                                    self.write_keyword("GENERATED");
9660                                    if gen.always {
9661                                        self.write_space();
9662                                        self.write_keyword("ALWAYS");
9663                                    } else {
9664                                        self.write_space();
9665                                        self.write_keyword("BY DEFAULT");
9666                                        if gen.on_null {
9667                                            self.write_space();
9668                                            self.write_keyword("ON NULL");
9669                                        }
9670                                    }
9671                                    self.write_space();
9672                                    self.write_keyword("AS IDENTITY");
9673
9674                                    let has_options = gen.start.is_some()
9675                                        || gen.increment.is_some()
9676                                        || gen.minvalue.is_some()
9677                                        || gen.maxvalue.is_some()
9678                                        || gen.cycle.is_some();
9679                                    if has_options {
9680                                        self.write(" (");
9681                                        let mut first = true;
9682                                        if let Some(ref start) = gen.start {
9683                                            if !first {
9684                                                self.write(" ");
9685                                            }
9686                                            first = false;
9687                                            self.write_keyword("START WITH");
9688                                            self.write_space();
9689                                            self.generate_expression(start)?;
9690                                        }
9691                                        if let Some(ref incr) = gen.increment {
9692                                            if !first {
9693                                                self.write(" ");
9694                                            }
9695                                            first = false;
9696                                            self.write_keyword("INCREMENT BY");
9697                                            self.write_space();
9698                                            self.generate_expression(incr)?;
9699                                        }
9700                                        if let Some(ref minv) = gen.minvalue {
9701                                            if !first {
9702                                                self.write(" ");
9703                                            }
9704                                            first = false;
9705                                            self.write_keyword("MINVALUE");
9706                                            self.write_space();
9707                                            self.generate_expression(minv)?;
9708                                        }
9709                                        if let Some(ref maxv) = gen.maxvalue {
9710                                            if !first {
9711                                                self.write(" ");
9712                                            }
9713                                            first = false;
9714                                            self.write_keyword("MAXVALUE");
9715                                            self.write_space();
9716                                            self.generate_expression(maxv)?;
9717                                        }
9718                                        if let Some(cycle) = gen.cycle {
9719                                            if !first {
9720                                                self.write(" ");
9721                                            }
9722                                            if cycle {
9723                                                self.write_keyword("CYCLE");
9724                                            } else {
9725                                                self.write_keyword("NO CYCLE");
9726                                            }
9727                                        }
9728                                        self.write(")");
9729                                    }
9730                                }
9731                                generated_idx += 1;
9732                                break;
9733                            }
9734                            generated_idx += 1;
9735                        }
9736                    }
9737                    ConstraintType::Collate => {
9738                        // Find next Collate constraint
9739                        while collate_idx < col.constraints.len() {
9740                            if let ColumnConstraint::Collate(collation) =
9741                                &col.constraints[collate_idx]
9742                            {
9743                                self.write_space();
9744                                self.write_keyword("COLLATE");
9745                                self.write_space();
9746                                self.generate_identifier(collation)?;
9747                                collate_idx += 1;
9748                                break;
9749                            }
9750                            collate_idx += 1;
9751                        }
9752                    }
9753                    ConstraintType::Comment => {
9754                        // Find next Comment constraint
9755                        while comment_idx < col.constraints.len() {
9756                            if let ColumnConstraint::Comment(comment) =
9757                                &col.constraints[comment_idx]
9758                            {
9759                                self.write_space();
9760                                self.write_keyword("COMMENT");
9761                                self.write_space();
9762                                self.generate_string_literal(comment)?;
9763                                comment_idx += 1;
9764                                break;
9765                            }
9766                            comment_idx += 1;
9767                        }
9768                    }
9769                    ConstraintType::Tags => {
9770                        // Find next Tags constraint (Snowflake)
9771                        for constraint in &col.constraints {
9772                            if let ColumnConstraint::Tags(tags) = constraint {
9773                                self.write_space();
9774                                self.write_keyword("TAG");
9775                                self.write(" (");
9776                                for (i, expr) in tags.expressions.iter().enumerate() {
9777                                    if i > 0 {
9778                                        self.write(", ");
9779                                    }
9780                                    self.generate_expression(expr)?;
9781                                }
9782                                self.write(")");
9783                                break;
9784                            }
9785                        }
9786                    }
9787                    ConstraintType::ComputedColumn => {
9788                        // Find next ComputedColumn constraint
9789                        for constraint in &col.constraints {
9790                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9791                                self.write_space();
9792                                self.generate_computed_column_inline(cc)?;
9793                                break;
9794                            }
9795                        }
9796                    }
9797                    ConstraintType::GeneratedAsRow => {
9798                        // Find next GeneratedAsRow constraint
9799                        for constraint in &col.constraints {
9800                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9801                                self.write_space();
9802                                self.generate_generated_as_row_inline(gar)?;
9803                                break;
9804                            }
9805                        }
9806                    }
9807                    ConstraintType::OnUpdate => {
9808                        if let Some(ref expr) = col.on_update {
9809                            self.write_space();
9810                            self.write_keyword("ON UPDATE");
9811                            self.write_space();
9812                            self.generate_expression(expr)?;
9813                        }
9814                    }
9815                    ConstraintType::Encode => {
9816                        if let Some(ref encoding) = col.encoding {
9817                            self.write_space();
9818                            self.write_keyword("ENCODE");
9819                            self.write_space();
9820                            self.write(encoding);
9821                        }
9822                    }
9823                    ConstraintType::Path => {
9824                        // Find next Path constraint
9825                        for constraint in &col.constraints {
9826                            if let ColumnConstraint::Path(path_expr) = constraint {
9827                                self.write_space();
9828                                self.write_keyword("PATH");
9829                                self.write_space();
9830                                self.generate_expression(path_expr)?;
9831                                break;
9832                            }
9833                        }
9834                    }
9835                }
9836            }
9837            if pending_not_null_after_identity {
9838                self.write_space();
9839                self.write_keyword("NOT NULL");
9840            }
9841        } else {
9842            // Legacy fixed order for backward compatibility
9843            if col.primary_key {
9844                self.write_space();
9845                self.write_keyword("PRIMARY KEY");
9846                if let Some(ref order) = col.primary_key_order {
9847                    self.write_space();
9848                    match order {
9849                        SortOrder::Asc => self.write_keyword("ASC"),
9850                        SortOrder::Desc => self.write_keyword("DESC"),
9851                    }
9852                }
9853            }
9854
9855            if col.unique {
9856                self.write_space();
9857                self.write_keyword("UNIQUE");
9858                // PostgreSQL 15+: NULLS NOT DISTINCT
9859                if col.unique_nulls_not_distinct {
9860                    self.write(" NULLS NOT DISTINCT");
9861                }
9862            }
9863
9864            match col.nullable {
9865                Some(false) => {
9866                    self.write_space();
9867                    self.write_keyword("NOT NULL");
9868                }
9869                Some(true) => {
9870                    self.write_space();
9871                    self.write_keyword("NULL");
9872                }
9873                None => {}
9874            }
9875
9876            if let Some(ref default) = col.default {
9877                self.write_space();
9878                self.write_keyword("DEFAULT");
9879                self.write_space();
9880                self.generate_expression(default)?;
9881            }
9882
9883            if col.auto_increment {
9884                self.write_space();
9885                self.generate_auto_increment_keyword(col)?;
9886            }
9887
9888            if let Some(ref auto_random) = col.auto_random {
9889                self.write_space();
9890                self.generate_tidb_auto_random(auto_random)?;
9891            }
9892
9893            // Column-level constraints from Vec
9894            for constraint in &col.constraints {
9895                match constraint {
9896                    ColumnConstraint::References(fk_ref) => {
9897                        self.write_space();
9898                        if fk_ref.has_foreign_key_keywords {
9899                            self.write_keyword("FOREIGN KEY");
9900                            self.write_space();
9901                        }
9902                        self.write_keyword("REFERENCES");
9903                        self.write_space();
9904                        self.generate_table(&fk_ref.table)?;
9905                        if !fk_ref.columns.is_empty() {
9906                            self.write(" (");
9907                            for (i, c) in fk_ref.columns.iter().enumerate() {
9908                                if i > 0 {
9909                                    self.write(", ");
9910                                }
9911                                self.generate_identifier(c)?;
9912                            }
9913                            self.write(")");
9914                        }
9915                        self.generate_referential_actions(fk_ref)?;
9916                    }
9917                    ColumnConstraint::Check(expr) => {
9918                        self.write_space();
9919                        self.write_keyword("CHECK");
9920                        self.write(" (");
9921                        self.generate_expression(expr)?;
9922                        self.write(")");
9923                    }
9924                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9925                        self.write_space();
9926                        // Redshift uses IDENTITY(start, increment) syntax
9927                        if matches!(
9928                            self.config.dialect,
9929                            Some(crate::dialects::DialectType::Redshift)
9930                        ) {
9931                            self.write_keyword("IDENTITY");
9932                            self.write("(");
9933                            if let Some(ref start) = gen.start {
9934                                self.generate_expression(start)?;
9935                            } else {
9936                                self.write("0");
9937                            }
9938                            self.write(", ");
9939                            if let Some(ref incr) = gen.increment {
9940                                self.generate_expression(incr)?;
9941                            } else {
9942                                self.write("1");
9943                            }
9944                            self.write(")");
9945                        } else {
9946                            self.write_keyword("GENERATED");
9947                            if gen.always {
9948                                self.write_space();
9949                                self.write_keyword("ALWAYS");
9950                            } else {
9951                                self.write_space();
9952                                self.write_keyword("BY DEFAULT");
9953                                if gen.on_null {
9954                                    self.write_space();
9955                                    self.write_keyword("ON NULL");
9956                                }
9957                            }
9958                            self.write_space();
9959                            self.write_keyword("AS IDENTITY");
9960
9961                            let has_options = gen.start.is_some()
9962                                || gen.increment.is_some()
9963                                || gen.minvalue.is_some()
9964                                || gen.maxvalue.is_some()
9965                                || gen.cycle.is_some();
9966                            if has_options {
9967                                self.write(" (");
9968                                let mut first = true;
9969                                if let Some(ref start) = gen.start {
9970                                    if !first {
9971                                        self.write(" ");
9972                                    }
9973                                    first = false;
9974                                    self.write_keyword("START WITH");
9975                                    self.write_space();
9976                                    self.generate_expression(start)?;
9977                                }
9978                                if let Some(ref incr) = gen.increment {
9979                                    if !first {
9980                                        self.write(" ");
9981                                    }
9982                                    first = false;
9983                                    self.write_keyword("INCREMENT BY");
9984                                    self.write_space();
9985                                    self.generate_expression(incr)?;
9986                                }
9987                                if let Some(ref minv) = gen.minvalue {
9988                                    if !first {
9989                                        self.write(" ");
9990                                    }
9991                                    first = false;
9992                                    self.write_keyword("MINVALUE");
9993                                    self.write_space();
9994                                    self.generate_expression(minv)?;
9995                                }
9996                                if let Some(ref maxv) = gen.maxvalue {
9997                                    if !first {
9998                                        self.write(" ");
9999                                    }
10000                                    first = false;
10001                                    self.write_keyword("MAXVALUE");
10002                                    self.write_space();
10003                                    self.generate_expression(maxv)?;
10004                                }
10005                                if let Some(cycle) = gen.cycle {
10006                                    if !first {
10007                                        self.write(" ");
10008                                    }
10009                                    if cycle {
10010                                        self.write_keyword("CYCLE");
10011                                    } else {
10012                                        self.write_keyword("NO CYCLE");
10013                                    }
10014                                }
10015                                self.write(")");
10016                            }
10017                        }
10018                    }
10019                    ColumnConstraint::Collate(collation) => {
10020                        self.write_space();
10021                        self.write_keyword("COLLATE");
10022                        self.write_space();
10023                        self.generate_identifier(collation)?;
10024                    }
10025                    ColumnConstraint::Comment(comment) => {
10026                        self.write_space();
10027                        self.write_keyword("COMMENT");
10028                        self.write_space();
10029                        self.generate_string_literal(comment)?;
10030                    }
10031                    ColumnConstraint::Path(path_expr) => {
10032                        self.write_space();
10033                        self.write_keyword("PATH");
10034                        self.write_space();
10035                        self.generate_expression(path_expr)?;
10036                    }
10037                    _ => {} // Other constraints handled above
10038                }
10039            }
10040
10041            // Redshift: ENCODE encoding_type (legacy path)
10042            if let Some(ref encoding) = col.encoding {
10043                self.write_space();
10044                self.write_keyword("ENCODE");
10045                self.write_space();
10046                self.write(encoding);
10047            }
10048        }
10049
10050        // ClickHouse: CODEC(...)
10051        if let Some(ref codec) = col.codec {
10052            self.write_space();
10053            self.write_keyword("CODEC");
10054            self.write("(");
10055            self.write(codec);
10056            self.write(")");
10057        }
10058
10059        if let Some(visible) = col.visible {
10060            self.write_space();
10061            if visible {
10062                self.write_keyword("VISIBLE");
10063            } else {
10064                self.write_keyword("INVISIBLE");
10065            }
10066        }
10067
10068        // ClickHouse: EPHEMERAL [expr]
10069        if let Some(ref ephemeral) = col.ephemeral {
10070            self.write_space();
10071            self.write_keyword("EPHEMERAL");
10072            if let Some(ref expr) = ephemeral {
10073                self.write_space();
10074                self.generate_expression(expr)?;
10075            }
10076        }
10077
10078        // ClickHouse: MATERIALIZED expr
10079        if let Some(ref mat_expr) = col.materialized_expr {
10080            self.write_space();
10081            self.write_keyword("MATERIALIZED");
10082            self.write_space();
10083            self.generate_expression(mat_expr)?;
10084        }
10085
10086        // ClickHouse: ALIAS expr
10087        if let Some(ref alias_expr) = col.alias_expr {
10088            self.write_space();
10089            self.write_keyword("ALIAS");
10090            self.write_space();
10091            self.generate_expression(alias_expr)?;
10092        }
10093
10094        // ClickHouse: TTL expr
10095        if let Some(ref ttl_expr) = col.ttl_expr {
10096            self.write_space();
10097            self.write_keyword("TTL");
10098            self.write_space();
10099            self.generate_expression(ttl_expr)?;
10100        }
10101
10102        // TSQL: NOT FOR REPLICATION
10103        if col.not_for_replication
10104            && matches!(
10105                self.config.dialect,
10106                Some(crate::dialects::DialectType::TSQL)
10107                    | Some(crate::dialects::DialectType::Fabric)
10108            )
10109        {
10110            self.write_space();
10111            self.write_keyword("NOT FOR REPLICATION");
10112        }
10113
10114        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
10115        if !col.options.is_empty() {
10116            self.write_space();
10117            self.generate_options_clause(&col.options)?;
10118        }
10119
10120        // SQLite: Inline PRIMARY KEY from table constraint
10121        // This comes at the end, after all existing column constraints
10122        if !col.primary_key
10123            && self
10124                .sqlite_inline_pk_columns
10125                .contains(&col.name.name.to_ascii_lowercase())
10126        {
10127            self.write_space();
10128            self.write_keyword("PRIMARY KEY");
10129            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
10130                self.write_space();
10131                self.generate_auto_increment_keyword(col)?;
10132            }
10133        }
10134
10135        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
10136        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
10137        if serial_expansion.is_some() {
10138            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
10139                self.write_space();
10140                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
10141            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
10142                self.write_space();
10143                self.write_keyword("NOT NULL");
10144            }
10145        }
10146
10147        Ok(())
10148    }
10149
10150    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
10151        match constraint {
10152            TableConstraint::PrimaryKey {
10153                name,
10154                columns,
10155                include_columns,
10156                modifiers,
10157                has_constraint_keyword,
10158            } => {
10159                if let Some(ref n) = name {
10160                    if *has_constraint_keyword {
10161                        self.write_keyword("CONSTRAINT");
10162                        self.write_space();
10163                        self.generate_identifier(n)?;
10164                        self.write_space();
10165                    }
10166                }
10167                self.write_keyword("PRIMARY KEY");
10168                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10169                if let Some(ref clustered) = modifiers.clustered {
10170                    self.write_space();
10171                    self.write_keyword(clustered);
10172                }
10173                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
10174                if let Some(ref n) = name {
10175                    if !*has_constraint_keyword {
10176                        self.write_space();
10177                        self.generate_identifier(n)?;
10178                    }
10179                }
10180                self.write(" (");
10181                for (i, col) in columns.iter().enumerate() {
10182                    if i > 0 {
10183                        self.write(", ");
10184                    }
10185                    self.generate_identifier(col)?;
10186                }
10187                self.write(")");
10188                if !include_columns.is_empty() {
10189                    self.write_space();
10190                    self.write_keyword("INCLUDE");
10191                    self.write(" (");
10192                    for (i, col) in include_columns.iter().enumerate() {
10193                        if i > 0 {
10194                            self.write(", ");
10195                        }
10196                        self.generate_identifier(col)?;
10197                    }
10198                    self.write(")");
10199                }
10200                self.generate_constraint_modifiers(modifiers);
10201            }
10202            TableConstraint::Unique {
10203                name,
10204                columns,
10205                columns_parenthesized,
10206                modifiers,
10207                has_constraint_keyword,
10208                nulls_not_distinct,
10209            } => {
10210                if let Some(ref n) = name {
10211                    if *has_constraint_keyword {
10212                        self.write_keyword("CONSTRAINT");
10213                        self.write_space();
10214                        self.generate_identifier(n)?;
10215                        self.write_space();
10216                    }
10217                }
10218                self.write_keyword("UNIQUE");
10219                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10220                if let Some(ref clustered) = modifiers.clustered {
10221                    self.write_space();
10222                    self.write_keyword(clustered);
10223                }
10224                // PostgreSQL 15+: NULLS NOT DISTINCT
10225                if *nulls_not_distinct {
10226                    self.write(" NULLS NOT DISTINCT");
10227                }
10228                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
10229                if let Some(ref n) = name {
10230                    if !*has_constraint_keyword {
10231                        self.write_space();
10232                        self.generate_identifier(n)?;
10233                    }
10234                }
10235                if *columns_parenthesized {
10236                    self.write(" (");
10237                    for (i, col) in columns.iter().enumerate() {
10238                        if i > 0 {
10239                            self.write(", ");
10240                        }
10241                        self.generate_identifier(col)?;
10242                    }
10243                    self.write(")");
10244                } else {
10245                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
10246                    for col in columns.iter() {
10247                        self.write_space();
10248                        self.generate_identifier(col)?;
10249                    }
10250                }
10251                self.generate_constraint_modifiers(modifiers);
10252            }
10253            TableConstraint::ForeignKey {
10254                name,
10255                columns,
10256                references,
10257                on_delete,
10258                on_update,
10259                modifiers,
10260            } => {
10261                if let Some(ref n) = name {
10262                    self.write_keyword("CONSTRAINT");
10263                    self.write_space();
10264                    self.generate_identifier(n)?;
10265                    self.write_space();
10266                }
10267                self.write_keyword("FOREIGN KEY");
10268                self.write(" (");
10269                for (i, col) in columns.iter().enumerate() {
10270                    if i > 0 {
10271                        self.write(", ");
10272                    }
10273                    self.generate_identifier(col)?;
10274                }
10275                self.write(")");
10276                if let Some(ref refs) = references {
10277                    self.write(" ");
10278                    self.write_keyword("REFERENCES");
10279                    self.write_space();
10280                    self.generate_table(&refs.table)?;
10281                    if !refs.columns.is_empty() {
10282                        if self.config.pretty {
10283                            self.write(" (");
10284                            self.write_newline();
10285                            self.indent_level += 1;
10286                            for (i, col) in refs.columns.iter().enumerate() {
10287                                if i > 0 {
10288                                    self.write(",");
10289                                    self.write_newline();
10290                                }
10291                                self.write_indent();
10292                                self.generate_identifier(col)?;
10293                            }
10294                            self.indent_level -= 1;
10295                            self.write_newline();
10296                            self.write_indent();
10297                            self.write(")");
10298                        } else {
10299                            self.write(" (");
10300                            for (i, col) in refs.columns.iter().enumerate() {
10301                                if i > 0 {
10302                                    self.write(", ");
10303                                }
10304                                self.generate_identifier(col)?;
10305                            }
10306                            self.write(")");
10307                        }
10308                    }
10309                    self.generate_referential_actions(refs)?;
10310                } else {
10311                    // No REFERENCES - output ON DELETE/ON UPDATE directly
10312                    if let Some(ref action) = on_delete {
10313                        self.write_space();
10314                        self.write_keyword("ON DELETE");
10315                        self.write_space();
10316                        self.generate_referential_action(action);
10317                    }
10318                    if let Some(ref action) = on_update {
10319                        self.write_space();
10320                        self.write_keyword("ON UPDATE");
10321                        self.write_space();
10322                        self.generate_referential_action(action);
10323                    }
10324                }
10325                self.generate_constraint_modifiers(modifiers);
10326            }
10327            TableConstraint::Check {
10328                name,
10329                expression,
10330                modifiers,
10331            } => {
10332                if let Some(ref n) = name {
10333                    self.write_keyword("CONSTRAINT");
10334                    self.write_space();
10335                    self.generate_identifier(n)?;
10336                    self.write_space();
10337                }
10338                self.write_keyword("CHECK");
10339                self.write(" (");
10340                self.generate_expression(expression)?;
10341                self.write(")");
10342                self.generate_constraint_modifiers(modifiers);
10343            }
10344            TableConstraint::Assume { name, expression } => {
10345                if let Some(ref n) = name {
10346                    self.write_keyword("CONSTRAINT");
10347                    self.write_space();
10348                    self.generate_identifier(n)?;
10349                    self.write_space();
10350                }
10351                self.write_keyword("ASSUME");
10352                self.write(" (");
10353                self.generate_expression(expression)?;
10354                self.write(")");
10355            }
10356            TableConstraint::Default {
10357                name,
10358                expression,
10359                column,
10360            } => {
10361                if let Some(ref n) = name {
10362                    self.write_keyword("CONSTRAINT");
10363                    self.write_space();
10364                    self.generate_identifier(n)?;
10365                    self.write_space();
10366                }
10367                self.write_keyword("DEFAULT");
10368                self.write_space();
10369                self.generate_expression(expression)?;
10370                self.write_space();
10371                self.write_keyword("FOR");
10372                self.write_space();
10373                self.generate_identifier(column)?;
10374            }
10375            TableConstraint::Index {
10376                name,
10377                columns,
10378                kind,
10379                modifiers,
10380                use_key_keyword,
10381                expression,
10382                index_type,
10383                granularity,
10384            } => {
10385                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
10386                if expression.is_some() {
10387                    self.write_keyword("INDEX");
10388                    if let Some(ref n) = name {
10389                        self.write_space();
10390                        self.generate_identifier(n)?;
10391                    }
10392                    if let Some(ref expr) = expression {
10393                        self.write_space();
10394                        self.generate_expression(expr)?;
10395                    }
10396                    if let Some(ref idx_type) = index_type {
10397                        self.write_space();
10398                        self.write_keyword("TYPE");
10399                        self.write_space();
10400                        self.generate_expression(idx_type)?;
10401                    }
10402                    if let Some(ref gran) = granularity {
10403                        self.write_space();
10404                        self.write_keyword("GRANULARITY");
10405                        self.write_space();
10406                        self.generate_expression(gran)?;
10407                    }
10408                } else {
10409                    // Standard INDEX syntax
10410                    // Determine the index keyword to use
10411                    // MySQL normalizes KEY to INDEX
10412                    use crate::dialects::DialectType;
10413                    let index_keyword = if *use_key_keyword
10414                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
10415                    {
10416                        "KEY"
10417                    } else {
10418                        "INDEX"
10419                    };
10420
10421                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
10422                    if let Some(ref k) = kind {
10423                        self.write_keyword(k);
10424                        // For UNIQUE, don't add INDEX/KEY keyword
10425                        if k != "UNIQUE" {
10426                            self.write_space();
10427                            self.write_keyword(index_keyword);
10428                        }
10429                    } else {
10430                        self.write_keyword(index_keyword);
10431                    }
10432
10433                    // Output USING before name if using_before_columns is true and there's no name
10434                    if modifiers.using_before_columns && name.is_none() {
10435                        if let Some(ref using) = modifiers.using {
10436                            self.write_space();
10437                            self.write_keyword("USING");
10438                            self.write_space();
10439                            self.write_keyword(using);
10440                        }
10441                    }
10442
10443                    // Output index name if present
10444                    if let Some(ref n) = name {
10445                        self.write_space();
10446                        self.generate_identifier(n)?;
10447                    }
10448
10449                    // Output USING after name but before columns if using_before_columns and there's a name
10450                    if modifiers.using_before_columns && name.is_some() {
10451                        if let Some(ref using) = modifiers.using {
10452                            self.write_space();
10453                            self.write_keyword("USING");
10454                            self.write_space();
10455                            self.write_keyword(using);
10456                        }
10457                    }
10458
10459                    // Output columns
10460                    self.write(" (");
10461                    for (i, col) in columns.iter().enumerate() {
10462                        if i > 0 {
10463                            self.write(", ");
10464                        }
10465                        self.generate_identifier(col)?;
10466                    }
10467                    self.write(")");
10468
10469                    // Output USING after columns if not using_before_columns
10470                    if !modifiers.using_before_columns {
10471                        if let Some(ref using) = modifiers.using {
10472                            self.write_space();
10473                            self.write_keyword("USING");
10474                            self.write_space();
10475                            self.write_keyword(using);
10476                        }
10477                    }
10478
10479                    // Output other constraint modifiers (but skip USING since we already handled it)
10480                    self.generate_constraint_modifiers_without_using(modifiers);
10481                }
10482            }
10483            TableConstraint::Projection { name, expression } => {
10484                // ClickHouse: PROJECTION name (SELECT ...)
10485                self.write_keyword("PROJECTION");
10486                self.write_space();
10487                self.generate_identifier(name)?;
10488                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
10489                    if let Expression::Raw(raw) = expression {
10490                        if raw
10491                            .sql
10492                            .trim_start()
10493                            .to_ascii_uppercase()
10494                            .starts_with("INDEX ")
10495                        {
10496                            self.write_space();
10497                            self.write(raw.sql.trim());
10498                            return Ok(());
10499                        }
10500                    }
10501                }
10502                self.write(" (");
10503                self.generate_expression(expression)?;
10504                self.write(")");
10505            }
10506            TableConstraint::Like { source, options } => {
10507                self.write_keyword("LIKE");
10508                self.write_space();
10509                self.generate_table(source)?;
10510                for (action, prop) in options {
10511                    self.write_space();
10512                    match action {
10513                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
10514                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
10515                    }
10516                    self.write_space();
10517                    self.write_keyword(prop);
10518                }
10519            }
10520            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
10521                self.write_keyword("PERIOD FOR SYSTEM_TIME");
10522                self.write(" (");
10523                self.generate_identifier(start_col)?;
10524                self.write(", ");
10525                self.generate_identifier(end_col)?;
10526                self.write(")");
10527            }
10528            TableConstraint::Exclude {
10529                name,
10530                using,
10531                elements,
10532                include_columns,
10533                where_clause,
10534                with_params,
10535                using_index_tablespace,
10536                modifiers: _,
10537            } => {
10538                if let Some(ref n) = name {
10539                    self.write_keyword("CONSTRAINT");
10540                    self.write_space();
10541                    self.generate_identifier(n)?;
10542                    self.write_space();
10543                }
10544                self.write_keyword("EXCLUDE");
10545                if let Some(ref method) = using {
10546                    self.write_space();
10547                    self.write_keyword("USING");
10548                    self.write_space();
10549                    self.write(method);
10550                    self.write("(");
10551                } else {
10552                    self.write(" (");
10553                }
10554                for (i, elem) in elements.iter().enumerate() {
10555                    if i > 0 {
10556                        self.write(", ");
10557                    }
10558                    self.write(&elem.expression);
10559                    self.write_space();
10560                    self.write_keyword("WITH");
10561                    self.write_space();
10562                    self.write(&elem.operator);
10563                }
10564                self.write(")");
10565                if !include_columns.is_empty() {
10566                    self.write_space();
10567                    self.write_keyword("INCLUDE");
10568                    self.write(" (");
10569                    for (i, col) in include_columns.iter().enumerate() {
10570                        if i > 0 {
10571                            self.write(", ");
10572                        }
10573                        self.generate_identifier(col)?;
10574                    }
10575                    self.write(")");
10576                }
10577                if !with_params.is_empty() {
10578                    self.write_space();
10579                    self.write_keyword("WITH");
10580                    self.write(" (");
10581                    for (i, (key, val)) in with_params.iter().enumerate() {
10582                        if i > 0 {
10583                            self.write(", ");
10584                        }
10585                        self.write(key);
10586                        self.write("=");
10587                        self.write(val);
10588                    }
10589                    self.write(")");
10590                }
10591                if let Some(ref tablespace) = using_index_tablespace {
10592                    self.write_space();
10593                    self.write_keyword("USING INDEX TABLESPACE");
10594                    self.write_space();
10595                    self.write(tablespace);
10596                }
10597                if let Some(ref where_expr) = where_clause {
10598                    self.write_space();
10599                    self.write_keyword("WHERE");
10600                    self.write(" (");
10601                    self.generate_expression(where_expr)?;
10602                    self.write(")");
10603                }
10604            }
10605            TableConstraint::Tags(tags) => {
10606                self.write_keyword("TAG");
10607                self.write(" (");
10608                for (i, expr) in tags.expressions.iter().enumerate() {
10609                    if i > 0 {
10610                        self.write(", ");
10611                    }
10612                    self.generate_expression(expr)?;
10613                }
10614                self.write(")");
10615            }
10616            TableConstraint::InitiallyDeferred { deferred } => {
10617                self.write_keyword("INITIALLY");
10618                self.write_space();
10619                if *deferred {
10620                    self.write_keyword("DEFERRED");
10621                } else {
10622                    self.write_keyword("IMMEDIATE");
10623                }
10624            }
10625        }
10626        Ok(())
10627    }
10628
10629    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10630        // Output USING BTREE/HASH (MySQL) - comes first
10631        if let Some(using) = &modifiers.using {
10632            self.write_space();
10633            self.write_keyword("USING");
10634            self.write_space();
10635            self.write_keyword(using);
10636        }
10637        // Output ENFORCED/NOT ENFORCED
10638        if let Some(enforced) = modifiers.enforced {
10639            self.write_space();
10640            if enforced {
10641                self.write_keyword("ENFORCED");
10642            } else {
10643                self.write_keyword("NOT ENFORCED");
10644            }
10645        }
10646        // Output DEFERRABLE/NOT DEFERRABLE
10647        if let Some(deferrable) = modifiers.deferrable {
10648            self.write_space();
10649            if deferrable {
10650                self.write_keyword("DEFERRABLE");
10651            } else {
10652                self.write_keyword("NOT DEFERRABLE");
10653            }
10654        }
10655        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10656        if let Some(initially_deferred) = modifiers.initially_deferred {
10657            self.write_space();
10658            if initially_deferred {
10659                self.write_keyword("INITIALLY DEFERRED");
10660            } else {
10661                self.write_keyword("INITIALLY IMMEDIATE");
10662            }
10663        }
10664        // Output NORELY
10665        if modifiers.norely {
10666            self.write_space();
10667            self.write_keyword("NORELY");
10668        }
10669        // Output RELY
10670        if modifiers.rely {
10671            self.write_space();
10672            self.write_keyword("RELY");
10673        }
10674        // Output NOT VALID (PostgreSQL)
10675        if modifiers.not_valid {
10676            self.write_space();
10677            self.write_keyword("NOT VALID");
10678        }
10679        // Output ON CONFLICT (SQLite)
10680        if let Some(on_conflict) = &modifiers.on_conflict {
10681            self.write_space();
10682            self.write_keyword("ON CONFLICT");
10683            self.write_space();
10684            self.write_keyword(on_conflict);
10685        }
10686        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10687        if !modifiers.with_options.is_empty() {
10688            self.write_space();
10689            self.write_keyword("WITH");
10690            self.write(" (");
10691            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10692                if i > 0 {
10693                    self.write(", ");
10694                }
10695                self.write(key);
10696                self.write("=");
10697                self.write(value);
10698            }
10699            self.write(")");
10700        }
10701        // Output TSQL ON filegroup
10702        if let Some(ref fg) = modifiers.on_filegroup {
10703            self.write_space();
10704            self.write_keyword("ON");
10705            self.write_space();
10706            let _ = self.generate_identifier(fg);
10707        }
10708    }
10709
10710    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10711    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10712        // Output ENFORCED/NOT ENFORCED
10713        if let Some(enforced) = modifiers.enforced {
10714            self.write_space();
10715            if enforced {
10716                self.write_keyword("ENFORCED");
10717            } else {
10718                self.write_keyword("NOT ENFORCED");
10719            }
10720        }
10721        // Output DEFERRABLE/NOT DEFERRABLE
10722        if let Some(deferrable) = modifiers.deferrable {
10723            self.write_space();
10724            if deferrable {
10725                self.write_keyword("DEFERRABLE");
10726            } else {
10727                self.write_keyword("NOT DEFERRABLE");
10728            }
10729        }
10730        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10731        if let Some(initially_deferred) = modifiers.initially_deferred {
10732            self.write_space();
10733            if initially_deferred {
10734                self.write_keyword("INITIALLY DEFERRED");
10735            } else {
10736                self.write_keyword("INITIALLY IMMEDIATE");
10737            }
10738        }
10739        // Output NORELY
10740        if modifiers.norely {
10741            self.write_space();
10742            self.write_keyword("NORELY");
10743        }
10744        // Output RELY
10745        if modifiers.rely {
10746            self.write_space();
10747            self.write_keyword("RELY");
10748        }
10749        // Output NOT VALID (PostgreSQL)
10750        if modifiers.not_valid {
10751            self.write_space();
10752            self.write_keyword("NOT VALID");
10753        }
10754        // Output ON CONFLICT (SQLite)
10755        if let Some(on_conflict) = &modifiers.on_conflict {
10756            self.write_space();
10757            self.write_keyword("ON CONFLICT");
10758            self.write_space();
10759            self.write_keyword(on_conflict);
10760        }
10761        // Output MySQL index-specific modifiers
10762        self.generate_index_specific_modifiers(modifiers);
10763    }
10764
10765    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10766    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10767        if let Some(ref comment) = modifiers.comment {
10768            self.write_space();
10769            self.write_keyword("COMMENT");
10770            self.write(" '");
10771            self.write(comment);
10772            self.write("'");
10773        }
10774        if let Some(visible) = modifiers.visible {
10775            self.write_space();
10776            if visible {
10777                self.write_keyword("VISIBLE");
10778            } else {
10779                self.write_keyword("INVISIBLE");
10780            }
10781        }
10782        if let Some(ref attr) = modifiers.engine_attribute {
10783            self.write_space();
10784            self.write_keyword("ENGINE_ATTRIBUTE");
10785            self.write(" = '");
10786            self.write(attr);
10787            self.write("'");
10788        }
10789        if let Some(ref parser) = modifiers.with_parser {
10790            self.write_space();
10791            self.write_keyword("WITH PARSER");
10792            self.write_space();
10793            self.write(parser);
10794        }
10795    }
10796
10797    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10798        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10799        if !fk_ref.match_after_actions {
10800            if let Some(ref match_type) = fk_ref.match_type {
10801                self.write_space();
10802                self.write_keyword("MATCH");
10803                self.write_space();
10804                match match_type {
10805                    MatchType::Full => self.write_keyword("FULL"),
10806                    MatchType::Partial => self.write_keyword("PARTIAL"),
10807                    MatchType::Simple => self.write_keyword("SIMPLE"),
10808                }
10809            }
10810        }
10811
10812        // Output ON UPDATE and ON DELETE in the original order
10813        if fk_ref.on_update_first {
10814            if let Some(ref action) = fk_ref.on_update {
10815                self.write_space();
10816                self.write_keyword("ON UPDATE");
10817                self.write_space();
10818                self.generate_referential_action(action);
10819            }
10820            if let Some(ref action) = fk_ref.on_delete {
10821                self.write_space();
10822                self.write_keyword("ON DELETE");
10823                self.write_space();
10824                self.generate_referential_action(action);
10825            }
10826        } else {
10827            if let Some(ref action) = fk_ref.on_delete {
10828                self.write_space();
10829                self.write_keyword("ON DELETE");
10830                self.write_space();
10831                self.generate_referential_action(action);
10832            }
10833            if let Some(ref action) = fk_ref.on_update {
10834                self.write_space();
10835                self.write_keyword("ON UPDATE");
10836                self.write_space();
10837                self.generate_referential_action(action);
10838            }
10839        }
10840
10841        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10842        if fk_ref.match_after_actions {
10843            if let Some(ref match_type) = fk_ref.match_type {
10844                self.write_space();
10845                self.write_keyword("MATCH");
10846                self.write_space();
10847                match match_type {
10848                    MatchType::Full => self.write_keyword("FULL"),
10849                    MatchType::Partial => self.write_keyword("PARTIAL"),
10850                    MatchType::Simple => self.write_keyword("SIMPLE"),
10851                }
10852            }
10853        }
10854
10855        // DEFERRABLE / NOT DEFERRABLE
10856        if let Some(deferrable) = fk_ref.deferrable {
10857            self.write_space();
10858            if deferrable {
10859                self.write_keyword("DEFERRABLE");
10860            } else {
10861                self.write_keyword("NOT DEFERRABLE");
10862            }
10863        }
10864
10865        Ok(())
10866    }
10867
10868    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10869        match action {
10870            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10871            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10872            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10873            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10874            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10875        }
10876    }
10877
10878    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10879        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10880        if let Some(ref object_id_args) = dt.object_id_args {
10881            if matches!(
10882                self.config.dialect,
10883                Some(crate::dialects::DialectType::TSQL)
10884                    | Some(crate::dialects::DialectType::Fabric)
10885            ) {
10886                self.write_keyword("IF NOT OBJECT_ID");
10887                self.write("(");
10888                self.write(object_id_args);
10889                self.write(")");
10890                self.write_space();
10891                self.write_keyword("IS NULL BEGIN DROP TABLE");
10892                self.write_space();
10893                for (i, table) in dt.names.iter().enumerate() {
10894                    if i > 0 {
10895                        self.write(", ");
10896                    }
10897                    self.generate_table(table)?;
10898                }
10899                self.write("; ");
10900                self.write_keyword("END");
10901                return Ok(());
10902            }
10903        }
10904
10905        // Athena: DROP TABLE uses Hive engine (backticks)
10906        let saved_athena_hive_context = self.athena_hive_context;
10907        if matches!(
10908            self.config.dialect,
10909            Some(crate::dialects::DialectType::Athena)
10910        ) {
10911            self.athena_hive_context = true;
10912        }
10913
10914        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10915        for comment in &dt.leading_comments {
10916            self.write_formatted_comment(comment);
10917            self.write_space();
10918        }
10919        if dt.iceberg {
10920            self.write_keyword("DROP ICEBERG TABLE");
10921        } else {
10922            self.write_keyword("DROP TABLE");
10923        }
10924
10925        if dt.if_exists {
10926            self.write_space();
10927            self.write_keyword("IF EXISTS");
10928        }
10929
10930        self.write_space();
10931        for (i, table) in dt.names.iter().enumerate() {
10932            if i > 0 {
10933                self.write(", ");
10934            }
10935            self.generate_table(table)?;
10936        }
10937
10938        if dt.cascade_constraints {
10939            self.write_space();
10940            self.write_keyword("CASCADE CONSTRAINTS");
10941        } else if dt.cascade {
10942            self.write_space();
10943            self.write_keyword("CASCADE");
10944        }
10945
10946        if dt.restrict {
10947            self.write_space();
10948            self.write_keyword("RESTRICT");
10949        }
10950
10951        if dt.purge {
10952            self.write_space();
10953            self.write_keyword("PURGE");
10954        }
10955
10956        if dt.sync {
10957            self.write_space();
10958            self.write_keyword("SYNC");
10959        }
10960
10961        // Restore Athena Hive context
10962        self.athena_hive_context = saved_athena_hive_context;
10963
10964        Ok(())
10965    }
10966
10967    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10968        self.write_keyword("UNDROP");
10969        self.write_space();
10970        self.write_keyword(&u.kind);
10971        if u.if_exists {
10972            self.write_space();
10973            self.write_keyword("IF EXISTS");
10974        }
10975        self.write_space();
10976        self.generate_table(&u.name)?;
10977        if let Some(rename_to) = &u.rename_to {
10978            self.write_space();
10979            self.write_keyword("RENAME TO");
10980            self.write_space();
10981            self.generate_table(rename_to)?;
10982        }
10983        Ok(())
10984    }
10985
10986    fn generate_split_table(&mut self, split: &SplitTable) -> Result<()> {
10987        if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
10988            return self
10989                .write_unsupported_comment("SPLIT TABLE is only supported by the TiDB dialect");
10990        }
10991
10992        match &split.partition_scope {
10993            SplitTablePartitionScope::Table => self.write_keyword("SPLIT TABLE"),
10994            SplitTablePartitionScope::AllPartitions
10995            | SplitTablePartitionScope::Partitions { .. } => {
10996                self.write_keyword("SPLIT PARTITION TABLE")
10997            }
10998        }
10999        self.write_space();
11000        self.generate_table(&split.table)?;
11001        if let SplitTablePartitionScope::Partitions { names } = &split.partition_scope {
11002            self.write_space();
11003            self.write_keyword("PARTITION");
11004            self.write(" (");
11005            for (index, name) in names.iter().enumerate() {
11006                if index > 0 {
11007                    self.write(", ");
11008                }
11009                self.generate_identifier(name)?;
11010            }
11011            self.write(")");
11012        }
11013        if let Some(index) = &split.index {
11014            self.write_space();
11015            self.write_keyword("INDEX");
11016            self.write_space();
11017            self.generate_identifier(index)?;
11018        }
11019        match &split.mode {
11020            SplitTableMode::Between {
11021                lower,
11022                upper,
11023                regions,
11024            } => {
11025                self.write_space();
11026                self.write_keyword("BETWEEN");
11027                self.write_space();
11028                self.generate_tidb_split_values(lower)?;
11029                self.write_space();
11030                self.write_keyword("AND");
11031                self.write_space();
11032                self.generate_tidb_split_values(upper)?;
11033                self.write_space();
11034                self.write_keyword("REGIONS");
11035                self.write_space();
11036                self.write(&regions.to_string());
11037            }
11038            SplitTableMode::By { points } => {
11039                self.write_space();
11040                self.write_keyword("BY");
11041                self.write_space();
11042                for (index, point) in points.iter().enumerate() {
11043                    if index > 0 {
11044                        self.write(", ");
11045                    }
11046                    self.generate_tidb_split_values(point)?;
11047                }
11048            }
11049        }
11050        Ok(())
11051    }
11052
11053    fn generate_tidb_split_values(&mut self, values: &[Expression]) -> Result<()> {
11054        self.write("(");
11055        for (index, value) in values.iter().enumerate() {
11056            if index > 0 {
11057                self.write(", ");
11058            }
11059            self.generate_expression(value)?;
11060        }
11061        self.write(")");
11062        Ok(())
11063    }
11064
11065    fn generate_flashback_table(&mut self, flashback: &FlashbackTable) -> Result<()> {
11066        if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11067            return self.write_unsupported_comment(
11068                "FLASHBACK TABLE is only supported by the TiDB dialect",
11069            );
11070        }
11071        self.write_keyword("FLASHBACK TABLE");
11072        self.write_space();
11073        self.generate_table(&flashback.table)?;
11074        if let Some(rename_to) = &flashback.rename_to {
11075            self.write_space();
11076            self.write_keyword("TO");
11077            self.write_space();
11078            self.generate_identifier(rename_to)?;
11079        }
11080        Ok(())
11081    }
11082
11083    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
11084        // Athena: ALTER TABLE uses Hive engine (backticks)
11085        let saved_athena_hive_context = self.athena_hive_context;
11086        if matches!(
11087            self.config.dialect,
11088            Some(crate::dialects::DialectType::Athena)
11089        ) {
11090            self.athena_hive_context = true;
11091        }
11092
11093        self.write_keyword("ALTER");
11094        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
11095        if let Some(ref modifier) = at.table_modifier {
11096            if !matches!(
11097                self.config.dialect,
11098                Some(crate::dialects::DialectType::DuckDB)
11099            ) {
11100                self.write_space();
11101                self.write_keyword(modifier);
11102            }
11103        }
11104        self.write(" ");
11105        self.write_keyword("TABLE");
11106        if at.if_exists {
11107            self.write_space();
11108            self.write_keyword("IF EXISTS");
11109        }
11110        self.write_space();
11111        self.generate_table(&at.name)?;
11112
11113        // ClickHouse: ON CLUSTER clause
11114        if let Some(ref on_cluster) = at.on_cluster {
11115            self.write_space();
11116            self.generate_on_cluster(on_cluster)?;
11117        }
11118
11119        // Hive: PARTITION(key=value, ...) clause
11120        if let Some(ref partition) = at.partition {
11121            self.write_space();
11122            self.write_keyword("PARTITION");
11123            self.write("(");
11124            for (i, (key, value)) in partition.iter().enumerate() {
11125                if i > 0 {
11126                    self.write(", ");
11127                }
11128                self.generate_identifier(key)?;
11129                self.write(" = ");
11130                self.generate_expression(value)?;
11131            }
11132            self.write(")");
11133        }
11134
11135        // TSQL: WITH CHECK / WITH NOCHECK modifier
11136        if let Some(ref with_check) = at.with_check {
11137            self.write_space();
11138            self.write_keyword(with_check);
11139        }
11140
11141        if self.config.pretty {
11142            // In pretty mode, format actions with newlines and indentation
11143            self.write_newline();
11144            self.indent_level += 1;
11145            for (i, action) in at.actions.iter().enumerate() {
11146                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11147                let is_continuation = i > 0
11148                    && matches!(
11149                        (&at.actions[i - 1], action),
11150                        (
11151                            AlterTableAction::AddColumn { .. },
11152                            AlterTableAction::AddColumn { .. }
11153                        ) | (
11154                            AlterTableAction::AddConstraint(_),
11155                            AlterTableAction::AddConstraint(_)
11156                        )
11157                    );
11158                if i > 0 {
11159                    self.write(",");
11160                    self.write_newline();
11161                }
11162                self.write_indent();
11163                self.generate_alter_action_with_continuation(action, is_continuation)?;
11164            }
11165            self.indent_level -= 1;
11166        } else {
11167            for (i, action) in at.actions.iter().enumerate() {
11168                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11169                let is_continuation = i > 0
11170                    && matches!(
11171                        (&at.actions[i - 1], action),
11172                        (
11173                            AlterTableAction::AddColumn { .. },
11174                            AlterTableAction::AddColumn { .. }
11175                        ) | (
11176                            AlterTableAction::AddConstraint(_),
11177                            AlterTableAction::AddConstraint(_)
11178                        )
11179                    );
11180                if i > 0 {
11181                    self.write(",");
11182                }
11183                self.write_space();
11184                self.generate_alter_action_with_continuation(action, is_continuation)?;
11185            }
11186        }
11187
11188        // MySQL ALTER TABLE trailing options
11189        if let Some(ref algorithm) = at.algorithm {
11190            self.write(", ");
11191            self.write_keyword("ALGORITHM");
11192            self.write("=");
11193            self.write_keyword(algorithm);
11194        }
11195        if let Some(ref lock) = at.lock {
11196            self.write(", ");
11197            self.write_keyword("LOCK");
11198            self.write("=");
11199            self.write_keyword(lock);
11200        }
11201
11202        // Restore Athena Hive context
11203        self.athena_hive_context = saved_athena_hive_context;
11204
11205        Ok(())
11206    }
11207
11208    fn generate_alter_action_with_continuation(
11209        &mut self,
11210        action: &AlterTableAction,
11211        is_continuation: bool,
11212    ) -> Result<()> {
11213        match action {
11214            AlterTableAction::AddColumn {
11215                column,
11216                if_not_exists,
11217                position,
11218            } => {
11219                use crate::dialects::DialectType;
11220                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
11221                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
11222                // For other dialects, repeat ADD COLUMN for each
11223                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
11224                let is_tsql_like = matches!(
11225                    self.config.dialect,
11226                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
11227                );
11228                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
11229                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
11230
11231                if is_continuation && (is_snowflake || is_tsql_like) {
11232                    // Don't write ADD keyword for continuation in Snowflake/TSQL
11233                } else if is_snowflake {
11234                    self.write_keyword("ADD");
11235                    self.write_space();
11236                } else if is_athena {
11237                    // Athena uses ADD COLUMNS (col_def) syntax
11238                    self.write_keyword("ADD COLUMNS");
11239                    self.write(" (");
11240                } else if self.config.alter_table_include_column_keyword {
11241                    self.write_keyword("ADD COLUMN");
11242                    self.write_space();
11243                } else {
11244                    // Dialects like Oracle and TSQL don't use COLUMN keyword
11245                    self.write_keyword("ADD");
11246                    self.write_space();
11247                }
11248
11249                if *if_not_exists {
11250                    self.write_keyword("IF NOT EXISTS");
11251                    self.write_space();
11252                }
11253                self.generate_column_def(column)?;
11254
11255                // Close parenthesis for Athena
11256                if is_athena {
11257                    self.write(")");
11258                }
11259
11260                // Column position (FIRST or AFTER)
11261                if let Some(pos) = position {
11262                    self.write_space();
11263                    match pos {
11264                        ColumnPosition::First => self.write_keyword("FIRST"),
11265                        ColumnPosition::After(col_name) => {
11266                            self.write_keyword("AFTER");
11267                            self.write_space();
11268                            self.generate_identifier(col_name)?;
11269                        }
11270                    }
11271                }
11272            }
11273            AlterTableAction::DropColumn {
11274                name,
11275                if_exists,
11276                cascade,
11277            } => {
11278                self.write_keyword("DROP COLUMN");
11279                if *if_exists {
11280                    self.write_space();
11281                    self.write_keyword("IF EXISTS");
11282                }
11283                self.write_space();
11284                self.generate_identifier(name)?;
11285                if *cascade {
11286                    self.write_space();
11287                    self.write_keyword("CASCADE");
11288                }
11289            }
11290            AlterTableAction::DropColumns { names } => {
11291                self.write_keyword("DROP COLUMNS");
11292                self.write(" (");
11293                for (i, name) in names.iter().enumerate() {
11294                    if i > 0 {
11295                        self.write(", ");
11296                    }
11297                    self.generate_identifier(name)?;
11298                }
11299                self.write(")");
11300            }
11301            AlterTableAction::RenameColumn {
11302                old_name,
11303                new_name,
11304                if_exists,
11305            } => {
11306                self.write_keyword("RENAME COLUMN");
11307                if *if_exists {
11308                    self.write_space();
11309                    self.write_keyword("IF EXISTS");
11310                }
11311                self.write_space();
11312                self.generate_identifier(old_name)?;
11313                self.write_space();
11314                self.write_keyword("TO");
11315                self.write_space();
11316                self.generate_identifier(new_name)?;
11317            }
11318            AlterTableAction::AlterColumn {
11319                name,
11320                action,
11321                use_modify_keyword,
11322            } => {
11323                use crate::dialects::DialectType;
11324                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
11325                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
11326                let use_modify = *use_modify_keyword
11327                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
11328                        && matches!(action, AlterColumnAction::SetDataType { .. }));
11329                if use_modify {
11330                    self.write_keyword("MODIFY COLUMN");
11331                    self.write_space();
11332                    self.generate_identifier(name)?;
11333                    // For MODIFY COLUMN, output the type directly
11334                    if let AlterColumnAction::SetDataType {
11335                        data_type,
11336                        using: _,
11337                        collate,
11338                    } = action
11339                    {
11340                        self.write_space();
11341                        self.generate_data_type(data_type)?;
11342                        // Output COLLATE clause if present
11343                        if let Some(collate_name) = collate {
11344                            self.write_space();
11345                            self.write_keyword("COLLATE");
11346                            self.write_space();
11347                            // Output as single-quoted string
11348                            self.write(&format!("'{}'", collate_name));
11349                        }
11350                    } else {
11351                        self.write_space();
11352                        self.generate_alter_column_action(action)?;
11353                    }
11354                } else if matches!(self.config.dialect, Some(DialectType::Hive))
11355                    && matches!(action, AlterColumnAction::SetDataType { .. })
11356                {
11357                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
11358                    self.write_keyword("CHANGE COLUMN");
11359                    self.write_space();
11360                    self.generate_identifier(name)?;
11361                    self.write_space();
11362                    self.generate_identifier(name)?;
11363                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
11364                        self.write_space();
11365                        self.generate_data_type(data_type)?;
11366                    }
11367                } else {
11368                    self.write_keyword("ALTER COLUMN");
11369                    self.write_space();
11370                    self.generate_identifier(name)?;
11371                    self.write_space();
11372                    self.generate_alter_column_action(action)?;
11373                }
11374            }
11375            AlterTableAction::ModifyColumn {
11376                column,
11377                if_exists,
11378                position,
11379            } => {
11380                if !matches!(
11381                    self.config.dialect,
11382                    Some(DialectType::TiDB) | Some(DialectType::MySQL)
11383                ) {
11384                    return self.write_unsupported_comment(
11385                        "MODIFY COLUMN is only supported by MySQL-compatible dialects",
11386                    );
11387                }
11388                self.write_keyword("MODIFY COLUMN");
11389                self.write_space();
11390                if *if_exists {
11391                    self.write_keyword("IF EXISTS");
11392                    self.write_space();
11393                }
11394                self.generate_column_def(column)?;
11395                if let Some(position) = position {
11396                    self.write_space();
11397                    match position {
11398                        ColumnPosition::First => self.write_keyword("FIRST"),
11399                        ColumnPosition::After(name) => {
11400                            self.write_keyword("AFTER");
11401                            self.write_space();
11402                            self.generate_identifier(name)?;
11403                        }
11404                    }
11405                }
11406            }
11407            AlterTableAction::RenameTable(new_name) => {
11408                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
11409                let mysql_like = matches!(
11410                    self.config.dialect,
11411                    Some(DialectType::MySQL)
11412                        | Some(DialectType::Doris)
11413                        | Some(DialectType::StarRocks)
11414                        | Some(DialectType::SingleStore)
11415                );
11416                if mysql_like {
11417                    self.write_keyword("RENAME");
11418                } else {
11419                    self.write_keyword("RENAME TO");
11420                }
11421                self.write_space();
11422                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
11423                let rename_table_with_db = !matches!(
11424                    self.config.dialect,
11425                    Some(DialectType::Doris)
11426                        | Some(DialectType::DuckDB)
11427                        | Some(DialectType::BigQuery)
11428                        | Some(DialectType::PostgreSQL)
11429                );
11430                if !rename_table_with_db {
11431                    let mut stripped = new_name.clone();
11432                    stripped.schema = None;
11433                    stripped.catalog = None;
11434                    self.generate_table(&stripped)?;
11435                } else {
11436                    self.generate_table(new_name)?;
11437                }
11438            }
11439            AlterTableAction::AddConstraint(constraint) => {
11440                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
11441                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
11442                if !is_continuation {
11443                    self.write_keyword("ADD");
11444                    self.write_space();
11445                }
11446                self.generate_table_constraint(constraint)?;
11447            }
11448            AlterTableAction::DropConstraint { name, if_exists } => {
11449                self.write_keyword("DROP CONSTRAINT");
11450                if *if_exists {
11451                    self.write_space();
11452                    self.write_keyword("IF EXISTS");
11453                }
11454                self.write_space();
11455                self.generate_identifier(name)?;
11456            }
11457            AlterTableAction::DropForeignKey { name } => {
11458                self.write_keyword("DROP FOREIGN KEY");
11459                self.write_space();
11460                self.generate_identifier(name)?;
11461            }
11462            AlterTableAction::DropPartition {
11463                partitions,
11464                if_exists,
11465            } => {
11466                self.write_keyword("DROP");
11467                if *if_exists {
11468                    self.write_space();
11469                    self.write_keyword("IF EXISTS");
11470                }
11471                for (i, partition) in partitions.iter().enumerate() {
11472                    if i > 0 {
11473                        self.write(",");
11474                    }
11475                    self.write_space();
11476                    self.write_keyword("PARTITION");
11477                    // Check for special ClickHouse partition formats
11478                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
11479                        // ClickHouse: PARTITION <expression>
11480                        self.write_space();
11481                        self.generate_expression(&partition[0].1)?;
11482                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
11483                        // ClickHouse: PARTITION ALL
11484                        self.write_space();
11485                        self.write_keyword("ALL");
11486                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
11487                        // ClickHouse: PARTITION ID 'string'
11488                        self.write_space();
11489                        self.write_keyword("ID");
11490                        self.write_space();
11491                        self.generate_expression(&partition[0].1)?;
11492                    } else {
11493                        // Standard SQL: PARTITION(key=value, ...)
11494                        self.write("(");
11495                        for (j, (key, value)) in partition.iter().enumerate() {
11496                            if j > 0 {
11497                                self.write(", ");
11498                            }
11499                            self.generate_identifier(key)?;
11500                            self.write(" = ");
11501                            self.generate_expression(value)?;
11502                        }
11503                        self.write(")");
11504                    }
11505                }
11506            }
11507            AlterTableAction::Delete { where_clause } => {
11508                self.write_keyword("DELETE");
11509                self.write_space();
11510                self.write_keyword("WHERE");
11511                self.write_space();
11512                self.generate_expression(where_clause)?;
11513            }
11514            AlterTableAction::SwapWith(target) => {
11515                self.write_keyword("SWAP WITH");
11516                self.write_space();
11517                self.generate_table(target)?;
11518            }
11519            AlterTableAction::SetProperty { properties } => {
11520                use crate::dialects::DialectType;
11521                self.write_keyword("SET");
11522                // Trino/Presto use SET PROPERTIES syntax with spaces around =
11523                let is_trino_presto = matches!(
11524                    self.config.dialect,
11525                    Some(DialectType::Trino) | Some(DialectType::Presto)
11526                );
11527                if is_trino_presto {
11528                    self.write_space();
11529                    self.write_keyword("PROPERTIES");
11530                }
11531                let eq = if is_trino_presto { " = " } else { "=" };
11532                for (i, (key, value)) in properties.iter().enumerate() {
11533                    if i > 0 {
11534                        self.write(",");
11535                    }
11536                    self.write_space();
11537                    // Handle quoted property names for Trino
11538                    if key.contains(' ') {
11539                        self.generate_string_literal(key)?;
11540                    } else {
11541                        self.write(key);
11542                    }
11543                    self.write(eq);
11544                    self.generate_expression(value)?;
11545                }
11546            }
11547            AlterTableAction::UnsetProperty { properties } => {
11548                self.write_keyword("UNSET");
11549                for (i, name) in properties.iter().enumerate() {
11550                    if i > 0 {
11551                        self.write(",");
11552                    }
11553                    self.write_space();
11554                    self.write(name);
11555                }
11556            }
11557            AlterTableAction::ClusterBy { expressions } => {
11558                self.write_keyword("CLUSTER BY");
11559                self.write(" (");
11560                for (i, expr) in expressions.iter().enumerate() {
11561                    if i > 0 {
11562                        self.write(", ");
11563                    }
11564                    self.generate_expression(expr)?;
11565                }
11566                self.write(")");
11567            }
11568            AlterTableAction::SetTag { expressions } => {
11569                self.write_keyword("SET TAG");
11570                for (i, (key, value)) in expressions.iter().enumerate() {
11571                    if i > 0 {
11572                        self.write(",");
11573                    }
11574                    self.write_space();
11575                    self.write(key);
11576                    self.write(" = ");
11577                    self.generate_expression(value)?;
11578                }
11579            }
11580            AlterTableAction::UnsetTag { names } => {
11581                self.write_keyword("UNSET TAG");
11582                for (i, name) in names.iter().enumerate() {
11583                    if i > 0 {
11584                        self.write(",");
11585                    }
11586                    self.write_space();
11587                    self.write(name);
11588                }
11589            }
11590            AlterTableAction::SetOptions { expressions } => {
11591                self.write_keyword("SET");
11592                self.write(" (");
11593                for (i, expr) in expressions.iter().enumerate() {
11594                    if i > 0 {
11595                        self.write(", ");
11596                    }
11597                    self.generate_expression(expr)?;
11598                }
11599                self.write(")");
11600            }
11601            AlterTableAction::AlterIndex { name, visible } => {
11602                self.write_keyword("ALTER INDEX");
11603                self.write_space();
11604                self.generate_identifier(name)?;
11605                self.write_space();
11606                if *visible {
11607                    self.write_keyword("VISIBLE");
11608                } else {
11609                    self.write_keyword("INVISIBLE");
11610                }
11611            }
11612            AlterTableAction::SetAttribute { attribute } => {
11613                self.write_keyword("SET");
11614                self.write_space();
11615                self.write_keyword(attribute);
11616            }
11617            AlterTableAction::SetStageFileFormat { options } => {
11618                self.write_keyword("SET");
11619                self.write_space();
11620                self.write_keyword("STAGE_FILE_FORMAT");
11621                self.write(" = (");
11622                if let Some(opts) = options {
11623                    self.generate_space_separated_properties(opts)?;
11624                }
11625                self.write(")");
11626            }
11627            AlterTableAction::SetStageCopyOptions { options } => {
11628                self.write_keyword("SET");
11629                self.write_space();
11630                self.write_keyword("STAGE_COPY_OPTIONS");
11631                self.write(" = (");
11632                if let Some(opts) = options {
11633                    self.generate_space_separated_properties(opts)?;
11634                }
11635                self.write(")");
11636            }
11637            AlterTableAction::AddColumns { columns, cascade } => {
11638                // Oracle uses ADD (...) without COLUMNS keyword
11639                // Hive/Spark uses ADD COLUMNS (...)
11640                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
11641                if is_oracle {
11642                    self.write_keyword("ADD");
11643                } else {
11644                    self.write_keyword("ADD COLUMNS");
11645                }
11646                self.write(" (");
11647                for (i, col) in columns.iter().enumerate() {
11648                    if i > 0 {
11649                        self.write(", ");
11650                    }
11651                    self.generate_column_def(col)?;
11652                }
11653                self.write(")");
11654                if *cascade {
11655                    self.write_space();
11656                    self.write_keyword("CASCADE");
11657                }
11658            }
11659            AlterTableAction::ChangeColumn {
11660                old_name,
11661                new_name,
11662                data_type,
11663                comment,
11664                cascade,
11665            } => {
11666                use crate::dialects::DialectType;
11667                let is_spark = matches!(
11668                    self.config.dialect,
11669                    Some(DialectType::Spark) | Some(DialectType::Databricks)
11670                );
11671                let is_rename = old_name.name != new_name.name;
11672
11673                if is_spark {
11674                    if is_rename {
11675                        // Spark: RENAME COLUMN old TO new
11676                        self.write_keyword("RENAME COLUMN");
11677                        self.write_space();
11678                        self.generate_identifier(old_name)?;
11679                        self.write_space();
11680                        self.write_keyword("TO");
11681                        self.write_space();
11682                        self.generate_identifier(new_name)?;
11683                    } else if comment.is_some() {
11684                        // Spark: ALTER COLUMN old COMMENT 'comment'
11685                        self.write_keyword("ALTER COLUMN");
11686                        self.write_space();
11687                        self.generate_identifier(old_name)?;
11688                        self.write_space();
11689                        self.write_keyword("COMMENT");
11690                        self.write_space();
11691                        self.write("'");
11692                        self.write(comment.as_ref().unwrap());
11693                        self.write("'");
11694                    } else if data_type.is_some() {
11695                        // Spark: ALTER COLUMN old TYPE data_type
11696                        self.write_keyword("ALTER COLUMN");
11697                        self.write_space();
11698                        self.generate_identifier(old_name)?;
11699                        self.write_space();
11700                        self.write_keyword("TYPE");
11701                        self.write_space();
11702                        self.generate_data_type(data_type.as_ref().unwrap())?;
11703                    } else {
11704                        // Fallback to CHANGE COLUMN
11705                        self.write_keyword("CHANGE COLUMN");
11706                        self.write_space();
11707                        self.generate_identifier(old_name)?;
11708                        self.write_space();
11709                        self.generate_identifier(new_name)?;
11710                    }
11711                } else {
11712                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
11713                    if data_type.is_some() {
11714                        self.write_keyword("CHANGE COLUMN");
11715                    } else {
11716                        self.write_keyword("CHANGE");
11717                    }
11718                    self.write_space();
11719                    self.generate_identifier(old_name)?;
11720                    self.write_space();
11721                    self.generate_identifier(new_name)?;
11722                    if let Some(ref dt) = data_type {
11723                        self.write_space();
11724                        self.generate_data_type(dt)?;
11725                    }
11726                    if let Some(ref c) = comment {
11727                        self.write_space();
11728                        self.write_keyword("COMMENT");
11729                        self.write_space();
11730                        self.write("'");
11731                        self.write(c);
11732                        self.write("'");
11733                    }
11734                    if *cascade {
11735                        self.write_space();
11736                        self.write_keyword("CASCADE");
11737                    }
11738                }
11739            }
11740            AlterTableAction::AddPartition {
11741                partition,
11742                if_not_exists,
11743                location,
11744            } => {
11745                self.write_keyword("ADD");
11746                self.write_space();
11747                if *if_not_exists {
11748                    self.write_keyword("IF NOT EXISTS");
11749                    self.write_space();
11750                }
11751                self.generate_expression(partition)?;
11752                if let Some(ref loc) = location {
11753                    self.write_space();
11754                    self.write_keyword("LOCATION");
11755                    self.write_space();
11756                    self.generate_expression(loc)?;
11757                }
11758            }
11759            AlterTableAction::AlterSortKey {
11760                this,
11761                expressions,
11762                compound,
11763            } => {
11764                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11765                self.write_keyword("ALTER");
11766                if *compound {
11767                    self.write_space();
11768                    self.write_keyword("COMPOUND");
11769                }
11770                self.write_space();
11771                self.write_keyword("SORTKEY");
11772                self.write_space();
11773                if let Some(style) = this {
11774                    self.write_keyword(style);
11775                } else if !expressions.is_empty() {
11776                    self.write("(");
11777                    for (i, expr) in expressions.iter().enumerate() {
11778                        if i > 0 {
11779                            self.write(", ");
11780                        }
11781                        self.generate_expression(expr)?;
11782                    }
11783                    self.write(")");
11784                }
11785            }
11786            AlterTableAction::AlterDistStyle { style, distkey } => {
11787                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11788                self.write_keyword("ALTER");
11789                self.write_space();
11790                self.write_keyword("DISTSTYLE");
11791                self.write_space();
11792                self.write_keyword(style);
11793                if let Some(col) = distkey {
11794                    self.write_space();
11795                    self.write_keyword("DISTKEY");
11796                    self.write_space();
11797                    self.generate_identifier(col)?;
11798                }
11799            }
11800            AlterTableAction::SetTableProperties { properties } => {
11801                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11802                self.write_keyword("SET TABLE PROPERTIES");
11803                self.write(" (");
11804                for (i, (key, value)) in properties.iter().enumerate() {
11805                    if i > 0 {
11806                        self.write(", ");
11807                    }
11808                    self.generate_expression(key)?;
11809                    self.write(" = ");
11810                    self.generate_expression(value)?;
11811                }
11812                self.write(")");
11813            }
11814            AlterTableAction::SetLocation { location } => {
11815                // Redshift: SET LOCATION 's3://bucket/folder/'
11816                self.write_keyword("SET LOCATION");
11817                self.write_space();
11818                self.write("'");
11819                self.write(location);
11820                self.write("'");
11821            }
11822            AlterTableAction::SetFileFormat { format } => {
11823                // Redshift: SET FILE FORMAT AVRO
11824                self.write_keyword("SET FILE FORMAT");
11825                self.write_space();
11826                self.write_keyword(format);
11827            }
11828            AlterTableAction::ReplacePartition { partition, source } => {
11829                // ClickHouse: REPLACE PARTITION expr FROM source
11830                self.write_keyword("REPLACE PARTITION");
11831                self.write_space();
11832                self.generate_expression(partition)?;
11833                if let Some(src) = source {
11834                    self.write_space();
11835                    self.write_keyword("FROM");
11836                    self.write_space();
11837                    self.generate_expression(src)?;
11838                }
11839            }
11840            AlterTableAction::SetTiDBTableOption { option, force } => {
11841                self.generate_tidb_table_option(option, *force)?;
11842            }
11843            AlterTableAction::RemoveTiDBTtl { executable_comment } => {
11844                if !matches!(self.config.dialect, Some(DialectType::TiDB)) {
11845                    return self.write_unsupported_comment(
11846                        "REMOVE TTL is only supported by the TiDB dialect",
11847                    );
11848                }
11849                if *executable_comment {
11850                    self.write("/*T![ttl] REMOVE TTL */");
11851                } else {
11852                    self.write_keyword("REMOVE TTL");
11853                }
11854            }
11855            AlterTableAction::Raw { sql } => {
11856                self.write(sql);
11857            }
11858        }
11859        Ok(())
11860    }
11861
11862    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11863        match action {
11864            AlterColumnAction::SetDataType {
11865                data_type,
11866                using,
11867                collate,
11868            } => {
11869                use crate::dialects::DialectType;
11870                // Dialect-specific type change syntax:
11871                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11872                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11873                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11874                let is_no_prefix = matches!(
11875                    self.config.dialect,
11876                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11877                );
11878                let is_type_only = matches!(
11879                    self.config.dialect,
11880                    Some(DialectType::Redshift)
11881                        | Some(DialectType::Spark)
11882                        | Some(DialectType::Databricks)
11883                );
11884                if is_type_only {
11885                    self.write_keyword("TYPE");
11886                    self.write_space();
11887                } else if !is_no_prefix {
11888                    self.write_keyword("SET DATA TYPE");
11889                    self.write_space();
11890                }
11891                self.generate_data_type(data_type)?;
11892                if let Some(ref collation) = collate {
11893                    self.write_space();
11894                    self.write_keyword("COLLATE");
11895                    self.write_space();
11896                    self.write(collation);
11897                }
11898                if let Some(ref using_expr) = using {
11899                    self.write_space();
11900                    self.write_keyword("USING");
11901                    self.write_space();
11902                    self.generate_expression(using_expr)?;
11903                }
11904            }
11905            AlterColumnAction::SetDefault(expr) => {
11906                self.write_keyword("SET DEFAULT");
11907                self.write_space();
11908                self.generate_expression(expr)?;
11909            }
11910            AlterColumnAction::DropDefault => {
11911                self.write_keyword("DROP DEFAULT");
11912            }
11913            AlterColumnAction::SetNotNull => {
11914                self.write_keyword("SET NOT NULL");
11915            }
11916            AlterColumnAction::DropNotNull => {
11917                self.write_keyword("DROP NOT NULL");
11918            }
11919            AlterColumnAction::Comment(comment) => {
11920                self.write_keyword("COMMENT");
11921                self.write_space();
11922                self.generate_string_literal(comment)?;
11923            }
11924            AlterColumnAction::SetVisible => {
11925                self.write_keyword("SET VISIBLE");
11926            }
11927            AlterColumnAction::SetInvisible => {
11928                self.write_keyword("SET INVISIBLE");
11929            }
11930        }
11931        Ok(())
11932    }
11933
11934    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11935        self.write_keyword("CREATE");
11936
11937        if ci.unique {
11938            self.write_space();
11939            self.write_keyword("UNIQUE");
11940        }
11941
11942        // TSQL CLUSTERED/NONCLUSTERED modifier
11943        if let Some(ref clustered) = ci.clustered {
11944            self.write_space();
11945            self.write_keyword(clustered);
11946        }
11947
11948        self.write_space();
11949        self.write_keyword("INDEX");
11950
11951        // PostgreSQL CONCURRENTLY modifier
11952        if ci.concurrently {
11953            self.write_space();
11954            self.write_keyword("CONCURRENTLY");
11955        }
11956
11957        if ci.if_not_exists {
11958            self.write_space();
11959            self.write_keyword("IF NOT EXISTS");
11960        }
11961
11962        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11963        if !ci.name.name.is_empty() {
11964            self.write_space();
11965            self.generate_identifier(&ci.name)?;
11966        }
11967        self.write_space();
11968        self.write_keyword("ON");
11969        // Hive uses ON TABLE
11970        if matches!(self.config.dialect, Some(DialectType::Hive)) {
11971            self.write_space();
11972            self.write_keyword("TABLE");
11973        }
11974        self.write_space();
11975        self.generate_table(&ci.table)?;
11976
11977        // Column list (optional for COLUMNSTORE indexes)
11978        // Standard SQL convention: ON t(a) without space before paren
11979        if !ci.columns.is_empty() || ci.using.is_some() {
11980            let space_before_paren = false;
11981
11982            if let Some(ref using) = ci.using {
11983                self.write_space();
11984                self.write_keyword("USING");
11985                self.write_space();
11986                self.write(using);
11987                if space_before_paren {
11988                    self.write(" (");
11989                } else {
11990                    self.write("(");
11991                }
11992            } else {
11993                if space_before_paren {
11994                    self.write(" (");
11995                } else {
11996                    self.write("(");
11997                }
11998            }
11999            for (i, col) in ci.columns.iter().enumerate() {
12000                if i > 0 {
12001                    self.write(", ");
12002                }
12003                self.generate_identifier(&col.column)?;
12004                if let Some(ref opclass) = col.opclass {
12005                    self.write_space();
12006                    self.write(opclass);
12007                }
12008                if col.desc {
12009                    self.write_space();
12010                    self.write_keyword("DESC");
12011                } else if col.asc {
12012                    self.write_space();
12013                    self.write_keyword("ASC");
12014                }
12015                if let Some(nulls_first) = col.nulls_first {
12016                    self.write_space();
12017                    self.write_keyword("NULLS");
12018                    self.write_space();
12019                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
12020                }
12021            }
12022            self.write(")");
12023        }
12024
12025        // PostgreSQL INCLUDE (col1, col2) clause
12026        if !ci.include_columns.is_empty() {
12027            self.write_space();
12028            self.write_keyword("INCLUDE");
12029            self.write(" (");
12030            for (i, col) in ci.include_columns.iter().enumerate() {
12031                if i > 0 {
12032                    self.write(", ");
12033                }
12034                self.generate_identifier(col)?;
12035            }
12036            self.write(")");
12037        }
12038
12039        // TSQL: WITH (option=value, ...) clause
12040        if !ci.with_options.is_empty() {
12041            self.write_space();
12042            self.write_keyword("WITH");
12043            self.write(" (");
12044            for (i, (key, value)) in ci.with_options.iter().enumerate() {
12045                if i > 0 {
12046                    self.write(", ");
12047                }
12048                self.write(key);
12049                self.write("=");
12050                self.write(value);
12051            }
12052            self.write(")");
12053        }
12054
12055        // PostgreSQL WHERE clause for partial indexes
12056        if let Some(ref where_clause) = ci.where_clause {
12057            self.write_space();
12058            self.write_keyword("WHERE");
12059            self.write_space();
12060            self.generate_expression(where_clause)?;
12061        }
12062
12063        // TSQL: ON filegroup or partition scheme clause
12064        if let Some(ref on_fg) = ci.on_filegroup {
12065            self.write_space();
12066            self.write_keyword("ON");
12067            self.write_space();
12068            self.write(on_fg);
12069        }
12070
12071        Ok(())
12072    }
12073
12074    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
12075        self.write_keyword("DROP INDEX");
12076
12077        if di.concurrently {
12078            self.write_space();
12079            self.write_keyword("CONCURRENTLY");
12080        }
12081
12082        if di.if_exists {
12083            self.write_space();
12084            self.write_keyword("IF EXISTS");
12085        }
12086
12087        self.write_space();
12088        self.generate_table(&di.name)?;
12089
12090        if let Some(ref table) = di.table {
12091            self.write_space();
12092            self.write_keyword("ON");
12093            self.write_space();
12094            self.generate_table(table)?;
12095        }
12096
12097        Ok(())
12098    }
12099
12100    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
12101        self.write_keyword("CREATE");
12102
12103        // MySQL: ALGORITHM=...
12104        if let Some(ref algorithm) = cv.algorithm {
12105            self.write_space();
12106            self.write_keyword("ALGORITHM");
12107            self.write("=");
12108            self.write_keyword(algorithm);
12109        }
12110
12111        // MySQL: DEFINER=...
12112        if let Some(ref definer) = cv.definer {
12113            self.write_space();
12114            self.write_keyword("DEFINER");
12115            self.write("=");
12116            self.write(definer);
12117        }
12118
12119        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
12120        if cv.security_sql_style && !cv.security_after_name {
12121            if let Some(ref security) = cv.security {
12122                self.write_space();
12123                self.write_keyword("SQL SECURITY");
12124                self.write_space();
12125                match security {
12126                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12127                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12128                    FunctionSecurity::None => self.write_keyword("NONE"),
12129                }
12130            }
12131        }
12132
12133        if cv.or_alter {
12134            self.write_space();
12135            self.write_keyword("OR ALTER");
12136        } else if cv.or_replace {
12137            self.write_space();
12138            self.write_keyword("OR REPLACE");
12139        }
12140
12141        if cv.temporary {
12142            self.write_space();
12143            self.write_keyword("TEMPORARY");
12144        }
12145
12146        if cv.materialized {
12147            self.write_space();
12148            self.write_keyword("MATERIALIZED");
12149        }
12150
12151        // Snowflake: SECURE VIEW
12152        if cv.secure {
12153            self.write_space();
12154            self.write_keyword("SECURE");
12155        }
12156
12157        self.write_space();
12158        self.write_keyword("VIEW");
12159
12160        if cv.if_not_exists {
12161            self.write_space();
12162            self.write_keyword("IF NOT EXISTS");
12163        }
12164
12165        self.write_space();
12166        self.generate_table(&cv.name)?;
12167
12168        // ClickHouse: ON CLUSTER clause
12169        if let Some(ref on_cluster) = cv.on_cluster {
12170            self.write_space();
12171            self.generate_on_cluster(on_cluster)?;
12172        }
12173
12174        // ClickHouse: TO destination_table
12175        if let Some(ref to_table) = cv.to_table {
12176            self.write_space();
12177            self.write_keyword("TO");
12178            self.write_space();
12179            self.generate_table(to_table)?;
12180        }
12181
12182        // For regular VIEW: columns come before COPY GRANTS
12183        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
12184        if !cv.materialized {
12185            // Regular VIEW: columns first
12186            if let Some(ref schema) = cv.schema {
12187                self.write(" (");
12188                for (i, expr) in schema.expressions.iter().enumerate() {
12189                    if i > 0 {
12190                        self.write(", ");
12191                    }
12192                    self.generate_expression(expr)?;
12193                }
12194                self.write(")");
12195            } else if !cv.columns.is_empty() {
12196                self.write(" (");
12197                for (i, col) in cv.columns.iter().enumerate() {
12198                    if i > 0 {
12199                        self.write(", ");
12200                    }
12201                    self.generate_identifier(&col.name)?;
12202                    // BigQuery: OPTIONS (key=value, ...) on view column
12203                    if !col.options.is_empty() {
12204                        self.write_space();
12205                        self.generate_options_clause(&col.options)?;
12206                    }
12207                    if let Some(ref comment) = col.comment {
12208                        self.write_space();
12209                        self.write_keyword("COMMENT");
12210                        self.write_space();
12211                        self.generate_string_literal(comment)?;
12212                    }
12213                }
12214                self.write(")");
12215            }
12216
12217            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
12218            // Also handles SQL SECURITY after view name (security_after_name)
12219            if !cv.security_sql_style || cv.security_after_name {
12220                if let Some(ref security) = cv.security {
12221                    self.write_space();
12222                    if cv.security_sql_style {
12223                        self.write_keyword("SQL SECURITY");
12224                    } else {
12225                        self.write_keyword("SECURITY");
12226                    }
12227                    self.write_space();
12228                    match security {
12229                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12230                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12231                        FunctionSecurity::None => self.write_keyword("NONE"),
12232                    }
12233                }
12234            }
12235
12236            // Snowflake: COPY GRANTS
12237            if cv.copy_grants {
12238                self.write_space();
12239                self.write_keyword("COPY GRANTS");
12240            }
12241        } else {
12242            // MATERIALIZED VIEW: COPY GRANTS first
12243            if cv.copy_grants {
12244                self.write_space();
12245                self.write_keyword("COPY GRANTS");
12246            }
12247
12248            // Doris: If we have a schema (typed columns), generate that instead
12249            if let Some(ref schema) = cv.schema {
12250                self.write(" (");
12251                for (i, expr) in schema.expressions.iter().enumerate() {
12252                    if i > 0 {
12253                        self.write(", ");
12254                    }
12255                    self.generate_expression(expr)?;
12256                }
12257                self.write(")");
12258            } else if !cv.columns.is_empty() {
12259                // Then columns (simple column names without types)
12260                self.write(" (");
12261                for (i, col) in cv.columns.iter().enumerate() {
12262                    if i > 0 {
12263                        self.write(", ");
12264                    }
12265                    self.generate_identifier(&col.name)?;
12266                    // BigQuery: OPTIONS (key=value, ...) on view column
12267                    if !col.options.is_empty() {
12268                        self.write_space();
12269                        self.generate_options_clause(&col.options)?;
12270                    }
12271                    if let Some(ref comment) = col.comment {
12272                        self.write_space();
12273                        self.write_keyword("COMMENT");
12274                        self.write_space();
12275                        self.generate_string_literal(comment)?;
12276                    }
12277                }
12278                self.write(")");
12279            }
12280
12281            // Doris: KEY (columns) for materialized views
12282            if let Some(ref unique_key) = cv.unique_key {
12283                self.write_space();
12284                self.write_keyword("KEY");
12285                self.write(" (");
12286                for (i, expr) in unique_key.expressions.iter().enumerate() {
12287                    if i > 0 {
12288                        self.write(", ");
12289                    }
12290                    self.generate_expression(expr)?;
12291                }
12292                self.write(")");
12293            }
12294        }
12295
12296        if let Some(ref row_access_policy) = cv.row_access_policy {
12297            self.write_space();
12298            self.write_keyword("WITH");
12299            self.write_space();
12300            self.write(row_access_policy);
12301        }
12302
12303        // Snowflake: COMMENT = 'text'
12304        if let Some(ref comment) = cv.comment {
12305            self.write_space();
12306            self.write_keyword("COMMENT");
12307            self.write("=");
12308            self.generate_string_literal(comment)?;
12309        }
12310
12311        // Snowflake: TAG (name='value', ...)
12312        if !cv.tags.is_empty() {
12313            self.write_space();
12314            self.write_keyword("TAG");
12315            self.write(" (");
12316            for (i, (name, value)) in cv.tags.iter().enumerate() {
12317                if i > 0 {
12318                    self.write(", ");
12319                }
12320                self.write(name);
12321                self.write("='");
12322                self.write(value);
12323                self.write("'");
12324            }
12325            self.write(")");
12326        }
12327
12328        // BigQuery: OPTIONS (key=value, ...)
12329        if !cv.options.is_empty() {
12330            self.write_space();
12331            self.generate_options_clause(&cv.options)?;
12332        }
12333
12334        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
12335        if let Some(ref build) = cv.build {
12336            self.write_space();
12337            self.write_keyword("BUILD");
12338            self.write_space();
12339            self.write_keyword(build);
12340        }
12341
12342        // Doris: REFRESH clause for materialized views
12343        if let Some(ref refresh) = cv.refresh {
12344            self.write_space();
12345            self.generate_refresh_trigger_property(refresh)?;
12346        }
12347
12348        // Redshift: AUTO REFRESH YES|NO for materialized views
12349        if let Some(auto_refresh) = cv.auto_refresh {
12350            self.write_space();
12351            self.write_keyword("AUTO REFRESH");
12352            self.write_space();
12353            if auto_refresh {
12354                self.write_keyword("YES");
12355            } else {
12356                self.write_keyword("NO");
12357            }
12358        }
12359
12360        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
12361        for prop in &cv.table_properties {
12362            self.write_space();
12363            self.generate_expression(prop)?;
12364        }
12365
12366        // ClickHouse: POPULATE / EMPTY before AS
12367        if let Some(ref population) = cv.clickhouse_population {
12368            self.write_space();
12369            self.write_keyword(population);
12370        }
12371
12372        // Only output AS clause if there's a real query (not just NULL placeholder)
12373        if !matches!(&cv.query, Expression::Null(_)) {
12374            self.write_space();
12375            self.write_keyword("AS");
12376            self.write_space();
12377
12378            // Teradata: LOCKING clause (between AS and query)
12379            if let Some(ref mode) = cv.locking_mode {
12380                self.write_keyword("LOCKING");
12381                self.write_space();
12382                self.write_keyword(mode);
12383                if let Some(ref access) = cv.locking_access {
12384                    self.write_space();
12385                    self.write_keyword("FOR");
12386                    self.write_space();
12387                    self.write_keyword(access);
12388                }
12389                self.write_space();
12390            }
12391
12392            if cv.query_parenthesized {
12393                self.write("(");
12394            }
12395            self.generate_expression(&cv.query)?;
12396            if cv.query_parenthesized {
12397                self.write(")");
12398            }
12399        }
12400
12401        // Redshift: WITH NO SCHEMA BINDING (after query)
12402        if cv.no_schema_binding {
12403            self.write_space();
12404            self.write_keyword("WITH NO SCHEMA BINDING");
12405        }
12406
12407        Ok(())
12408    }
12409
12410    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
12411        self.write_keyword("DROP");
12412
12413        if dv.materialized {
12414            self.write_space();
12415            self.write_keyword("MATERIALIZED");
12416        }
12417
12418        self.write_space();
12419        self.write_keyword("VIEW");
12420
12421        if dv.if_exists {
12422            self.write_space();
12423            self.write_keyword("IF EXISTS");
12424        }
12425
12426        self.write_space();
12427        self.generate_table(&dv.name)?;
12428
12429        Ok(())
12430    }
12431
12432    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
12433        match tr.target {
12434            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
12435            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
12436        }
12437        if tr.if_exists {
12438            self.write_space();
12439            self.write_keyword("IF EXISTS");
12440        }
12441        self.write_space();
12442        self.generate_table(&tr.table)?;
12443
12444        // ClickHouse: ON CLUSTER clause
12445        if let Some(ref on_cluster) = tr.on_cluster {
12446            self.write_space();
12447            self.generate_on_cluster(on_cluster)?;
12448        }
12449
12450        // Check if first table has a * (multi-table with star)
12451        if !tr.extra_tables.is_empty() {
12452            // Check if the first entry matches the main table (star case)
12453            let skip_first = if let Some(first) = tr.extra_tables.first() {
12454                first.table.name == tr.table.name && first.star
12455            } else {
12456                false
12457            };
12458
12459            // PostgreSQL normalizes away the * suffix (it's the default behavior)
12460            let strip_star = matches!(
12461                self.config.dialect,
12462                Some(crate::dialects::DialectType::PostgreSQL)
12463                    | Some(crate::dialects::DialectType::Redshift)
12464            );
12465            if skip_first && !strip_star {
12466                self.write("*");
12467            }
12468
12469            // Generate additional tables
12470            for (i, entry) in tr.extra_tables.iter().enumerate() {
12471                if i == 0 && skip_first {
12472                    continue; // Already handled the star for first table
12473                }
12474                self.write(", ");
12475                self.generate_table(&entry.table)?;
12476                if entry.star && !strip_star {
12477                    self.write("*");
12478                }
12479            }
12480        }
12481
12482        // RESTART/CONTINUE IDENTITY
12483        if let Some(identity) = &tr.identity {
12484            self.write_space();
12485            match identity {
12486                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
12487                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
12488            }
12489        }
12490
12491        if tr.cascade {
12492            self.write_space();
12493            self.write_keyword("CASCADE");
12494        }
12495
12496        if tr.restrict {
12497            self.write_space();
12498            self.write_keyword("RESTRICT");
12499        }
12500
12501        // Output Hive PARTITION clause
12502        if let Some(ref partition) = tr.partition {
12503            self.write_space();
12504            self.generate_expression(partition)?;
12505        }
12506
12507        Ok(())
12508    }
12509
12510    fn generate_use(&mut self, u: &Use) -> Result<()> {
12511        // Teradata uses "DATABASE <name>" instead of "USE <name>"
12512        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
12513            self.write_keyword("DATABASE");
12514            self.write_space();
12515            self.generate_identifier(&u.this)?;
12516            return Ok(());
12517        }
12518
12519        self.write_keyword("USE");
12520
12521        if let Some(kind) = &u.kind {
12522            self.write_space();
12523            match kind {
12524                UseKind::Database => self.write_keyword("DATABASE"),
12525                UseKind::Schema => self.write_keyword("SCHEMA"),
12526                UseKind::Role => self.write_keyword("ROLE"),
12527                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
12528                UseKind::Catalog => self.write_keyword("CATALOG"),
12529                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
12530            }
12531        }
12532
12533        self.write_space();
12534        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
12535        // without quoting, since these are keywords not identifiers
12536        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
12537            self.write(&u.this.name);
12538        } else {
12539            self.generate_identifier(&u.this)?;
12540        }
12541        Ok(())
12542    }
12543
12544    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
12545        self.write_keyword("CACHE");
12546        if c.lazy {
12547            self.write_space();
12548            self.write_keyword("LAZY");
12549        }
12550        self.write_space();
12551        self.write_keyword("TABLE");
12552        self.write_space();
12553        self.generate_identifier(&c.table)?;
12554
12555        // OPTIONS clause
12556        if !c.options.is_empty() {
12557            self.write_space();
12558            self.write_keyword("OPTIONS");
12559            self.write("(");
12560            for (i, (key, value)) in c.options.iter().enumerate() {
12561                if i > 0 {
12562                    self.write(", ");
12563                }
12564                self.generate_expression(key)?;
12565                self.write(" = ");
12566                self.generate_expression(value)?;
12567            }
12568            self.write(")");
12569        }
12570
12571        // AS query
12572        if let Some(query) = &c.query {
12573            self.write_space();
12574            self.write_keyword("AS");
12575            self.write_space();
12576            self.generate_expression(query)?;
12577        }
12578
12579        Ok(())
12580    }
12581
12582    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
12583        self.write_keyword("UNCACHE TABLE");
12584        if u.if_exists {
12585            self.write_space();
12586            self.write_keyword("IF EXISTS");
12587        }
12588        self.write_space();
12589        self.generate_identifier(&u.table)?;
12590        Ok(())
12591    }
12592
12593    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
12594        self.write_keyword("LOAD DATA");
12595        if l.local {
12596            self.write_space();
12597            self.write_keyword("LOCAL");
12598        }
12599        self.write_space();
12600        self.write_keyword("INPATH");
12601        self.write_space();
12602        self.write("'");
12603        self.write(&l.inpath);
12604        self.write("'");
12605
12606        if l.overwrite {
12607            self.write_space();
12608            self.write_keyword("OVERWRITE");
12609        }
12610
12611        self.write_space();
12612        self.write_keyword("INTO TABLE");
12613        self.write_space();
12614        self.generate_expression(&l.table)?;
12615
12616        // PARTITION clause
12617        if !l.partition.is_empty() {
12618            self.write_space();
12619            self.write_keyword("PARTITION");
12620            self.write("(");
12621            for (i, (col, val)) in l.partition.iter().enumerate() {
12622                if i > 0 {
12623                    self.write(", ");
12624                }
12625                self.generate_identifier(col)?;
12626                self.write(" = ");
12627                self.generate_expression(val)?;
12628            }
12629            self.write(")");
12630        }
12631
12632        // INPUTFORMAT clause
12633        if let Some(fmt) = &l.input_format {
12634            self.write_space();
12635            self.write_keyword("INPUTFORMAT");
12636            self.write_space();
12637            self.write("'");
12638            self.write(fmt);
12639            self.write("'");
12640        }
12641
12642        // SERDE clause
12643        if let Some(serde) = &l.serde {
12644            self.write_space();
12645            self.write_keyword("SERDE");
12646            self.write_space();
12647            self.write("'");
12648            self.write(serde);
12649            self.write("'");
12650        }
12651
12652        Ok(())
12653    }
12654
12655    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
12656        self.write_keyword("PRAGMA");
12657        self.write_space();
12658
12659        // Schema prefix if present
12660        if let Some(schema) = &p.schema {
12661            self.generate_identifier(schema)?;
12662            self.write(".");
12663        }
12664
12665        // Pragma name
12666        self.generate_identifier(&p.name)?;
12667
12668        // Value assignment or function call
12669        if p.use_assignment_syntax {
12670            self.write(" = ");
12671            if let Some(value) = &p.value {
12672                self.generate_expression(value)?;
12673            } else if let Some(arg) = p.args.first() {
12674                self.generate_expression(arg)?;
12675            }
12676        } else if !p.args.is_empty() {
12677            self.write("(");
12678            for (i, arg) in p.args.iter().enumerate() {
12679                if i > 0 {
12680                    self.write(", ");
12681                }
12682                self.generate_expression(arg)?;
12683            }
12684            self.write(")");
12685        }
12686
12687        Ok(())
12688    }
12689
12690    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
12691        self.write_keyword("GRANT");
12692        self.write_space();
12693
12694        // Privileges (with optional column lists)
12695        for (i, privilege) in g.privileges.iter().enumerate() {
12696            if i > 0 {
12697                self.write(", ");
12698            }
12699            self.write_keyword(&privilege.name);
12700            // Output column list if present: SELECT(col1, col2)
12701            if !privilege.columns.is_empty() {
12702                self.write("(");
12703                for (j, col) in privilege.columns.iter().enumerate() {
12704                    if j > 0 {
12705                        self.write(", ");
12706                    }
12707                    self.write(col);
12708                }
12709                self.write(")");
12710            }
12711        }
12712
12713        self.write_space();
12714        self.write_keyword("ON");
12715        self.write_space();
12716
12717        // Object kind (TABLE, SCHEMA, etc.)
12718        if let Some(kind) = &g.kind {
12719            self.write_keyword(kind);
12720            self.write_space();
12721        }
12722
12723        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12724        {
12725            use crate::dialects::DialectType;
12726            let should_upper = matches!(
12727                self.config.dialect,
12728                Some(DialectType::PostgreSQL)
12729                    | Some(DialectType::CockroachDB)
12730                    | Some(DialectType::Materialize)
12731                    | Some(DialectType::RisingWave)
12732            ) && (g.kind.as_deref() == Some("FUNCTION")
12733                || g.kind.as_deref() == Some("PROCEDURE"));
12734            if should_upper {
12735                use crate::expressions::Identifier;
12736                let upper_id = Identifier {
12737                    name: g.securable.name.to_ascii_uppercase(),
12738                    quoted: g.securable.quoted,
12739                    ..g.securable.clone()
12740                };
12741                self.generate_identifier(&upper_id)?;
12742            } else {
12743                self.generate_identifier(&g.securable)?;
12744            }
12745        }
12746
12747        // Function parameter types (if present)
12748        if !g.function_params.is_empty() {
12749            self.write("(");
12750            for (i, param) in g.function_params.iter().enumerate() {
12751                if i > 0 {
12752                    self.write(", ");
12753                }
12754                self.write(param);
12755            }
12756            self.write(")");
12757        }
12758
12759        self.write_space();
12760        self.write_keyword("TO");
12761        self.write_space();
12762
12763        // Principals
12764        for (i, principal) in g.principals.iter().enumerate() {
12765            if i > 0 {
12766                self.write(", ");
12767            }
12768            if principal.is_role {
12769                self.write_keyword("ROLE");
12770                self.write_space();
12771            } else if principal.is_group {
12772                self.write_keyword("GROUP");
12773                self.write_space();
12774            } else if principal.is_share {
12775                self.write_keyword("SHARE");
12776                self.write_space();
12777            }
12778            self.generate_identifier(&principal.name)?;
12779        }
12780
12781        // WITH GRANT OPTION
12782        if g.grant_option {
12783            self.write_space();
12784            self.write_keyword("WITH GRANT OPTION");
12785        }
12786
12787        // TSQL: AS principal
12788        if let Some(ref principal) = g.as_principal {
12789            self.write_space();
12790            self.write_keyword("AS");
12791            self.write_space();
12792            self.generate_identifier(principal)?;
12793        }
12794
12795        Ok(())
12796    }
12797
12798    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12799        self.write_keyword("REVOKE");
12800        self.write_space();
12801
12802        // GRANT OPTION FOR
12803        if r.grant_option {
12804            self.write_keyword("GRANT OPTION FOR");
12805            self.write_space();
12806        }
12807
12808        // Privileges (with optional column lists)
12809        for (i, privilege) in r.privileges.iter().enumerate() {
12810            if i > 0 {
12811                self.write(", ");
12812            }
12813            self.write_keyword(&privilege.name);
12814            // Output column list if present: SELECT(col1, col2)
12815            if !privilege.columns.is_empty() {
12816                self.write("(");
12817                for (j, col) in privilege.columns.iter().enumerate() {
12818                    if j > 0 {
12819                        self.write(", ");
12820                    }
12821                    self.write(col);
12822                }
12823                self.write(")");
12824            }
12825        }
12826
12827        self.write_space();
12828        self.write_keyword("ON");
12829        self.write_space();
12830
12831        // Object kind
12832        if let Some(kind) = &r.kind {
12833            self.write_keyword(kind);
12834            self.write_space();
12835        }
12836
12837        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12838        {
12839            use crate::dialects::DialectType;
12840            let should_upper = matches!(
12841                self.config.dialect,
12842                Some(DialectType::PostgreSQL)
12843                    | Some(DialectType::CockroachDB)
12844                    | Some(DialectType::Materialize)
12845                    | Some(DialectType::RisingWave)
12846            ) && (r.kind.as_deref() == Some("FUNCTION")
12847                || r.kind.as_deref() == Some("PROCEDURE"));
12848            if should_upper {
12849                use crate::expressions::Identifier;
12850                let upper_id = Identifier {
12851                    name: r.securable.name.to_ascii_uppercase(),
12852                    quoted: r.securable.quoted,
12853                    ..r.securable.clone()
12854                };
12855                self.generate_identifier(&upper_id)?;
12856            } else {
12857                self.generate_identifier(&r.securable)?;
12858            }
12859        }
12860
12861        // Function parameter types (if present)
12862        if !r.function_params.is_empty() {
12863            self.write("(");
12864            for (i, param) in r.function_params.iter().enumerate() {
12865                if i > 0 {
12866                    self.write(", ");
12867                }
12868                self.write(param);
12869            }
12870            self.write(")");
12871        }
12872
12873        self.write_space();
12874        self.write_keyword("FROM");
12875        self.write_space();
12876
12877        // Principals
12878        for (i, principal) in r.principals.iter().enumerate() {
12879            if i > 0 {
12880                self.write(", ");
12881            }
12882            if principal.is_role {
12883                self.write_keyword("ROLE");
12884                self.write_space();
12885            } else if principal.is_group {
12886                self.write_keyword("GROUP");
12887                self.write_space();
12888            } else if principal.is_share {
12889                self.write_keyword("SHARE");
12890                self.write_space();
12891            }
12892            self.generate_identifier(&principal.name)?;
12893        }
12894
12895        // CASCADE or RESTRICT
12896        if r.cascade {
12897            self.write_space();
12898            self.write_keyword("CASCADE");
12899        } else if r.restrict {
12900            self.write_space();
12901            self.write_keyword("RESTRICT");
12902        }
12903
12904        Ok(())
12905    }
12906
12907    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12908        self.write_keyword("COMMENT");
12909
12910        // IF EXISTS
12911        if c.exists {
12912            self.write_space();
12913            self.write_keyword("IF EXISTS");
12914        }
12915
12916        self.write_space();
12917        self.write_keyword("ON");
12918
12919        // MATERIALIZED
12920        if c.materialized {
12921            self.write_space();
12922            self.write_keyword("MATERIALIZED");
12923        }
12924
12925        self.write_space();
12926        self.write_keyword(&c.kind);
12927        self.write_space();
12928
12929        // Object name
12930        self.generate_expression(&c.this)?;
12931
12932        self.write_space();
12933        self.write_keyword("IS");
12934        self.write_space();
12935
12936        // Comment expression
12937        self.generate_expression(&c.expression)?;
12938
12939        Ok(())
12940    }
12941
12942    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
12943        self.write_keyword("SET");
12944
12945        for (i, item) in s.items.iter().enumerate() {
12946            if i > 0 {
12947                self.write(",");
12948            }
12949            self.write_space();
12950
12951            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
12952            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
12953            if let Some(ref kind) = item.kind {
12954                // For VARIABLE kind, only output the keyword for dialects that require it
12955                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
12956                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
12957                if has_variable_kind {
12958                    if matches!(
12959                        self.config.dialect,
12960                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
12961                    ) {
12962                        self.write_keyword("VARIABLE");
12963                        self.write_space();
12964                    }
12965                } else {
12966                    self.write_keyword(kind);
12967                    self.write_space();
12968                }
12969            }
12970
12971            // Check for special SET forms by name
12972            let name_str = match &item.name {
12973                Expression::Identifier(id) => Some(id.name.as_str()),
12974                _ => None,
12975            };
12976
12977            let is_transaction = name_str == Some("TRANSACTION");
12978            let is_character_set = name_str == Some("CHARACTER SET");
12979            let is_names = name_str == Some("NAMES");
12980            let is_collate = name_str == Some("COLLATE");
12981            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
12982            let is_value_only =
12983                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
12984
12985            if is_transaction {
12986                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
12987                self.write_keyword("TRANSACTION");
12988                if let Expression::Identifier(id) = &item.value {
12989                    if !id.name.is_empty() {
12990                        self.write_space();
12991                        self.write(&id.name);
12992                    }
12993                }
12994            } else if is_character_set {
12995                // Output: SET CHARACTER SET <charset>
12996                self.write_keyword("CHARACTER SET");
12997                self.write_space();
12998                self.generate_set_value(&item.value)?;
12999            } else if is_names {
13000                // Output: SET NAMES <charset>
13001                self.write_keyword("NAMES");
13002                self.write_space();
13003                self.generate_set_value(&item.value)?;
13004            } else if is_collate {
13005                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
13006                self.write_keyword("COLLATE");
13007                self.write_space();
13008                self.generate_set_value(&item.value)?;
13009            } else if is_identity_insert {
13010                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
13011                self.write_keyword("IDENTITY_INSERT");
13012                self.write_space();
13013                self.generate_identity_insert_value(&item.value)?;
13014            } else if has_variable_kind {
13015                // Output: SET [VARIABLE] <name> = <value>
13016                // VARIABLE keyword already written above if dialect requires it
13017                if let Some(ns) = name_str {
13018                    self.write(ns);
13019                } else {
13020                    self.generate_expression(&item.name)?;
13021                }
13022                self.write(" = ");
13023                self.generate_set_value(&item.value)?;
13024            } else if is_value_only {
13025                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
13026                self.generate_expression(&item.name)?;
13027            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
13028                // SET key value without = (TSQL style)
13029                self.generate_expression(&item.name)?;
13030                self.write_space();
13031                self.generate_set_value(&item.value)?;
13032            } else {
13033                // Standard: variable = value
13034                // SET item names should not be quoted (they are config parameter names, not column refs)
13035                match &item.name {
13036                    Expression::Identifier(id) => {
13037                        self.write(&id.name);
13038                    }
13039                    _ => {
13040                        self.generate_expression(&item.name)?;
13041                    }
13042                }
13043                self.write(" = ");
13044                self.generate_set_value(&item.value)?;
13045            }
13046        }
13047
13048        Ok(())
13049    }
13050
13051    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
13052        if let Expression::Tuple(tuple) = value {
13053            if tuple.expressions.len() == 2 {
13054                self.generate_expression(&tuple.expressions[0])?;
13055                self.write_space();
13056                self.generate_set_value(&tuple.expressions[1])?;
13057                return Ok(());
13058            }
13059        }
13060
13061        self.generate_set_value(value)
13062    }
13063
13064    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
13065    /// directly to avoid reserved keyword quoting.
13066    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
13067        if let Expression::Identifier(id) = value {
13068            match id.name.as_str() {
13069                "DEFAULT" | "ON" | "OFF" => {
13070                    self.write_keyword(&id.name);
13071                    return Ok(());
13072                }
13073                _ => {}
13074            }
13075        }
13076        self.generate_expression(value)
13077    }
13078
13079    // ==================== Phase 4: Additional DDL Generation ====================
13080
13081    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
13082        self.write_keyword("ALTER");
13083        // MySQL modifiers before VIEW
13084        if let Some(ref algorithm) = av.algorithm {
13085            self.write_space();
13086            self.write_keyword("ALGORITHM");
13087            self.write(" = ");
13088            self.write_keyword(algorithm);
13089        }
13090        if let Some(ref definer) = av.definer {
13091            self.write_space();
13092            self.write_keyword("DEFINER");
13093            self.write(" = ");
13094            self.write(definer);
13095        }
13096        if let Some(ref sql_security) = av.sql_security {
13097            self.write_space();
13098            self.write_keyword("SQL SECURITY");
13099            self.write(" = ");
13100            self.write_keyword(sql_security);
13101        }
13102        self.write_space();
13103        self.write_keyword("VIEW");
13104        self.write_space();
13105        self.generate_table(&av.name)?;
13106
13107        // Hive: Column aliases with optional COMMENT
13108        if !av.columns.is_empty() {
13109            self.write(" (");
13110            for (i, col) in av.columns.iter().enumerate() {
13111                if i > 0 {
13112                    self.write(", ");
13113                }
13114                self.generate_identifier(&col.name)?;
13115                if let Some(ref comment) = col.comment {
13116                    self.write_space();
13117                    self.write_keyword("COMMENT");
13118                    self.write(" ");
13119                    self.generate_string_literal(comment)?;
13120                }
13121            }
13122            self.write(")");
13123        }
13124
13125        // TSQL: WITH option before actions
13126        if let Some(ref opt) = av.with_option {
13127            self.write_space();
13128            self.write_keyword("WITH");
13129            self.write_space();
13130            self.write_keyword(opt);
13131        }
13132
13133        for action in &av.actions {
13134            self.write_space();
13135            match action {
13136                AlterViewAction::Rename(new_name) => {
13137                    self.write_keyword("RENAME TO");
13138                    self.write_space();
13139                    self.generate_table(new_name)?;
13140                }
13141                AlterViewAction::OwnerTo(owner) => {
13142                    self.write_keyword("OWNER TO");
13143                    self.write_space();
13144                    self.generate_identifier(owner)?;
13145                }
13146                AlterViewAction::SetSchema(schema) => {
13147                    self.write_keyword("SET SCHEMA");
13148                    self.write_space();
13149                    self.generate_identifier(schema)?;
13150                }
13151                AlterViewAction::SetAuthorization(auth) => {
13152                    self.write_keyword("SET AUTHORIZATION");
13153                    self.write_space();
13154                    self.write(auth);
13155                }
13156                AlterViewAction::AlterColumn { name, action } => {
13157                    self.write_keyword("ALTER COLUMN");
13158                    self.write_space();
13159                    self.generate_identifier(name)?;
13160                    self.write_space();
13161                    self.generate_alter_column_action(action)?;
13162                }
13163                AlterViewAction::AsSelect(query) => {
13164                    self.write_keyword("AS");
13165                    self.write_space();
13166                    self.generate_expression(query)?;
13167                }
13168                AlterViewAction::SetTblproperties(props) => {
13169                    self.write_keyword("SET TBLPROPERTIES");
13170                    self.write(" (");
13171                    for (i, (key, value)) in props.iter().enumerate() {
13172                        if i > 0 {
13173                            self.write(", ");
13174                        }
13175                        self.generate_string_literal(key)?;
13176                        self.write("=");
13177                        self.generate_string_literal(value)?;
13178                    }
13179                    self.write(")");
13180                }
13181                AlterViewAction::UnsetTblproperties(keys) => {
13182                    self.write_keyword("UNSET TBLPROPERTIES");
13183                    self.write(" (");
13184                    for (i, key) in keys.iter().enumerate() {
13185                        if i > 0 {
13186                            self.write(", ");
13187                        }
13188                        self.generate_string_literal(key)?;
13189                    }
13190                    self.write(")");
13191                }
13192            }
13193        }
13194
13195        Ok(())
13196    }
13197
13198    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
13199        self.write_keyword("ALTER INDEX");
13200        self.write_space();
13201        self.generate_identifier(&ai.name)?;
13202
13203        if let Some(table) = &ai.table {
13204            self.write_space();
13205            self.write_keyword("ON");
13206            self.write_space();
13207            self.generate_table(table)?;
13208        }
13209
13210        for action in &ai.actions {
13211            self.write_space();
13212            match action {
13213                AlterIndexAction::Rename(new_name) => {
13214                    self.write_keyword("RENAME TO");
13215                    self.write_space();
13216                    self.generate_identifier(new_name)?;
13217                }
13218                AlterIndexAction::SetTablespace(tablespace) => {
13219                    self.write_keyword("SET TABLESPACE");
13220                    self.write_space();
13221                    self.generate_identifier(tablespace)?;
13222                }
13223                AlterIndexAction::Visible(visible) => {
13224                    if *visible {
13225                        self.write_keyword("VISIBLE");
13226                    } else {
13227                        self.write_keyword("INVISIBLE");
13228                    }
13229                }
13230            }
13231        }
13232
13233        Ok(())
13234    }
13235
13236    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
13237        // Output leading comments
13238        for comment in &cs.leading_comments {
13239            self.write_formatted_comment(comment);
13240            self.write_space();
13241        }
13242
13243        // Athena: CREATE SCHEMA uses Hive engine (backticks)
13244        let saved_athena_hive_context = self.athena_hive_context;
13245        if matches!(
13246            self.config.dialect,
13247            Some(crate::dialects::DialectType::Athena)
13248        ) {
13249            self.athena_hive_context = true;
13250        }
13251
13252        self.write_keyword("CREATE SCHEMA");
13253
13254        if cs.if_not_exists {
13255            self.write_space();
13256            self.write_keyword("IF NOT EXISTS");
13257        }
13258
13259        self.write_space();
13260        for (i, part) in cs.name.iter().enumerate() {
13261            if i > 0 {
13262                self.write(".");
13263            }
13264            self.generate_identifier(part)?;
13265        }
13266
13267        if let Some(ref clone_parts) = cs.clone_from {
13268            self.write_keyword(" CLONE ");
13269            for (i, part) in clone_parts.iter().enumerate() {
13270                if i > 0 {
13271                    self.write(".");
13272                }
13273                self.generate_identifier(part)?;
13274            }
13275        }
13276
13277        if let Some(ref at_clause) = cs.at_clause {
13278            self.write_space();
13279            self.generate_expression(at_clause)?;
13280        }
13281
13282        if let Some(auth) = &cs.authorization {
13283            self.write_space();
13284            self.write_keyword("AUTHORIZATION");
13285            self.write_space();
13286            self.generate_identifier(auth)?;
13287        }
13288
13289        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
13290        // Separate WITH properties from other properties
13291        let with_properties: Vec<_> = cs
13292            .properties
13293            .iter()
13294            .filter(|p| matches!(p, Expression::Property(_)))
13295            .collect();
13296        let other_properties: Vec<_> = cs
13297            .properties
13298            .iter()
13299            .filter(|p| !matches!(p, Expression::Property(_)))
13300            .collect();
13301
13302        // Generate WITH (props) if we have Property expressions
13303        if !with_properties.is_empty() {
13304            self.write_space();
13305            self.write_keyword("WITH");
13306            self.write(" (");
13307            for (i, prop) in with_properties.iter().enumerate() {
13308                if i > 0 {
13309                    self.write(", ");
13310                }
13311                self.generate_expression(prop)?;
13312            }
13313            self.write(")");
13314        }
13315
13316        // Generate other properties (like DEFAULT COLLATE)
13317        for prop in other_properties {
13318            self.write_space();
13319            self.generate_expression(prop)?;
13320        }
13321
13322        // Restore Athena Hive context
13323        self.athena_hive_context = saved_athena_hive_context;
13324
13325        Ok(())
13326    }
13327
13328    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
13329        self.write_keyword("DROP SCHEMA");
13330
13331        if ds.if_exists {
13332            self.write_space();
13333            self.write_keyword("IF EXISTS");
13334        }
13335
13336        self.write_space();
13337        self.generate_identifier(&ds.name)?;
13338
13339        if ds.cascade {
13340            self.write_space();
13341            self.write_keyword("CASCADE");
13342        }
13343
13344        Ok(())
13345    }
13346
13347    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
13348        self.write_keyword("DROP NAMESPACE");
13349
13350        if dn.if_exists {
13351            self.write_space();
13352            self.write_keyword("IF EXISTS");
13353        }
13354
13355        self.write_space();
13356        self.generate_identifier(&dn.name)?;
13357
13358        if dn.cascade {
13359            self.write_space();
13360            self.write_keyword("CASCADE");
13361        }
13362
13363        Ok(())
13364    }
13365
13366    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
13367        self.write_keyword("CREATE DATABASE");
13368
13369        if cd.if_not_exists {
13370            self.write_space();
13371            self.write_keyword("IF NOT EXISTS");
13372        }
13373
13374        self.write_space();
13375        self.generate_identifier(&cd.name)?;
13376
13377        if let Some(ref clone_src) = cd.clone_from {
13378            self.write_keyword(" CLONE ");
13379            self.generate_identifier(clone_src)?;
13380        }
13381
13382        // AT/BEFORE clause for time travel (Snowflake)
13383        if let Some(ref at_clause) = cd.at_clause {
13384            self.write_space();
13385            self.generate_expression(at_clause)?;
13386        }
13387
13388        for option in &cd.options {
13389            self.write_space();
13390            match option {
13391                DatabaseOption::CharacterSet(charset) => {
13392                    self.write_keyword("CHARACTER SET");
13393                    self.write(" = ");
13394                    self.write(&format!("'{}'", charset));
13395                }
13396                DatabaseOption::Collate(collate) => {
13397                    self.write_keyword("COLLATE");
13398                    self.write(" = ");
13399                    self.write(&format!("'{}'", collate));
13400                }
13401                DatabaseOption::Owner(owner) => {
13402                    self.write_keyword("OWNER");
13403                    self.write(" = ");
13404                    self.generate_identifier(owner)?;
13405                }
13406                DatabaseOption::Template(template) => {
13407                    self.write_keyword("TEMPLATE");
13408                    self.write(" = ");
13409                    self.generate_identifier(template)?;
13410                }
13411                DatabaseOption::Encoding(encoding) => {
13412                    self.write_keyword("ENCODING");
13413                    self.write(" = ");
13414                    self.write(&format!("'{}'", encoding));
13415                }
13416                DatabaseOption::Location(location) => {
13417                    self.write_keyword("LOCATION");
13418                    self.write(" = ");
13419                    self.write(&format!("'{}'", location));
13420                }
13421            }
13422        }
13423
13424        Ok(())
13425    }
13426
13427    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
13428        self.write_keyword("DROP DATABASE");
13429
13430        if dd.if_exists {
13431            self.write_space();
13432            self.write_keyword("IF EXISTS");
13433        }
13434
13435        self.write_space();
13436        self.generate_identifier(&dd.name)?;
13437
13438        if dd.sync {
13439            self.write_space();
13440            self.write_keyword("SYNC");
13441        }
13442
13443        Ok(())
13444    }
13445
13446    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
13447        self.write_keyword("CREATE");
13448
13449        if cf.or_alter {
13450            self.write_space();
13451            self.write_keyword("OR ALTER");
13452        } else if cf.or_replace {
13453            self.write_space();
13454            self.write_keyword("OR REPLACE");
13455        }
13456
13457        if cf.temporary {
13458            self.write_space();
13459            self.write_keyword("TEMPORARY");
13460        }
13461
13462        self.write_space();
13463        if cf.is_table_function {
13464            self.write_keyword("TABLE FUNCTION");
13465        } else {
13466            self.write_keyword("FUNCTION");
13467        }
13468
13469        if cf.if_not_exists {
13470            self.write_space();
13471            self.write_keyword("IF NOT EXISTS");
13472        }
13473
13474        self.write_space();
13475        self.generate_table(&cf.name)?;
13476        if cf.has_parens {
13477            let func_multiline = self.config.pretty
13478                && matches!(
13479                    self.config.dialect,
13480                    Some(crate::dialects::DialectType::TSQL)
13481                        | Some(crate::dialects::DialectType::Fabric)
13482                )
13483                && !cf.parameters.is_empty();
13484            if func_multiline {
13485                self.write("(\n");
13486                self.indent_level += 2;
13487                self.write_indent();
13488                self.generate_function_parameters(&cf.parameters)?;
13489                self.write("\n");
13490                self.indent_level -= 2;
13491                self.write(")");
13492            } else {
13493                self.write("(");
13494                self.generate_function_parameters(&cf.parameters)?;
13495                self.write(")");
13496            }
13497        }
13498
13499        // Output RETURNS clause (always comes first after parameters)
13500        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
13501        let use_multiline = self.config.pretty
13502            && matches!(
13503                self.config.dialect,
13504                Some(crate::dialects::DialectType::BigQuery)
13505                    | Some(crate::dialects::DialectType::TSQL)
13506                    | Some(crate::dialects::DialectType::Fabric)
13507            );
13508
13509        if cf.language_first {
13510            // LANGUAGE first, then SQL data access, then RETURNS
13511            if let Some(lang) = &cf.language {
13512                if use_multiline {
13513                    self.write_newline();
13514                } else {
13515                    self.write_space();
13516                }
13517                self.write_keyword("LANGUAGE");
13518                self.write_space();
13519                self.write(lang);
13520            }
13521
13522            // SQL data access comes after LANGUAGE in this case
13523            if let Some(sql_data) = &cf.sql_data_access {
13524                self.write_space();
13525                match sql_data {
13526                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13527                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13528                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13529                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13530                }
13531            }
13532
13533            if let Some(ref rtb) = cf.returns_table_body {
13534                if use_multiline {
13535                    self.write_newline();
13536                } else {
13537                    self.write_space();
13538                }
13539                self.write_keyword("RETURNS");
13540                self.write_space();
13541                self.write(rtb);
13542            } else if let Some(return_type) = &cf.return_type {
13543                if use_multiline {
13544                    self.write_newline();
13545                } else {
13546                    self.write_space();
13547                }
13548                self.write_keyword("RETURNS");
13549                self.write_space();
13550                self.generate_function_return_type(return_type)?;
13551            }
13552        } else {
13553            // RETURNS first (default)
13554            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
13555            let is_duckdb = matches!(
13556                self.config.dialect,
13557                Some(crate::dialects::DialectType::DuckDB)
13558            );
13559            if let Some(ref rtb) = cf.returns_table_body {
13560                if !(is_duckdb && rtb.is_empty()) {
13561                    if use_multiline {
13562                        self.write_newline();
13563                    } else {
13564                        self.write_space();
13565                    }
13566                    self.write_keyword("RETURNS");
13567                    self.write_space();
13568                    self.write(rtb);
13569                }
13570            } else if let Some(return_type) = &cf.return_type {
13571                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
13572                if !is_duckdb {
13573                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
13574                    if use_multiline {
13575                        self.write_newline();
13576                    } else {
13577                        self.write_space();
13578                    }
13579                    self.write_keyword("RETURNS");
13580                    self.write_space();
13581                    if is_table_return {
13582                        self.write_keyword("TABLE");
13583                    } else {
13584                        self.generate_function_return_type(return_type)?;
13585                    }
13586                }
13587            }
13588        }
13589
13590        // If we have property_order, use it to output properties in original order
13591        if !cf.property_order.is_empty() {
13592            // For BigQuery, OPTIONS must come before AS - reorder if needed
13593            let is_bigquery = matches!(
13594                self.config.dialect,
13595                Some(crate::dialects::DialectType::BigQuery)
13596            );
13597            let is_postgres = matches!(
13598                self.config.dialect,
13599                Some(crate::dialects::DialectType::PostgreSQL)
13600            );
13601            let property_order = if is_bigquery {
13602                // Move Options before As if both are present
13603                let mut reordered = Vec::new();
13604                let mut has_as = false;
13605                let mut has_options = false;
13606                for prop in &cf.property_order {
13607                    match prop {
13608                        FunctionPropertyKind::As => has_as = true,
13609                        FunctionPropertyKind::Options => has_options = true,
13610                        _ => {}
13611                    }
13612                }
13613                if has_as && has_options {
13614                    // Output all props except As and Options, then Options, then As
13615                    for prop in &cf.property_order {
13616                        if *prop != FunctionPropertyKind::As
13617                            && *prop != FunctionPropertyKind::Options
13618                        {
13619                            reordered.push(*prop);
13620                        }
13621                    }
13622                    reordered.push(FunctionPropertyKind::Options);
13623                    reordered.push(FunctionPropertyKind::As);
13624                    reordered
13625                } else {
13626                    cf.property_order.clone()
13627                }
13628            } else if is_postgres
13629                && cf.property_order.contains(&FunctionPropertyKind::As)
13630                && cf.property_order.contains(&FunctionPropertyKind::NullInput)
13631            {
13632                let mut reordered: Vec<_> = cf
13633                    .property_order
13634                    .iter()
13635                    .copied()
13636                    .filter(|prop| *prop != FunctionPropertyKind::As)
13637                    .collect();
13638                reordered.push(FunctionPropertyKind::As);
13639                reordered
13640            } else {
13641                cf.property_order.clone()
13642            };
13643
13644            for prop in &property_order {
13645                match prop {
13646                    FunctionPropertyKind::Set => {
13647                        self.generate_function_set_options(cf)?;
13648                    }
13649                    FunctionPropertyKind::As => {
13650                        self.generate_function_body(cf)?;
13651                    }
13652                    FunctionPropertyKind::Using => {
13653                        self.generate_function_using_resources(cf)?;
13654                    }
13655                    FunctionPropertyKind::Language => {
13656                        if !cf.language_first {
13657                            // Only output here if not already output above
13658                            if let Some(lang) = &cf.language {
13659                                // Only BigQuery uses multiline formatting
13660                                let use_multiline = self.config.pretty
13661                                    && matches!(
13662                                        self.config.dialect,
13663                                        Some(crate::dialects::DialectType::BigQuery)
13664                                    );
13665                                if use_multiline {
13666                                    self.write_newline();
13667                                } else {
13668                                    self.write_space();
13669                                }
13670                                self.write_keyword("LANGUAGE");
13671                                self.write_space();
13672                                self.write(lang);
13673                            }
13674                        }
13675                    }
13676                    FunctionPropertyKind::Determinism => {
13677                        self.generate_function_determinism(cf)?;
13678                    }
13679                    FunctionPropertyKind::NullInput => {
13680                        self.generate_function_null_input(cf)?;
13681                    }
13682                    FunctionPropertyKind::Security => {
13683                        self.generate_function_security(cf)?;
13684                    }
13685                    FunctionPropertyKind::SqlDataAccess => {
13686                        if !cf.language_first {
13687                            // Only output here if not already output above
13688                            self.generate_function_sql_data_access(cf)?;
13689                        }
13690                    }
13691                    FunctionPropertyKind::Options => {
13692                        if !cf.options.is_empty() {
13693                            self.write_space();
13694                            self.generate_options_clause(&cf.options)?;
13695                        }
13696                    }
13697                    FunctionPropertyKind::Environment => {
13698                        if !cf.environment.is_empty() {
13699                            self.write_space();
13700                            self.generate_environment_clause(&cf.environment)?;
13701                        }
13702                    }
13703                    FunctionPropertyKind::Handler => {
13704                        if let Some(ref h) = cf.handler {
13705                            self.write_space();
13706                            self.write_keyword("HANDLER");
13707                            if cf.handler_uses_eq {
13708                                self.write(" = ");
13709                            } else {
13710                                self.write_space();
13711                            }
13712                            self.write("'");
13713                            self.write(h);
13714                            self.write("'");
13715                        }
13716                    }
13717                    FunctionPropertyKind::RuntimeVersion => {
13718                        if let Some(ref runtime_version) = cf.runtime_version {
13719                            self.write_space();
13720                            self.write_keyword("RUNTIME_VERSION");
13721                            self.write("='");
13722                            self.write(runtime_version);
13723                            self.write("'");
13724                        }
13725                    }
13726                    FunctionPropertyKind::Packages => {
13727                        if let Some(ref packages) = cf.packages {
13728                            self.write_space();
13729                            self.write_keyword("PACKAGES");
13730                            self.write("=(");
13731                            for (i, package) in packages.iter().enumerate() {
13732                                if i > 0 {
13733                                    self.write(", ");
13734                                }
13735                                self.write("'");
13736                                self.write(package);
13737                                self.write("'");
13738                            }
13739                            self.write(")");
13740                        }
13741                    }
13742                    FunctionPropertyKind::ParameterStyle => {
13743                        if let Some(ref ps) = cf.parameter_style {
13744                            self.write_space();
13745                            self.write_keyword("PARAMETER STYLE");
13746                            self.write_space();
13747                            self.write_keyword(ps);
13748                        }
13749                    }
13750                }
13751            }
13752
13753            // Output OPTIONS if not tracked in property_order (legacy)
13754            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13755            {
13756                self.write_space();
13757                self.generate_options_clause(&cf.options)?;
13758            }
13759
13760            // Output ENVIRONMENT if not tracked in property_order (legacy)
13761            if !cf.environment.is_empty()
13762                && !cf
13763                    .property_order
13764                    .contains(&FunctionPropertyKind::Environment)
13765            {
13766                self.write_space();
13767                self.generate_environment_clause(&cf.environment)?;
13768            }
13769        } else {
13770            // Legacy behavior when property_order is empty
13771            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13772            if matches!(
13773                self.config.dialect,
13774                Some(crate::dialects::DialectType::BigQuery)
13775            ) {
13776                self.generate_function_determinism(cf)?;
13777            }
13778
13779            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13780            let use_multiline = self.config.pretty
13781                && matches!(
13782                    self.config.dialect,
13783                    Some(crate::dialects::DialectType::BigQuery)
13784                );
13785
13786            if !cf.language_first {
13787                if let Some(lang) = &cf.language {
13788                    if use_multiline {
13789                        self.write_newline();
13790                    } else {
13791                        self.write_space();
13792                    }
13793                    self.write_keyword("LANGUAGE");
13794                    self.write_space();
13795                    self.write(lang);
13796                }
13797
13798                // SQL data access characteristic comes after LANGUAGE
13799                self.generate_function_sql_data_access(cf)?;
13800            }
13801
13802            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13803            if !matches!(
13804                self.config.dialect,
13805                Some(crate::dialects::DialectType::BigQuery)
13806            ) {
13807                self.generate_function_determinism(cf)?;
13808            }
13809
13810            self.generate_function_null_input(cf)?;
13811            self.generate_function_security(cf)?;
13812            self.generate_function_set_options(cf)?;
13813
13814            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13815            if !cf.options.is_empty() {
13816                self.write_space();
13817                self.generate_options_clause(&cf.options)?;
13818            }
13819
13820            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13821            if !cf.environment.is_empty() {
13822                self.write_space();
13823                self.generate_environment_clause(&cf.environment)?;
13824            }
13825
13826            if let Some(ref h) = cf.handler {
13827                self.write_space();
13828                self.write_keyword("HANDLER");
13829                if cf.handler_uses_eq {
13830                    self.write(" = ");
13831                } else {
13832                    self.write_space();
13833                }
13834                self.write("'");
13835                self.write(h);
13836                self.write("'");
13837            }
13838
13839            if let Some(ref runtime_version) = cf.runtime_version {
13840                self.write_space();
13841                self.write_keyword("RUNTIME_VERSION");
13842                self.write("='");
13843                self.write(runtime_version);
13844                self.write("'");
13845            }
13846
13847            if let Some(ref packages) = cf.packages {
13848                self.write_space();
13849                self.write_keyword("PACKAGES");
13850                self.write("=(");
13851                for (i, package) in packages.iter().enumerate() {
13852                    if i > 0 {
13853                        self.write(", ");
13854                    }
13855                    self.write("'");
13856                    self.write(package);
13857                    self.write("'");
13858                }
13859                self.write(")");
13860            }
13861
13862            self.generate_function_body(cf)?;
13863            self.generate_function_using_resources(cf)?;
13864        }
13865
13866        Ok(())
13867    }
13868
13869    fn generate_function_return_type(&mut self, return_type: &DataType) -> Result<()> {
13870        if matches!(
13871            self.config.dialect,
13872            Some(crate::dialects::DialectType::PostgreSQL)
13873        ) {
13874            if let DataType::Custom { name } = return_type {
13875                if name.eq_ignore_ascii_case("integer") {
13876                    self.write_keyword("INT");
13877                    return Ok(());
13878                }
13879            }
13880        }
13881
13882        self.generate_data_type(return_type)
13883    }
13884
13885    /// Generate SET options for CREATE FUNCTION
13886    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13887        for opt in &cf.set_options {
13888            self.write_space();
13889            self.write_keyword("SET");
13890            self.write_space();
13891            self.write(&opt.name);
13892            match &opt.value {
13893                FunctionSetValue::Value { value, use_to } => {
13894                    if *use_to {
13895                        self.write(" TO ");
13896                    } else {
13897                        self.write(" = ");
13898                    }
13899                    self.write(value);
13900                }
13901                FunctionSetValue::FromCurrent => {
13902                    self.write_space();
13903                    self.write_keyword("FROM CURRENT");
13904                }
13905            }
13906        }
13907        Ok(())
13908    }
13909
13910    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13911        if cf.using_resources.is_empty() {
13912            return Ok(());
13913        }
13914
13915        self.write_space();
13916        self.write_keyword("USING");
13917        for resource in &cf.using_resources {
13918            self.write_space();
13919            self.write_keyword(&resource.kind);
13920            self.write_space();
13921            self.generate_string_literal(&resource.uri)?;
13922        }
13923        Ok(())
13924    }
13925
13926    /// Generate function body (AS clause)
13927    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
13928        if let Some(body) = &cf.body {
13929            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
13930            self.write_space();
13931            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
13932            let use_multiline = self.config.pretty
13933                && matches!(
13934                    self.config.dialect,
13935                    Some(crate::dialects::DialectType::BigQuery)
13936                );
13937            match body {
13938                FunctionBody::Block(block) => {
13939                    self.write_keyword("AS");
13940                    if matches!(
13941                        self.config.dialect,
13942                        Some(crate::dialects::DialectType::TSQL)
13943                    ) {
13944                        self.write(" BEGIN ");
13945                        self.write(block);
13946                        self.write(" END");
13947                    } else if matches!(
13948                        self.config.dialect,
13949                        Some(crate::dialects::DialectType::PostgreSQL)
13950                    ) {
13951                        self.write(" $$");
13952                        self.write(block);
13953                        self.write("$$");
13954                    } else {
13955                        // Escape content for single-quoted output
13956                        let escaped = self.escape_block_for_single_quote(block);
13957                        // In BigQuery pretty mode, body content goes on new line
13958                        if use_multiline {
13959                            self.write_newline();
13960                        } else {
13961                            self.write(" ");
13962                        }
13963                        self.write("'");
13964                        self.write(&escaped);
13965                        self.write("'");
13966                    }
13967                }
13968                FunctionBody::StringLiteral(s) => {
13969                    self.write_keyword("AS");
13970                    // In BigQuery pretty mode, body content goes on new line
13971                    if use_multiline {
13972                        self.write_newline();
13973                    } else {
13974                        self.write(" ");
13975                    }
13976                    self.generate_string_literal(s)?;
13977                }
13978                FunctionBody::Expression(expr) => {
13979                    self.write_keyword("AS");
13980                    self.write_space();
13981                    self.generate_expression(expr)?;
13982                }
13983                FunctionBody::External(name) => {
13984                    self.write_keyword("EXTERNAL NAME");
13985                    self.write(" '");
13986                    self.write(name);
13987                    self.write("'");
13988                }
13989                FunctionBody::Return(expr) => {
13990                    if matches!(
13991                        self.config.dialect,
13992                        Some(crate::dialects::DialectType::DuckDB)
13993                    ) {
13994                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
13995                        self.write_keyword("AS");
13996                        self.write_space();
13997                        // Check both returns_table_body marker and return_type = Custom "TABLE"
13998                        let is_table_return = cf.returns_table_body.is_some()
13999                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
14000                        if is_table_return {
14001                            self.write_keyword("TABLE");
14002                            self.write_space();
14003                        }
14004                        self.generate_expression(expr)?;
14005                    } else {
14006                        if self.config.create_function_return_as {
14007                            self.write_keyword("AS");
14008                            // TSQL pretty: newline between AS and RETURN
14009                            if self.config.pretty
14010                                && matches!(
14011                                    self.config.dialect,
14012                                    Some(crate::dialects::DialectType::TSQL)
14013                                        | Some(crate::dialects::DialectType::Fabric)
14014                                )
14015                            {
14016                                self.write_newline();
14017                            } else {
14018                                self.write_space();
14019                            }
14020                        }
14021                        self.write_keyword("RETURN");
14022                        self.write_space();
14023                        self.generate_expression(expr)?;
14024                    }
14025                }
14026                FunctionBody::Statements(stmts) => {
14027                    self.write_keyword("AS");
14028                    self.write(" BEGIN ");
14029                    for (i, stmt) in stmts.iter().enumerate() {
14030                        if i > 0 {
14031                            self.write(" ");
14032                        }
14033                        self.generate_expression(stmt)?;
14034                        self.write(";");
14035                    }
14036                    self.write(" END");
14037                }
14038                FunctionBody::RawBlock(text) => {
14039                    self.write_newline();
14040                    self.write(text);
14041                }
14042                FunctionBody::DollarQuoted { content, tag } => {
14043                    self.write_keyword("AS");
14044                    self.write(" ");
14045                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14046                    let supports_dollar_quoting = matches!(
14047                        self.config.dialect,
14048                        Some(crate::dialects::DialectType::PostgreSQL)
14049                            | Some(crate::dialects::DialectType::Databricks)
14050                            | Some(crate::dialects::DialectType::Redshift)
14051                            | Some(crate::dialects::DialectType::DuckDB)
14052                    );
14053                    if supports_dollar_quoting {
14054                        // Output in dollar-quoted format
14055                        self.write("$");
14056                        if let Some(t) = tag {
14057                            self.write(t);
14058                        }
14059                        self.write("$");
14060                        self.write(content);
14061                        self.write("$");
14062                        if let Some(t) = tag {
14063                            self.write(t);
14064                        }
14065                        self.write("$");
14066                    } else {
14067                        // Convert to single-quoted string for other dialects
14068                        let escaped = self.escape_block_for_single_quote(content);
14069                        self.write("'");
14070                        self.write(&escaped);
14071                        self.write("'");
14072                    }
14073                }
14074            }
14075        }
14076        Ok(())
14077    }
14078
14079    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
14080    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
14081        if let Some(det) = cf.deterministic {
14082            self.write_space();
14083            if matches!(
14084                self.config.dialect,
14085                Some(crate::dialects::DialectType::BigQuery)
14086            ) {
14087                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
14088                if det {
14089                    self.write_keyword("DETERMINISTIC");
14090                } else {
14091                    self.write_keyword("NOT DETERMINISTIC");
14092                }
14093            } else {
14094                // PostgreSQL and others use IMMUTABLE/VOLATILE
14095                if det {
14096                    self.write_keyword("IMMUTABLE");
14097                } else {
14098                    self.write_keyword("VOLATILE");
14099                }
14100            }
14101        }
14102        Ok(())
14103    }
14104
14105    /// Generate null input handling clause
14106    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
14107        if let Some(returns_null) = cf.returns_null_on_null_input {
14108            self.write_space();
14109            if returns_null {
14110                if cf.strict {
14111                    self.write_keyword("STRICT");
14112                } else {
14113                    self.write_keyword("RETURNS NULL ON NULL INPUT");
14114                }
14115            } else {
14116                self.write_keyword("CALLED ON NULL INPUT");
14117            }
14118        }
14119        Ok(())
14120    }
14121
14122    /// Generate security clause
14123    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
14124        if let Some(security) = &cf.security {
14125            self.write_space();
14126            // MySQL uses SQL SECURITY prefix
14127            if matches!(
14128                self.config.dialect,
14129                Some(crate::dialects::DialectType::MySQL)
14130            ) {
14131                self.write_keyword("SQL SECURITY");
14132            } else {
14133                self.write_keyword("SECURITY");
14134            }
14135            self.write_space();
14136            match security {
14137                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14138                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14139                FunctionSecurity::None => self.write_keyword("NONE"),
14140            }
14141        }
14142        Ok(())
14143    }
14144
14145    /// Generate SQL data access clause
14146    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
14147        if let Some(sql_data) = &cf.sql_data_access {
14148            self.write_space();
14149            match sql_data {
14150                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
14151                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
14152                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
14153                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
14154            }
14155        }
14156        Ok(())
14157    }
14158
14159    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
14160        for (i, param) in params.iter().enumerate() {
14161            if i > 0 {
14162                self.write(", ");
14163            }
14164
14165            if let Some(mode) = &param.mode {
14166                if let Some(text) = &param.mode_text {
14167                    self.write(text);
14168                } else {
14169                    match mode {
14170                        ParameterMode::In => self.write_keyword("IN"),
14171                        ParameterMode::Out => self.write_keyword("OUT"),
14172                        ParameterMode::InOut => self.write_keyword("INOUT"),
14173                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
14174                    }
14175                }
14176                self.write_space();
14177            }
14178
14179            if let Some(name) = &param.name {
14180                self.generate_identifier(name)?;
14181                // Skip space and type for empty Custom types (e.g., DuckDB macros)
14182                let skip_type =
14183                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
14184                if !skip_type {
14185                    self.write_space();
14186                    self.generate_data_type(&param.data_type)?;
14187                }
14188            } else {
14189                self.generate_data_type(&param.data_type)?;
14190            }
14191
14192            if let Some(default) = &param.default {
14193                if self.config.parameter_default_equals {
14194                    self.write(" = ");
14195                } else {
14196                    self.write(" DEFAULT ");
14197                }
14198                self.generate_expression(default)?;
14199            }
14200        }
14201
14202        Ok(())
14203    }
14204
14205    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
14206        self.write_keyword("DROP FUNCTION");
14207
14208        if df.if_exists {
14209            self.write_space();
14210            self.write_keyword("IF EXISTS");
14211        }
14212
14213        self.write_space();
14214        self.generate_table(&df.name)?;
14215
14216        if let Some(params) = &df.parameters {
14217            self.write(" (");
14218            for (i, dt) in params.iter().enumerate() {
14219                if i > 0 {
14220                    self.write(", ");
14221                }
14222                self.generate_data_type(dt)?;
14223            }
14224            self.write(")");
14225        }
14226
14227        if df.cascade {
14228            self.write_space();
14229            self.write_keyword("CASCADE");
14230        }
14231
14232        Ok(())
14233    }
14234
14235    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
14236        self.write_keyword("CREATE");
14237
14238        if cp.or_alter {
14239            self.write_space();
14240            self.write_keyword("OR ALTER");
14241        } else if cp.or_replace {
14242            self.write_space();
14243            self.write_keyword("OR REPLACE");
14244        }
14245
14246        self.write_space();
14247        if cp.use_proc_keyword {
14248            self.write_keyword("PROC");
14249        } else {
14250            self.write_keyword("PROCEDURE");
14251        }
14252
14253        if cp.if_not_exists {
14254            self.write_space();
14255            self.write_keyword("IF NOT EXISTS");
14256        }
14257
14258        self.write_space();
14259        self.generate_table(&cp.name)?;
14260        if cp.has_parens {
14261            self.write("(");
14262            self.generate_function_parameters(&cp.parameters)?;
14263            self.write(")");
14264        } else if !cp.parameters.is_empty() {
14265            // TSQL: unparenthesized parameters
14266            self.write_space();
14267            self.generate_function_parameters(&cp.parameters)?;
14268        }
14269
14270        // RETURNS clause (Snowflake)
14271        if let Some(return_type) = &cp.return_type {
14272            self.write_space();
14273            self.write_keyword("RETURNS");
14274            self.write_space();
14275            self.generate_data_type(return_type)?;
14276        }
14277
14278        // EXECUTE AS clause (Snowflake)
14279        if let Some(execute_as) = &cp.execute_as {
14280            self.write_space();
14281            self.write_keyword("EXECUTE AS");
14282            self.write_space();
14283            self.write_keyword(execute_as);
14284        }
14285
14286        if let Some(lang) = &cp.language {
14287            self.write_space();
14288            self.write_keyword("LANGUAGE");
14289            self.write_space();
14290            self.write(lang);
14291        }
14292
14293        if let Some(security) = &cp.security {
14294            self.write_space();
14295            self.write_keyword("SECURITY");
14296            self.write_space();
14297            match security {
14298                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14299                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14300                FunctionSecurity::None => self.write_keyword("NONE"),
14301            }
14302        }
14303
14304        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
14305        if !cp.with_options.is_empty() {
14306            self.write_space();
14307            self.write_keyword("WITH");
14308            self.write_space();
14309            for (i, opt) in cp.with_options.iter().enumerate() {
14310                if i > 0 {
14311                    self.write(", ");
14312                }
14313                self.write(opt);
14314            }
14315        }
14316
14317        if let Some(body) = &cp.body {
14318            self.write_space();
14319            match body {
14320                FunctionBody::Block(block) => {
14321                    self.write_keyword("AS");
14322                    if matches!(
14323                        self.config.dialect,
14324                        Some(crate::dialects::DialectType::TSQL)
14325                    ) {
14326                        self.write(" BEGIN ");
14327                        self.write(block);
14328                        self.write(" END");
14329                    } else if matches!(
14330                        self.config.dialect,
14331                        Some(crate::dialects::DialectType::PostgreSQL)
14332                    ) {
14333                        self.write(" $$");
14334                        self.write(block);
14335                        self.write("$$");
14336                    } else {
14337                        // Escape content for single-quoted output
14338                        let escaped = self.escape_block_for_single_quote(block);
14339                        self.write(" '");
14340                        self.write(&escaped);
14341                        self.write("'");
14342                    }
14343                }
14344                FunctionBody::StringLiteral(s) => {
14345                    self.write_keyword("AS");
14346                    self.write_space();
14347                    self.generate_string_literal(s)?;
14348                }
14349                FunctionBody::Expression(expr) => {
14350                    self.write_keyword("AS");
14351                    self.write_space();
14352                    self.generate_expression(expr)?;
14353                }
14354                FunctionBody::External(name) => {
14355                    self.write_keyword("EXTERNAL NAME");
14356                    self.write(" '");
14357                    self.write(name);
14358                    self.write("'");
14359                }
14360                FunctionBody::Return(expr) => {
14361                    self.write_keyword("RETURN");
14362                    self.write_space();
14363                    self.generate_expression(expr)?;
14364                }
14365                FunctionBody::Statements(stmts) => {
14366                    self.write_keyword("AS");
14367                    self.write(" BEGIN ");
14368                    for (i, stmt) in stmts.iter().enumerate() {
14369                        if i > 0 {
14370                            self.write(" ");
14371                        }
14372                        self.generate_expression(stmt)?;
14373                        self.write(";");
14374                    }
14375                    self.write(" END");
14376                }
14377                FunctionBody::RawBlock(text) => {
14378                    self.write_newline();
14379                    self.write(text);
14380                }
14381                FunctionBody::DollarQuoted { content, tag } => {
14382                    self.write_keyword("AS");
14383                    self.write(" ");
14384                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14385                    let supports_dollar_quoting = matches!(
14386                        self.config.dialect,
14387                        Some(crate::dialects::DialectType::PostgreSQL)
14388                            | Some(crate::dialects::DialectType::Databricks)
14389                            | Some(crate::dialects::DialectType::Redshift)
14390                            | Some(crate::dialects::DialectType::DuckDB)
14391                    );
14392                    if supports_dollar_quoting {
14393                        // Output in dollar-quoted format
14394                        self.write("$");
14395                        if let Some(t) = tag {
14396                            self.write(t);
14397                        }
14398                        self.write("$");
14399                        self.write(content);
14400                        self.write("$");
14401                        if let Some(t) = tag {
14402                            self.write(t);
14403                        }
14404                        self.write("$");
14405                    } else {
14406                        // Convert to single-quoted string for other dialects
14407                        let escaped = self.escape_block_for_single_quote(content);
14408                        self.write("'");
14409                        self.write(&escaped);
14410                        self.write("'");
14411                    }
14412                }
14413            }
14414        }
14415
14416        Ok(())
14417    }
14418
14419    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
14420        self.write_keyword("DROP PROCEDURE");
14421
14422        if dp.if_exists {
14423            self.write_space();
14424            self.write_keyword("IF EXISTS");
14425        }
14426
14427        self.write_space();
14428        self.generate_table(&dp.name)?;
14429
14430        if let Some(params) = &dp.parameters {
14431            self.write(" (");
14432            for (i, dt) in params.iter().enumerate() {
14433                if i > 0 {
14434                    self.write(", ");
14435                }
14436                self.generate_data_type(dt)?;
14437            }
14438            self.write(")");
14439        }
14440
14441        if dp.cascade {
14442            self.write_space();
14443            self.write_keyword("CASCADE");
14444        }
14445
14446        Ok(())
14447    }
14448
14449    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
14450        self.write_keyword("CREATE");
14451
14452        if cs.or_replace {
14453            self.write_space();
14454            self.write_keyword("OR REPLACE");
14455        }
14456
14457        if cs.temporary {
14458            self.write_space();
14459            self.write_keyword("TEMPORARY");
14460        }
14461
14462        self.write_space();
14463        self.write_keyword("SEQUENCE");
14464
14465        if cs.if_not_exists {
14466            self.write_space();
14467            self.write_keyword("IF NOT EXISTS");
14468        }
14469
14470        self.write_space();
14471        self.generate_table(&cs.name)?;
14472
14473        // Output AS <type> if present
14474        if let Some(as_type) = &cs.as_type {
14475            self.write_space();
14476            self.write_keyword("AS");
14477            self.write_space();
14478            self.generate_data_type(as_type)?;
14479        }
14480
14481        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
14482        if let Some(comment) = &cs.comment {
14483            self.write_space();
14484            self.write_keyword("COMMENT");
14485            self.write("=");
14486            self.generate_string_literal(comment)?;
14487        }
14488
14489        // If property_order is available, use it to preserve original order
14490        if !cs.property_order.is_empty() {
14491            for prop in &cs.property_order {
14492                match prop {
14493                    SeqPropKind::Start => {
14494                        if let Some(start) = cs.start {
14495                            self.write_space();
14496                            self.write_keyword("START WITH");
14497                            self.write(&format!(" {}", start));
14498                        }
14499                    }
14500                    SeqPropKind::Increment => {
14501                        if let Some(inc) = cs.increment {
14502                            self.write_space();
14503                            self.write_keyword("INCREMENT BY");
14504                            self.write(&format!(" {}", inc));
14505                        }
14506                    }
14507                    SeqPropKind::Minvalue => {
14508                        if let Some(min) = &cs.minvalue {
14509                            self.write_space();
14510                            match min {
14511                                SequenceBound::Value(v) => {
14512                                    self.write_keyword("MINVALUE");
14513                                    self.write(&format!(" {}", v));
14514                                }
14515                                SequenceBound::None => {
14516                                    self.write_keyword("NO MINVALUE");
14517                                }
14518                            }
14519                        }
14520                    }
14521                    SeqPropKind::Maxvalue => {
14522                        if let Some(max) = &cs.maxvalue {
14523                            self.write_space();
14524                            match max {
14525                                SequenceBound::Value(v) => {
14526                                    self.write_keyword("MAXVALUE");
14527                                    self.write(&format!(" {}", v));
14528                                }
14529                                SequenceBound::None => {
14530                                    self.write_keyword("NO MAXVALUE");
14531                                }
14532                            }
14533                        }
14534                    }
14535                    SeqPropKind::Cache => {
14536                        if let Some(cache) = cs.cache {
14537                            self.write_space();
14538                            self.write_keyword("CACHE");
14539                            self.write(&format!(" {}", cache));
14540                        }
14541                    }
14542                    SeqPropKind::NoCache => {
14543                        self.write_space();
14544                        self.write_keyword("NO CACHE");
14545                    }
14546                    SeqPropKind::NoCacheWord => {
14547                        self.write_space();
14548                        self.write_keyword("NOCACHE");
14549                    }
14550                    SeqPropKind::Cycle => {
14551                        self.write_space();
14552                        self.write_keyword("CYCLE");
14553                    }
14554                    SeqPropKind::NoCycle => {
14555                        self.write_space();
14556                        self.write_keyword("NO CYCLE");
14557                    }
14558                    SeqPropKind::NoCycleWord => {
14559                        self.write_space();
14560                        self.write_keyword("NOCYCLE");
14561                    }
14562                    SeqPropKind::OwnedBy => {
14563                        // Skip OWNED BY NONE (it's a no-op)
14564                        if !cs.owned_by_none {
14565                            if let Some(owned) = &cs.owned_by {
14566                                self.write_space();
14567                                self.write_keyword("OWNED BY");
14568                                self.write_space();
14569                                self.generate_table(owned)?;
14570                            }
14571                        }
14572                    }
14573                    SeqPropKind::Order => {
14574                        self.write_space();
14575                        self.write_keyword("ORDER");
14576                    }
14577                    SeqPropKind::NoOrder => {
14578                        self.write_space();
14579                        self.write_keyword("NOORDER");
14580                    }
14581                    SeqPropKind::Comment => {
14582                        // COMMENT is output above, before property_order iteration
14583                    }
14584                    SeqPropKind::Sharing => {
14585                        if let Some(val) = &cs.sharing {
14586                            self.write_space();
14587                            self.write(&format!("SHARING={}", val));
14588                        }
14589                    }
14590                    SeqPropKind::Keep => {
14591                        self.write_space();
14592                        self.write_keyword("KEEP");
14593                    }
14594                    SeqPropKind::NoKeep => {
14595                        self.write_space();
14596                        self.write_keyword("NOKEEP");
14597                    }
14598                    SeqPropKind::Scale => {
14599                        self.write_space();
14600                        self.write_keyword("SCALE");
14601                        if let Some(modifier) = &cs.scale_modifier {
14602                            if !modifier.is_empty() {
14603                                self.write_space();
14604                                self.write_keyword(modifier);
14605                            }
14606                        }
14607                    }
14608                    SeqPropKind::NoScale => {
14609                        self.write_space();
14610                        self.write_keyword("NOSCALE");
14611                    }
14612                    SeqPropKind::Shard => {
14613                        self.write_space();
14614                        self.write_keyword("SHARD");
14615                        if let Some(modifier) = &cs.shard_modifier {
14616                            if !modifier.is_empty() {
14617                                self.write_space();
14618                                self.write_keyword(modifier);
14619                            }
14620                        }
14621                    }
14622                    SeqPropKind::NoShard => {
14623                        self.write_space();
14624                        self.write_keyword("NOSHARD");
14625                    }
14626                    SeqPropKind::Session => {
14627                        self.write_space();
14628                        self.write_keyword("SESSION");
14629                    }
14630                    SeqPropKind::Global => {
14631                        self.write_space();
14632                        self.write_keyword("GLOBAL");
14633                    }
14634                    SeqPropKind::NoMinvalueWord => {
14635                        self.write_space();
14636                        self.write_keyword("NOMINVALUE");
14637                    }
14638                    SeqPropKind::NoMaxvalueWord => {
14639                        self.write_space();
14640                        self.write_keyword("NOMAXVALUE");
14641                    }
14642                }
14643            }
14644        } else {
14645            // Fallback: default order for backwards compatibility
14646            if let Some(inc) = cs.increment {
14647                self.write_space();
14648                self.write_keyword("INCREMENT BY");
14649                self.write(&format!(" {}", inc));
14650            }
14651
14652            if let Some(min) = &cs.minvalue {
14653                self.write_space();
14654                match min {
14655                    SequenceBound::Value(v) => {
14656                        self.write_keyword("MINVALUE");
14657                        self.write(&format!(" {}", v));
14658                    }
14659                    SequenceBound::None => {
14660                        self.write_keyword("NO MINVALUE");
14661                    }
14662                }
14663            }
14664
14665            if let Some(max) = &cs.maxvalue {
14666                self.write_space();
14667                match max {
14668                    SequenceBound::Value(v) => {
14669                        self.write_keyword("MAXVALUE");
14670                        self.write(&format!(" {}", v));
14671                    }
14672                    SequenceBound::None => {
14673                        self.write_keyword("NO MAXVALUE");
14674                    }
14675                }
14676            }
14677
14678            if let Some(start) = cs.start {
14679                self.write_space();
14680                self.write_keyword("START WITH");
14681                self.write(&format!(" {}", start));
14682            }
14683
14684            if let Some(cache) = cs.cache {
14685                self.write_space();
14686                self.write_keyword("CACHE");
14687                self.write(&format!(" {}", cache));
14688            }
14689
14690            if cs.cycle {
14691                self.write_space();
14692                self.write_keyword("CYCLE");
14693            }
14694
14695            if let Some(owned) = &cs.owned_by {
14696                self.write_space();
14697                self.write_keyword("OWNED BY");
14698                self.write_space();
14699                self.generate_table(owned)?;
14700            }
14701        }
14702
14703        Ok(())
14704    }
14705
14706    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
14707        self.write_keyword("DROP SEQUENCE");
14708
14709        if ds.if_exists {
14710            self.write_space();
14711            self.write_keyword("IF EXISTS");
14712        }
14713
14714        self.write_space();
14715        self.generate_table(&ds.name)?;
14716
14717        if ds.cascade {
14718            self.write_space();
14719            self.write_keyword("CASCADE");
14720        }
14721
14722        Ok(())
14723    }
14724
14725    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
14726        self.write_keyword("ALTER SEQUENCE");
14727
14728        if als.if_exists {
14729            self.write_space();
14730            self.write_keyword("IF EXISTS");
14731        }
14732
14733        self.write_space();
14734        self.generate_table(&als.name)?;
14735
14736        if let Some(inc) = als.increment {
14737            self.write_space();
14738            self.write_keyword("INCREMENT BY");
14739            self.write(&format!(" {}", inc));
14740        }
14741
14742        if let Some(min) = &als.minvalue {
14743            self.write_space();
14744            match min {
14745                SequenceBound::Value(v) => {
14746                    self.write_keyword("MINVALUE");
14747                    self.write(&format!(" {}", v));
14748                }
14749                SequenceBound::None => {
14750                    self.write_keyword("NO MINVALUE");
14751                }
14752            }
14753        }
14754
14755        if let Some(max) = &als.maxvalue {
14756            self.write_space();
14757            match max {
14758                SequenceBound::Value(v) => {
14759                    self.write_keyword("MAXVALUE");
14760                    self.write(&format!(" {}", v));
14761                }
14762                SequenceBound::None => {
14763                    self.write_keyword("NO MAXVALUE");
14764                }
14765            }
14766        }
14767
14768        if let Some(start) = als.start {
14769            self.write_space();
14770            self.write_keyword("START WITH");
14771            self.write(&format!(" {}", start));
14772        }
14773
14774        if let Some(restart) = &als.restart {
14775            self.write_space();
14776            self.write_keyword("RESTART");
14777            if let Some(val) = restart {
14778                self.write_keyword(" WITH");
14779                self.write(&format!(" {}", val));
14780            }
14781        }
14782
14783        if let Some(cache) = als.cache {
14784            self.write_space();
14785            self.write_keyword("CACHE");
14786            self.write(&format!(" {}", cache));
14787        }
14788
14789        if let Some(cycle) = als.cycle {
14790            self.write_space();
14791            if cycle {
14792                self.write_keyword("CYCLE");
14793            } else {
14794                self.write_keyword("NO CYCLE");
14795            }
14796        }
14797
14798        if let Some(owned) = &als.owned_by {
14799            self.write_space();
14800            self.write_keyword("OWNED BY");
14801            self.write_space();
14802            if let Some(table) = owned {
14803                self.generate_table(table)?;
14804            } else {
14805                self.write_keyword("NONE");
14806            }
14807        }
14808
14809        Ok(())
14810    }
14811
14812    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14813        self.write_keyword("CREATE");
14814
14815        if ct.or_alter {
14816            self.write_space();
14817            self.write_keyword("OR ALTER");
14818        } else if ct.or_replace {
14819            self.write_space();
14820            self.write_keyword("OR REPLACE");
14821        }
14822
14823        if ct.constraint {
14824            self.write_space();
14825            self.write_keyword("CONSTRAINT");
14826        }
14827
14828        self.write_space();
14829        self.write_keyword("TRIGGER");
14830        self.write_space();
14831        self.generate_identifier(&ct.name)?;
14832
14833        self.write_space();
14834        match ct.timing {
14835            TriggerTiming::Before => self.write_keyword("BEFORE"),
14836            TriggerTiming::After => self.write_keyword("AFTER"),
14837            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14838        }
14839
14840        // Events
14841        for (i, event) in ct.events.iter().enumerate() {
14842            if i > 0 {
14843                self.write_keyword(" OR");
14844            }
14845            self.write_space();
14846            match event {
14847                TriggerEvent::Insert => self.write_keyword("INSERT"),
14848                TriggerEvent::Update(cols) => {
14849                    self.write_keyword("UPDATE");
14850                    if let Some(cols) = cols {
14851                        self.write_space();
14852                        self.write_keyword("OF");
14853                        for (j, col) in cols.iter().enumerate() {
14854                            if j > 0 {
14855                                self.write(",");
14856                            }
14857                            self.write_space();
14858                            self.generate_identifier(col)?;
14859                        }
14860                    }
14861                }
14862                TriggerEvent::Delete => self.write_keyword("DELETE"),
14863                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14864            }
14865        }
14866
14867        self.write_space();
14868        self.write_keyword("ON");
14869        self.write_space();
14870        self.generate_table(&ct.table)?;
14871
14872        // Referencing clause
14873        if let Some(ref_clause) = &ct.referencing {
14874            self.write_space();
14875            self.write_keyword("REFERENCING");
14876            if let Some(old_table) = &ref_clause.old_table {
14877                self.write_space();
14878                self.write_keyword("OLD TABLE AS");
14879                self.write_space();
14880                self.generate_identifier(old_table)?;
14881            }
14882            if let Some(new_table) = &ref_clause.new_table {
14883                self.write_space();
14884                self.write_keyword("NEW TABLE AS");
14885                self.write_space();
14886                self.generate_identifier(new_table)?;
14887            }
14888            if let Some(old_row) = &ref_clause.old_row {
14889                self.write_space();
14890                self.write_keyword("OLD ROW AS");
14891                self.write_space();
14892                self.generate_identifier(old_row)?;
14893            }
14894            if let Some(new_row) = &ref_clause.new_row {
14895                self.write_space();
14896                self.write_keyword("NEW ROW AS");
14897                self.write_space();
14898                self.generate_identifier(new_row)?;
14899            }
14900        }
14901
14902        // Deferrable options for constraint triggers (must come before FOR EACH)
14903        if let Some(deferrable) = ct.deferrable {
14904            self.write_space();
14905            if deferrable {
14906                self.write_keyword("DEFERRABLE");
14907            } else {
14908                self.write_keyword("NOT DEFERRABLE");
14909            }
14910        }
14911
14912        if let Some(initially) = ct.initially_deferred {
14913            self.write_space();
14914            self.write_keyword("INITIALLY");
14915            self.write_space();
14916            if initially {
14917                self.write_keyword("DEFERRED");
14918            } else {
14919                self.write_keyword("IMMEDIATE");
14920            }
14921        }
14922
14923        if let Some(for_each) = ct.for_each {
14924            self.write_space();
14925            self.write_keyword("FOR EACH");
14926            self.write_space();
14927            match for_each {
14928                TriggerForEach::Row => self.write_keyword("ROW"),
14929                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
14930            }
14931        }
14932
14933        // When clause
14934        if let Some(when) = &ct.when {
14935            self.write_space();
14936            self.write_keyword("WHEN");
14937            if ct.when_paren {
14938                self.write(" (");
14939                self.generate_expression(when)?;
14940                self.write(")");
14941            } else {
14942                self.write_space();
14943                self.generate_expression(when)?;
14944            }
14945        }
14946
14947        // Body
14948        self.write_space();
14949        match &ct.body {
14950            TriggerBody::Execute { function, args } => {
14951                self.write_keyword("EXECUTE FUNCTION");
14952                self.write_space();
14953                self.generate_table(function)?;
14954                self.write("(");
14955                for (i, arg) in args.iter().enumerate() {
14956                    if i > 0 {
14957                        self.write(", ");
14958                    }
14959                    self.generate_expression(arg)?;
14960                }
14961                self.write(")");
14962            }
14963            TriggerBody::Block(block) => {
14964                self.write_keyword("BEGIN");
14965                self.write_space();
14966                self.write(block);
14967                self.write_space();
14968                self.write_keyword("END");
14969            }
14970        }
14971
14972        Ok(())
14973    }
14974
14975    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
14976        self.write_keyword("DROP TRIGGER");
14977
14978        if dt.if_exists {
14979            self.write_space();
14980            self.write_keyword("IF EXISTS");
14981        }
14982
14983        self.write_space();
14984        self.generate_identifier(&dt.name)?;
14985
14986        if let Some(table) = &dt.table {
14987            self.write_space();
14988            self.write_keyword("ON");
14989            self.write_space();
14990            self.generate_table(table)?;
14991        }
14992
14993        if dt.cascade {
14994            self.write_space();
14995            self.write_keyword("CASCADE");
14996        }
14997
14998        Ok(())
14999    }
15000
15001    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
15002        self.write_keyword("CREATE TYPE");
15003
15004        if ct.if_not_exists {
15005            self.write_space();
15006            self.write_keyword("IF NOT EXISTS");
15007        }
15008
15009        self.write_space();
15010        self.generate_table(&ct.name)?;
15011
15012        if let TypeDefinition::Base {
15013            input,
15014            output,
15015            internallength,
15016        } = &ct.definition
15017        {
15018            if input.is_empty() && output.is_empty() && internallength.is_none() {
15019                return Ok(());
15020            }
15021        }
15022
15023        self.write_space();
15024        self.write_keyword("AS");
15025        self.write_space();
15026
15027        match &ct.definition {
15028            TypeDefinition::Enum(values) => {
15029                self.write_keyword("ENUM");
15030                self.write(" (");
15031                for (i, val) in values.iter().enumerate() {
15032                    if i > 0 {
15033                        self.write(", ");
15034                    }
15035                    self.write(&format!("'{}'", val));
15036                }
15037                self.write(")");
15038            }
15039            TypeDefinition::Composite(attrs) => {
15040                self.write("(");
15041                for (i, attr) in attrs.iter().enumerate() {
15042                    if i > 0 {
15043                        self.write(", ");
15044                    }
15045                    self.generate_identifier(&attr.name)?;
15046                    self.write_space();
15047                    self.generate_data_type(&attr.data_type)?;
15048                    if let Some(collate) = &attr.collate {
15049                        self.write_space();
15050                        self.write_keyword("COLLATE");
15051                        self.write_space();
15052                        self.generate_identifier(collate)?;
15053                    }
15054                }
15055                self.write(")");
15056            }
15057            TypeDefinition::Range {
15058                subtype,
15059                subtype_diff,
15060                canonical,
15061            } => {
15062                self.write_keyword("RANGE");
15063                self.write(" (");
15064                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15065                    self.write("subtype");
15066                } else {
15067                    self.write_keyword("SUBTYPE");
15068                }
15069                self.write(" = ");
15070                self.generate_data_type(subtype)?;
15071                if let Some(diff) = subtype_diff {
15072                    self.write(", ");
15073                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15074                        self.write("subtype_diff");
15075                    } else {
15076                        self.write_keyword("SUBTYPE_DIFF");
15077                    }
15078                    self.write(" = ");
15079                    self.write(diff);
15080                }
15081                if let Some(canon) = canonical {
15082                    self.write(", ");
15083                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
15084                        self.write("canonical");
15085                    } else {
15086                        self.write_keyword("CANONICAL");
15087                    }
15088                    self.write(" = ");
15089                    self.write(canon);
15090                }
15091                self.write(")");
15092            }
15093            TypeDefinition::Base {
15094                input,
15095                output,
15096                internallength,
15097            } => {
15098                self.write("(");
15099                self.write_keyword("INPUT");
15100                self.write(" = ");
15101                self.write(input);
15102                self.write(", ");
15103                self.write_keyword("OUTPUT");
15104                self.write(" = ");
15105                self.write(output);
15106                if let Some(len) = internallength {
15107                    self.write(", ");
15108                    self.write_keyword("INTERNALLENGTH");
15109                    self.write(" = ");
15110                    self.write(&len.to_string());
15111                }
15112                self.write(")");
15113            }
15114            TypeDefinition::Domain {
15115                base_type,
15116                default,
15117                constraints,
15118            } => {
15119                self.generate_data_type(base_type)?;
15120                if let Some(def) = default {
15121                    self.write_space();
15122                    self.write_keyword("DEFAULT");
15123                    self.write_space();
15124                    self.generate_expression(def)?;
15125                }
15126                for constr in constraints {
15127                    self.write_space();
15128                    if let Some(name) = &constr.name {
15129                        self.write_keyword("CONSTRAINT");
15130                        self.write_space();
15131                        self.generate_identifier(name)?;
15132                        self.write_space();
15133                    }
15134                    self.write_keyword("CHECK");
15135                    self.write(" (");
15136                    self.generate_expression(&constr.check)?;
15137                    self.write(")");
15138                }
15139            }
15140        }
15141
15142        Ok(())
15143    }
15144
15145    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
15146        self.write_keyword("CREATE");
15147        if task.or_replace {
15148            self.write_space();
15149            self.write_keyword("OR REPLACE");
15150        }
15151        self.write_space();
15152        self.write_keyword("TASK");
15153        if task.if_not_exists {
15154            self.write_space();
15155            self.write_keyword("IF NOT EXISTS");
15156        }
15157        self.write_space();
15158        self.write(&task.name);
15159        if !task.properties.is_empty() {
15160            // Properties already include leading whitespace from tokens_to_sql
15161            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
15162                self.write_space();
15163            }
15164            self.write(&task.properties);
15165        }
15166        self.write_space();
15167        self.write_keyword("AS");
15168        self.write_space();
15169        self.generate_expression(&task.body)?;
15170        Ok(())
15171    }
15172
15173    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
15174        self.write_keyword("BEGIN TRY");
15175        self.generate_tsql_block_statements(&try_catch.try_body)?;
15176        self.write_keyword("END TRY");
15177
15178        if let Some(catch_body) = &try_catch.catch_body {
15179            if self.config.pretty {
15180                self.write_newline();
15181                self.write_indent();
15182            } else {
15183                self.write_space();
15184            }
15185            self.write_keyword("BEGIN CATCH");
15186            self.generate_tsql_block_statements(catch_body)?;
15187            self.write_keyword("END CATCH");
15188        }
15189
15190        Ok(())
15191    }
15192
15193    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
15194        if statements.is_empty() {
15195            self.write_space();
15196            return Ok(());
15197        }
15198
15199        if self.config.pretty {
15200            self.indent_level += 1;
15201            for stmt in statements {
15202                self.write_newline();
15203                self.write_indent();
15204                self.generate_expression(stmt)?;
15205                self.write(";");
15206            }
15207            self.indent_level -= 1;
15208            self.write_newline();
15209            self.write_indent();
15210        } else {
15211            self.write_space();
15212            for (i, stmt) in statements.iter().enumerate() {
15213                if i > 0 {
15214                    self.write_space();
15215                }
15216                self.generate_expression(stmt)?;
15217                self.write(";");
15218            }
15219            self.write_space();
15220        }
15221
15222        Ok(())
15223    }
15224
15225    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
15226        self.write_keyword("DROP TYPE");
15227
15228        if dt.if_exists {
15229            self.write_space();
15230            self.write_keyword("IF EXISTS");
15231        }
15232
15233        self.write_space();
15234        self.generate_table(&dt.name)?;
15235
15236        if dt.cascade {
15237            self.write_space();
15238            self.write_keyword("CASCADE");
15239        }
15240
15241        Ok(())
15242    }
15243
15244    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
15245        // Athena: DESCRIBE uses Hive engine (backticks)
15246        let saved_athena_hive_context = self.athena_hive_context;
15247        if matches!(
15248            self.config.dialect,
15249            Some(crate::dialects::DialectType::Athena)
15250        ) {
15251            self.athena_hive_context = true;
15252        }
15253
15254        // Output leading comments before DESCRIBE
15255        for comment in &d.leading_comments {
15256            self.write_formatted_comment(comment);
15257            self.write(" ");
15258        }
15259
15260        self.write_keyword("DESCRIBE");
15261
15262        if d.extended {
15263            self.write_space();
15264            self.write_keyword("EXTENDED");
15265        } else if d.formatted {
15266            self.write_space();
15267            self.write_keyword("FORMATTED");
15268        }
15269
15270        // Output style like ANALYZE, HISTORY
15271        if let Some(ref style) = d.style {
15272            self.write_space();
15273            self.write_keyword(style);
15274        }
15275
15276        // Handle object kind (TABLE, VIEW) based on dialect
15277        let should_output_kind = match self.config.dialect {
15278            // Spark doesn't use TABLE/VIEW after DESCRIBE
15279            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
15280                false
15281            }
15282            // Snowflake always includes TABLE
15283            Some(DialectType::Snowflake) => true,
15284            _ => d.kind.is_some(),
15285        };
15286        if should_output_kind {
15287            if let Some(ref kind) = d.kind {
15288                self.write_space();
15289                self.write_keyword(kind);
15290            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15291                self.write_space();
15292                self.write_keyword("TABLE");
15293            }
15294        }
15295
15296        self.write_space();
15297        self.generate_expression(&d.target)?;
15298
15299        // Output parenthesized parameter types for PROCEDURE/FUNCTION
15300        if !d.params.is_empty() {
15301            self.write("(");
15302            for (i, param) in d.params.iter().enumerate() {
15303                if i > 0 {
15304                    self.write(", ");
15305                }
15306                self.write(param);
15307            }
15308            self.write(")");
15309        }
15310
15311        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
15312        if let Some(ref partition) = d.partition {
15313            self.write_space();
15314            self.generate_expression(partition)?;
15315        }
15316
15317        // Databricks: AS JSON
15318        if d.as_json {
15319            self.write_space();
15320            self.write_keyword("AS JSON");
15321        }
15322
15323        // Output properties like type=stage
15324        for (name, value) in &d.properties {
15325            self.write_space();
15326            self.write(name);
15327            self.write("=");
15328            self.write(value);
15329        }
15330
15331        // Restore Athena Hive context
15332        self.athena_hive_context = saved_athena_hive_context;
15333
15334        Ok(())
15335    }
15336
15337    /// Generate SHOW statement (Snowflake, MySQL, etc.)
15338    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
15339    fn generate_show(&mut self, s: &Show) -> Result<()> {
15340        self.write_keyword("SHOW");
15341        self.write_space();
15342
15343        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
15344        // where TERSE is syntactically valid but has no effect on output
15345        let show_terse = s.terse
15346            && !matches!(
15347                s.this.as_str(),
15348                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
15349            );
15350        if show_terse {
15351            self.write_keyword("TERSE");
15352            self.write_space();
15353        }
15354
15355        // Object type (USERS, TABLES, DATABASES, etc.)
15356        self.write_keyword(&s.this);
15357
15358        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
15359        if let Some(ref target_expr) = s.target {
15360            self.write_space();
15361            self.generate_expression(target_expr)?;
15362        }
15363
15364        // HISTORY keyword
15365        if s.history {
15366            self.write_space();
15367            self.write_keyword("HISTORY");
15368        }
15369
15370        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
15371        if let Some(ref for_target) = s.for_target {
15372            self.write_space();
15373            self.write_keyword("FOR");
15374            self.write_space();
15375            self.generate_expression(for_target)?;
15376        }
15377
15378        // Determine ordering based on dialect:
15379        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
15380        // - MySQL: IN, FROM, LIKE (when FROM is present)
15381        use crate::dialects::DialectType;
15382        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
15383        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
15384        let mysql_tables_scope_as_from = is_mysql
15385            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
15386            && s.scope_kind.as_deref() == Some("SCHEMA")
15387            && s.scope.is_some()
15388            && s.from.is_none();
15389
15390        if !is_snowflake && s.from.is_some() {
15391            // MySQL ordering: IN, FROM, LIKE
15392
15393            // IN scope_kind [scope]
15394            if let Some(ref scope_kind) = s.scope_kind {
15395                self.write_space();
15396                self.write_keyword("IN");
15397                self.write_space();
15398                self.write_keyword(scope_kind);
15399                if let Some(ref scope) = s.scope {
15400                    self.write_space();
15401                    self.generate_expression(scope)?;
15402                }
15403            } else if let Some(ref scope) = s.scope {
15404                self.write_space();
15405                self.write_keyword("IN");
15406                self.write_space();
15407                self.generate_expression(scope)?;
15408            }
15409
15410            // FROM clause
15411            if let Some(ref from) = s.from {
15412                self.write_space();
15413                self.write_keyword("FROM");
15414                self.write_space();
15415                self.generate_expression(from)?;
15416            }
15417
15418            // Second FROM clause (db name)
15419            if let Some(ref db) = s.db {
15420                self.write_space();
15421                self.write_keyword("FROM");
15422                self.write_space();
15423                self.generate_expression(db)?;
15424            }
15425
15426            // LIKE pattern
15427            if let Some(ref like) = s.like {
15428                self.write_space();
15429                self.write_keyword("LIKE");
15430                self.write_space();
15431                self.generate_expression(like)?;
15432            }
15433        } else {
15434            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
15435
15436            // LIKE pattern
15437            if let Some(ref like) = s.like {
15438                self.write_space();
15439                self.write_keyword("LIKE");
15440                self.write_space();
15441                self.generate_expression(like)?;
15442            }
15443
15444            // IN scope_kind [scope]
15445            if mysql_tables_scope_as_from {
15446                self.write_space();
15447                self.write_keyword("FROM");
15448                self.write_space();
15449                self.generate_expression(s.scope.as_ref().unwrap())?;
15450            } else if let Some(ref scope_kind) = s.scope_kind {
15451                self.write_space();
15452                self.write_keyword("IN");
15453                self.write_space();
15454                self.write_keyword(scope_kind);
15455                if let Some(ref scope) = s.scope {
15456                    self.write_space();
15457                    self.generate_expression(scope)?;
15458                }
15459            } else if let Some(ref scope) = s.scope {
15460                self.write_space();
15461                self.write_keyword("IN");
15462                self.write_space();
15463                self.generate_expression(scope)?;
15464            }
15465        }
15466
15467        // STARTS WITH pattern
15468        if let Some(ref starts_with) = s.starts_with {
15469            self.write_space();
15470            self.write_keyword("STARTS WITH");
15471            self.write_space();
15472            self.generate_expression(starts_with)?;
15473        }
15474
15475        // LIMIT clause
15476        if let Some(ref limit) = s.limit {
15477            self.write_space();
15478            self.generate_limit(limit)?;
15479        }
15480
15481        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
15482        if is_snowflake {
15483            if let Some(ref from) = s.from {
15484                self.write_space();
15485                self.write_keyword("FROM");
15486                self.write_space();
15487                self.generate_expression(from)?;
15488            }
15489        }
15490
15491        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
15492        if let Some(ref where_clause) = s.where_clause {
15493            self.write_space();
15494            self.write_keyword("WHERE");
15495            self.write_space();
15496            self.generate_expression(where_clause)?;
15497        }
15498
15499        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
15500        if let Some(is_mutex) = s.mutex {
15501            self.write_space();
15502            if is_mutex {
15503                self.write_keyword("MUTEX");
15504            } else {
15505                self.write_keyword("STATUS");
15506            }
15507        }
15508
15509        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
15510        if !s.privileges.is_empty() {
15511            self.write_space();
15512            self.write_keyword("WITH PRIVILEGES");
15513            self.write_space();
15514            for (i, priv_name) in s.privileges.iter().enumerate() {
15515                if i > 0 {
15516                    self.write(", ");
15517                }
15518                self.write_keyword(priv_name);
15519            }
15520        }
15521
15522        Ok(())
15523    }
15524
15525    // ==================== End DDL Generation ====================
15526
15527    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
15528        use crate::dialects::DialectType;
15529        match lit {
15530            Literal::String(s) => {
15531                self.generate_string_literal(s)?;
15532            }
15533            Literal::Number(n) => {
15534                if matches!(self.config.dialect, Some(DialectType::MySQL))
15535                    && n.len() > 2
15536                    && (n.starts_with("0x") || n.starts_with("0X"))
15537                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
15538                {
15539                    return self.generate_identifier(&Identifier {
15540                        name: n.clone(),
15541                        quoted: true,
15542                        trailing_comments: Vec::new(),
15543                        span: None,
15544                    });
15545                }
15546                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
15547                // for dialects that don't support them (MySQL interprets as identifier).
15548                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
15549                let n = if n.contains('_')
15550                    && !matches!(
15551                        self.config.dialect,
15552                        Some(DialectType::ClickHouse)
15553                            | Some(DialectType::DuckDB)
15554                            | Some(DialectType::PostgreSQL)
15555                            | Some(DialectType::Hive)
15556                            | Some(DialectType::Spark)
15557                            | Some(DialectType::Databricks)
15558                    ) {
15559                    std::borrow::Cow::Owned(n.replace('_', ""))
15560                } else {
15561                    std::borrow::Cow::Borrowed(n.as_str())
15562                };
15563                // Normalize numbers starting with decimal point to have leading zero
15564                // e.g., .25 -> 0.25 (matches sqlglot behavior)
15565                if n.starts_with('.') {
15566                    self.write("0");
15567                    self.write(&n);
15568                } else if n.starts_with("-.") {
15569                    // Handle negative numbers like -.25 -> -0.25
15570                    self.write("-0");
15571                    self.write(&n[1..]);
15572                } else {
15573                    self.write(&n);
15574                }
15575            }
15576            Literal::HexString(h) => {
15577                // Most dialects use lowercase x'...' for hex literals.
15578                match self.config.dialect {
15579                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15580                        self.write("0x");
15581                        self.write(h);
15582                        return Ok(());
15583                    }
15584                    Some(DialectType::Spark)
15585                    | Some(DialectType::Databricks)
15586                    | Some(DialectType::Teradata) => self.write("X'"),
15587                    _ => self.write("x'"),
15588                }
15589                self.write(h);
15590                self.write("'");
15591            }
15592            Literal::HexNumber(h) => {
15593                // Hex number (0xA) - integer in hex notation (from BigQuery)
15594                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
15595                // For other dialects, convert to decimal integer
15596                match self.config.dialect {
15597                    Some(DialectType::BigQuery)
15598                    | Some(DialectType::ClickHouse)
15599                    | Some(DialectType::TSQL)
15600                    | Some(DialectType::Fabric) => {
15601                        self.write("0x");
15602                        self.write(h);
15603                    }
15604                    _ => {
15605                        // Convert hex to decimal
15606                        if let Ok(val) = u64::from_str_radix(h, 16) {
15607                            self.write(&val.to_string());
15608                        } else {
15609                            // Fallback: keep as 0x notation
15610                            self.write("0x");
15611                            self.write(h);
15612                        }
15613                    }
15614                }
15615            }
15616            Literal::BitString(b) => {
15617                // Bit string B'0101...'
15618                self.write("B'");
15619                self.write(b);
15620                self.write("'");
15621            }
15622            Literal::ByteString(b) => {
15623                // Byte string b'...' (BigQuery style)
15624                self.write("b'");
15625                // Escape special characters for output
15626                self.write_escaped_byte_string(b);
15627                self.write("'");
15628            }
15629            Literal::NationalString(s) => {
15630                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
15631                // Other dialects strip the N prefix and output as regular string
15632                let keep_n_prefix = matches!(
15633                    self.config.dialect,
15634                    Some(DialectType::TSQL)
15635                        | Some(DialectType::Oracle)
15636                        | Some(DialectType::MySQL)
15637                        | None
15638                );
15639                if keep_n_prefix {
15640                    self.write("N'");
15641                } else {
15642                    self.write("'");
15643                }
15644                self.write(s);
15645                self.write("'");
15646            }
15647            Literal::Date(d) => {
15648                self.generate_date_literal(d)?;
15649            }
15650            Literal::Time(t) => {
15651                self.generate_time_literal(t)?;
15652            }
15653            Literal::Timestamp(ts) => {
15654                self.generate_timestamp_literal(ts)?;
15655            }
15656            Literal::Datetime(dt) => {
15657                self.generate_datetime_literal(dt)?;
15658            }
15659            Literal::TripleQuotedString(s, _quote_char) => {
15660                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
15661                if matches!(
15662                    self.config.dialect,
15663                    Some(crate::dialects::DialectType::BigQuery)
15664                        | Some(crate::dialects::DialectType::DuckDB)
15665                        | Some(crate::dialects::DialectType::Snowflake)
15666                        | Some(crate::dialects::DialectType::Spark)
15667                        | Some(crate::dialects::DialectType::Hive)
15668                        | Some(crate::dialects::DialectType::Presto)
15669                        | Some(crate::dialects::DialectType::Trino)
15670                        | Some(crate::dialects::DialectType::PostgreSQL)
15671                        | Some(crate::dialects::DialectType::MySQL)
15672                        | Some(crate::dialects::DialectType::Redshift)
15673                        | Some(crate::dialects::DialectType::TSQL)
15674                        | Some(crate::dialects::DialectType::Oracle)
15675                        | Some(crate::dialects::DialectType::ClickHouse)
15676                        | Some(crate::dialects::DialectType::Databricks)
15677                        | Some(crate::dialects::DialectType::SQLite)
15678                ) {
15679                    self.generate_string_literal(s)?;
15680                } else {
15681                    // Preserve triple-quoted string syntax for generic/unknown dialects
15682                    let quotes = format!("{0}{0}{0}", _quote_char);
15683                    self.write(&quotes);
15684                    self.write(s);
15685                    self.write(&quotes);
15686                }
15687            }
15688            Literal::EscapeString(s) => {
15689                // PostgreSQL escape string: e'...' or E'...'
15690                // Token text format is "e:content" or "E:content"
15691                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
15692                use crate::dialects::DialectType;
15693                let content = if let Some(c) = s.strip_prefix("e:") {
15694                    c
15695                } else if let Some(c) = s.strip_prefix("E:") {
15696                    c
15697                } else {
15698                    s.as_str()
15699                };
15700
15701                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
15702                if matches!(
15703                    self.config.dialect,
15704                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
15705                ) {
15706                    self.write("'");
15707                    self.write(&content.replace('\'', "''"));
15708                    self.write("'");
15709                } else {
15710                    // Some dialects use lowercase e' prefix
15711                    let prefix = if matches!(
15712                        self.config.dialect,
15713                        Some(DialectType::SingleStore)
15714                            | Some(DialectType::DuckDB)
15715                            | Some(DialectType::PostgreSQL)
15716                            | Some(DialectType::CockroachDB)
15717                            | Some(DialectType::Materialize)
15718                            | Some(DialectType::RisingWave)
15719                    ) {
15720                        "e'"
15721                    } else {
15722                        "E'"
15723                    };
15724
15725                    // Normalize \' to '' for output
15726                    let normalized = content.replace("\\'", "''");
15727                    self.write(prefix);
15728                    self.write(&normalized);
15729                    self.write("'");
15730                }
15731            }
15732            Literal::DollarString(s) => {
15733                // Convert dollar-quoted strings to single-quoted strings
15734                // (like Python sqlglot's rawstring_sql)
15735                use crate::dialects::DialectType;
15736                // Extract content from tag\x00content format
15737                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
15738                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
15739                let escape_backslash = matches!(
15740                    self.config.dialect,
15741                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
15742                );
15743                // Step 2: Determine quote escaping style
15744                // Snowflake: ' -> \' (backslash escape)
15745                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
15746                let use_backslash_quote =
15747                    matches!(self.config.dialect, Some(DialectType::Snowflake));
15748
15749                let mut escaped = String::with_capacity(content.len() + 4);
15750                for ch in content.chars() {
15751                    if escape_backslash && ch == '\\' {
15752                        // Escape backslash first (before quote escaping)
15753                        escaped.push('\\');
15754                        escaped.push('\\');
15755                    } else if ch == '\'' {
15756                        if use_backslash_quote {
15757                            escaped.push('\\');
15758                            escaped.push('\'');
15759                        } else {
15760                            escaped.push('\'');
15761                            escaped.push('\'');
15762                        }
15763                    } else {
15764                        escaped.push(ch);
15765                    }
15766                }
15767                self.write("'");
15768                self.write(&escaped);
15769                self.write("'");
15770            }
15771            Literal::RawString(s) => {
15772                // Raw strings (r"..." or r'...') contain literal backslashes.
15773                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15774                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15775                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15776                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15777                use crate::dialects::DialectType;
15778
15779                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15780                let escape_backslash = matches!(
15781                    self.config.dialect,
15782                    Some(DialectType::BigQuery)
15783                        | Some(DialectType::MySQL)
15784                        | Some(DialectType::SingleStore)
15785                        | Some(DialectType::TiDB)
15786                        | Some(DialectType::Hive)
15787                        | Some(DialectType::Spark)
15788                        | Some(DialectType::Databricks)
15789                        | Some(DialectType::Drill)
15790                        | Some(DialectType::Snowflake)
15791                        | Some(DialectType::Redshift)
15792                        | Some(DialectType::ClickHouse)
15793                );
15794
15795                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15796                // These escape quotes as \' instead of ''
15797                let backslash_escapes_quote = matches!(
15798                    self.config.dialect,
15799                    Some(DialectType::BigQuery)
15800                        | Some(DialectType::Hive)
15801                        | Some(DialectType::Spark)
15802                        | Some(DialectType::Databricks)
15803                        | Some(DialectType::Drill)
15804                        | Some(DialectType::Snowflake)
15805                        | Some(DialectType::Redshift)
15806                );
15807
15808                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15809                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15810                let supports_escape_sequences = escape_backslash;
15811
15812                let mut escaped = String::with_capacity(s.len() + 4);
15813                for ch in s.chars() {
15814                    if escape_backslash && ch == '\\' {
15815                        // Double the backslash for the target dialect
15816                        escaped.push('\\');
15817                        escaped.push('\\');
15818                    } else if ch == '\'' {
15819                        if backslash_escapes_quote {
15820                            // Use backslash to escape the quote: \'
15821                            escaped.push('\\');
15822                            escaped.push('\'');
15823                        } else {
15824                            // Use SQL standard quote doubling: ''
15825                            escaped.push('\'');
15826                            escaped.push('\'');
15827                        }
15828                    } else if supports_escape_sequences {
15829                        // Apply ESCAPED_SEQUENCES mapping for special chars
15830                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15831                        match ch {
15832                            '\n' => {
15833                                escaped.push('\\');
15834                                escaped.push('n');
15835                            }
15836                            '\r' => {
15837                                escaped.push('\\');
15838                                escaped.push('r');
15839                            }
15840                            '\t' => {
15841                                escaped.push('\\');
15842                                escaped.push('t');
15843                            }
15844                            '\x07' => {
15845                                escaped.push('\\');
15846                                escaped.push('a');
15847                            }
15848                            '\x08' => {
15849                                escaped.push('\\');
15850                                escaped.push('b');
15851                            }
15852                            '\x0C' => {
15853                                escaped.push('\\');
15854                                escaped.push('f');
15855                            }
15856                            '\x0B' => {
15857                                escaped.push('\\');
15858                                escaped.push('v');
15859                            }
15860                            _ => escaped.push(ch),
15861                        }
15862                    } else {
15863                        escaped.push(ch);
15864                    }
15865                }
15866                self.write("'");
15867                self.write(&escaped);
15868                self.write("'");
15869            }
15870        }
15871        Ok(())
15872    }
15873
15874    /// Generate a DATE literal with dialect-specific formatting
15875    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15876        use crate::dialects::DialectType;
15877
15878        match self.config.dialect {
15879            // SQL Server / Fabric use CONVERT or CAST
15880            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15881                self.write("CAST('");
15882                self.write(d);
15883                self.write("' AS DATE)");
15884            }
15885            // BigQuery uses CAST syntax for type literals
15886            // DATE 'value' -> CAST('value' AS DATE)
15887            Some(DialectType::BigQuery) => {
15888                self.write("CAST('");
15889                self.write(d);
15890                self.write("' AS DATE)");
15891            }
15892            // Exasol uses CAST syntax for DATE literals
15893            // DATE 'value' -> CAST('value' AS DATE)
15894            Some(DialectType::Exasol) => {
15895                self.write("CAST('");
15896                self.write(d);
15897                self.write("' AS DATE)");
15898            }
15899            // Snowflake uses CAST syntax for DATE literals
15900            // DATE 'value' -> CAST('value' AS DATE)
15901            Some(DialectType::Snowflake) => {
15902                self.write("CAST('");
15903                self.write(d);
15904                self.write("' AS DATE)");
15905            }
15906            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15907            Some(DialectType::PostgreSQL)
15908            | Some(DialectType::MySQL)
15909            | Some(DialectType::SingleStore)
15910            | Some(DialectType::TiDB)
15911            | Some(DialectType::Redshift) => {
15912                self.write("CAST('");
15913                self.write(d);
15914                self.write("' AS DATE)");
15915            }
15916            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15917            Some(DialectType::DuckDB)
15918            | Some(DialectType::Presto)
15919            | Some(DialectType::Trino)
15920            | Some(DialectType::Athena)
15921            | Some(DialectType::Spark)
15922            | Some(DialectType::Databricks)
15923            | Some(DialectType::Hive) => {
15924                self.write("CAST('");
15925                self.write(d);
15926                self.write("' AS DATE)");
15927            }
15928            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
15929            Some(DialectType::Oracle) => {
15930                self.write("TO_DATE('");
15931                self.write(d);
15932                self.write("', 'YYYY-MM-DD')");
15933            }
15934            // Standard SQL: DATE '...'
15935            _ => {
15936                self.write_keyword("DATE");
15937                self.write(" '");
15938                self.write(d);
15939                self.write("'");
15940            }
15941        }
15942        Ok(())
15943    }
15944
15945    /// Generate a TIME literal with dialect-specific formatting
15946    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
15947        use crate::dialects::DialectType;
15948
15949        match self.config.dialect {
15950            // SQL Server uses CONVERT or CAST
15951            Some(DialectType::TSQL) => {
15952                self.write("CAST('");
15953                self.write(t);
15954                self.write("' AS TIME)");
15955            }
15956            Some(DialectType::Fabric) => {
15957                self.write("CAST('");
15958                self.write(t);
15959                self.write("' AS TIME(6))");
15960            }
15961            // Standard SQL: TIME '...'
15962            _ => {
15963                self.write_keyword("TIME");
15964                self.write(" '");
15965                self.write(t);
15966                self.write("'");
15967            }
15968        }
15969        Ok(())
15970    }
15971
15972    /// Generate a date expression for Dremio, converting DATE literals to CAST
15973    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
15974        use crate::expressions::Literal;
15975
15976        match expr {
15977            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
15978                let Literal::Date(d) = lit.as_ref() else {
15979                    unreachable!()
15980                };
15981                // DATE 'value' -> CAST('value' AS DATE)
15982                self.write("CAST('");
15983                self.write(d);
15984                self.write("' AS DATE)");
15985            }
15986            _ => {
15987                // For all other expressions, generate normally
15988                self.generate_expression(expr)?;
15989            }
15990        }
15991        Ok(())
15992    }
15993
15994    /// Generate a TIMESTAMP literal with dialect-specific formatting
15995    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
15996        use crate::dialects::DialectType;
15997
15998        match self.config.dialect {
15999            // SQL Server uses CONVERT or CAST
16000            Some(DialectType::TSQL) => {
16001                self.write("CAST('");
16002                self.write(ts);
16003                self.write("' AS DATETIME2)");
16004            }
16005            // BigQuery uses CAST syntax for type literals
16006            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16007            Some(DialectType::BigQuery) => {
16008                self.write("CAST('");
16009                self.write(ts);
16010                self.write("' AS TIMESTAMP)");
16011            }
16012            // Snowflake uses CAST syntax for TIMESTAMP literals
16013            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16014            Some(DialectType::Snowflake) => {
16015                self.write("CAST('");
16016                self.write(ts);
16017                self.write("' AS TIMESTAMP)");
16018            }
16019            // Dremio uses CAST syntax for TIMESTAMP literals
16020            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16021            Some(DialectType::Dremio) => {
16022                self.write("CAST('");
16023                self.write(ts);
16024                self.write("' AS TIMESTAMP)");
16025            }
16026            // Exasol uses CAST syntax for TIMESTAMP literals
16027            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
16028            Some(DialectType::Exasol) => {
16029                self.write("CAST('");
16030                self.write(ts);
16031                self.write("' AS TIMESTAMP)");
16032            }
16033            // Oracle prefers TO_TIMESTAMP function call
16034            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
16035            Some(DialectType::Oracle) => {
16036                self.write("TO_TIMESTAMP('");
16037                self.write(ts);
16038                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
16039            }
16040            // Presto/Trino: always use CAST for TIMESTAMP literals
16041            Some(DialectType::Presto) | Some(DialectType::Trino) => {
16042                if Self::timestamp_has_timezone(ts) {
16043                    self.write("CAST('");
16044                    self.write(ts);
16045                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
16046                } else {
16047                    self.write("CAST('");
16048                    self.write(ts);
16049                    self.write("' AS TIMESTAMP)");
16050                }
16051            }
16052            // ClickHouse: CAST('...' AS Nullable(DateTime))
16053            Some(DialectType::ClickHouse) => {
16054                self.write("CAST('");
16055                self.write(ts);
16056                self.write("' AS Nullable(DateTime))");
16057            }
16058            // Spark: CAST('...' AS TIMESTAMP)
16059            Some(DialectType::Spark) => {
16060                self.write("CAST('");
16061                self.write(ts);
16062                self.write("' AS TIMESTAMP)");
16063            }
16064            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
16065            // but TIMESTAMP '...' for special values like 'epoch'
16066            Some(DialectType::Redshift) => {
16067                if ts == "epoch" {
16068                    self.write_keyword("TIMESTAMP");
16069                    self.write(" '");
16070                    self.write(ts);
16071                    self.write("'");
16072                } else {
16073                    self.write("CAST('");
16074                    self.write(ts);
16075                    self.write("' AS TIMESTAMP)");
16076                }
16077            }
16078            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
16079            Some(DialectType::PostgreSQL)
16080            | Some(DialectType::Hive)
16081            | Some(DialectType::SQLite)
16082            | Some(DialectType::DuckDB)
16083            | Some(DialectType::Athena)
16084            | Some(DialectType::Drill)
16085            | Some(DialectType::Teradata) => {
16086                self.write("CAST('");
16087                self.write(ts);
16088                self.write("' AS TIMESTAMP)");
16089            }
16090            // MySQL/StarRocks: CAST('...' AS DATETIME)
16091            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
16092                self.write("CAST('");
16093                self.write(ts);
16094                self.write("' AS DATETIME)");
16095            }
16096            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
16097            Some(DialectType::Databricks) => {
16098                self.write("CAST('");
16099                self.write(ts);
16100                self.write("' AS TIMESTAMP_NTZ)");
16101            }
16102            // Standard SQL: TIMESTAMP '...'
16103            _ => {
16104                self.write_keyword("TIMESTAMP");
16105                self.write(" '");
16106                self.write(ts);
16107                self.write("'");
16108            }
16109        }
16110        Ok(())
16111    }
16112
16113    /// Check if a timestamp string contains a timezone identifier
16114    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
16115    fn timestamp_has_timezone(ts: &str) -> bool {
16116        // Check for common IANA timezone patterns: Continent/City format
16117        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
16118        // Also handles: UTC, GMT, Etc/GMT+0, etc.
16119        let ts_lower = ts.to_ascii_lowercase();
16120
16121        // Check for Continent/City pattern (most common)
16122        let continent_prefixes = [
16123            "africa/",
16124            "america/",
16125            "antarctica/",
16126            "arctic/",
16127            "asia/",
16128            "atlantic/",
16129            "australia/",
16130            "europe/",
16131            "indian/",
16132            "pacific/",
16133            "etc/",
16134            "brazil/",
16135            "canada/",
16136            "chile/",
16137            "mexico/",
16138            "us/",
16139        ];
16140
16141        for prefix in &continent_prefixes {
16142            if ts_lower.contains(prefix) {
16143                return true;
16144            }
16145        }
16146
16147        // Check for standalone timezone abbreviations at the end
16148        // These typically appear after the time portion
16149        let tz_abbrevs = [
16150            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
16151            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
16152            " sgt", " aest", " aedt", " acst", " acdt", " awst",
16153        ];
16154
16155        for abbrev in &tz_abbrevs {
16156            if ts_lower.ends_with(abbrev) {
16157                return true;
16158            }
16159        }
16160
16161        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
16162        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
16163        // Look for pattern: space followed by + or - and digits (optionally with :)
16164        let trimmed = ts.trim();
16165        if let Some(last_space) = trimmed.rfind(' ') {
16166            let suffix = &trimmed[last_space + 1..];
16167            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
16168                // Check if rest is numeric (possibly with : for hh:mm format)
16169                let rest = &suffix[1..];
16170                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
16171                    return true;
16172                }
16173            }
16174        }
16175
16176        false
16177    }
16178
16179    /// Generate a DATETIME literal with dialect-specific formatting
16180    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
16181        use crate::dialects::DialectType;
16182
16183        match self.config.dialect {
16184            // BigQuery uses CAST syntax for type literals
16185            // DATETIME 'value' -> CAST('value' AS DATETIME)
16186            Some(DialectType::BigQuery) => {
16187                self.write("CAST('");
16188                self.write(dt);
16189                self.write("' AS DATETIME)");
16190            }
16191            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
16192            Some(DialectType::DuckDB) => {
16193                self.write("CAST('");
16194                self.write(dt);
16195                self.write("' AS TIMESTAMP)");
16196            }
16197            // DATETIME is primarily a BigQuery type
16198            // Output as DATETIME '...' for dialects that support it
16199            _ => {
16200                self.write_keyword("DATETIME");
16201                self.write(" '");
16202                self.write(dt);
16203                self.write("'");
16204            }
16205        }
16206        Ok(())
16207    }
16208
16209    /// Generate a string literal with dialect-specific escaping
16210    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
16211        use crate::dialects::DialectType;
16212
16213        match self.config.dialect {
16214            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
16215            // and backslash escaping for special characters like newlines
16216            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
16217            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
16218                // Hive/Spark use backslash escaping for quotes (\') and special chars
16219                self.write("'");
16220                for c in s.chars() {
16221                    match c {
16222                        '\'' => self.write("\\'"),
16223                        '\\' => self.write("\\\\"),
16224                        '\n' => self.write("\\n"),
16225                        '\r' => self.write("\\r"),
16226                        '\t' => self.write("\\t"),
16227                        '\0' => self.write("\\0"),
16228                        _ => self.output.push(c),
16229                    }
16230                }
16231                self.write("'");
16232            }
16233            Some(DialectType::Drill) => {
16234                // Drill uses SQL-standard quote doubling ('') for quotes,
16235                // but backslash escaping for special characters
16236                self.write("'");
16237                for c in s.chars() {
16238                    match c {
16239                        '\'' => self.write("''"),
16240                        '\\' => self.write("\\\\"),
16241                        '\n' => self.write("\\n"),
16242                        '\r' => self.write("\\r"),
16243                        '\t' => self.write("\\t"),
16244                        '\0' => self.write("\\0"),
16245                        _ => self.output.push(c),
16246                    }
16247                }
16248                self.write("'");
16249            }
16250            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
16251                self.write("'");
16252                for c in s.chars() {
16253                    match c {
16254                        // MySQL uses SQL standard quote doubling
16255                        '\'' => self.write("''"),
16256                        '\\' => self.write("\\\\"),
16257                        '\n' => self.write("\\n"),
16258                        '\r' => self.write("\\r"),
16259                        '\t' => self.write("\\t"),
16260                        // sqlglot writes a literal NUL for this case
16261                        '\0' => self.output.push('\0'),
16262                        _ => self.output.push(c),
16263                    }
16264                }
16265                self.write("'");
16266            }
16267            // BigQuery: Uses backslash escaping
16268            Some(DialectType::BigQuery) => {
16269                self.write("'");
16270                for c in s.chars() {
16271                    match c {
16272                        '\'' => self.write("\\'"),
16273                        '\\' => self.write("\\\\"),
16274                        '\n' => self.write("\\n"),
16275                        '\r' => self.write("\\r"),
16276                        '\t' => self.write("\\t"),
16277                        '\0' => self.write("\\0"),
16278                        '\x07' => self.write("\\a"),
16279                        '\x08' => self.write("\\b"),
16280                        '\x0C' => self.write("\\f"),
16281                        '\x0B' => self.write("\\v"),
16282                        _ => self.output.push(c),
16283                    }
16284                }
16285                self.write("'");
16286            }
16287            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
16288            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
16289            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
16290            Some(DialectType::Athena) => {
16291                if self.athena_hive_context {
16292                    // Hive-style: backslash escaping
16293                    self.write("'");
16294                    for c in s.chars() {
16295                        match c {
16296                            '\'' => self.write("\\'"),
16297                            '\\' => self.write("\\\\"),
16298                            '\n' => self.write("\\n"),
16299                            '\r' => self.write("\\r"),
16300                            '\t' => self.write("\\t"),
16301                            '\0' => self.write("\\0"),
16302                            _ => self.output.push(c),
16303                        }
16304                    }
16305                    self.write("'");
16306                } else {
16307                    // Trino-style: SQL-standard escaping, preserve backslashes
16308                    self.write("'");
16309                    for c in s.chars() {
16310                        match c {
16311                            '\'' => self.write("''"),
16312                            // Preserve backslashes literally (no re-escaping)
16313                            _ => self.output.push(c),
16314                        }
16315                    }
16316                    self.write("'");
16317                }
16318            }
16319            // Snowflake uses backslash escaping (STRING_ESCAPES = ["\\", "'"]).
16320            Some(DialectType::Snowflake) => {
16321                self.write("'");
16322                for c in s.chars() {
16323                    match c {
16324                        '\'' => self.write("\\'"),
16325                        '\\' => self.write("\\\\"),
16326                        '\0' => self.write("\\x00"),
16327                        '\x08' => self.write("\\b"),
16328                        '\x0C' => self.write("\\f"),
16329                        '\n' => self.write("\\n"),
16330                        '\r' => self.write("\\r"),
16331                        '\t' => self.write("\\t"),
16332                        _ => self.output.push(c),
16333                    }
16334                }
16335                self.write("'");
16336            }
16337            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
16338            Some(DialectType::PostgreSQL) => {
16339                self.write("'");
16340                for c in s.chars() {
16341                    match c {
16342                        '\'' => self.write("''"),
16343                        _ => self.output.push(c),
16344                    }
16345                }
16346                self.write("'");
16347            }
16348            // Redshift: Uses backslash escaping for single quotes
16349            Some(DialectType::Redshift) => {
16350                self.write("'");
16351                for c in s.chars() {
16352                    match c {
16353                        '\'' => self.write("\\'"),
16354                        _ => self.output.push(c),
16355                    }
16356                }
16357                self.write("'");
16358            }
16359            // Oracle: Uses standard double single-quote escaping
16360            Some(DialectType::Oracle) => {
16361                self.write("'");
16362                for ch in s.chars() {
16363                    if ch == '\'' {
16364                        self.output.push_str("''");
16365                    } else {
16366                        self.output.push(ch);
16367                    }
16368                }
16369                self.write("'");
16370            }
16371            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
16372            // backslash escaping for backslashes and special characters
16373            Some(DialectType::ClickHouse) => {
16374                self.write("'");
16375                for c in s.chars() {
16376                    match c {
16377                        '\'' => self.write("''"),
16378                        '\\' => self.write("\\\\"),
16379                        '\n' => self.write("\\n"),
16380                        '\r' => self.write("\\r"),
16381                        '\t' => self.write("\\t"),
16382                        '\0' => self.write("\\0"),
16383                        '\x07' => self.write("\\a"),
16384                        '\x08' => self.write("\\b"),
16385                        '\x0C' => self.write("\\f"),
16386                        '\x0B' => self.write("\\v"),
16387                        // Non-printable characters: emit as \xNN hex escapes
16388                        c if c.is_control() || (c as u32) < 0x20 => {
16389                            let byte = c as u32;
16390                            if byte < 256 {
16391                                self.write(&format!("\\x{:02X}", byte));
16392                            } else {
16393                                self.output.push(c);
16394                            }
16395                        }
16396                        _ => self.output.push(c),
16397                    }
16398                }
16399                self.write("'");
16400            }
16401            // Default: SQL standard double single quotes (works for most dialects)
16402            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
16403            _ => {
16404                self.write("'");
16405                for ch in s.chars() {
16406                    if ch == '\'' {
16407                        self.output.push_str("''");
16408                    } else {
16409                        self.output.push(ch);
16410                    }
16411                }
16412                self.write("'");
16413            }
16414        }
16415        Ok(())
16416    }
16417
16418    /// Write a byte string with proper escaping for BigQuery-style byte literals
16419    /// Escapes characters as \xNN hex escapes where needed
16420    fn write_escaped_byte_string(&mut self, s: &str) {
16421        for c in s.chars() {
16422            match c {
16423                // Escape single quotes
16424                '\'' => self.write("\\'"),
16425                // Escape backslashes
16426                '\\' => self.write("\\\\"),
16427                // Keep all printable characters (including non-ASCII) as-is
16428                _ if !c.is_control() => self.output.push(c),
16429                // Escape control characters as hex
16430                _ => {
16431                    let byte = c as u32;
16432                    if byte < 256 {
16433                        self.write(&format!("\\x{:02x}", byte));
16434                    } else {
16435                        // For unicode characters, write each UTF-8 byte
16436                        for b in c.to_string().as_bytes() {
16437                            self.write(&format!("\\x{:02x}", b));
16438                        }
16439                    }
16440                }
16441            }
16442        }
16443    }
16444
16445    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
16446        use crate::dialects::DialectType;
16447
16448        // Different dialects have different boolean literal formats
16449        match self.config.dialect {
16450            // SQL Server typically uses 1/0 for boolean literals in many contexts
16451            // However, TRUE/FALSE also works in modern versions
16452            Some(DialectType::TSQL) => {
16453                self.write(if b.value { "1" } else { "0" });
16454            }
16455            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
16456            Some(DialectType::Oracle) => {
16457                self.write(if b.value { "1" } else { "0" });
16458            }
16459            // MySQL accepts TRUE/FALSE as aliases for 1/0
16460            Some(DialectType::MySQL) => {
16461                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16462            }
16463            // Most other dialects support TRUE/FALSE
16464            _ => {
16465                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16466            }
16467        }
16468        Ok(())
16469    }
16470
16471    /// Generate an identifier that's used as an alias name
16472    /// This quotes reserved keywords in addition to already-quoted identifiers
16473    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
16474        let name = &id.name;
16475        let quote_style = &self.config.identifier_quote_style;
16476
16477        // For aliases, quote if:
16478        // 1. The identifier was explicitly quoted in the source
16479        // 2. The identifier is a reserved keyword for the current dialect
16480        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
16481
16482        // Normalize identifier if configured
16483        let output_name = if self.config.normalize_identifiers && !id.quoted {
16484            name.to_ascii_lowercase()
16485        } else {
16486            name.to_string()
16487        };
16488
16489        if needs_quoting {
16490            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
16491                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16492                && quote_style.start == '"'
16493                && output_name.contains('"')
16494            {
16495                &IdentifierQuoteStyle::BACKTICK
16496            } else {
16497                quote_style
16498            };
16499            // Escape any quote characters within the identifier
16500            let escaped_name = if quote_style.start == quote_style.end {
16501                output_name.replace(
16502                    quote_style.end,
16503                    &format!("{}{}", quote_style.end, quote_style.end),
16504                )
16505            } else {
16506                output_name.replace(
16507                    quote_style.end,
16508                    &format!("{}{}", quote_style.end, quote_style.end),
16509                )
16510            };
16511            self.write(&format!(
16512                "{}{}{}",
16513                quote_style.start, escaped_name, quote_style.end
16514            ));
16515        } else {
16516            self.write(&output_name);
16517        }
16518
16519        // Output trailing comments
16520        for comment in &id.trailing_comments {
16521            self.write(" ");
16522            self.write_formatted_comment(comment);
16523        }
16524        Ok(())
16525    }
16526
16527    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
16528        use crate::dialects::DialectType;
16529
16530        let name = &id.name;
16531
16532        // For Athena, use backticks in Hive context, double quotes in Trino context
16533        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
16534            && self.athena_hive_context
16535        {
16536            &IdentifierQuoteStyle::BACKTICK
16537        } else {
16538            &self.config.identifier_quote_style
16539        };
16540
16541        // Quote if:
16542        // 1. The identifier was explicitly quoted in the source
16543        // 2. The identifier is a reserved keyword for the current dialect
16544        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
16545        // This matches Python sqlglot's identifier_sql behavior
16546        // Also quote identifiers starting with digits if the target dialect doesn't support them
16547        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
16548        let needs_digit_quoting = starts_with_digit
16549            && !self.config.identifiers_can_start_with_digit
16550            && self.config.dialect.is_some();
16551        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
16552            && name.len() > 2
16553            && (name.starts_with("0x") || name.starts_with("0X"))
16554            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
16555        let clickhouse_unsafe_identifier =
16556            matches!(self.config.dialect, Some(DialectType::ClickHouse))
16557                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16558                && !name.starts_with('{')
16559                && !name.contains('(')
16560                && !name.contains(')')
16561                && name != "?"
16562                && name
16563                    .chars()
16564                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
16565        let needs_quoting = id.quoted
16566            || self.is_reserved_keyword(name)
16567            || self.config.always_quote_identifiers
16568            || needs_digit_quoting
16569            || mysql_invalid_hex_identifier
16570            || clickhouse_unsafe_identifier;
16571
16572        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
16573        // When quoted, we need to output `name`(16) not `name(16)`
16574        let (base_name, suffix) = if needs_quoting {
16575            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
16576            if let Some(paren_pos) = name.find('(') {
16577                let base = &name[..paren_pos];
16578                let rest = &name[paren_pos..];
16579                // Verify it looks like (digits) or (digits) ASC/DESC
16580                if rest.starts_with('(')
16581                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
16582                {
16583                    // Check if content between parens is all digits
16584                    let close_paren = rest.find(')').unwrap_or(rest.len());
16585                    let inside = &rest[1..close_paren];
16586                    if inside.chars().all(|c| c.is_ascii_digit()) {
16587                        (base.to_string(), rest.to_string())
16588                    } else {
16589                        (name.to_string(), String::new())
16590                    }
16591                } else {
16592                    (name.to_string(), String::new())
16593                }
16594            } else if name.ends_with(" ASC") {
16595                let base = &name[..name.len() - 4];
16596                (base.to_string(), " ASC".to_string())
16597            } else if name.ends_with(" DESC") {
16598                let base = &name[..name.len() - 5];
16599                (base.to_string(), " DESC".to_string())
16600            } else {
16601                (name.to_string(), String::new())
16602            }
16603        } else {
16604            (name.to_string(), String::new())
16605        };
16606
16607        // Normalize identifier if configured, with special handling for Exasol
16608        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
16609        // should be uppercased when not already quoted (to match Python sqlglot behavior)
16610        let output_name = if self.config.normalize_identifiers && !id.quoted {
16611            base_name.to_ascii_lowercase()
16612        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
16613            && !id.quoted
16614            && self.is_reserved_keyword(name)
16615        {
16616            // Exasol: uppercase reserved keywords when quoting them
16617            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
16618            base_name.to_ascii_uppercase()
16619        } else {
16620            base_name
16621        };
16622
16623        if needs_quoting {
16624            // Escape any quote characters within the identifier
16625            let escaped_name = if quote_style.start == quote_style.end {
16626                // Same start/end char (e.g., " or `) - double the quote char
16627                output_name.replace(
16628                    quote_style.end,
16629                    &format!("{}{}", quote_style.end, quote_style.end),
16630                )
16631            } else {
16632                // Different start/end (e.g., [ and ]) - escape only the end char
16633                output_name.replace(
16634                    quote_style.end,
16635                    &format!("{}{}", quote_style.end, quote_style.end),
16636                )
16637            };
16638            self.write(&format!(
16639                "{}{}{}{}",
16640                quote_style.start, escaped_name, quote_style.end, suffix
16641            ));
16642        } else {
16643            self.write(&output_name);
16644        }
16645
16646        // Output trailing comments
16647        for comment in &id.trailing_comments {
16648            self.write(" ");
16649            self.write_formatted_comment(comment);
16650        }
16651        Ok(())
16652    }
16653
16654    fn generate_column(&mut self, col: &Column) -> Result<()> {
16655        use crate::dialects::DialectType;
16656
16657        if let Some(table) = &col.table {
16658            // Exasol special case: LOCAL as column table prefix should NOT be quoted
16659            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
16660            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
16661            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
16662                && !table.quoted
16663                && table.name.eq_ignore_ascii_case("LOCAL");
16664
16665            if is_exasol_local_prefix {
16666                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
16667                self.write("LOCAL");
16668            } else {
16669                self.generate_identifier(table)?;
16670            }
16671            self.write(".");
16672        }
16673        self.generate_identifier(&col.name)?;
16674        // Oracle-style join marker (+)
16675        // Only output if dialect supports it (Oracle, Exasol)
16676        if col.join_mark && self.config.supports_column_join_marks {
16677            self.write(" (+)");
16678        }
16679        // Output trailing comments
16680        for comment in &col.trailing_comments {
16681            self.write_space();
16682            self.write_formatted_comment(comment);
16683        }
16684        Ok(())
16685    }
16686
16687    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
16688        self.write_keyword("PREPARE");
16689        self.write_space();
16690        self.generate_identifier(&prepare.name)?;
16691
16692        if !prepare.parameter_types.is_empty() {
16693            self.write(" (");
16694            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
16695                if i > 0 {
16696                    self.write(", ");
16697                }
16698                self.generate_data_type(data_type)?;
16699            }
16700            self.write(")");
16701        }
16702
16703        self.write_space();
16704        self.write_keyword("AS");
16705        self.write_space();
16706        self.generate_expression(&prepare.statement)
16707    }
16708
16709    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
16710    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
16711    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
16712        use crate::dialects::DialectType;
16713        use crate::expressions::PseudocolumnType;
16714
16715        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
16716        if pc.kind == PseudocolumnType::Sysdate
16717            && !matches!(
16718                self.config.dialect,
16719                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
16720            )
16721        {
16722            self.write_keyword("CURRENT_TIMESTAMP");
16723            // Add () for dialects that expect it
16724            if matches!(
16725                self.config.dialect,
16726                Some(DialectType::MySQL)
16727                    | Some(DialectType::ClickHouse)
16728                    | Some(DialectType::Spark)
16729                    | Some(DialectType::Databricks)
16730                    | Some(DialectType::Hive)
16731            ) {
16732                self.write("()");
16733            }
16734        } else {
16735            self.write(pc.kind.as_str());
16736        }
16737        Ok(())
16738    }
16739
16740    /// Generate CONNECT BY clause (Oracle hierarchical queries)
16741    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
16742        use crate::dialects::DialectType;
16743
16744        // Generate native CONNECT BY for Oracle and Snowflake
16745        // For other dialects, add a comment noting manual conversion needed
16746        let supports_connect_by = matches!(
16747            self.config.dialect,
16748            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
16749        );
16750
16751        if !supports_connect_by && self.config.dialect.is_some() {
16752            // Add comment for unsupported dialects
16753            if self.config.pretty {
16754                self.write_newline();
16755            } else {
16756                self.write_space();
16757            }
16758            self.write_unsupported_comment(
16759                "CONNECT BY requires manual conversion to recursive CTE",
16760            )?;
16761        }
16762
16763        // Generate START WITH if present (before CONNECT BY)
16764        if let Some(start) = &connect.start {
16765            if self.config.pretty {
16766                self.write_newline();
16767            } else {
16768                self.write_space();
16769            }
16770            self.write_keyword("START WITH");
16771            self.write_space();
16772            self.generate_expression(start)?;
16773        }
16774
16775        // Generate CONNECT BY
16776        if self.config.pretty {
16777            self.write_newline();
16778        } else {
16779            self.write_space();
16780        }
16781        self.write_keyword("CONNECT BY");
16782        if connect.nocycle {
16783            self.write_space();
16784            self.write_keyword("NOCYCLE");
16785        }
16786        self.write_space();
16787        self.generate_expression(&connect.connect)?;
16788
16789        Ok(())
16790    }
16791
16792    /// Generate Connect expression (for Expression::Connect variant)
16793    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16794        self.generate_connect(connect)
16795    }
16796
16797    /// Generate PRIOR expression
16798    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16799        self.write_keyword("PRIOR");
16800        self.write_space();
16801        self.generate_expression(&prior.this)?;
16802        Ok(())
16803    }
16804
16805    /// Generate CONNECT_BY_ROOT function
16806    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16807    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16808        self.write_keyword("CONNECT_BY_ROOT");
16809        self.write_space();
16810        self.generate_expression(&cbr.this)?;
16811        Ok(())
16812    }
16813
16814    /// Generate MATCH_RECOGNIZE clause
16815    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16816        use crate::dialects::DialectType;
16817
16818        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16819        let supports_match_recognize = matches!(
16820            self.config.dialect,
16821            Some(DialectType::Oracle)
16822                | Some(DialectType::Snowflake)
16823                | Some(DialectType::Presto)
16824                | Some(DialectType::Trino)
16825        );
16826
16827        // Generate the source table first
16828        if let Some(source) = &mr.this {
16829            self.generate_expression(source)?;
16830        }
16831
16832        if !supports_match_recognize {
16833            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16834            return Ok(());
16835        }
16836
16837        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16838        if self.config.pretty {
16839            self.write_newline();
16840        } else {
16841            self.write_space();
16842        }
16843
16844        self.write_keyword("MATCH_RECOGNIZE");
16845        self.write(" (");
16846
16847        if self.config.pretty {
16848            self.indent_level += 1;
16849        }
16850
16851        let mut needs_separator = false;
16852
16853        // PARTITION BY
16854        if let Some(partition_by) = &mr.partition_by {
16855            if !partition_by.is_empty() {
16856                if self.config.pretty {
16857                    self.write_newline();
16858                    self.write_indent();
16859                }
16860                self.write_keyword("PARTITION BY");
16861                self.write_space();
16862                for (i, expr) in partition_by.iter().enumerate() {
16863                    if i > 0 {
16864                        self.write(", ");
16865                    }
16866                    self.generate_expression(expr)?;
16867                }
16868                needs_separator = true;
16869            }
16870        }
16871
16872        // ORDER BY
16873        if let Some(order_by) = &mr.order_by {
16874            if !order_by.is_empty() {
16875                if needs_separator {
16876                    if self.config.pretty {
16877                        self.write_newline();
16878                        self.write_indent();
16879                    } else {
16880                        self.write_space();
16881                    }
16882                } else if self.config.pretty {
16883                    self.write_newline();
16884                    self.write_indent();
16885                }
16886                self.write_keyword("ORDER BY");
16887                // In pretty mode, put each ORDER BY column on a new indented line
16888                if self.config.pretty {
16889                    self.indent_level += 1;
16890                    for (i, ordered) in order_by.iter().enumerate() {
16891                        if i > 0 {
16892                            self.write(",");
16893                        }
16894                        self.write_newline();
16895                        self.write_indent();
16896                        self.generate_ordered(ordered)?;
16897                    }
16898                    self.indent_level -= 1;
16899                } else {
16900                    self.write_space();
16901                    for (i, ordered) in order_by.iter().enumerate() {
16902                        if i > 0 {
16903                            self.write(", ");
16904                        }
16905                        self.generate_ordered(ordered)?;
16906                    }
16907                }
16908                needs_separator = true;
16909            }
16910        }
16911
16912        // MEASURES
16913        if let Some(measures) = &mr.measures {
16914            if !measures.is_empty() {
16915                if needs_separator {
16916                    if self.config.pretty {
16917                        self.write_newline();
16918                        self.write_indent();
16919                    } else {
16920                        self.write_space();
16921                    }
16922                } else if self.config.pretty {
16923                    self.write_newline();
16924                    self.write_indent();
16925                }
16926                self.write_keyword("MEASURES");
16927                // In pretty mode, put each MEASURE on a new indented line
16928                if self.config.pretty {
16929                    self.indent_level += 1;
16930                    for (i, measure) in measures.iter().enumerate() {
16931                        if i > 0 {
16932                            self.write(",");
16933                        }
16934                        self.write_newline();
16935                        self.write_indent();
16936                        // Handle RUNNING/FINAL prefix
16937                        if let Some(semantics) = &measure.window_frame {
16938                            match semantics {
16939                                MatchRecognizeSemantics::Running => {
16940                                    self.write_keyword("RUNNING");
16941                                    self.write_space();
16942                                }
16943                                MatchRecognizeSemantics::Final => {
16944                                    self.write_keyword("FINAL");
16945                                    self.write_space();
16946                                }
16947                            }
16948                        }
16949                        self.generate_expression(&measure.this)?;
16950                    }
16951                    self.indent_level -= 1;
16952                } else {
16953                    self.write_space();
16954                    for (i, measure) in measures.iter().enumerate() {
16955                        if i > 0 {
16956                            self.write(", ");
16957                        }
16958                        // Handle RUNNING/FINAL prefix
16959                        if let Some(semantics) = &measure.window_frame {
16960                            match semantics {
16961                                MatchRecognizeSemantics::Running => {
16962                                    self.write_keyword("RUNNING");
16963                                    self.write_space();
16964                                }
16965                                MatchRecognizeSemantics::Final => {
16966                                    self.write_keyword("FINAL");
16967                                    self.write_space();
16968                                }
16969                            }
16970                        }
16971                        self.generate_expression(&measure.this)?;
16972                    }
16973                }
16974                needs_separator = true;
16975            }
16976        }
16977
16978        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
16979        if let Some(rows) = &mr.rows {
16980            if needs_separator {
16981                if self.config.pretty {
16982                    self.write_newline();
16983                    self.write_indent();
16984                } else {
16985                    self.write_space();
16986                }
16987            } else if self.config.pretty {
16988                self.write_newline();
16989                self.write_indent();
16990            }
16991            match rows {
16992                MatchRecognizeRows::OneRowPerMatch => {
16993                    self.write_keyword("ONE ROW PER MATCH");
16994                }
16995                MatchRecognizeRows::AllRowsPerMatch => {
16996                    self.write_keyword("ALL ROWS PER MATCH");
16997                }
16998                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
16999                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
17000                }
17001                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
17002                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
17003                }
17004                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
17005                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
17006                }
17007            }
17008            needs_separator = true;
17009        }
17010
17011        // AFTER MATCH SKIP
17012        if let Some(after) = &mr.after {
17013            if needs_separator {
17014                if self.config.pretty {
17015                    self.write_newline();
17016                    self.write_indent();
17017                } else {
17018                    self.write_space();
17019                }
17020            } else if self.config.pretty {
17021                self.write_newline();
17022                self.write_indent();
17023            }
17024            match after {
17025                MatchRecognizeAfter::PastLastRow => {
17026                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
17027                }
17028                MatchRecognizeAfter::ToNextRow => {
17029                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
17030                }
17031                MatchRecognizeAfter::ToFirst(ident) => {
17032                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
17033                    self.write_space();
17034                    self.generate_identifier(ident)?;
17035                }
17036                MatchRecognizeAfter::ToLast(ident) => {
17037                    self.write_keyword("AFTER MATCH SKIP TO LAST");
17038                    self.write_space();
17039                    self.generate_identifier(ident)?;
17040                }
17041            }
17042            needs_separator = true;
17043        }
17044
17045        // PATTERN
17046        if let Some(pattern) = &mr.pattern {
17047            if needs_separator {
17048                if self.config.pretty {
17049                    self.write_newline();
17050                    self.write_indent();
17051                } else {
17052                    self.write_space();
17053                }
17054            } else if self.config.pretty {
17055                self.write_newline();
17056                self.write_indent();
17057            }
17058            self.write_keyword("PATTERN");
17059            self.write_space();
17060            self.write("(");
17061            self.write(pattern);
17062            self.write(")");
17063            needs_separator = true;
17064        }
17065
17066        // DEFINE
17067        if let Some(define) = &mr.define {
17068            if !define.is_empty() {
17069                if needs_separator {
17070                    if self.config.pretty {
17071                        self.write_newline();
17072                        self.write_indent();
17073                    } else {
17074                        self.write_space();
17075                    }
17076                } else if self.config.pretty {
17077                    self.write_newline();
17078                    self.write_indent();
17079                }
17080                self.write_keyword("DEFINE");
17081                // In pretty mode, put each DEFINE on a new indented line
17082                if self.config.pretty {
17083                    self.indent_level += 1;
17084                    for (i, (name, expr)) in define.iter().enumerate() {
17085                        if i > 0 {
17086                            self.write(",");
17087                        }
17088                        self.write_newline();
17089                        self.write_indent();
17090                        self.generate_identifier(name)?;
17091                        self.write(" AS ");
17092                        self.generate_expression(expr)?;
17093                    }
17094                    self.indent_level -= 1;
17095                } else {
17096                    self.write_space();
17097                    for (i, (name, expr)) in define.iter().enumerate() {
17098                        if i > 0 {
17099                            self.write(", ");
17100                        }
17101                        self.generate_identifier(name)?;
17102                        self.write(" AS ");
17103                        self.generate_expression(expr)?;
17104                    }
17105                }
17106            }
17107        }
17108
17109        if self.config.pretty {
17110            self.indent_level -= 1;
17111            self.write_newline();
17112        }
17113        self.write(")");
17114
17115        // Alias - only include AS if it was explicitly present in the input
17116        if let Some(alias) = &mr.alias {
17117            self.write(" ");
17118            if mr.alias_explicit_as {
17119                self.write_keyword("AS");
17120                self.write(" ");
17121            }
17122            self.generate_identifier(alias)?;
17123        }
17124
17125        Ok(())
17126    }
17127
17128    /// Generate a query hint /*+ ... */
17129    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
17130        use crate::dialects::DialectType;
17131
17132        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
17133        let supports_hints = matches!(
17134            self.config.dialect,
17135            None |  // No dialect = preserve everything
17136            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
17137            Some(DialectType::Spark) | Some(DialectType::Hive) |
17138            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
17139        );
17140
17141        if !supports_hints || hint.expressions.is_empty() {
17142            return Ok(());
17143        }
17144
17145        // First, expand raw hint text into individual hint strings
17146        // This handles the case where the parser stored multiple hints as a single raw string
17147        let mut hint_strings: Vec<String> = Vec::new();
17148        for expr in &hint.expressions {
17149            match expr {
17150                HintExpression::Raw(text) => {
17151                    // Parse raw hint text into individual hint function calls
17152                    let parsed = self.parse_raw_hint_text(text);
17153                    hint_strings.extend(parsed);
17154                }
17155                _ => {
17156                    hint_strings.push(self.hint_expression_to_string(expr)?);
17157                }
17158            }
17159        }
17160
17161        // In pretty mode with multiple hints, always use multiline format
17162        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
17163        // always joins with newlines in pretty mode
17164        let use_multiline = self.config.pretty && hint_strings.len() > 1;
17165
17166        if use_multiline {
17167            // Pretty print with each hint on its own line
17168            self.write(" /*+ ");
17169            for (i, hint_str) in hint_strings.iter().enumerate() {
17170                if i > 0 {
17171                    self.write_newline();
17172                    self.write("  "); // 2-space indent within hint block
17173                }
17174                self.write(hint_str);
17175            }
17176            self.write(" */");
17177        } else {
17178            // Single line format
17179            self.write(" /*+ ");
17180            let sep = match self.config.dialect {
17181                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
17182                _ => " ",
17183            };
17184            for (i, hint_str) in hint_strings.iter().enumerate() {
17185                if i > 0 {
17186                    self.write(sep);
17187                }
17188                self.write(hint_str);
17189            }
17190            self.write(" */");
17191        }
17192
17193        Ok(())
17194    }
17195
17196    /// Parse raw hint text into individual hint function calls
17197    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
17198    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
17199    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
17200        let mut results = Vec::new();
17201        let mut chars = text.chars().peekable();
17202        let mut current = String::new();
17203        let mut paren_depth = 0;
17204        let mut has_unparseable_content = false;
17205        let mut position_after_last_function = 0;
17206        let mut char_position = 0;
17207
17208        while let Some(c) = chars.next() {
17209            char_position += c.len_utf8();
17210            match c {
17211                '(' => {
17212                    paren_depth += 1;
17213                    current.push(c);
17214                }
17215                ')' => {
17216                    paren_depth -= 1;
17217                    current.push(c);
17218                    // When we close the outer parenthesis, we've completed a hint function
17219                    if paren_depth == 0 {
17220                        let trimmed = current.trim().to_string();
17221                        if !trimmed.is_empty() {
17222                            // Format this hint for pretty printing if needed
17223                            let formatted = self.format_hint_function(&trimmed);
17224                            results.push(formatted);
17225                        }
17226                        current.clear();
17227                        position_after_last_function = char_position;
17228                    }
17229                }
17230                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
17231                    // Space/comma/whitespace outside parentheses - skip
17232                }
17233                _ if paren_depth == 0 => {
17234                    // Character outside parentheses - accumulate for potential hint name
17235                    current.push(c);
17236                }
17237                _ => {
17238                    current.push(c);
17239                }
17240            }
17241        }
17242
17243        // Check if there's remaining text after the last function call
17244        let remaining_text = text[position_after_last_function..].trim();
17245        if !remaining_text.is_empty() {
17246            // Check if it looks like valid hint function names
17247            // Valid hint identifiers typically are uppercase alphanumeric with underscores
17248            // If we see multiple words without parens, it's likely unparseable
17249            let words: Vec<&str> = remaining_text.split_whitespace().collect();
17250            let looks_like_hint_functions = words.iter().all(|word| {
17251                // A valid hint name followed by opening paren, or a standalone uppercase identifier
17252                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
17253            });
17254
17255            if !looks_like_hint_functions && words.len() > 1 {
17256                has_unparseable_content = true;
17257            }
17258        }
17259
17260        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
17261        if has_unparseable_content {
17262            return vec![text.trim().to_string()];
17263        }
17264
17265        // If we couldn't parse anything, return the original text as a single hint
17266        if results.is_empty() {
17267            results.push(text.trim().to_string());
17268        }
17269
17270        results
17271    }
17272
17273    /// Format a hint function for pretty printing
17274    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
17275    fn format_hint_function(&self, hint: &str) -> String {
17276        if !self.config.pretty {
17277            return hint.to_string();
17278        }
17279
17280        // Try to parse NAME(args) pattern
17281        if let Some(paren_pos) = hint.find('(') {
17282            if hint.ends_with(')') {
17283                let name = &hint[..paren_pos];
17284                let args_str = &hint[paren_pos + 1..hint.len() - 1];
17285
17286                // Parse arguments (space-separated for Oracle hints)
17287                let args: Vec<&str> = args_str.split_whitespace().collect();
17288
17289                // Calculate total width of arguments
17290                let total_args_width: usize =
17291                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
17292
17293                // If too wide, format on multiple lines
17294                if total_args_width > self.config.max_text_width && !args.is_empty() {
17295                    let mut result = format!("{}(\n", name);
17296                    for arg in &args {
17297                        result.push_str("    "); // 4-space indent for args
17298                        result.push_str(arg);
17299                        result.push('\n');
17300                    }
17301                    result.push_str("  )"); // 2-space indent for closing paren
17302                    return result;
17303                }
17304            }
17305        }
17306
17307        hint.to_string()
17308    }
17309
17310    /// Convert a hint expression to a string, handling multiline formatting for long arguments
17311    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
17312        match expr {
17313            HintExpression::Function { name, args } => {
17314                // Generate each argument to a string
17315                let arg_strings: Vec<String> = args
17316                    .iter()
17317                    .map(|arg| {
17318                        let mut gen = Generator::with_arc_config(self.config.clone());
17319                        gen.generate_expression(arg)?;
17320                        Ok(gen.output)
17321                    })
17322                    .collect::<Result<Vec<_>>>()?;
17323
17324                // Oracle hints use space-separated arguments, not comma-separated
17325                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
17326                    + arg_strings.len().saturating_sub(1); // spaces between args
17327
17328                // Check if function args need multiline formatting
17329                // Use too_wide check for argument formatting
17330                let args_multiline =
17331                    self.config.pretty && total_args_width > self.config.max_text_width;
17332
17333                if args_multiline && !arg_strings.is_empty() {
17334                    // Multiline format for long argument lists
17335                    let mut result = format!("{}(\n", name);
17336                    for arg_str in &arg_strings {
17337                        result.push_str("    "); // 4-space indent for args
17338                        result.push_str(arg_str);
17339                        result.push('\n');
17340                    }
17341                    result.push_str("  )"); // 2-space indent for closing paren
17342                    Ok(result)
17343                } else {
17344                    // Single line format with space-separated args (Oracle style)
17345                    let args_str = arg_strings.join(" ");
17346                    Ok(format!("{}({})", name, args_str))
17347                }
17348            }
17349            HintExpression::Identifier(name) => Ok(name.clone()),
17350            HintExpression::Raw(text) => {
17351                // For pretty printing, try to format the raw text
17352                if self.config.pretty {
17353                    Ok(self.format_hint_function(text))
17354                } else {
17355                    Ok(text.clone())
17356                }
17357            }
17358        }
17359    }
17360
17361    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
17362        // PostgreSQL ONLY modifier: prevents scanning child tables
17363        if table.only {
17364            self.write_keyword("ONLY");
17365            self.write_space();
17366        }
17367
17368        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
17369        if let Some(ref identifier_func) = table.identifier_func {
17370            self.generate_expression(identifier_func)?;
17371            // If table name parts are present, emit .catalog.schema.name after the function
17372            if !table.name.name.is_empty() {
17373                if let Some(catalog) = &table.catalog {
17374                    self.write(".");
17375                    self.generate_identifier(catalog)?;
17376                }
17377                if let Some(schema) = &table.schema {
17378                    self.write(".");
17379                    self.generate_identifier(schema)?;
17380                }
17381                self.write(".");
17382                self.generate_identifier(&table.name)?;
17383            }
17384        } else {
17385            if let Some(catalog) = &table.catalog {
17386                self.generate_identifier(catalog)?;
17387                self.write(".");
17388            }
17389            if let Some(schema) = &table.schema {
17390                self.generate_identifier(schema)?;
17391                self.write(".");
17392            }
17393            self.generate_identifier(&table.name)?;
17394        }
17395
17396        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
17397        if let Some(changes) = &table.changes {
17398            self.write(" ");
17399            self.generate_changes(changes)?;
17400        }
17401
17402        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
17403        if !table.partitions.is_empty() {
17404            self.write_space();
17405            self.write_keyword("PARTITION");
17406            self.write("(");
17407            for (i, partition) in table.partitions.iter().enumerate() {
17408                if i > 0 {
17409                    self.write(", ");
17410                }
17411                self.generate_identifier(partition)?;
17412            }
17413            self.write(")");
17414        }
17415
17416        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
17417        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
17418        if table.changes.is_none() {
17419            if let Some(when) = &table.when {
17420                self.write_space();
17421                self.generate_historical_data(when)?;
17422            }
17423        }
17424
17425        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
17426        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
17427        if !system_time_post_alias {
17428            if let Some(ref system_time) = table.system_time {
17429                self.write_space();
17430                self.write(system_time);
17431            }
17432        }
17433
17434        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
17435        if let Some(ref version) = table.version {
17436            self.write_space();
17437            self.generate_version(version)?;
17438        }
17439
17440        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
17441        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
17442        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
17443        let alias_post_tablesample = self.config.alias_post_tablesample;
17444
17445        if alias_post_tablesample {
17446            // TABLESAMPLE before alias (Oracle, Hive, Spark)
17447            self.generate_table_sample_clause(table)?;
17448        }
17449
17450        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
17451        // For SQLite, INDEXED BY hints come after the alias, so skip here
17452        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
17453            && table.hints.iter().any(|h| {
17454                if let Expression::Identifier(id) = h {
17455                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
17456                } else {
17457                    false
17458                }
17459            });
17460        if !table.hints.is_empty() && !is_sqlite_hint {
17461            for hint in &table.hints {
17462                self.write_space();
17463                self.generate_expression(hint)?;
17464            }
17465        }
17466
17467        if let Some(alias) = &table.alias {
17468            self.write_space();
17469            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
17470            // Generic mode and most dialects always use AS for table aliases
17471            let always_use_as = self.config.dialect.is_none()
17472                || matches!(
17473                    self.config.dialect,
17474                    Some(DialectType::Generic)
17475                        | Some(DialectType::PostgreSQL)
17476                        | Some(DialectType::Redshift)
17477                        | Some(DialectType::Snowflake)
17478                        | Some(DialectType::BigQuery)
17479                        | Some(DialectType::DuckDB)
17480                        | Some(DialectType::Presto)
17481                        | Some(DialectType::Trino)
17482                        | Some(DialectType::TSQL)
17483                        | Some(DialectType::Fabric)
17484                        | Some(DialectType::MySQL)
17485                        | Some(DialectType::Spark)
17486                        | Some(DialectType::Hive)
17487                        | Some(DialectType::SQLite)
17488                        | Some(DialectType::Drill)
17489                );
17490            let is_stage_ref = table.name.name.starts_with('@');
17491            // Oracle never uses AS for table aliases
17492            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17493            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
17494                self.write_keyword("AS");
17495                self.write_space();
17496            }
17497            self.generate_identifier(alias)?;
17498
17499            // Output column aliases if present: AS t(c1, c2)
17500            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
17501            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
17502                self.write("(");
17503                for (i, col_alias) in table.column_aliases.iter().enumerate() {
17504                    if i > 0 {
17505                        self.write(", ");
17506                    }
17507                    self.generate_identifier(col_alias)?;
17508                }
17509                self.write(")");
17510            }
17511        }
17512
17513        // BigQuery: FOR SYSTEM_TIME AS OF after alias
17514        if system_time_post_alias {
17515            if let Some(ref system_time) = table.system_time {
17516                self.write_space();
17517                self.write(system_time);
17518            }
17519        }
17520
17521        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
17522        if !alias_post_tablesample {
17523            self.generate_table_sample_clause(table)?;
17524        }
17525
17526        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
17527        if is_sqlite_hint {
17528            for hint in &table.hints {
17529                self.write_space();
17530                self.generate_expression(hint)?;
17531            }
17532        }
17533
17534        // ClickHouse FINAL modifier
17535        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17536            self.write_space();
17537            self.write_keyword("FINAL");
17538        }
17539
17540        // Output trailing comments
17541        for comment in &table.trailing_comments {
17542            self.write_space();
17543            self.write_formatted_comment(comment);
17544        }
17545        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
17546        // output here - they are output by the FROM/PIVOT generator after the full expression
17547
17548        Ok(())
17549    }
17550
17551    /// Helper to output TABLESAMPLE clause for a table reference
17552    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
17553        if let Some(ref ts) = table.table_sample {
17554            self.write_space();
17555            if ts.is_using_sample {
17556                self.write_keyword("USING SAMPLE");
17557            } else {
17558                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
17559                self.write_keyword(self.config.tablesample_keywords);
17560            }
17561            self.generate_sample_body(ts)?;
17562            // Seed for table-level sample - use dialect's configured keyword
17563            if let Some(ref seed) = ts.seed {
17564                self.write_space();
17565                self.write_keyword(self.config.tablesample_seed_keyword);
17566                self.write(" (");
17567                self.generate_expression(seed)?;
17568                self.write(")");
17569            }
17570        }
17571        Ok(())
17572    }
17573
17574    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
17575        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
17576        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
17577
17578        if sr.quoted {
17579            self.write("'");
17580        }
17581
17582        self.write(&sr.name);
17583        if let Some(path) = &sr.path {
17584            self.write(path);
17585        }
17586
17587        if sr.quoted {
17588            self.write("'");
17589        }
17590
17591        // Output FILE_FORMAT and PATTERN if present
17592        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
17593        if has_options {
17594            self.write(" (");
17595            let mut first = true;
17596
17597            if let Some(file_format) = &sr.file_format {
17598                if !first {
17599                    self.write(", ");
17600                }
17601                self.write_keyword("FILE_FORMAT");
17602                self.write(" => ");
17603                self.generate_expression(file_format)?;
17604                first = false;
17605            }
17606
17607            if let Some(pattern) = &sr.pattern {
17608                if !first {
17609                    self.write(", ");
17610                }
17611                self.write_keyword("PATTERN");
17612                self.write(" => '");
17613                self.write(pattern);
17614                self.write("'");
17615            }
17616
17617            self.write(")");
17618        }
17619        Ok(())
17620    }
17621
17622    fn generate_star(&mut self, star: &Star) -> Result<()> {
17623        use crate::dialects::DialectType;
17624
17625        if let Some(table) = &star.table {
17626            self.generate_identifier(table)?;
17627            self.write(".");
17628        }
17629        self.write("*");
17630
17631        // Generate EXCLUDE/EXCEPT clause based on dialect
17632        if let Some(except) = &star.except {
17633            if !except.is_empty() {
17634                self.write_space();
17635                // Use dialect-appropriate keyword
17636                match self.config.dialect {
17637                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
17638                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
17639                        self.write_keyword("EXCLUDE")
17640                    }
17641                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
17642                }
17643                self.write(" (");
17644                for (i, col) in except.iter().enumerate() {
17645                    if i > 0 {
17646                        self.write(", ");
17647                    }
17648                    self.generate_identifier(col)?;
17649                }
17650                self.write(")");
17651            }
17652        }
17653
17654        // Generate REPLACE clause
17655        if let Some(replace) = &star.replace {
17656            if !replace.is_empty() {
17657                self.write_space();
17658                self.write_keyword("REPLACE");
17659                self.write(" (");
17660                for (i, alias) in replace.iter().enumerate() {
17661                    if i > 0 {
17662                        self.write(", ");
17663                    }
17664                    self.generate_expression(&alias.this)?;
17665                    self.write_space();
17666                    self.write_keyword("AS");
17667                    self.write_space();
17668                    self.generate_identifier(&alias.alias)?;
17669                }
17670                self.write(")");
17671            }
17672        }
17673
17674        // Generate RENAME clause (Snowflake specific)
17675        if let Some(rename) = &star.rename {
17676            if !rename.is_empty() {
17677                self.write_space();
17678                self.write_keyword("RENAME");
17679                self.write(" (");
17680                for (i, (old_name, new_name)) in rename.iter().enumerate() {
17681                    if i > 0 {
17682                        self.write(", ");
17683                    }
17684                    self.generate_identifier(old_name)?;
17685                    self.write_space();
17686                    self.write_keyword("AS");
17687                    self.write_space();
17688                    self.generate_identifier(new_name)?;
17689                }
17690                self.write(")");
17691            }
17692        }
17693
17694        // Output trailing comments
17695        for comment in &star.trailing_comments {
17696            self.write_space();
17697            self.write_formatted_comment(comment);
17698        }
17699
17700        Ok(())
17701    }
17702
17703    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
17704    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
17705        self.write("{");
17706        match expr {
17707            Expression::Star(star) => {
17708                // Generate the star (table.* or just * with optional EXCLUDE)
17709                self.generate_star(star)?;
17710            }
17711            Expression::ILike(ilike) => {
17712                // {* ILIKE 'pattern'} syntax
17713                self.generate_expression(&ilike.left)?;
17714                self.write_space();
17715                self.write_keyword("ILIKE");
17716                self.write_space();
17717                self.generate_expression(&ilike.right)?;
17718            }
17719            _ => {
17720                self.generate_expression(expr)?;
17721            }
17722        }
17723        self.write("}");
17724        Ok(())
17725    }
17726
17727    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
17728        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
17729        // to avoid duplication (comments are captured as both Column.trailing_comments
17730        // and Alias.pre_alias_comments during parsing)
17731        match &alias.this {
17732            Expression::Column(col) => {
17733                // Generate column without trailing comments - they're in pre_alias_comments
17734                if let Some(table) = &col.table {
17735                    self.generate_identifier(table)?;
17736                    self.write(".");
17737                }
17738                self.generate_identifier(&col.name)?;
17739            }
17740            _ => {
17741                self.generate_expression(&alias.this)?;
17742            }
17743        }
17744
17745        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
17746        // moves pre-alias comments to after the alias. When there are also trailing_comments,
17747        // keep pre-alias comments in their original position (between expression and AS).
17748        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
17749            for comment in &alias.pre_alias_comments {
17750                self.write_space();
17751                self.write_formatted_comment(comment);
17752            }
17753        }
17754
17755        use crate::dialects::DialectType;
17756
17757        // Determine if we should skip AS keyword for table-valued function aliases
17758        // Oracle and some other dialects don't use AS for table aliases
17759        // Note: We specifically use TableFromRows here, NOT Function, because Function
17760        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
17761        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
17762        let is_table_source = matches!(
17763            &alias.this,
17764            Expression::JSONTable(_)
17765                | Expression::XMLTable(_)
17766                | Expression::TableFromRows(_)
17767                | Expression::Unnest(_)
17768                | Expression::MatchRecognize(_)
17769                | Expression::Select(_)
17770                | Expression::Subquery(_)
17771                | Expression::Paren(_)
17772                | Expression::JoinedTable(_)
17773        );
17774        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17775        let skip_as = is_table_source && dialect_skips_table_alias_as;
17776
17777        self.write_space();
17778        if !skip_as {
17779            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17780                if let Some(ref alias_keyword) = alias.alias_keyword {
17781                    self.write(alias_keyword);
17782                } else {
17783                    self.write_keyword("AS");
17784                }
17785            } else {
17786                self.write_keyword("AS");
17787            }
17788            self.write_space();
17789        }
17790
17791        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17792        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17793
17794        // Check if we have column aliases only (no table alias name)
17795        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17796            // Generate AS (col1, col2, ...)
17797            self.write("(");
17798            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17799                if i > 0 {
17800                    self.write(", ");
17801                }
17802                self.generate_alias_identifier(col_alias)?;
17803            }
17804            self.write(")");
17805        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17806            // Generate AS alias(col1, col2, ...)
17807            self.generate_alias_identifier(&alias.alias)?;
17808            self.write("(");
17809            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17810                if i > 0 {
17811                    self.write(", ");
17812                }
17813                self.generate_alias_identifier(col_alias)?;
17814            }
17815            self.write(")");
17816        } else {
17817            // Simple alias (or BigQuery without column aliases)
17818            self.generate_alias_identifier(&alias.alias)?;
17819        }
17820
17821        // Output trailing comments (comments after the alias)
17822        for comment in &alias.trailing_comments {
17823            self.write_space();
17824            self.write_formatted_comment(comment);
17825        }
17826
17827        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17828        // moves pre-alias comments to after the alias. When there are trailing_comments,
17829        // the pre-alias comments were already lost (consumed as column trailing comments
17830        // that were then used as pre_alias_comments). We always emit them after alias.
17831        if alias.trailing_comments.is_empty() {
17832            for comment in &alias.pre_alias_comments {
17833                self.write_space();
17834                self.write_formatted_comment(comment);
17835            }
17836        }
17837
17838        Ok(())
17839    }
17840
17841    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17842        use crate::dialects::DialectType;
17843
17844        // SingleStore uses :> syntax
17845        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17846            self.generate_expression(&cast.this)?;
17847            self.write(" :> ");
17848            self.generate_data_type(&cast.to)?;
17849            return Ok(());
17850        }
17851
17852        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17853        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17854            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17855                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17856            if is_unknown_type {
17857                if let Some(format) = &cast.format {
17858                    self.write_keyword("CAST");
17859                    self.write("(");
17860                    self.generate_expression(&cast.this)?;
17861                    self.write_space();
17862                    self.write_keyword("AS");
17863                    self.write_space();
17864                    self.write_keyword("FORMAT");
17865                    self.write_space();
17866                    self.generate_expression(format)?;
17867                    self.write(")");
17868                    return Ok(());
17869                }
17870            }
17871        }
17872
17873        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17874        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17875        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17876            if let Some(format) = &cast.format {
17877                // Check if target type is DATE or TIMESTAMP
17878                let is_date = matches!(cast.to, DataType::Date);
17879                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
17880
17881                if is_date || is_timestamp {
17882                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17883                    self.write_keyword(func_name);
17884                    self.write("(");
17885                    self.generate_expression(&cast.this)?;
17886                    self.write(", ");
17887
17888                    // Normalize format string for Oracle (HH -> HH12)
17889                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17890                    if let Expression::Literal(lit) = format.as_ref() {
17891                        if let Literal::String(fmt_str) = lit.as_ref() {
17892                            let normalized = self.normalize_oracle_format(fmt_str);
17893                            self.write("'");
17894                            self.write(&normalized);
17895                            self.write("'");
17896                        }
17897                    } else {
17898                        self.generate_expression(format)?;
17899                    }
17900
17901                    self.write(")");
17902                    return Ok(());
17903                }
17904            }
17905        }
17906
17907        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
17908        // This preserves sqlglot's typed inline array literal output.
17909        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17910            if let Expression::Array(arr) = &cast.this {
17911                self.generate_data_type(&cast.to)?;
17912                // Output just the bracket content [values] without the ARRAY prefix
17913                self.write("[");
17914                for (i, expr) in arr.expressions.iter().enumerate() {
17915                    if i > 0 {
17916                        self.write(", ");
17917                    }
17918                    self.generate_expression(expr)?;
17919                }
17920                self.write("]");
17921                return Ok(());
17922            }
17923            if matches!(&cast.this, Expression::ArrayFunc(_)) {
17924                self.generate_data_type(&cast.to)?;
17925                self.generate_expression(&cast.this)?;
17926                return Ok(());
17927            }
17928        }
17929
17930        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
17931        // convert the inner Struct to ROW(values...) format
17932        if matches!(
17933            self.config.dialect,
17934            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
17935        ) {
17936            if let Expression::Struct(ref s) = cast.this {
17937                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
17938                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
17939                    self.write_keyword("CAST");
17940                    self.write("(");
17941                    self.generate_struct_as_row(s)?;
17942                    self.write_space();
17943                    self.write_keyword("AS");
17944                    self.write_space();
17945                    self.generate_data_type(&cast.to)?;
17946                    self.write(")");
17947                    return Ok(());
17948                }
17949            }
17950        }
17951
17952        // Determine if we should use :: syntax based on dialect
17953        // PostgreSQL prefers :: for identity, most others prefer CAST()
17954        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
17955
17956        if use_double_colon {
17957            // PostgreSQL :: syntax: expr::type
17958            self.generate_expression(&cast.this)?;
17959            self.write("::");
17960            self.generate_data_type(&cast.to)?;
17961        } else {
17962            // Standard CAST() syntax
17963            self.write_keyword("CAST");
17964            self.write("(");
17965            self.generate_expression(&cast.this)?;
17966            self.write_space();
17967            self.write_keyword("AS");
17968            self.write_space();
17969            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
17970            // This matches Python sqlglot's CAST_MAPPING behavior
17971            if matches!(
17972                self.config.dialect,
17973                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
17974            ) {
17975                match &cast.to {
17976                    DataType::Custom { ref name } => {
17977                        if name.eq_ignore_ascii_case("LONGTEXT")
17978                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
17979                            || name.eq_ignore_ascii_case("TINYTEXT")
17980                            || name.eq_ignore_ascii_case("LONGBLOB")
17981                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
17982                            || name.eq_ignore_ascii_case("TINYBLOB")
17983                        {
17984                            self.write_keyword("CHAR");
17985                        } else {
17986                            self.generate_data_type(&cast.to)?;
17987                        }
17988                    }
17989                    DataType::VarChar { length, .. } => {
17990                        // MySQL CAST: VARCHAR -> CHAR
17991                        self.write_keyword("CHAR");
17992                        if let Some(n) = length {
17993                            self.write(&format!("({})", n));
17994                        }
17995                    }
17996                    DataType::Text => {
17997                        // MySQL CAST: TEXT -> CHAR
17998                        self.write_keyword("CHAR");
17999                    }
18000                    DataType::Timestamp {
18001                        precision,
18002                        timezone: false,
18003                    } => {
18004                        // MySQL CAST: TIMESTAMP -> DATETIME
18005                        self.write_keyword("DATETIME");
18006                        if let Some(p) = precision {
18007                            self.write(&format!("({})", p));
18008                        }
18009                    }
18010                    _ => {
18011                        self.generate_data_type(&cast.to)?;
18012                    }
18013                }
18014            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
18015                // Snowflake CAST: STRING -> VARCHAR
18016                match &cast.to {
18017                    DataType::String { length } => {
18018                        self.write_keyword("VARCHAR");
18019                        if let Some(n) = length {
18020                            self.write(&format!("({})", n));
18021                        }
18022                    }
18023                    _ => {
18024                        self.generate_data_type(&cast.to)?;
18025                    }
18026                }
18027            } else {
18028                self.generate_data_type(&cast.to)?;
18029            }
18030
18031            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
18032            if let Some(default) = &cast.default {
18033                self.write_space();
18034                self.write_keyword("DEFAULT");
18035                self.write_space();
18036                self.generate_expression(default)?;
18037                self.write_space();
18038                self.write_keyword("ON");
18039                self.write_space();
18040                self.write_keyword("CONVERSION");
18041                self.write_space();
18042                self.write_keyword("ERROR");
18043            }
18044
18045            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
18046            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
18047            if let Some(format) = &cast.format {
18048                // Check if Oracle dialect - use comma syntax
18049                if matches!(
18050                    self.config.dialect,
18051                    Some(crate::dialects::DialectType::Oracle)
18052                ) {
18053                    self.write(", ");
18054                } else {
18055                    self.write_space();
18056                    self.write_keyword("FORMAT");
18057                    self.write_space();
18058                }
18059                self.generate_expression(format)?;
18060            }
18061
18062            self.write(")");
18063            // Output trailing comments
18064            for comment in &cast.trailing_comments {
18065                self.write_space();
18066                self.write_formatted_comment(comment);
18067            }
18068        }
18069        Ok(())
18070    }
18071
18072    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
18073    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
18074    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
18075        self.write_keyword("ROW");
18076        self.write("(");
18077        for (i, (_, expr)) in s.fields.iter().enumerate() {
18078            if i > 0 {
18079                self.write(", ");
18080            }
18081            // Recursively convert inner Struct to ROW format
18082            if let Expression::Struct(ref inner_s) = expr {
18083                self.generate_struct_as_row(inner_s)?;
18084            } else {
18085                self.generate_expression(expr)?;
18086            }
18087        }
18088        self.write(")");
18089        Ok(())
18090    }
18091
18092    /// Normalize Oracle date/time format strings
18093    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
18094    fn normalize_oracle_format(&self, format: &str) -> String {
18095        // Replace standalone HH with HH12 (but not HH12 or HH24)
18096        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
18097        let mut result = String::new();
18098        let chars: Vec<char> = format.chars().collect();
18099        let mut i = 0;
18100
18101        while i < chars.len() {
18102            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
18103                // Check what follows HH
18104                if i + 2 < chars.len() {
18105                    let next = chars[i + 2];
18106                    if next == '1' || next == '2' {
18107                        // This is HH12 or HH24, keep as is
18108                        result.push('H');
18109                        result.push('H');
18110                        i += 2;
18111                        continue;
18112                    }
18113                }
18114                // Standalone HH -> HH12
18115                result.push_str("HH12");
18116                i += 2;
18117            } else {
18118                result.push(chars[i]);
18119                i += 1;
18120            }
18121        }
18122
18123        result
18124    }
18125
18126    /// Check if the current dialect prefers :: cast syntax
18127    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
18128    fn dialect_prefers_double_colon(&self) -> bool {
18129        matches!(self.config.dialect, Some(DialectType::ClickHouse))
18130    }
18131
18132    /// Generate MOD function - uses % operator for dialects that prefer or require it.
18133    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18134        use crate::dialects::DialectType;
18135
18136        // Several dialects prefer or require x % y instead of MOD(x, y).
18137        let use_percent_operator = matches!(
18138            self.config.dialect,
18139            Some(DialectType::Snowflake)
18140                | Some(DialectType::MySQL)
18141                | Some(DialectType::Presto)
18142                | Some(DialectType::Trino)
18143                | Some(DialectType::PostgreSQL)
18144                | Some(DialectType::DuckDB)
18145                | Some(DialectType::Hive)
18146                | Some(DialectType::Spark)
18147                | Some(DialectType::Databricks)
18148                | Some(DialectType::Athena)
18149                | Some(DialectType::TSQL)
18150                | Some(DialectType::Fabric)
18151        );
18152
18153        if use_percent_operator {
18154            // MOD(a, b) treats both arguments as grouped expressions. When
18155            // lowering to an infix operator, keep binary arguments grouped.
18156            let needs_paren = |e: &Expression| {
18157                matches!(
18158                    e,
18159                    Expression::Add(_)
18160                        | Expression::Sub(_)
18161                        | Expression::Mul(_)
18162                        | Expression::Div(_)
18163                        | Expression::Mod(_)
18164                        | Expression::ModFunc(_)
18165                )
18166            };
18167            if needs_paren(&f.this) {
18168                self.write("(");
18169                self.generate_expression(&f.this)?;
18170                self.write(")");
18171            } else {
18172                self.generate_expression(&f.this)?;
18173            }
18174            self.write(" % ");
18175            if needs_paren(&f.expression) {
18176                self.write("(");
18177                self.generate_expression(&f.expression)?;
18178                self.write(")");
18179            } else {
18180                self.generate_expression(&f.expression)?;
18181            }
18182            Ok(())
18183        } else {
18184            self.generate_binary_func("MOD", &f.this, &f.expression)
18185        }
18186    }
18187
18188    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
18189    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18190        use crate::dialects::DialectType;
18191
18192        // Snowflake normalizes IFNULL to COALESCE
18193        let func_name = match self.config.dialect {
18194            Some(DialectType::Snowflake) => "COALESCE",
18195            _ => "IFNULL",
18196        };
18197
18198        self.generate_binary_func(func_name, &f.this, &f.expression)
18199    }
18200
18201    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
18202    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18203        // Use original function name if preserved (for identity tests)
18204        if let Some(ref original_name) = f.original_name {
18205            return self.generate_binary_func(original_name, &f.this, &f.expression);
18206        }
18207
18208        // Otherwise, use dialect-specific function names
18209        use crate::dialects::DialectType;
18210        let func_name = match self.config.dialect {
18211            Some(DialectType::Snowflake)
18212            | Some(DialectType::ClickHouse)
18213            | Some(DialectType::PostgreSQL)
18214            | Some(DialectType::Presto)
18215            | Some(DialectType::Trino)
18216            | Some(DialectType::Athena)
18217            | Some(DialectType::DuckDB)
18218            | Some(DialectType::BigQuery)
18219            | Some(DialectType::Spark)
18220            | Some(DialectType::Databricks)
18221            | Some(DialectType::Hive) => "COALESCE",
18222            Some(DialectType::MySQL)
18223            | Some(DialectType::Doris)
18224            | Some(DialectType::StarRocks)
18225            | Some(DialectType::SingleStore)
18226            | Some(DialectType::TiDB) => "IFNULL",
18227            _ => "NVL",
18228        };
18229
18230        self.generate_binary_func(func_name, &f.this, &f.expression)
18231    }
18232
18233    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
18234    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
18235        use crate::dialects::DialectType;
18236
18237        // Snowflake normalizes STDDEV_SAMP to STDDEV
18238        let func_name = match self.config.dialect {
18239            Some(DialectType::Snowflake) => "STDDEV",
18240            _ => "STDDEV_SAMP",
18241        };
18242
18243        self.generate_agg_func(func_name, f)
18244    }
18245
18246    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
18247        self.generate_expression(&coll.this)?;
18248        self.write_space();
18249        self.write_keyword("COLLATE");
18250        self.write_space();
18251        if coll.quoted {
18252            // Single-quoted string: COLLATE 'de_DE'
18253            self.write("'");
18254            self.write(&coll.collation);
18255            self.write("'");
18256        } else if coll.double_quoted {
18257            // Double-quoted identifier: COLLATE "de_DE"
18258            self.write("\"");
18259            self.write(&coll.collation);
18260            self.write("\"");
18261        } else {
18262            // Unquoted identifier: COLLATE de_DE
18263            self.write(&coll.collation);
18264        }
18265        Ok(())
18266    }
18267
18268    fn generate_case(&mut self, case: &Case) -> Result<()> {
18269        // In pretty mode, decide whether to expand based on total text width
18270        let multiline_case = if self.config.pretty {
18271            // Build the flat representation to check width
18272            let mut statements: Vec<String> = Vec::new();
18273            let operand_str = if let Some(operand) = &case.operand {
18274                let s = self.generate_to_string(operand)?;
18275                statements.push(format!("CASE {}", s));
18276                s
18277            } else {
18278                statements.push("CASE".to_string());
18279                String::new()
18280            };
18281            let _ = operand_str;
18282            for (condition, result) in &case.whens {
18283                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
18284                statements.push(format!("THEN {}", self.generate_to_string(result)?));
18285            }
18286            if let Some(else_) = &case.else_ {
18287                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
18288            }
18289            statements.push("END".to_string());
18290            self.too_wide(&statements)
18291        } else {
18292            false
18293        };
18294
18295        self.write_keyword("CASE");
18296        if let Some(operand) = &case.operand {
18297            self.write_space();
18298            self.generate_expression(operand)?;
18299        }
18300        if multiline_case {
18301            self.indent_level += 1;
18302        }
18303        for (condition, result) in &case.whens {
18304            if multiline_case {
18305                self.write_newline();
18306                self.write_indent();
18307            } else {
18308                self.write_space();
18309            }
18310            self.write_keyword("WHEN");
18311            self.write_space();
18312            self.generate_expression(condition)?;
18313            if multiline_case {
18314                self.write_newline();
18315                self.write_indent();
18316            } else {
18317                self.write_space();
18318            }
18319            self.write_keyword("THEN");
18320            self.write_space();
18321            self.generate_expression(result)?;
18322        }
18323        if let Some(else_) = &case.else_ {
18324            if multiline_case {
18325                self.write_newline();
18326                self.write_indent();
18327            } else {
18328                self.write_space();
18329            }
18330            self.write_keyword("ELSE");
18331            self.write_space();
18332            self.generate_expression(else_)?;
18333        }
18334        if multiline_case {
18335            self.indent_level -= 1;
18336            self.write_newline();
18337            self.write_indent();
18338        } else {
18339            self.write_space();
18340        }
18341        self.write_keyword("END");
18342        // Emit any comments that were attached to the CASE keyword
18343        for comment in &case.comments {
18344            self.write(" ");
18345            self.write_formatted_comment(comment);
18346        }
18347        Ok(())
18348    }
18349
18350    fn generate_function(&mut self, func: &Function) -> Result<()> {
18351        // Normalize function name based on dialect settings
18352        let normalized_name = if func.name.eq_ignore_ascii_case("GROUPING")
18353            && func.args.len() > 1
18354            && matches!(
18355                self.config.dialect,
18356                Some(DialectType::TSQL | DialectType::Fabric)
18357            ) {
18358            Cow::Borrowed("GROUPING_ID")
18359        } else if matches!(self.config.dialect, Some(DialectType::Snowflake))
18360            && func.name.to_ascii_uppercase().starts_with("IDENTIFIER(")
18361        {
18362            Cow::Borrowed(func.name.as_str())
18363        } else {
18364            self.normalize_func_name(&func.name)
18365        };
18366
18367        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
18368        if matches!(self.config.dialect, Some(DialectType::DuckDB))
18369            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
18370        {
18371            self.write("LIST_FILTER(");
18372            self.write("[");
18373            for (i, arg) in func.args.iter().enumerate() {
18374                if i > 0 {
18375                    self.write(", ");
18376                }
18377                self.generate_expression(arg)?;
18378            }
18379            self.write("], _u -> NOT _u IS NULL)");
18380            return Ok(());
18381        }
18382
18383        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
18384        // rather than bracket-array syntax.
18385        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18386            && func.name.eq_ignore_ascii_case("TO_VARIANT")
18387            && func.args.len() == 1
18388        {
18389            let array_expressions = match &func.args[0] {
18390                Expression::ArrayFunc(arr) => Some(&arr.expressions),
18391                Expression::Array(arr) => Some(&arr.expressions),
18392                _ => None,
18393            };
18394            if let Some(expressions) = array_expressions {
18395                self.write_keyword("TO_VARIANT");
18396                self.write("(");
18397                self.write_keyword("ARRAY_CONSTRUCT");
18398                self.write("(");
18399                for (i, arg) in expressions.iter().enumerate() {
18400                    if i > 0 {
18401                        self.write(", ");
18402                    }
18403                    self.generate_expression(arg)?;
18404                }
18405                self.write(")");
18406                self.write(")");
18407                return Ok(());
18408            }
18409        }
18410
18411        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
18412        if func.name.eq_ignore_ascii_case("STRUCT")
18413            && !matches!(
18414                self.config.dialect,
18415                Some(DialectType::BigQuery)
18416                    | Some(DialectType::Spark)
18417                    | Some(DialectType::Databricks)
18418                    | Some(DialectType::Hive)
18419                    | None
18420            )
18421        {
18422            return self.generate_struct_function_cross_dialect(func);
18423        }
18424
18425        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
18426        // This is an internal marker function for ::? JSON path syntax
18427        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
18428            self.generate_expression(&func.args[0])?;
18429            self.write("::?");
18430            // Extract the key from the string literal
18431            if let Expression::Literal(lit) = &func.args[1] {
18432                if let crate::expressions::Literal::String(key) = lit.as_ref() {
18433                    self.write(key);
18434                }
18435            } else {
18436                self.generate_expression(&func.args[1])?;
18437            }
18438            return Ok(());
18439        }
18440
18441        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
18442        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
18443            self.generate_expression(&func.args[0])?;
18444            self.write(" # ");
18445            self.generate_expression(&func.args[1])?;
18446            return Ok(());
18447        }
18448
18449        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
18450        if matches!(
18451            self.config.dialect,
18452            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
18453        ) && func.name.eq_ignore_ascii_case("TRY")
18454            && func.args.len() == 1
18455        {
18456            self.generate_expression(&func.args[0])?;
18457            return Ok(());
18458        }
18459
18460        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
18461        if self.config.dialect == Some(DialectType::ClickHouse)
18462            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
18463            && func.args.len() == 1
18464        {
18465            self.write("dateTrunc('DAY', ");
18466            self.generate_expression(&func.args[0])?;
18467            self.write(")");
18468            return Ok(());
18469        }
18470
18471        // ClickHouse uses dateTrunc casing.
18472        if self.config.dialect == Some(DialectType::ClickHouse)
18473            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18474            && func.args.len() == 2
18475        {
18476            self.write("dateTrunc(");
18477            self.generate_expression(&func.args[0])?;
18478            self.write(", ");
18479            self.generate_expression(&func.args[1])?;
18480            self.write(")");
18481            return Ok(());
18482        }
18483
18484        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
18485        if matches!(
18486            self.config.dialect,
18487            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
18488        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
18489        {
18490            self.write_keyword("SUBSTR");
18491            self.write("(");
18492            for (i, arg) in func.args.iter().enumerate() {
18493                if i > 0 {
18494                    self.write(", ");
18495                }
18496                self.generate_expression(arg)?;
18497            }
18498            self.write(")");
18499            return Ok(());
18500        }
18501
18502        if self.config.dialect == Some(DialectType::Snowflake)
18503            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
18504            && func.args.len() == 1
18505        {
18506            self.write_keyword("ARRAY_DISTINCT");
18507            self.write("(");
18508            self.write_keyword("ARRAY_COMPACT");
18509            self.write("(");
18510            self.generate_expression(&func.args[0])?;
18511            self.write("))");
18512            return Ok(());
18513        }
18514
18515        if self.config.dialect == Some(DialectType::Snowflake)
18516            && func.name.eq_ignore_ascii_case("LIST")
18517            && func.args.len() == 1
18518            && !matches!(func.args.first(), Some(Expression::Select(_)))
18519        {
18520            self.write_keyword("ARRAY_AGG");
18521            self.write("(");
18522            self.generate_expression(&func.args[0])?;
18523            self.write(")");
18524            return Ok(());
18525        }
18526
18527        // Redshift: CONCAT(a, b, ...) -> a || b || ...
18528        if self.config.dialect == Some(DialectType::Redshift)
18529            && func.name.eq_ignore_ascii_case("CONCAT")
18530            && func.args.len() >= 2
18531        {
18532            for (i, arg) in func.args.iter().enumerate() {
18533                if i > 0 {
18534                    self.write(" || ");
18535                }
18536                self.generate_expression(arg)?;
18537            }
18538            return Ok(());
18539        }
18540
18541        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
18542        if self.config.dialect == Some(DialectType::Redshift)
18543            && func.name.eq_ignore_ascii_case("CONCAT_WS")
18544            && func.args.len() >= 2
18545        {
18546            let sep = &func.args[0];
18547            for (i, arg) in func.args.iter().skip(1).enumerate() {
18548                if i > 0 {
18549                    self.write(" || ");
18550                    self.generate_expression(sep)?;
18551                    self.write(" || ");
18552                }
18553                self.generate_expression(arg)?;
18554            }
18555            return Ok(());
18556        }
18557
18558        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
18559        // Unit should be unquoted uppercase identifier
18560        if self.config.dialect == Some(DialectType::Redshift)
18561            && (func.name.eq_ignore_ascii_case("DATEDIFF")
18562                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
18563            && func.args.len() == 3
18564        {
18565            self.write_keyword("DATEDIFF");
18566            self.write("(");
18567            // First arg is unit - normalize to unquoted uppercase
18568            self.write_redshift_date_part(&func.args[0]);
18569            self.write(", ");
18570            self.generate_expression(&func.args[1])?;
18571            self.write(", ");
18572            self.generate_expression(&func.args[2])?;
18573            self.write(")");
18574            return Ok(());
18575        }
18576
18577        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
18578        // Unit should be unquoted uppercase identifier
18579        if self.config.dialect == Some(DialectType::Redshift)
18580            && (func.name.eq_ignore_ascii_case("DATEADD")
18581                || func.name.eq_ignore_ascii_case("DATE_ADD"))
18582            && func.args.len() == 3
18583        {
18584            self.write_keyword("DATEADD");
18585            self.write("(");
18586            // First arg is unit - normalize to unquoted uppercase
18587            self.write_redshift_date_part(&func.args[0]);
18588            self.write(", ");
18589            self.generate_expression(&func.args[1])?;
18590            self.write(", ");
18591            self.generate_expression(&func.args[2])?;
18592            self.write(")");
18593            return Ok(());
18594        }
18595
18596        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
18597        if func.name.eq_ignore_ascii_case("UUID_STRING")
18598            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
18599        {
18600            if matches!(
18601                self.config.dialect,
18602                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
18603            ) {
18604                self.write_keyword("CAST");
18605                self.write("(");
18606                self.write_keyword("UUID");
18607                self.write("() ");
18608                self.write_keyword("AS");
18609                self.write(" ");
18610                self.write_keyword("STRING");
18611                self.write(")");
18612                return Ok(());
18613            }
18614
18615            if matches!(
18616                self.config.dialect,
18617                Some(DialectType::Presto | DialectType::Trino)
18618            ) {
18619                self.write_keyword("CAST");
18620                self.write("(");
18621                self.write_keyword("UUID");
18622                self.write("() ");
18623                self.write_keyword("AS");
18624                self.write(" ");
18625                self.write_keyword("VARCHAR");
18626                self.write(")");
18627                return Ok(());
18628            }
18629
18630            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
18631                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(");
18632                self.generate_expression(&func.args[0])?;
18633                self.write(", '-', '')) || ENCODE(");
18634                self.generate_expression(&func.args[1])?;
18635                self.write(")), 1, 32) AS h))");
18636                return Ok(());
18637            }
18638
18639            let func_name = match self.config.dialect {
18640                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
18641                Some(DialectType::BigQuery) => "GENERATE_UUID",
18642                _ => "UUID",
18643            };
18644            self.write_keyword(func_name);
18645            self.write("()");
18646            return Ok(());
18647        }
18648
18649        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
18650        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
18651        // Positional args are mapped to named parameters.
18652        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18653            && func.name.eq_ignore_ascii_case("GENERATOR")
18654        {
18655            let has_positional_args =
18656                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
18657            if has_positional_args {
18658                let param_names = ["ROWCOUNT", "TIMELIMIT"];
18659                self.write_keyword("GENERATOR");
18660                self.write("(");
18661                for (i, arg) in func.args.iter().enumerate() {
18662                    if i > 0 {
18663                        self.write(", ");
18664                    }
18665                    if i < param_names.len() {
18666                        self.write_keyword(param_names[i]);
18667                        self.write(" => ");
18668                        self.generate_expression(arg)?;
18669                    } else {
18670                        self.generate_expression(arg)?;
18671                    }
18672                }
18673                self.write(")");
18674                return Ok(());
18675            }
18676        }
18677
18678        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
18679        // Unit should be quoted uppercase string
18680        if self.config.dialect == Some(DialectType::Redshift)
18681            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18682            && func.args.len() == 2
18683        {
18684            self.write_keyword("DATE_TRUNC");
18685            self.write("(");
18686            // First arg is unit - normalize to quoted uppercase
18687            self.write_redshift_date_part_quoted(&func.args[0]);
18688            self.write(", ");
18689            self.generate_expression(&func.args[1])?;
18690            self.write(")");
18691            return Ok(());
18692        }
18693
18694        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
18695        if matches!(
18696            self.config.dialect,
18697            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18698        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18699            || func.name.eq_ignore_ascii_case("DATEPART"))
18700            && func.args.len() == 2
18701        {
18702            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18703                let date_part = self.classify_tsql_date_part_name(&part);
18704                self.generate_tsql_date_part(date_part, &func.args[1])?;
18705            } else {
18706                self.write_keyword("DATEPART");
18707                self.write("(");
18708                self.generate_expression(&func.args[0])?;
18709                self.write(", ");
18710                self.generate_expression(&func.args[1])?;
18711                self.write(")");
18712            }
18713            return Ok(());
18714        }
18715
18716        // TSQL/Fabric: DATETRUNC(datepart, value) requires an unquoted datepart keyword.
18717        if matches!(
18718            self.config.dialect,
18719            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18720        ) && (func.name.eq_ignore_ascii_case("DATETRUNC")
18721            || func.name.eq_ignore_ascii_case("DATE_TRUNC"))
18722            && func.args.len() == 2
18723        {
18724            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18725                let date_part = self.classify_tsql_date_trunc_name(&part);
18726                self.generate_tsql_date_trunc(date_part, &func.args[1])?;
18727            } else {
18728                self.write_keyword("DATETRUNC");
18729                self.write("(");
18730                self.generate_expression(&func.args[0])?;
18731                self.write(", ");
18732                self.generate_expression(&func.args[1])?;
18733                self.write(")");
18734            }
18735            return Ok(());
18736        }
18737
18738        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
18739        if matches!(
18740            self.config.dialect,
18741            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
18742        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18743            || func.name.eq_ignore_ascii_case("DATEPART"))
18744            && func.args.len() == 2
18745        {
18746            self.write_keyword("EXTRACT");
18747            self.write("(");
18748            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
18749            match &func.args[0] {
18750                Expression::Literal(lit)
18751                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18752                {
18753                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
18754                        unreachable!()
18755                    };
18756                    self.write(&s.to_ascii_lowercase());
18757                }
18758                _ => self.generate_expression(&func.args[0])?,
18759            }
18760            self.write_space();
18761            self.write_keyword("FROM");
18762            self.write_space();
18763            self.generate_expression(&func.args[1])?;
18764            self.write(")");
18765            return Ok(());
18766        }
18767
18768        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
18769        if self.config.dialect == Some(DialectType::PostgreSQL)
18770            && matches!(
18771                func.name.to_ascii_uppercase().as_str(),
18772                "DATE_ADD" | "DATE_SUB"
18773            )
18774            && func.args.len() == 2
18775            && matches!(func.args[1], Expression::Interval(_))
18776        {
18777            self.generate_expression(&func.args[0])?;
18778            self.write_space();
18779            if func.name.eq_ignore_ascii_case("DATE_SUB") {
18780                self.write("-");
18781            } else {
18782                self.write("+");
18783            }
18784            self.write_space();
18785            self.generate_expression(&func.args[1])?;
18786            return Ok(());
18787        }
18788
18789        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
18790        // Also DATE literals in Dremio should be CAST(...AS DATE)
18791        if self.config.dialect == Some(DialectType::Dremio)
18792            && (func.name.eq_ignore_ascii_case("DATE_PART")
18793                || func.name.eq_ignore_ascii_case("DATEPART"))
18794            && func.args.len() == 2
18795        {
18796            self.write_keyword("EXTRACT");
18797            self.write("(");
18798            self.generate_expression(&func.args[0])?;
18799            self.write_space();
18800            self.write_keyword("FROM");
18801            self.write_space();
18802            // For Dremio, DATE literals should become CAST('value' AS DATE)
18803            self.generate_dremio_date_expression(&func.args[1])?;
18804            self.write(")");
18805            return Ok(());
18806        }
18807
18808        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18809        if self.config.dialect == Some(DialectType::Dremio)
18810            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18811            && func.args.is_empty()
18812        {
18813            self.write_keyword("CURRENT_DATE_UTC");
18814            return Ok(());
18815        }
18816
18817        // Dremio: DATETYPE(year, month, day) transformation
18818        // - If all args are integer literals: DATE('YYYY-MM-DD')
18819        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18820        if self.config.dialect == Some(DialectType::Dremio)
18821            && func.name.eq_ignore_ascii_case("DATETYPE")
18822            && func.args.len() == 3
18823        {
18824            // Helper function to extract integer from number literal
18825            fn get_int_literal(expr: &Expression) -> Option<i64> {
18826                if let Expression::Literal(lit) = expr {
18827                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18828                        s.parse::<i64>().ok()
18829                    } else {
18830                        None
18831                    }
18832                } else {
18833                    None
18834                }
18835            }
18836
18837            // Check if all arguments are integer literals
18838            if let (Some(year), Some(month), Some(day)) = (
18839                get_int_literal(&func.args[0]),
18840                get_int_literal(&func.args[1]),
18841                get_int_literal(&func.args[2]),
18842            ) {
18843                // All are integer literals: DATE('YYYY-MM-DD')
18844                self.write_keyword("DATE");
18845                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18846                return Ok(());
18847            }
18848
18849            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18850            self.write_keyword("CAST");
18851            self.write("(");
18852            self.write_keyword("CONCAT");
18853            self.write("(");
18854            self.generate_expression(&func.args[0])?;
18855            self.write(", '-', ");
18856            self.generate_expression(&func.args[1])?;
18857            self.write(", '-', ");
18858            self.generate_expression(&func.args[2])?;
18859            self.write(")");
18860            self.write_space();
18861            self.write_keyword("AS");
18862            self.write_space();
18863            self.write_keyword("DATE");
18864            self.write(")");
18865            return Ok(());
18866        }
18867
18868        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18869        // when it's not an integer literal
18870        let is_presto_like = matches!(
18871            self.config.dialect,
18872            Some(DialectType::Presto) | Some(DialectType::Trino)
18873        );
18874        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18875            self.write_keyword("DATE_ADD");
18876            self.write("(");
18877            // First arg: unit (pass through as-is, e.g., 'DAY')
18878            self.generate_expression(&func.args[0])?;
18879            self.write(", ");
18880            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18881            let interval = &func.args[1];
18882            let needs_cast = !self.returns_integer_type(interval);
18883            if needs_cast {
18884                self.write_keyword("CAST");
18885                self.write("(");
18886            }
18887            self.generate_expression(interval)?;
18888            if needs_cast {
18889                self.write_space();
18890                self.write_keyword("AS");
18891                self.write_space();
18892                self.write_keyword("BIGINT");
18893                self.write(")");
18894            }
18895            self.write(", ");
18896            // Third arg: date
18897            self.generate_expression(&func.args[2])?;
18898            self.write(")");
18899            return Ok(());
18900        }
18901
18902        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
18903        let use_brackets = func.use_bracket_syntax;
18904
18905        // Special case: functions WITH ORDINALITY need special output order
18906        // Input: FUNC(args) WITH ORDINALITY
18907        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
18908        // Output must be: FUNC(args) WITH ORDINALITY
18909        let has_ordinality = func.name.len() >= 16
18910            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
18911        let output_name = if has_ordinality {
18912            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
18913            self.normalize_func_name(base_name)
18914        } else {
18915            normalized_name.clone()
18916        };
18917
18918        // For qualified names (schema.function or object.method), preserve original case
18919        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
18920        let quote_source_clickhouse_function =
18921            matches!(self.config.dialect, Some(DialectType::ClickHouse))
18922                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
18923                && func.quoted;
18924
18925        if quote_source_clickhouse_function {
18926            self.generate_identifier(&Identifier {
18927                name: func.name.clone(),
18928                quoted: true,
18929                trailing_comments: Vec::new(),
18930                span: None,
18931            })?;
18932        } else if func.name.contains('.') && !has_ordinality {
18933            // Don't normalize qualified functions - preserve original case
18934            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
18935            if func.quoted {
18936                self.write("`");
18937                self.write(&func.name);
18938                self.write("`");
18939            } else {
18940                self.write(&func.name);
18941            }
18942        } else {
18943            self.write(&output_name);
18944        }
18945
18946        // If no_parens is true and there are no args, output just the function name
18947        // Unless the target dialect requires parens for this function
18948        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
18949            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
18950                || func.name.eq_ignore_ascii_case("SESSION_USER")
18951                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
18952            {
18953                matches!(
18954                    self.config.dialect,
18955                    Some(DialectType::Snowflake)
18956                        | Some(DialectType::Spark)
18957                        | Some(DialectType::Databricks)
18958                        | Some(DialectType::Hive)
18959                )
18960            } else {
18961                false
18962            };
18963            !needs_parens
18964        };
18965        if force_parens {
18966            // Output trailing comments
18967            for comment in &func.trailing_comments {
18968                self.write_space();
18969                self.write_formatted_comment(comment);
18970            }
18971            return Ok(());
18972        }
18973
18974        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
18975        if func.name.eq_ignore_ascii_case("CUBE")
18976            || func.name.eq_ignore_ascii_case("ROLLUP")
18977            || func.name.eq_ignore_ascii_case("GROUPING SETS")
18978        {
18979            self.write(" (");
18980        } else if use_brackets {
18981            self.write("[");
18982        } else {
18983            self.write("(");
18984        }
18985        if func.distinct {
18986            self.write_keyword("DISTINCT");
18987            self.write_space();
18988        }
18989
18990        // Check if arguments should be split onto multiple lines (pretty + too wide)
18991        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
18992            && (func.name.eq_ignore_ascii_case("TABLE")
18993                || func.name.eq_ignore_ascii_case("FLATTEN"));
18994        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
18995        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
18996            || func.name.eq_ignore_ascii_case("CUBE")
18997            || func.name.eq_ignore_ascii_case("ROLLUP");
18998        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
18999            if is_grouping_func {
19000                true
19001            } else {
19002                // Pre-render arguments to check total width
19003                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
19004                for arg in &func.args {
19005                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
19006                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
19007                    temp_gen.generate_expression(arg)?;
19008                    expr_strings.push(temp_gen.output);
19009                }
19010                self.too_wide(&expr_strings)
19011            }
19012        } else {
19013            false
19014        };
19015
19016        if should_split {
19017            // Split onto multiple lines
19018            self.write_newline();
19019            self.indent_level += 1;
19020            for (i, arg) in func.args.iter().enumerate() {
19021                self.write_indent();
19022                self.generate_expression(arg)?;
19023                if i + 1 < func.args.len() {
19024                    self.write(",");
19025                }
19026                self.write_newline();
19027            }
19028            self.indent_level -= 1;
19029            self.write_indent();
19030        } else {
19031            // All on one line
19032            for (i, arg) in func.args.iter().enumerate() {
19033                if i > 0 {
19034                    self.write(", ");
19035                }
19036                self.generate_expression(arg)?;
19037            }
19038        }
19039
19040        if use_brackets {
19041            self.write("]");
19042        } else {
19043            self.write(")");
19044        }
19045        // Append WITH ORDINALITY after closing paren for table-valued functions
19046        if has_ordinality {
19047            self.write_space();
19048            self.write_keyword("WITH ORDINALITY");
19049        }
19050        // Output trailing comments
19051        for comment in &func.trailing_comments {
19052            self.write_space();
19053            self.write_formatted_comment(comment);
19054        }
19055        Ok(())
19056    }
19057
19058    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
19059        self.generate_expression(&fe.this)?;
19060        self.write_keyword(" EMITS ");
19061        self.generate_expression(&fe.emits)?;
19062        Ok(())
19063    }
19064
19065    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
19066        // Normalize function name based on dialect settings
19067        let mut normalized_name = self.normalize_func_name(&func.name);
19068
19069        // Dialect-specific name mappings for aggregate functions
19070        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
19071            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
19072            match self.config.dialect {
19073                Some(DialectType::ClickHouse) => {
19074                    normalized_name = if is_max {
19075                        Cow::Borrowed("argMax")
19076                    } else {
19077                        Cow::Borrowed("argMin")
19078                    };
19079                }
19080                Some(DialectType::DuckDB) => {
19081                    normalized_name = if is_max {
19082                        Cow::Borrowed("ARG_MAX")
19083                    } else {
19084                        Cow::Borrowed("ARG_MIN")
19085                    };
19086                }
19087                _ => {}
19088            }
19089        }
19090        self.write(normalized_name.as_ref());
19091        self.write("(");
19092        if func.distinct {
19093            self.write_keyword("DISTINCT");
19094            self.write_space();
19095        }
19096
19097        // Check if we need to transform multi-arg COUNT DISTINCT
19098        // When dialect doesn't support multi_arg_distinct, transform:
19099        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19100        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
19101        let needs_multi_arg_transform =
19102            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
19103
19104        if needs_multi_arg_transform {
19105            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19106            self.write_keyword("CASE");
19107            for arg in &func.args {
19108                self.write_space();
19109                self.write_keyword("WHEN");
19110                self.write_space();
19111                self.generate_expression(arg)?;
19112                self.write_space();
19113                self.write_keyword("IS NULL THEN NULL");
19114            }
19115            self.write_space();
19116            self.write_keyword("ELSE");
19117            self.write(" (");
19118            for (i, arg) in func.args.iter().enumerate() {
19119                if i > 0 {
19120                    self.write(", ");
19121                }
19122                self.generate_expression(arg)?;
19123            }
19124            self.write(")");
19125            self.write_space();
19126            self.write_keyword("END");
19127        } else {
19128            for (i, arg) in func.args.iter().enumerate() {
19129                if i > 0 {
19130                    self.write(", ");
19131                }
19132                self.generate_expression(arg)?;
19133            }
19134        }
19135
19136        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
19137        let clickhouse_ignore_nulls_outside =
19138            matches!(self.config.dialect, Some(DialectType::ClickHouse));
19139        if self.config.ignore_nulls_in_func
19140            && !matches!(
19141                self.config.dialect,
19142                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
19143            )
19144        {
19145            if let Some(ignore) = func.ignore_nulls {
19146                self.write_space();
19147                if ignore {
19148                    self.write_keyword("IGNORE NULLS");
19149                } else {
19150                    self.write_keyword("RESPECT NULLS");
19151                }
19152            }
19153        }
19154
19155        // ORDER BY inside aggregate
19156        if !func.order_by.is_empty() {
19157            self.write_space();
19158            self.write_keyword("ORDER BY");
19159            self.write_space();
19160            for (i, ord) in func.order_by.iter().enumerate() {
19161                if i > 0 {
19162                    self.write(", ");
19163                }
19164                self.generate_ordered(ord)?;
19165            }
19166        }
19167
19168        // LIMIT inside aggregate
19169        if let Some(limit) = &func.limit {
19170            self.write_space();
19171            self.write_keyword("LIMIT");
19172            self.write_space();
19173            // Check if this is a Tuple representing LIMIT offset, count
19174            if let Expression::Tuple(t) = limit.as_ref() {
19175                if t.expressions.len() == 2 {
19176                    self.generate_expression(&t.expressions[0])?;
19177                    self.write(", ");
19178                    self.generate_expression(&t.expressions[1])?;
19179                } else {
19180                    self.generate_expression(limit)?;
19181                }
19182            } else {
19183                self.generate_expression(limit)?;
19184            }
19185        }
19186
19187        self.write(")");
19188
19189        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
19190        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
19191            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19192        {
19193            if let Some(ignore) = func.ignore_nulls {
19194                self.write_space();
19195                if ignore {
19196                    self.write_keyword("IGNORE NULLS");
19197                } else {
19198                    self.write_keyword("RESPECT NULLS");
19199                }
19200            }
19201        }
19202
19203        if let Some(filter) = &func.filter {
19204            self.write_space();
19205            self.write_keyword("FILTER");
19206            self.write("(");
19207            self.write_keyword("WHERE");
19208            self.write_space();
19209            self.generate_expression(filter)?;
19210            self.write(")");
19211        }
19212
19213        Ok(())
19214    }
19215
19216    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
19217        self.generate_expression(&wf.this)?;
19218
19219        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
19220        if let Some(keep) = &wf.keep {
19221            self.write_space();
19222            self.write_keyword("KEEP");
19223            self.write(" (");
19224            self.write_keyword("DENSE_RANK");
19225            self.write_space();
19226            if keep.first {
19227                self.write_keyword("FIRST");
19228            } else {
19229                self.write_keyword("LAST");
19230            }
19231            self.write_space();
19232            self.write_keyword("ORDER BY");
19233            self.write_space();
19234            for (i, ord) in keep.order_by.iter().enumerate() {
19235                if i > 0 {
19236                    self.write(", ");
19237                }
19238                self.generate_ordered(ord)?;
19239            }
19240            self.write(")");
19241        }
19242
19243        // Check if there's any OVER clause content
19244        let has_over = !wf.over.partition_by.is_empty()
19245            || !wf.over.order_by.is_empty()
19246            || wf.over.frame.is_some()
19247            || wf.over.window_name.is_some();
19248
19249        // Only output OVER if there's actual window specification (not just KEEP alone)
19250        if has_over {
19251            self.write_space();
19252            self.write_keyword("OVER");
19253
19254            // Check if this is just a bare named window reference (no parens needed)
19255            let has_specs = !wf.over.partition_by.is_empty()
19256                || !wf.over.order_by.is_empty()
19257                || wf.over.frame.is_some();
19258
19259            if wf.over.window_name.is_some() && !has_specs {
19260                // OVER window_name (without parentheses)
19261                self.write_space();
19262                self.write(&wf.over.window_name.as_ref().unwrap().name);
19263            } else {
19264                // OVER (...) or OVER (window_name ...)
19265                self.write(" (");
19266                self.generate_over(&wf.over)?;
19267                self.write(")");
19268            }
19269        } else if wf.keep.is_none() {
19270            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
19271            self.write_space();
19272            self.write_keyword("OVER");
19273            self.write(" ()");
19274        }
19275
19276        Ok(())
19277    }
19278
19279    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
19280    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
19281        self.generate_expression(&wg.this)?;
19282        self.write_space();
19283        self.write_keyword("WITHIN GROUP");
19284        self.write(" (");
19285        self.write_keyword("ORDER BY");
19286        self.write_space();
19287        for (i, ord) in wg.order_by.iter().enumerate() {
19288            if i > 0 {
19289                self.write(", ");
19290            }
19291            self.generate_ordered(ord)?;
19292        }
19293        self.write(")");
19294        Ok(())
19295    }
19296
19297    /// Generate the contents of an OVER clause (without parentheses)
19298    fn generate_over(&mut self, over: &Over) -> Result<()> {
19299        let mut has_content = false;
19300
19301        // Named window reference
19302        if let Some(name) = &over.window_name {
19303            self.write(&name.name);
19304            has_content = true;
19305        }
19306
19307        // PARTITION BY
19308        if !over.partition_by.is_empty() {
19309            if has_content {
19310                self.write_space();
19311            }
19312            self.write_keyword("PARTITION BY");
19313            self.write_space();
19314            for (i, expr) in over.partition_by.iter().enumerate() {
19315                if i > 0 {
19316                    self.write(", ");
19317                }
19318                self.generate_expression(expr)?;
19319            }
19320            has_content = true;
19321        }
19322
19323        // ORDER BY
19324        if !over.order_by.is_empty() {
19325            if has_content {
19326                self.write_space();
19327            }
19328            self.write_keyword("ORDER BY");
19329            self.write_space();
19330            for (i, ordered) in over.order_by.iter().enumerate() {
19331                if i > 0 {
19332                    self.write(", ");
19333                }
19334                self.generate_ordered(ordered)?;
19335            }
19336            has_content = true;
19337        }
19338
19339        // Window frame
19340        if let Some(frame) = &over.frame {
19341            if has_content {
19342                self.write_space();
19343            }
19344            self.generate_window_frame(frame)?;
19345        }
19346
19347        Ok(())
19348    }
19349
19350    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
19351        // Exasol uses lowercase for frame kind (rows/range/groups)
19352        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19353
19354        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
19355        if !lowercase_frame {
19356            if let Some(kind_text) = &frame.kind_text {
19357                self.write(kind_text);
19358            } else {
19359                match frame.kind {
19360                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
19361                    WindowFrameKind::Range => self.write_keyword("RANGE"),
19362                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
19363                }
19364            }
19365        } else {
19366            match frame.kind {
19367                WindowFrameKind::Rows => self.write("rows"),
19368                WindowFrameKind::Range => self.write("range"),
19369                WindowFrameKind::Groups => self.write("groups"),
19370            }
19371        }
19372
19373        // Use BETWEEN format only when there's an explicit end bound,
19374        // or when normalize_window_frame_between is enabled and the start is a directional bound
19375        self.write_space();
19376        let should_normalize = self.config.normalize_window_frame_between
19377            && frame.end.is_none()
19378            && matches!(
19379                frame.start,
19380                WindowFrameBound::Preceding(_)
19381                    | WindowFrameBound::Following(_)
19382                    | WindowFrameBound::UnboundedPreceding
19383                    | WindowFrameBound::UnboundedFollowing
19384            );
19385
19386        if let Some(end) = &frame.end {
19387            // BETWEEN format: RANGE BETWEEN start AND end
19388            self.write_keyword("BETWEEN");
19389            self.write_space();
19390            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19391            self.write_space();
19392            self.write_keyword("AND");
19393            self.write_space();
19394            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
19395        } else if should_normalize {
19396            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
19397            self.write_keyword("BETWEEN");
19398            self.write_space();
19399            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19400            self.write_space();
19401            self.write_keyword("AND");
19402            self.write_space();
19403            self.write_keyword("CURRENT ROW");
19404        } else {
19405            // Single bound format: RANGE CURRENT ROW
19406            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19407        }
19408
19409        // EXCLUDE clause
19410        if let Some(exclude) = &frame.exclude {
19411            self.write_space();
19412            self.write_keyword("EXCLUDE");
19413            self.write_space();
19414            match exclude {
19415                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
19416                WindowFrameExclude::Group => self.write_keyword("GROUP"),
19417                WindowFrameExclude::Ties => self.write_keyword("TIES"),
19418                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
19419            }
19420        }
19421
19422        Ok(())
19423    }
19424
19425    fn generate_window_frame_bound(
19426        &mut self,
19427        bound: &WindowFrameBound,
19428        side_text: Option<&str>,
19429    ) -> Result<()> {
19430        // Exasol uses lowercase for preceding/following
19431        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19432
19433        match bound {
19434            WindowFrameBound::CurrentRow => {
19435                self.write_keyword("CURRENT ROW");
19436            }
19437            WindowFrameBound::UnboundedPreceding => {
19438                self.write_keyword("UNBOUNDED");
19439                self.write_space();
19440                if lowercase_frame {
19441                    self.write("preceding");
19442                } else if let Some(text) = side_text {
19443                    self.write(text);
19444                } else {
19445                    self.write_keyword("PRECEDING");
19446                }
19447            }
19448            WindowFrameBound::UnboundedFollowing => {
19449                self.write_keyword("UNBOUNDED");
19450                self.write_space();
19451                if lowercase_frame {
19452                    self.write("following");
19453                } else if let Some(text) = side_text {
19454                    self.write(text);
19455                } else {
19456                    self.write_keyword("FOLLOWING");
19457                }
19458            }
19459            WindowFrameBound::Preceding(expr) => {
19460                self.generate_expression(expr)?;
19461                self.write_space();
19462                if lowercase_frame {
19463                    self.write("preceding");
19464                } else if let Some(text) = side_text {
19465                    self.write(text);
19466                } else {
19467                    self.write_keyword("PRECEDING");
19468                }
19469            }
19470            WindowFrameBound::Following(expr) => {
19471                self.generate_expression(expr)?;
19472                self.write_space();
19473                if lowercase_frame {
19474                    self.write("following");
19475                } else if let Some(text) = side_text {
19476                    self.write(text);
19477                } else {
19478                    self.write_keyword("FOLLOWING");
19479                }
19480            }
19481            WindowFrameBound::BarePreceding => {
19482                if lowercase_frame {
19483                    self.write("preceding");
19484                } else if let Some(text) = side_text {
19485                    self.write(text);
19486                } else {
19487                    self.write_keyword("PRECEDING");
19488                }
19489            }
19490            WindowFrameBound::BareFollowing => {
19491                if lowercase_frame {
19492                    self.write("following");
19493                } else if let Some(text) = side_text {
19494                    self.write(text);
19495                } else {
19496                    self.write_keyword("FOLLOWING");
19497                }
19498            }
19499            WindowFrameBound::Value(expr) => {
19500                // Bare numeric bound without PRECEDING/FOLLOWING
19501                self.generate_expression(expr)?;
19502            }
19503        }
19504        Ok(())
19505    }
19506
19507    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
19508        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
19509        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
19510        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
19511            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
19512            && !matches!(&interval.this, Some(Expression::Literal(_)));
19513
19514        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
19515        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
19516        if self.config.single_string_interval {
19517            if let (
19518                Some(Expression::Literal(lit)),
19519                Some(IntervalUnitSpec::Simple {
19520                    ref unit,
19521                    ref use_plural,
19522                }),
19523            ) = (&interval.this, &interval.unit)
19524            {
19525                if let Literal::String(ref val) = lit.as_ref() {
19526                    self.write_keyword("INTERVAL");
19527                    self.write_space();
19528                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19529                    let unit_str = self.interval_unit_str(unit, effective_plural);
19530                    self.write("'");
19531                    self.write(val);
19532                    self.write(" ");
19533                    self.write(&unit_str);
19534                    self.write("'");
19535                    return Ok(());
19536                }
19537            }
19538        }
19539
19540        if !skip_interval_keyword {
19541            self.write_keyword("INTERVAL");
19542        }
19543
19544        // Generate value if present
19545        if let Some(ref value) = interval.this {
19546            if !skip_interval_keyword {
19547                self.write_space();
19548            }
19549            // If the value is a complex expression (not a literal/column/function call)
19550            // and there's a unit, wrap it in parentheses
19551            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
19552            let needs_parens = interval.unit.is_some()
19553                && matches!(
19554                    value,
19555                    Expression::Add(_)
19556                        | Expression::Sub(_)
19557                        | Expression::Mul(_)
19558                        | Expression::Div(_)
19559                        | Expression::Mod(_)
19560                        | Expression::BitwiseAnd(_)
19561                        | Expression::BitwiseOr(_)
19562                        | Expression::BitwiseXor(_)
19563                );
19564            if needs_parens {
19565                self.write("(");
19566            }
19567            self.generate_expression(value)?;
19568            if needs_parens {
19569                self.write(")");
19570            }
19571        }
19572
19573        // Generate unit if present
19574        if let Some(ref unit_spec) = interval.unit {
19575            self.write_space();
19576            self.write_interval_unit_spec(unit_spec)?;
19577        }
19578
19579        Ok(())
19580    }
19581
19582    /// Return the string representation of an interval unit
19583    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
19584        match (unit, use_plural) {
19585            (IntervalUnit::Year, false) => "YEAR",
19586            (IntervalUnit::Year, true) => "YEARS",
19587            (IntervalUnit::Quarter, false) => "QUARTER",
19588            (IntervalUnit::Quarter, true) => "QUARTERS",
19589            (IntervalUnit::Month, false) => "MONTH",
19590            (IntervalUnit::Month, true) => "MONTHS",
19591            (IntervalUnit::Week, false) => "WEEK",
19592            (IntervalUnit::Week, true) => "WEEKS",
19593            (IntervalUnit::Day, false) => "DAY",
19594            (IntervalUnit::Day, true) => "DAYS",
19595            (IntervalUnit::Hour, false) => "HOUR",
19596            (IntervalUnit::Hour, true) => "HOURS",
19597            (IntervalUnit::Minute, false) => "MINUTE",
19598            (IntervalUnit::Minute, true) => "MINUTES",
19599            (IntervalUnit::Second, false) => "SECOND",
19600            (IntervalUnit::Second, true) => "SECONDS",
19601            (IntervalUnit::Millisecond, false) => "MILLISECOND",
19602            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
19603            (IntervalUnit::Microsecond, false) => "MICROSECOND",
19604            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
19605            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
19606            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
19607        }
19608    }
19609
19610    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
19611        match unit_spec {
19612            IntervalUnitSpec::Simple { unit, use_plural } => {
19613                // If dialect doesn't allow plural forms, force singular
19614                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19615                self.write_simple_interval_unit(unit, effective_plural);
19616            }
19617            IntervalUnitSpec::Span(span) => {
19618                self.write_simple_interval_unit(&span.this, false);
19619                self.write_space();
19620                self.write_keyword("TO");
19621                self.write_space();
19622                self.write_simple_interval_unit(&span.expression, false);
19623            }
19624            IntervalUnitSpec::ExprSpan(span) => {
19625                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
19626                self.generate_expression(&span.this)?;
19627                self.write_space();
19628                self.write_keyword("TO");
19629                self.write_space();
19630                self.generate_expression(&span.expression)?;
19631            }
19632            IntervalUnitSpec::Expr(expr) => {
19633                self.generate_expression(expr)?;
19634            }
19635        }
19636        Ok(())
19637    }
19638
19639    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
19640        // Output interval unit, respecting plural preference
19641        match (unit, use_plural) {
19642            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
19643            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
19644            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
19645            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
19646            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
19647            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
19648            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
19649            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
19650            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
19651            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
19652            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
19653            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
19654            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
19655            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
19656            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
19657            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
19658            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
19659            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
19660            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
19661            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
19662            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
19663            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
19664        }
19665    }
19666
19667    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
19668    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
19669    fn write_redshift_date_part(&mut self, expr: &Expression) {
19670        let part_str = self.extract_date_part_string(expr);
19671        if let Some(part) = part_str {
19672            let normalized = self.normalize_date_part(&part);
19673            self.write_keyword(&normalized);
19674        } else {
19675            // If we can't extract a date part string, fall back to generating the expression
19676            let _ = self.generate_expression(expr);
19677        }
19678    }
19679
19680    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
19681    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
19682    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
19683        let part_str = self.extract_date_part_string(expr);
19684        if let Some(part) = part_str {
19685            let normalized = self.normalize_date_part(&part);
19686            self.write("'");
19687            self.write(&normalized);
19688            self.write("'");
19689        } else {
19690            // If we can't extract a date part string, fall back to generating the expression
19691            let _ = self.generate_expression(expr);
19692        }
19693    }
19694
19695    /// Extract date part string from expression (handles string literals and identifiers)
19696    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
19697        match expr {
19698            Expression::Literal(lit)
19699                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
19700            {
19701                let crate::expressions::Literal::String(s) = lit.as_ref() else {
19702                    unreachable!()
19703                };
19704                Some(s.clone())
19705            }
19706            Expression::Identifier(id) => Some(id.name.clone()),
19707            Expression::Var(v) => Some(v.this.clone()),
19708            Expression::Column(col) if col.table.is_none() => {
19709                // Simple column reference without table prefix, treat as identifier
19710                Some(col.name.name.clone())
19711            }
19712            Expression::Cast(cast)
19713                if cast.format.is_none()
19714                    && cast.default.is_none()
19715                    && Self::is_string_data_type(&cast.to)
19716                    && matches!(
19717                        &cast.this,
19718                        Expression::Literal(lit)
19719                            if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
19720                    ) =>
19721            {
19722                self.extract_date_part_string(&cast.this)
19723            }
19724            _ => None,
19725        }
19726    }
19727
19728    fn classify_tsql_datetime_field(&self, field: &DateTimeField) -> TsqlDatePart {
19729        match field {
19730            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19731            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19732            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19733            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19734            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19735            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19736            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19737            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19738            DateTimeField::DayOfWeek => TsqlDatePart::Native("WEEKDAY".to_string()),
19739            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19740            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19741            DateTimeField::WeekWithModifier(modifier) => {
19742                TsqlDatePart::Unsupported(format!("WEEK({modifier})"))
19743            }
19744            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19745            DateTimeField::Epoch => TsqlDatePart::Epoch,
19746            DateTimeField::TimezoneMinute => TsqlDatePart::Native("TZOFFSET".to_string()),
19747            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19748            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19749            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19750            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19751            DateTimeField::Custom(name) => self.classify_tsql_date_part_name(name),
19752        }
19753    }
19754
19755    fn classify_tsql_date_part_name(&self, part: &str) -> TsqlDatePart {
19756        let trimmed = part.trim();
19757        let upper = trimmed.to_ascii_uppercase();
19758        match upper.as_str() {
19759            "YEAR" => TsqlDatePart::Native(trimmed.to_string()),
19760            "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19761                TsqlDatePart::Native("YEAR".to_string())
19762            }
19763            "QUARTER" => TsqlDatePart::Native(trimmed.to_string()),
19764            "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => TsqlDatePart::Native("QUARTER".to_string()),
19765            "MONTH" => TsqlDatePart::Native(trimmed.to_string()),
19766            "MM" | "M" | "MON" | "MONS" | "MONTHS" => TsqlDatePart::Native("MONTH".to_string()),
19767            "DAYOFYEAR" => TsqlDatePart::Native(trimmed.to_string()),
19768            "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19769            "DAY" => TsqlDatePart::Native(trimmed.to_string()),
19770            "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19771            "WEEK" | "W" | "WK" | "WW" | "WEEKOFYEAR" | "WOY" | "WY" | "WEEKS" => {
19772                if upper == "WEEK" {
19773                    TsqlDatePart::Native(trimmed.to_string())
19774                } else {
19775                    TsqlDatePart::Native("WEEK".to_string())
19776                }
19777            }
19778            "WEEKDAY" => TsqlDatePart::Native(trimmed.to_string()),
19779            "DAYOFWEEK" | "DOW" | "DW" => TsqlDatePart::Native("WEEKDAY".to_string()),
19780            "ISODOW" | "ISO_DOW" | "DOW_ISO" | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY"
19781            | "DAYOFWEEKISO" | "DAYOFWEEK_ISO" | "WEEKDAY_ISO" => TsqlDatePart::IsoDayOfWeek,
19782            "HOUR" => TsqlDatePart::Native(trimmed.to_string()),
19783            "H" | "HH" | "HR" | "HOURS" | "HRS" => TsqlDatePart::Native("HOUR".to_string()),
19784            "MINUTE" => TsqlDatePart::Native(trimmed.to_string()),
19785            "MI" | "MIN" | "MINUTES" | "MINS" | "N" => TsqlDatePart::Native("MINUTE".to_string()),
19786            "SECOND" => TsqlDatePart::Native(trimmed.to_string()),
19787            "S" | "SEC" | "SECONDS" | "SECS" | "SS" => TsqlDatePart::Native("SECOND".to_string()),
19788            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19789            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19790                if upper == "MILLISECOND" {
19791                    TsqlDatePart::Native(trimmed.to_string())
19792                } else {
19793                    TsqlDatePart::Native("MILLISECOND".to_string())
19794                }
19795            }
19796            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19797            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19798                if upper == "MICROSECOND" {
19799                    TsqlDatePart::Native(trimmed.to_string())
19800                } else {
19801                    TsqlDatePart::Native("MICROSECOND".to_string())
19802                }
19803            }
19804            "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS" | "NANOSECS" => {
19805                if upper == "NANOSECOND" {
19806                    TsqlDatePart::Native(trimmed.to_string())
19807                } else {
19808                    TsqlDatePart::Native("NANOSECOND".to_string())
19809                }
19810            }
19811            "TZOFFSET" => TsqlDatePart::Native(trimmed.to_string()),
19812            "TZ" | "TZM" | "TIMEZONE_MINUTE" => TsqlDatePart::Native("TZOFFSET".to_string()),
19813            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19814            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => {
19815                if upper == "ISO_WEEK" {
19816                    TsqlDatePart::Native(trimmed.to_string())
19817                } else {
19818                    TsqlDatePart::Native("ISO_WEEK".to_string())
19819                }
19820            }
19821            "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" => TsqlDatePart::Epoch,
19822            "DECADE" | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19823            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "TIMEZONE" | "TIMEZONE_HOUR" | "TZH"
19824            | "DATE" | "TIME" => TsqlDatePart::Unsupported(upper),
19825            _ => TsqlDatePart::Unsupported(upper),
19826        }
19827    }
19828
19829    fn classify_tsql_date_trunc_field(&self, field: &DateTimeField) -> TsqlDatePart {
19830        match field {
19831            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19832            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19833            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19834            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19835            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19836            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19837            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19838            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19839            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19840            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19841            DateTimeField::WeekWithModifier(modifier) => {
19842                match modifier.to_ascii_uppercase().as_str() {
19843                    "MONDAY" | "ISO" | "ISO_WEEK" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19844                    "SUNDAY" => TsqlDatePart::Native("WEEK".to_string()),
19845                    _ => TsqlDatePart::Unsupported(format!("WEEK({modifier})")),
19846                }
19847            }
19848            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19849            DateTimeField::DayOfWeek => TsqlDatePart::Unsupported("WEEKDAY".to_string()),
19850            DateTimeField::Epoch => TsqlDatePart::Unsupported("EPOCH".to_string()),
19851            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19852            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19853            DateTimeField::TimezoneMinute => {
19854                TsqlDatePart::Unsupported("TIMEZONE_MINUTE".to_string())
19855            }
19856            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19857            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19858            DateTimeField::Custom(name) => self.classify_tsql_date_trunc_name(name),
19859        }
19860    }
19861
19862    fn classify_tsql_date_trunc_name(&self, part: &str) -> TsqlDatePart {
19863        let upper = part.trim().to_ascii_uppercase();
19864        match upper.as_str() {
19865            "YEAR" | "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19866                TsqlDatePart::Native("YEAR".to_string())
19867            }
19868            "QUARTER" | "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => {
19869                TsqlDatePart::Native("QUARTER".to_string())
19870            }
19871            "MONTH" | "MM" | "M" | "MON" | "MONS" | "MONTHS" => {
19872                TsqlDatePart::Native("MONTH".to_string())
19873            }
19874            "DAYOFYEAR" | "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19875            "DAY" | "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19876            "WEEK" | "W" | "WK" | "WW" | "WEEKS" | "WEEKOFYEAR" | "WOY" | "WY" => {
19877                TsqlDatePart::Native("WEEK".to_string())
19878            }
19879            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19880            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19881            "HOUR" | "H" | "HH" | "HR" | "HOURS" | "HRS" => {
19882                TsqlDatePart::Native("HOUR".to_string())
19883            }
19884            "MINUTE" | "MI" | "MIN" | "MINUTES" | "MINS" | "N" => {
19885                TsqlDatePart::Native("MINUTE".to_string())
19886            }
19887            "SECOND" | "S" | "SEC" | "SECONDS" | "SECS" | "SS" => {
19888                TsqlDatePart::Native("SECOND".to_string())
19889            }
19890            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19891            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19892                TsqlDatePart::Native("MILLISECOND".to_string())
19893            }
19894            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19895            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19896                TsqlDatePart::Native("MICROSECOND".to_string())
19897            }
19898            "WEEKDAY" | "DAYOFWEEK" | "DOW" | "DW" | "ISODOW" | "ISO_DOW" | "DOW_ISO"
19899            | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY" | "DAYOFWEEKISO" | "DAYOFWEEK_ISO"
19900            | "WEEKDAY_ISO" | "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS"
19901            | "NANOSECS" | "TZOFFSET" | "TZ" | "TZM" | "TIMEZONE" | "TIMEZONE_HOUR"
19902            | "TIMEZONE_MINUTE" | "TZH" | "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" | "DECADE"
19903            | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19904            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "DATE" | "TIME" => {
19905                TsqlDatePart::Unsupported(upper)
19906            }
19907            _ => TsqlDatePart::Unsupported(upper),
19908        }
19909    }
19910
19911    fn generate_tsql_date_part(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
19912        match part {
19913            TsqlDatePart::Native(name) => self.generate_tsql_native_datepart(&name, expr),
19914            TsqlDatePart::Epoch => self.generate_tsql_epoch_seconds(expr),
19915            TsqlDatePart::IsoDayOfWeek => self.generate_tsql_iso_day_of_week(expr),
19916            TsqlDatePart::Unsupported(name) => {
19917                self.unsupported(format!("DATEPART {name} is not supported by T-SQL/Fabric"))?;
19918                self.write_keyword("DATEPART");
19919                self.write("(");
19920                self.write_keyword(&name);
19921                self.write(", ");
19922                self.generate_expression(expr)?;
19923                self.write(")");
19924                Ok(())
19925            }
19926        }
19927    }
19928
19929    fn generate_tsql_native_datepart(&mut self, name: &str, expr: &Expression) -> Result<()> {
19930        self.write_keyword("DATEPART");
19931        self.write("(");
19932        self.write_keyword(name);
19933        self.write(", ");
19934        self.generate_expression(expr)?;
19935        self.write(")");
19936        Ok(())
19937    }
19938
19939    fn generate_tsql_epoch_seconds(&mut self, expr: &Expression) -> Result<()> {
19940        self.write_keyword("DATEDIFF");
19941        self.write("(SECOND, ");
19942        self.write_keyword("CAST");
19943        self.write("('1970-01-01' AS ");
19944        self.write_keyword("DATETIME2");
19945        self.write("), ");
19946        self.generate_expression(expr)?;
19947        self.write(")");
19948        Ok(())
19949    }
19950
19951    fn generate_tsql_iso_day_of_week(&mut self, expr: &Expression) -> Result<()> {
19952        self.write("(((DATEPART(WEEKDAY, ");
19953        self.generate_expression(expr)?;
19954        self.write(") + @@DATEFIRST - 2) % 7) + 1)");
19955        Ok(())
19956    }
19957
19958    fn generate_tsql_date_trunc(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
19959        match part {
19960            TsqlDatePart::Native(name) => {
19961                self.write_keyword("DATETRUNC");
19962                self.write("(");
19963                self.write_keyword(&name);
19964                self.write(", ");
19965                self.generate_expression(expr)?;
19966                self.write(")");
19967                Ok(())
19968            }
19969            TsqlDatePart::Epoch => {
19970                self.generate_tsql_unsupported_date_trunc("EPOCH".to_string(), expr)
19971            }
19972            TsqlDatePart::IsoDayOfWeek => {
19973                self.generate_tsql_unsupported_date_trunc("ISODOW".to_string(), expr)
19974            }
19975            TsqlDatePart::Unsupported(name) => {
19976                self.generate_tsql_unsupported_date_trunc(name, expr)
19977            }
19978        }
19979    }
19980
19981    fn generate_tsql_unsupported_date_trunc(
19982        &mut self,
19983        name: String,
19984        expr: &Expression,
19985    ) -> Result<()> {
19986        self.unsupported(format!("DATETRUNC {name} is not supported by T-SQL/Fabric"))?;
19987        self.write_keyword("DATETRUNC");
19988        self.write("(");
19989        self.write_keyword(&name);
19990        self.write(", ");
19991        self.generate_expression(expr)?;
19992        self.write(")");
19993        Ok(())
19994    }
19995
19996    /// Normalize date part to uppercase singular form
19997    /// days -> DAY, months -> MONTH, etc.
19998    fn normalize_date_part(&self, part: &str) -> String {
19999        let mut buf = [0u8; 64];
20000        let lower: &str = if part.len() <= 64 {
20001            for (i, b) in part.bytes().enumerate() {
20002                buf[i] = b.to_ascii_lowercase();
20003            }
20004            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
20005        } else {
20006            return part.to_ascii_uppercase();
20007        };
20008        match lower {
20009            "day" | "days" | "d" => "DAY".to_string(),
20010            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
20011            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
20012            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
20013            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
20014            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
20015            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
20016            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
20017            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
20018            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
20019            _ => part.to_ascii_uppercase(),
20020        }
20021    }
20022
20023    fn write_datetime_field(&mut self, field: &DateTimeField) {
20024        match field {
20025            DateTimeField::Year => self.write_keyword("YEAR"),
20026            DateTimeField::Month => self.write_keyword("MONTH"),
20027            DateTimeField::Day => self.write_keyword("DAY"),
20028            DateTimeField::Hour => self.write_keyword("HOUR"),
20029            DateTimeField::Minute => self.write_keyword("MINUTE"),
20030            DateTimeField::Second => self.write_keyword("SECOND"),
20031            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
20032            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
20033            DateTimeField::DayOfWeek => {
20034                let name = match self.config.dialect {
20035                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
20036                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
20037                    _ => "DOW",
20038                };
20039                self.write_keyword(name);
20040            }
20041            DateTimeField::DayOfYear => {
20042                let name = match self.config.dialect {
20043                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
20044                    _ => "DOY",
20045                };
20046                self.write_keyword(name);
20047            }
20048            DateTimeField::Week => self.write_keyword("WEEK"),
20049            DateTimeField::WeekWithModifier(modifier) => {
20050                self.write_keyword("WEEK");
20051                self.write("(");
20052                self.write(modifier);
20053                self.write(")");
20054            }
20055            DateTimeField::Quarter => self.write_keyword("QUARTER"),
20056            DateTimeField::Epoch => self.write_keyword("EPOCH"),
20057            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
20058            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
20059            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
20060            DateTimeField::Date => self.write_keyword("DATE"),
20061            DateTimeField::Time => self.write_keyword("TIME"),
20062            DateTimeField::Custom(name) => self.write(name),
20063        }
20064    }
20065
20066    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
20067    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
20068        match field {
20069            DateTimeField::Year => self.write("year"),
20070            DateTimeField::Month => self.write("month"),
20071            DateTimeField::Day => self.write("day"),
20072            DateTimeField::Hour => self.write("hour"),
20073            DateTimeField::Minute => self.write("minute"),
20074            DateTimeField::Second => self.write("second"),
20075            DateTimeField::Millisecond => self.write("millisecond"),
20076            DateTimeField::Microsecond => self.write("microsecond"),
20077            DateTimeField::DayOfWeek => self.write("dow"),
20078            DateTimeField::DayOfYear => self.write("doy"),
20079            DateTimeField::Week => self.write("week"),
20080            DateTimeField::WeekWithModifier(modifier) => {
20081                self.write("week(");
20082                self.write(modifier);
20083                self.write(")");
20084            }
20085            DateTimeField::Quarter => self.write("quarter"),
20086            DateTimeField::Epoch => self.write("epoch"),
20087            DateTimeField::Timezone => self.write("timezone"),
20088            DateTimeField::TimezoneHour => self.write("timezone_hour"),
20089            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
20090            DateTimeField::Date => self.write("date"),
20091            DateTimeField::Time => self.write("time"),
20092            DateTimeField::Custom(name) => self.write(name),
20093        }
20094    }
20095
20096    // Helper function generators
20097
20098    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
20099        self.write_keyword(name);
20100        self.write("(");
20101        self.generate_expression(arg)?;
20102        self.write(")");
20103        Ok(())
20104    }
20105
20106    /// Generate a unary function, using the original name if available for round-trip preservation
20107    fn generate_unary_func(
20108        &mut self,
20109        default_name: &str,
20110        f: &crate::expressions::UnaryFunc,
20111    ) -> Result<()> {
20112        let name = f.original_name.as_deref().unwrap_or(default_name);
20113        self.write_keyword(name);
20114        self.write("(");
20115        self.generate_expression(&f.this)?;
20116        self.write(")");
20117        Ok(())
20118    }
20119
20120    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
20121    fn generate_sqrt_cbrt(
20122        &mut self,
20123        f: &crate::expressions::UnaryFunc,
20124        func_name: &str,
20125        _op: &str,
20126    ) -> Result<()> {
20127        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
20128        // Always use function syntax for consistency
20129        self.write_keyword(func_name);
20130        self.write("(");
20131        self.generate_expression(&f.this)?;
20132        self.write(")");
20133        Ok(())
20134    }
20135
20136    fn generate_binary_func(
20137        &mut self,
20138        name: &str,
20139        arg1: &Expression,
20140        arg2: &Expression,
20141    ) -> Result<()> {
20142        self.write_keyword(name);
20143        self.write("(");
20144        self.generate_expression(arg1)?;
20145        self.write(", ");
20146        self.generate_expression(arg2)?;
20147        self.write(")");
20148        Ok(())
20149    }
20150
20151    /// Generate CHAR/CHR function with optional USING charset
20152    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
20153    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
20154    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
20155        // Use stored name if available, otherwise default to CHAR
20156        let func_name = f.name.as_deref().unwrap_or("CHAR");
20157        self.write_keyword(func_name);
20158        self.write("(");
20159        for (i, arg) in f.args.iter().enumerate() {
20160            if i > 0 {
20161                self.write(", ");
20162            }
20163            self.generate_expression(arg)?;
20164        }
20165        if let Some(ref charset) = f.charset {
20166            self.write(" ");
20167            self.write_keyword("USING");
20168            self.write(" ");
20169            self.write(charset);
20170        }
20171        self.write(")");
20172        Ok(())
20173    }
20174
20175    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
20176        use crate::dialects::DialectType;
20177
20178        match self.config.dialect {
20179            Some(DialectType::Teradata) => {
20180                // Teradata uses ** operator for exponentiation
20181                self.generate_expression(&f.this)?;
20182                self.write(" ** ");
20183                self.generate_expression(&f.expression)?;
20184                Ok(())
20185            }
20186            _ => {
20187                // Other dialects use POWER function
20188                self.generate_binary_func("POWER", &f.this, &f.expression)
20189            }
20190        }
20191    }
20192
20193    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
20194        self.write_func_name(name);
20195        self.write("(");
20196        for (i, arg) in args.iter().enumerate() {
20197            if i > 0 {
20198                self.write(", ");
20199            }
20200            self.generate_expression(arg)?;
20201        }
20202        self.write(")");
20203        Ok(())
20204    }
20205
20206    // String function generators
20207
20208    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
20209        self.write_keyword("CONCAT_WS");
20210        self.write("(");
20211        self.generate_expression(&f.separator)?;
20212        for expr in &f.expressions {
20213            self.write(", ");
20214            self.generate_expression(expr)?;
20215        }
20216        self.write(")");
20217        Ok(())
20218    }
20219
20220    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20221        if let Expression::Concat(op) = expr {
20222            Self::collect_concat_operands(&op.left, out);
20223            Self::collect_concat_operands(&op.right, out);
20224        } else {
20225            out.push(expr);
20226        }
20227    }
20228
20229    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
20230        let mut operands = Vec::new();
20231        Self::collect_concat_operands(&op.left, &mut operands);
20232        Self::collect_concat_operands(&op.right, &mut operands);
20233
20234        self.write_keyword("CONCAT");
20235        self.write("(");
20236        for (i, operand) in operands.iter().enumerate() {
20237            if i > 0 {
20238                self.write(", ");
20239            }
20240            self.generate_expression(operand)?;
20241        }
20242        self.write(")");
20243        Ok(())
20244    }
20245
20246    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20247        if let Expression::DPipe(dpipe) = expr {
20248            Self::collect_dpipe_operands(&dpipe.this, out);
20249            Self::collect_dpipe_operands(&dpipe.expression, out);
20250        } else {
20251            out.push(expr);
20252        }
20253    }
20254
20255    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
20256        let mut operands = Vec::new();
20257        Self::collect_dpipe_operands(&e.this, &mut operands);
20258        Self::collect_dpipe_operands(&e.expression, &mut operands);
20259
20260        self.write_keyword("CONCAT");
20261        self.write("(");
20262        for (i, operand) in operands.iter().enumerate() {
20263            if i > 0 {
20264                self.write(", ");
20265            }
20266            self.generate_expression(operand)?;
20267        }
20268        self.write(")");
20269        Ok(())
20270    }
20271
20272    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
20273        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
20274        let use_substr = matches!(
20275            self.config.dialect,
20276            Some(
20277                DialectType::Oracle
20278                    | DialectType::Presto
20279                    | DialectType::Trino
20280                    | DialectType::Athena
20281            )
20282        );
20283        if use_substr {
20284            self.write_keyword("SUBSTR");
20285        } else {
20286            self.write_keyword("SUBSTRING");
20287        }
20288        self.write("(");
20289        self.generate_expression(&f.this)?;
20290        // PostgreSQL always uses FROM/FOR syntax
20291        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
20292        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
20293        let use_comma_syntax = matches!(
20294            self.config.dialect,
20295            Some(DialectType::Spark)
20296                | Some(DialectType::Hive)
20297                | Some(DialectType::Databricks)
20298                | Some(DialectType::TSQL)
20299                | Some(DialectType::Fabric)
20300        );
20301        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
20302            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
20303            self.write_space();
20304            self.write_keyword("FROM");
20305            self.write_space();
20306            self.generate_expression(&f.start)?;
20307            if let Some(length) = &f.length {
20308                self.write_space();
20309                self.write_keyword("FOR");
20310                self.write_space();
20311                self.generate_expression(length)?;
20312            }
20313        } else {
20314            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
20315            self.write(", ");
20316            self.generate_expression(&f.start)?;
20317            if let Some(length) = &f.length {
20318                self.write(", ");
20319                self.generate_expression(length)?;
20320            }
20321        }
20322        self.write(")");
20323        Ok(())
20324    }
20325
20326    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
20327        self.write_keyword("OVERLAY");
20328        self.write("(");
20329        self.generate_expression(&f.this)?;
20330        self.write_space();
20331        self.write_keyword("PLACING");
20332        self.write_space();
20333        self.generate_expression(&f.replacement)?;
20334        self.write_space();
20335        self.write_keyword("FROM");
20336        self.write_space();
20337        self.generate_expression(&f.from)?;
20338        if let Some(length) = &f.length {
20339            self.write_space();
20340            self.write_keyword("FOR");
20341            self.write_space();
20342            self.generate_expression(length)?;
20343        }
20344        self.write(")");
20345        Ok(())
20346    }
20347
20348    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
20349        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
20350        // when no characters are specified (PostgreSQL style)
20351        if f.position_explicit && f.characters.is_none() {
20352            match f.position {
20353                TrimPosition::Leading => {
20354                    self.write_keyword("LTRIM");
20355                    self.write("(");
20356                    self.generate_expression(&f.this)?;
20357                    self.write(")");
20358                    return Ok(());
20359                }
20360                TrimPosition::Trailing => {
20361                    self.write_keyword("RTRIM");
20362                    self.write("(");
20363                    self.generate_expression(&f.this)?;
20364                    self.write(")");
20365                    return Ok(());
20366                }
20367                TrimPosition::Both => {
20368                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
20369                    // Fall through to standard TRIM handling
20370                }
20371            }
20372        }
20373
20374        self.write_keyword("TRIM");
20375        self.write("(");
20376        // When BOTH is specified without trim characters, simplify to just TRIM(str)
20377        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
20378        let force_standard = f.characters.is_some()
20379            && !f.sql_standard_syntax
20380            && matches!(
20381                self.config.dialect,
20382                Some(DialectType::Hive)
20383                    | Some(DialectType::Spark)
20384                    | Some(DialectType::Databricks)
20385                    | Some(DialectType::ClickHouse)
20386            );
20387        let use_standard = (f.sql_standard_syntax || force_standard)
20388            && !(f.position_explicit
20389                && f.characters.is_none()
20390                && matches!(f.position, TrimPosition::Both));
20391        if use_standard {
20392            // SQL standard syntax: TRIM(BOTH chars FROM str)
20393            // Only output position if it was explicitly specified
20394            if f.position_explicit {
20395                match f.position {
20396                    TrimPosition::Both => self.write_keyword("BOTH"),
20397                    TrimPosition::Leading => self.write_keyword("LEADING"),
20398                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
20399                }
20400                self.write_space();
20401            }
20402            if let Some(chars) = &f.characters {
20403                self.generate_expression(chars)?;
20404                self.write_space();
20405            }
20406            self.write_keyword("FROM");
20407            self.write_space();
20408            self.generate_expression(&f.this)?;
20409        } else {
20410            // Simple function syntax: TRIM(str) or TRIM(str, chars)
20411            self.generate_expression(&f.this)?;
20412            if let Some(chars) = &f.characters {
20413                self.write(", ");
20414                self.generate_expression(chars)?;
20415            }
20416        }
20417        self.write(")");
20418        Ok(())
20419    }
20420
20421    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
20422        self.write_keyword("REPLACE");
20423        self.write("(");
20424        self.generate_expression(&f.this)?;
20425        self.write(", ");
20426        self.generate_expression(&f.old)?;
20427        self.write(", ");
20428        self.generate_expression(&f.new)?;
20429        self.write(")");
20430        Ok(())
20431    }
20432
20433    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
20434        self.write_keyword(name);
20435        self.write("(");
20436        self.generate_expression(&f.this)?;
20437        self.write(", ");
20438        self.generate_expression(&f.length)?;
20439        self.write(")");
20440        Ok(())
20441    }
20442
20443    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
20444        self.write_keyword("REPEAT");
20445        self.write("(");
20446        self.generate_expression(&f.this)?;
20447        self.write(", ");
20448        self.generate_expression(&f.times)?;
20449        self.write(")");
20450        Ok(())
20451    }
20452
20453    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
20454        self.write_keyword(name);
20455        self.write("(");
20456        self.generate_expression(&f.this)?;
20457        self.write(", ");
20458        self.generate_expression(&f.length)?;
20459        if let Some(fill) = &f.fill {
20460            self.write(", ");
20461            self.generate_expression(fill)?;
20462        }
20463        self.write(")");
20464        Ok(())
20465    }
20466
20467    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
20468        self.write_keyword("SPLIT");
20469        self.write("(");
20470        self.generate_expression(&f.this)?;
20471        self.write(", ");
20472        self.generate_expression(&f.delimiter)?;
20473        self.write(")");
20474        Ok(())
20475    }
20476
20477    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
20478        use crate::dialects::DialectType;
20479        // PostgreSQL uses ~ operator for regex matching
20480        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
20481            self.generate_expression(&f.this)?;
20482            self.write(" ~ ");
20483            self.generate_expression(&f.pattern)?;
20484        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && f.flags.is_none()
20485        {
20486            // ClickHouse has no REGEXP_LIKE; the regex-match operator is match(haystack, pattern).
20487            self.write("match(");
20488            self.generate_expression(&f.this)?;
20489            self.write(", ");
20490            self.generate_expression(&f.pattern)?;
20491            self.write(")");
20492        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
20493            // Exasol uses REGEXP_LIKE as infix binary operator
20494            self.generate_expression(&f.this)?;
20495            self.write_keyword(" REGEXP_LIKE ");
20496            self.generate_expression(&f.pattern)?;
20497        } else if matches!(
20498            self.config.dialect,
20499            Some(DialectType::SingleStore)
20500                | Some(DialectType::Spark)
20501                | Some(DialectType::Hive)
20502                | Some(DialectType::Databricks)
20503        ) && f.flags.is_none()
20504        {
20505            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
20506            self.generate_expression(&f.this)?;
20507            self.write_keyword(" RLIKE ");
20508            self.generate_expression(&f.pattern)?;
20509        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
20510            // StarRocks uses REGEXP function syntax
20511            self.write_keyword("REGEXP");
20512            self.write("(");
20513            self.generate_expression(&f.this)?;
20514            self.write(", ");
20515            self.generate_expression(&f.pattern)?;
20516            if let Some(flags) = &f.flags {
20517                self.write(", ");
20518                self.generate_expression(flags)?;
20519            }
20520            self.write(")");
20521        } else {
20522            self.write_keyword("REGEXP_LIKE");
20523            self.write("(");
20524            self.generate_expression(&f.this)?;
20525            self.write(", ");
20526            self.generate_expression(&f.pattern)?;
20527            if let Some(flags) = &f.flags {
20528                self.write(", ");
20529                self.generate_expression(flags)?;
20530            }
20531            self.write(")");
20532        }
20533        Ok(())
20534    }
20535
20536    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
20537        self.write_keyword("REGEXP_REPLACE");
20538        self.write("(");
20539        self.generate_expression(&f.this)?;
20540        self.write(", ");
20541        self.generate_expression(&f.pattern)?;
20542        self.write(", ");
20543        self.generate_expression(&f.replacement)?;
20544        if let Some(flags) = &f.flags {
20545            self.write(", ");
20546            self.generate_expression(flags)?;
20547        }
20548        self.write(")");
20549        Ok(())
20550    }
20551
20552    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
20553        self.write_keyword("REGEXP_EXTRACT");
20554        self.write("(");
20555        self.generate_expression(&f.this)?;
20556        self.write(", ");
20557        self.generate_expression(&f.pattern)?;
20558        if let Some(group) = &f.group {
20559            self.write(", ");
20560            self.generate_expression(group)?;
20561        }
20562        self.write(")");
20563        Ok(())
20564    }
20565
20566    // Math function generators
20567
20568    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
20569        self.write_keyword("ROUND");
20570        self.write("(");
20571        self.generate_expression(&f.this)?;
20572        if let Some(decimals) = &f.decimals {
20573            self.write(", ");
20574            self.generate_expression(decimals)?;
20575        }
20576        self.write(")");
20577        Ok(())
20578    }
20579
20580    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
20581        self.write_keyword("FLOOR");
20582        self.write("(");
20583        self.generate_expression(&f.this)?;
20584        // Handle Druid-style FLOOR(time TO unit) syntax
20585        if let Some(to) = &f.to {
20586            self.write(" ");
20587            self.write_keyword("TO");
20588            self.write(" ");
20589            self.generate_expression(to)?;
20590        } else if let Some(scale) = &f.scale {
20591            self.write(", ");
20592            self.generate_expression(scale)?;
20593        }
20594        self.write(")");
20595        Ok(())
20596    }
20597
20598    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
20599        self.write_keyword("CEIL");
20600        self.write("(");
20601        self.generate_expression(&f.this)?;
20602        // Handle Druid-style CEIL(time TO unit) syntax
20603        if let Some(to) = &f.to {
20604            self.write(" ");
20605            self.write_keyword("TO");
20606            self.write(" ");
20607            self.generate_expression(to)?;
20608        } else if let Some(decimals) = &f.decimals {
20609            self.write(", ");
20610            self.generate_expression(decimals)?;
20611        }
20612        self.write(")");
20613        Ok(())
20614    }
20615
20616    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
20617        use crate::expressions::Literal;
20618
20619        if let Some(base) = &f.base {
20620            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
20621            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
20622            if self.is_log_base_none() {
20623                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
20624                {
20625                    self.write_func_name("LOG2");
20626                    self.write("(");
20627                    self.generate_expression(&f.this)?;
20628                    self.write(")");
20629                    return Ok(());
20630                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
20631                {
20632                    self.write_func_name("LOG10");
20633                    self.write("(");
20634                    self.generate_expression(&f.this)?;
20635                    self.write(")");
20636                    return Ok(());
20637                }
20638                // Other bases: fall through to LOG(base, value) — best effort
20639            }
20640
20641            self.write_func_name("LOG");
20642            self.write("(");
20643            if self.is_log_value_first() {
20644                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
20645                self.generate_expression(&f.this)?;
20646                self.write(", ");
20647                self.generate_expression(base)?;
20648            } else {
20649                // Default (PostgreSQL, etc.): LOG(base, value)
20650                self.generate_expression(base)?;
20651                self.write(", ");
20652                self.generate_expression(&f.this)?;
20653            }
20654            self.write(")");
20655        } else {
20656            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
20657            self.write_func_name("LOG");
20658            self.write("(");
20659            self.generate_expression(&f.this)?;
20660            self.write(")");
20661        }
20662        Ok(())
20663    }
20664
20665    /// Whether the target dialect uses LOG(value, base) order (value first).
20666    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
20667    fn is_log_value_first(&self) -> bool {
20668        use crate::dialects::DialectType;
20669        matches!(
20670            self.config.dialect,
20671            Some(DialectType::BigQuery)
20672                | Some(DialectType::TSQL)
20673                | Some(DialectType::Tableau)
20674                | Some(DialectType::Fabric)
20675        )
20676    }
20677
20678    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
20679    /// Presto, Trino, ClickHouse, Athena.
20680    fn is_log_base_none(&self) -> bool {
20681        use crate::dialects::DialectType;
20682        matches!(
20683            self.config.dialect,
20684            Some(DialectType::Presto)
20685                | Some(DialectType::Trino)
20686                | Some(DialectType::ClickHouse)
20687                | Some(DialectType::Athena)
20688        )
20689    }
20690
20691    // Date/time function generators
20692
20693    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
20694        self.write_keyword("CURRENT_TIME");
20695        if let Some(precision) = f.precision {
20696            self.write(&format!("({})", precision));
20697        } else if matches!(
20698            self.config.dialect,
20699            Some(crate::dialects::DialectType::MySQL)
20700                | Some(crate::dialects::DialectType::SingleStore)
20701                | Some(crate::dialects::DialectType::TiDB)
20702        ) {
20703            self.write("()");
20704        }
20705        Ok(())
20706    }
20707
20708    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
20709        use crate::dialects::DialectType;
20710
20711        // Oracle/Redshift SYSDATE handling
20712        if f.sysdate {
20713            match self.config.dialect {
20714                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
20715                    self.write_keyword("SYSDATE");
20716                    return Ok(());
20717                }
20718                Some(DialectType::Snowflake) => {
20719                    // Snowflake uses SYSDATE() function
20720                    self.write_keyword("SYSDATE");
20721                    self.write("()");
20722                    return Ok(());
20723                }
20724                _ => {
20725                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
20726                }
20727            }
20728        }
20729
20730        self.write_keyword("CURRENT_TIMESTAMP");
20731        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
20732        if let Some(precision) = f.precision {
20733            self.write(&format!("({})", precision));
20734        } else if matches!(
20735            self.config.dialect,
20736            Some(crate::dialects::DialectType::MySQL)
20737                | Some(crate::dialects::DialectType::SingleStore)
20738                | Some(crate::dialects::DialectType::TiDB)
20739                | Some(crate::dialects::DialectType::Spark)
20740                | Some(crate::dialects::DialectType::Hive)
20741                | Some(crate::dialects::DialectType::Databricks)
20742                | Some(crate::dialects::DialectType::ClickHouse)
20743                | Some(crate::dialects::DialectType::BigQuery)
20744                | Some(crate::dialects::DialectType::Snowflake)
20745                | Some(crate::dialects::DialectType::Exasol)
20746        ) {
20747            self.write("()");
20748        }
20749        Ok(())
20750    }
20751
20752    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
20753        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
20754        if self.config.dialect == Some(DialectType::Exasol) {
20755            self.write_keyword("CONVERT_TZ");
20756            self.write("(");
20757            self.generate_expression(&f.this)?;
20758            self.write(", 'UTC', ");
20759            self.generate_expression(&f.zone)?;
20760            self.write(")");
20761            return Ok(());
20762        }
20763
20764        self.generate_expression(&f.this)?;
20765        self.write_space();
20766        self.write_keyword("AT TIME ZONE");
20767        self.write_space();
20768        self.generate_expression(&f.zone)?;
20769        Ok(())
20770    }
20771
20772    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
20773        use crate::dialects::DialectType;
20774
20775        // Presto/Trino use DATE_ADD('unit', interval, date) format
20776        // with the interval cast to BIGINT when needed
20777        let is_presto_like = matches!(
20778            self.config.dialect,
20779            Some(DialectType::Presto) | Some(DialectType::Trino)
20780        );
20781
20782        if is_presto_like {
20783            self.write_keyword(name);
20784            self.write("(");
20785            // Unit as string literal
20786            self.write("'");
20787            self.write_simple_interval_unit(&f.unit, false);
20788            self.write("'");
20789            self.write(", ");
20790            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
20791            let needs_cast = !self.returns_integer_type(&f.interval);
20792            if needs_cast {
20793                self.write_keyword("CAST");
20794                self.write("(");
20795            }
20796            self.generate_expression(&f.interval)?;
20797            if needs_cast {
20798                self.write_space();
20799                self.write_keyword("AS");
20800                self.write_space();
20801                self.write_keyword("BIGINT");
20802                self.write(")");
20803            }
20804            self.write(", ");
20805            self.generate_expression(&f.this)?;
20806            self.write(")");
20807        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20808            self.generate_expression(&f.this)?;
20809            self.write_space();
20810            if name.eq_ignore_ascii_case("DATE_SUB") {
20811                self.write("-");
20812            } else {
20813                self.write("+");
20814            }
20815            self.write_space();
20816            self.write_keyword("INTERVAL");
20817            self.write_space();
20818            self.write("'");
20819            let mut interval_gen = Generator::with_arc_config(self.config.clone());
20820            let interval_sql = interval_gen.generate(&f.interval)?;
20821            self.write(&interval_sql);
20822            self.write(" ");
20823            self.write_simple_interval_unit(&f.unit, false);
20824            self.write("'");
20825        } else {
20826            self.write_keyword(name);
20827            self.write("(");
20828            self.generate_expression(&f.this)?;
20829            self.write(", ");
20830            self.write_keyword("INTERVAL");
20831            self.write_space();
20832            self.generate_expression(&f.interval)?;
20833            self.write_space();
20834            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
20835            self.write(")");
20836        }
20837        Ok(())
20838    }
20839
20840    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
20841    /// This is a heuristic to avoid full type inference
20842    fn returns_integer_type(&self, expr: &Expression) -> bool {
20843        use crate::expressions::{DataType, Literal};
20844        match expr {
20845            // Integer literals (no decimal point)
20846            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
20847                let Literal::Number(n) = lit.as_ref() else {
20848                    unreachable!()
20849                };
20850                !n.contains('.')
20851            }
20852
20853            // FLOOR(x) returns integer if x is integer
20854            Expression::Floor(f) => self.returns_integer_type(&f.this),
20855
20856            // ROUND(x) returns integer if x is integer
20857            Expression::Round(f) => {
20858                // Only if no decimals arg or it's returning an integer
20859                f.decimals.is_none() && self.returns_integer_type(&f.this)
20860            }
20861
20862            // SIGN returns integer if input is integer
20863            Expression::Sign(f) => self.returns_integer_type(&f.this),
20864
20865            // ABS returns the same type as input
20866            Expression::Abs(f) => self.returns_integer_type(&f.this),
20867
20868            // Arithmetic operations on integers return integers
20869            Expression::Mul(op) => {
20870                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20871            }
20872            Expression::Add(op) => {
20873                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20874            }
20875            Expression::Sub(op) => {
20876                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20877            }
20878            Expression::Mod(op) => self.returns_integer_type(&op.left),
20879
20880            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
20881            Expression::Cast(c) => matches!(
20882                &c.to,
20883                DataType::BigInt { .. }
20884                    | DataType::Int { .. }
20885                    | DataType::SmallInt { .. }
20886                    | DataType::TinyInt { .. }
20887            ),
20888
20889            // Negation: -x returns integer if x is integer
20890            Expression::Neg(op) => self.returns_integer_type(&op.this),
20891
20892            // Parenthesized expression
20893            Expression::Paren(p) => self.returns_integer_type(&p.this),
20894
20895            // Column references and most expressions are assumed to need casting
20896            // since we don't have full type information
20897            _ => false,
20898        }
20899    }
20900
20901    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
20902        self.write_keyword("DATEDIFF");
20903        self.write("(");
20904        if let Some(unit) = &f.unit {
20905            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
20906            self.write(", ");
20907        }
20908        if self.config.dialect == Some(DialectType::Snowflake) {
20909            self.generate_expression(&f.expression)?;
20910            self.write(", ");
20911            self.generate_expression(&f.this)?;
20912        } else {
20913            self.generate_expression(&f.this)?;
20914            self.write(", ");
20915            self.generate_expression(&f.expression)?;
20916        }
20917        self.write(")");
20918        Ok(())
20919    }
20920
20921    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
20922        if matches!(
20923            self.config.dialect,
20924            Some(DialectType::TSQL) | Some(DialectType::Fabric)
20925        ) {
20926            let date_part = self.classify_tsql_date_trunc_field(&f.unit);
20927            self.generate_tsql_date_trunc(date_part, &f.this)?;
20928            return Ok(());
20929        }
20930        if self.config.dialect == Some(DialectType::ClickHouse) {
20931            self.write("dateTrunc");
20932        } else {
20933            self.write_keyword("DATE_TRUNC");
20934        }
20935        self.write("('");
20936        self.write_datetime_field(&f.unit);
20937        self.write("', ");
20938        self.generate_expression(&f.this)?;
20939        self.write(")");
20940        Ok(())
20941    }
20942
20943    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
20944        use crate::dialects::DialectType;
20945        use crate::expressions::DateTimeField;
20946
20947        self.write_keyword("LAST_DAY");
20948        self.write("(");
20949        self.generate_expression(&f.this)?;
20950        if let Some(unit) = &f.unit {
20951            self.write(", ");
20952            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
20953            // WEEK(SUNDAY) -> WEEK
20954            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
20955                if let DateTimeField::WeekWithModifier(_) = unit {
20956                    self.write_keyword("WEEK");
20957                } else {
20958                    self.write_datetime_field(unit);
20959                }
20960            } else {
20961                self.write_datetime_field(unit);
20962            }
20963        }
20964        self.write(")");
20965        Ok(())
20966    }
20967
20968    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
20969        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
20970        if matches!(
20971            self.config.dialect,
20972            Some(DialectType::TSQL) | Some(DialectType::Fabric)
20973        ) {
20974            let date_part = self.classify_tsql_datetime_field(&f.field);
20975            self.generate_tsql_date_part(date_part, &f.this)?;
20976            return Ok(());
20977        }
20978        self.write_keyword("EXTRACT");
20979        self.write("(");
20980        // Hive/Spark use lowercase datetime fields in EXTRACT
20981        if matches!(
20982            self.config.dialect,
20983            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
20984        ) {
20985            self.write_datetime_field_lower(&f.field);
20986        } else {
20987            self.write_datetime_field(&f.field);
20988        }
20989        self.write_space();
20990        self.write_keyword("FROM");
20991        self.write_space();
20992        self.generate_expression(&f.this)?;
20993        self.write(")");
20994        Ok(())
20995    }
20996
20997    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
20998        self.write_keyword("TO_DATE");
20999        self.write("(");
21000        self.generate_expression(&f.this)?;
21001        if let Some(format) = &f.format {
21002            self.write(", ");
21003            self.generate_expression(format)?;
21004        }
21005        self.write(")");
21006        Ok(())
21007    }
21008
21009    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
21010        self.write_keyword("TO_TIMESTAMP");
21011        self.write("(");
21012        self.generate_expression(&f.this)?;
21013        if let Some(format) = &f.format {
21014            self.write(", ");
21015            self.generate_expression(format)?;
21016        }
21017        self.write(")");
21018        Ok(())
21019    }
21020
21021    // Control flow function generators
21022
21023    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
21024        use crate::dialects::DialectType;
21025
21026        // Generic mode: normalize IF to CASE WHEN
21027        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
21028            self.write_keyword("CASE WHEN");
21029            self.write_space();
21030            self.generate_expression(&f.condition)?;
21031            self.write_space();
21032            self.write_keyword("THEN");
21033            self.write_space();
21034            self.generate_expression(&f.true_value)?;
21035            if let Some(false_val) = &f.false_value {
21036                self.write_space();
21037                self.write_keyword("ELSE");
21038                self.write_space();
21039                self.generate_expression(false_val)?;
21040            }
21041            self.write_space();
21042            self.write_keyword("END");
21043            return Ok(());
21044        }
21045
21046        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
21047        if self.config.dialect == Some(DialectType::Exasol) {
21048            self.write_keyword("IF");
21049            self.write_space();
21050            self.generate_expression(&f.condition)?;
21051            self.write_space();
21052            self.write_keyword("THEN");
21053            self.write_space();
21054            self.generate_expression(&f.true_value)?;
21055            if let Some(false_val) = &f.false_value {
21056                self.write_space();
21057                self.write_keyword("ELSE");
21058                self.write_space();
21059                self.generate_expression(false_val)?;
21060            }
21061            self.write_space();
21062            self.write_keyword("ENDIF");
21063            return Ok(());
21064        }
21065
21066        // Choose function name based on target dialect
21067        let func_name = match self.config.dialect {
21068            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
21069            Some(DialectType::Snowflake) => "IFF",
21070            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
21071            Some(DialectType::Drill) => "`IF`",
21072            _ => "IF",
21073        };
21074        self.write(func_name);
21075        self.write("(");
21076        self.generate_expression(&f.condition)?;
21077        self.write(", ");
21078        self.generate_expression(&f.true_value)?;
21079        if let Some(false_val) = &f.false_value {
21080            self.write(", ");
21081            self.generate_expression(false_val)?;
21082        }
21083        self.write(")");
21084        Ok(())
21085    }
21086
21087    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
21088        self.write_keyword("NVL2");
21089        self.write("(");
21090        self.generate_expression(&f.this)?;
21091        self.write(", ");
21092        self.generate_expression(&f.true_value)?;
21093        self.write(", ");
21094        self.generate_expression(&f.false_value)?;
21095        self.write(")");
21096        Ok(())
21097    }
21098
21099    // Typed aggregate function generators
21100
21101    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
21102        // Use normalize_functions for COUNT to respect ClickHouse case preservation
21103        let count_name = match self.config.normalize_functions {
21104            NormalizeFunctions::Upper => "COUNT".to_string(),
21105            NormalizeFunctions::Lower => "count".to_string(),
21106            NormalizeFunctions::None => f
21107                .original_name
21108                .clone()
21109                .unwrap_or_else(|| "COUNT".to_string()),
21110        };
21111        self.write(&count_name);
21112        self.write("(");
21113        if f.distinct {
21114            self.write_keyword("DISTINCT");
21115            self.write_space();
21116        }
21117        if f.star {
21118            self.write("*");
21119        } else if let Some(ref expr) = f.this {
21120            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
21121            if let Expression::Tuple(tuple) = expr {
21122                // Check if we need to transform multi-arg COUNT DISTINCT
21123                // When dialect doesn't support multi_arg_distinct, transform:
21124                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
21125                let needs_transform =
21126                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
21127
21128                if needs_transform {
21129                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
21130                    self.write_keyword("CASE");
21131                    for e in &tuple.expressions {
21132                        self.write_space();
21133                        self.write_keyword("WHEN");
21134                        self.write_space();
21135                        self.generate_expression(e)?;
21136                        self.write_space();
21137                        self.write_keyword("IS NULL THEN NULL");
21138                    }
21139                    self.write_space();
21140                    self.write_keyword("ELSE");
21141                    self.write(" (");
21142                    for (i, e) in tuple.expressions.iter().enumerate() {
21143                        if i > 0 {
21144                            self.write(", ");
21145                        }
21146                        self.generate_expression(e)?;
21147                    }
21148                    self.write(")");
21149                    self.write_space();
21150                    self.write_keyword("END");
21151                } else {
21152                    for (i, e) in tuple.expressions.iter().enumerate() {
21153                        if i > 0 {
21154                            self.write(", ");
21155                        }
21156                        self.generate_expression(e)?;
21157                    }
21158                }
21159            } else {
21160                self.generate_expression(expr)?;
21161            }
21162        }
21163        let clickhouse_ignore_nulls_outside =
21164            matches!(self.config.dialect, Some(DialectType::ClickHouse));
21165        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
21166            self.write_space();
21167            if ignore {
21168                self.write_keyword("IGNORE NULLS");
21169            } else {
21170                self.write_keyword("RESPECT NULLS");
21171            }
21172        }
21173        self.write(")");
21174        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
21175            self.write_space();
21176            if ignore {
21177                self.write_keyword("IGNORE NULLS");
21178            } else {
21179                self.write_keyword("RESPECT NULLS");
21180            }
21181        }
21182        if let Some(ref filter) = f.filter {
21183            self.write_space();
21184            self.write_keyword("FILTER");
21185            self.write("(");
21186            self.write_keyword("WHERE");
21187            self.write_space();
21188            self.generate_expression(filter)?;
21189            self.write(")");
21190        }
21191        Ok(())
21192    }
21193
21194    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21195        // Apply function name normalization based on config
21196        let func_name: Cow<'_, str> = match self.config.normalize_functions {
21197            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21198            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21199            NormalizeFunctions::None => {
21200                // Use the original function name from parsing if available,
21201                // otherwise fall back to lowercase of the hardcoded constant
21202                if let Some(ref original) = f.name {
21203                    Cow::Owned(original.clone())
21204                } else {
21205                    Cow::Owned(name.to_ascii_lowercase())
21206                }
21207            }
21208        };
21209        self.write(func_name.as_ref());
21210        self.write("(");
21211        if f.distinct {
21212            self.write_keyword("DISTINCT");
21213            self.write_space();
21214        }
21215        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
21216        // Other aggregates may legitimately receive NULL as an explicit argument.
21217        let is_zero_arg_mode =
21218            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
21219        if !is_zero_arg_mode {
21220            self.generate_expression(&f.this)?;
21221        }
21222        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
21223        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21224        if self.config.ignore_nulls_in_func
21225            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21226        {
21227            match f.ignore_nulls {
21228                Some(true) => {
21229                    self.write_space();
21230                    self.write_keyword("IGNORE NULLS");
21231                }
21232                Some(false) => {
21233                    self.write_space();
21234                    self.write_keyword("RESPECT NULLS");
21235                }
21236                None => {}
21237            }
21238        }
21239        // Generate HAVING MAX/MIN if present (BigQuery syntax)
21240        // e.g., ANY_VALUE(fruit HAVING MAX sold)
21241        if let Some((ref expr, is_max)) = f.having_max {
21242            self.write_space();
21243            self.write_keyword("HAVING");
21244            self.write_space();
21245            if is_max {
21246                self.write_keyword("MAX");
21247            } else {
21248                self.write_keyword("MIN");
21249            }
21250            self.write_space();
21251            self.generate_expression(expr)?;
21252        }
21253        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
21254        if !f.order_by.is_empty() {
21255            self.write_space();
21256            self.write_keyword("ORDER BY");
21257            self.write_space();
21258            for (i, ord) in f.order_by.iter().enumerate() {
21259                if i > 0 {
21260                    self.write(", ");
21261                }
21262                self.generate_ordered(ord)?;
21263            }
21264        }
21265        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
21266        if let Some(ref limit) = f.limit {
21267            self.write_space();
21268            self.write_keyword("LIMIT");
21269            self.write_space();
21270            // Check if this is a Tuple representing LIMIT offset, count
21271            if let Expression::Tuple(t) = limit.as_ref() {
21272                if t.expressions.len() == 2 {
21273                    self.generate_expression(&t.expressions[0])?;
21274                    self.write(", ");
21275                    self.generate_expression(&t.expressions[1])?;
21276                } else {
21277                    self.generate_expression(limit)?;
21278                }
21279            } else {
21280                self.generate_expression(limit)?;
21281            }
21282        }
21283        self.write(")");
21284        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
21285        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21286        if !self.config.ignore_nulls_in_func
21287            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21288        {
21289            match f.ignore_nulls {
21290                Some(true) => {
21291                    self.write_space();
21292                    self.write_keyword("IGNORE NULLS");
21293                }
21294                Some(false) => {
21295                    self.write_space();
21296                    self.write_keyword("RESPECT NULLS");
21297                }
21298                None => {}
21299            }
21300        }
21301        if let Some(ref filter) = f.filter {
21302            self.write_space();
21303            self.write_keyword("FILTER");
21304            self.write("(");
21305            self.write_keyword("WHERE");
21306            self.write_space();
21307            self.generate_expression(filter)?;
21308            self.write(")");
21309        }
21310        Ok(())
21311    }
21312
21313    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
21314    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
21315    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21316        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
21317        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21318            // Create a modified copy without ignore_nulls, add TRUE as part of the output
21319            let func_name: Cow<'_, str> = match self.config.normalize_functions {
21320                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21321                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21322                NormalizeFunctions::None => {
21323                    if let Some(ref original) = f.name {
21324                        Cow::Owned(original.clone())
21325                    } else {
21326                        Cow::Owned(name.to_ascii_lowercase())
21327                    }
21328                }
21329            };
21330            self.write(func_name.as_ref());
21331            self.write("(");
21332            if f.distinct {
21333                self.write_keyword("DISTINCT");
21334                self.write_space();
21335            }
21336            if !matches!(f.this, Expression::Null(_)) {
21337                self.generate_expression(&f.this)?;
21338            }
21339            self.write(", ");
21340            self.write_keyword("TRUE");
21341            self.write(")");
21342            return Ok(());
21343        }
21344        self.generate_agg_func(name, f)
21345    }
21346
21347    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
21348        self.write_keyword("GROUP_CONCAT");
21349        self.write("(");
21350        if f.distinct {
21351            self.write_keyword("DISTINCT");
21352            self.write_space();
21353        }
21354        self.generate_expression(&f.this)?;
21355        if let Some(ref order_by) = f.order_by {
21356            self.write_space();
21357            self.write_keyword("ORDER BY");
21358            self.write_space();
21359            for (i, ord) in order_by.iter().enumerate() {
21360                if i > 0 {
21361                    self.write(", ");
21362                }
21363                self.generate_ordered(ord)?;
21364            }
21365        }
21366        if let Some(ref sep) = f.separator {
21367            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
21368            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
21369            if matches!(
21370                self.config.dialect,
21371                Some(crate::dialects::DialectType::SQLite)
21372            ) {
21373                self.write(", ");
21374                self.generate_expression(sep)?;
21375            } else {
21376                self.write_space();
21377                self.write_keyword("SEPARATOR");
21378                self.write_space();
21379                self.generate_expression(sep)?;
21380            }
21381        }
21382        if let Some(ref limit) = f.limit {
21383            self.write_space();
21384            self.write_keyword("LIMIT");
21385            self.write_space();
21386            self.generate_expression(limit)?;
21387        }
21388        self.write(")");
21389        if let Some(ref filter) = f.filter {
21390            self.write_space();
21391            self.write_keyword("FILTER");
21392            self.write("(");
21393            self.write_keyword("WHERE");
21394            self.write_space();
21395            self.generate_expression(filter)?;
21396            self.write(")");
21397        }
21398        Ok(())
21399    }
21400
21401    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
21402        let uses_within_group_order = matches!(
21403            self.config.dialect,
21404            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21405        );
21406        self.write_keyword("STRING_AGG");
21407        self.write("(");
21408        if f.distinct {
21409            self.write_keyword("DISTINCT");
21410            self.write_space();
21411        }
21412        self.generate_expression(&f.this)?;
21413        if let Some(ref separator) = f.separator {
21414            self.write(", ");
21415            self.generate_string_agg_separator(separator)?;
21416        }
21417        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
21418        if !uses_within_group_order {
21419            if let Some(ref order_by) = f.order_by {
21420                self.write_space();
21421                self.write_keyword("ORDER BY");
21422                self.write_space();
21423                for (i, ord) in order_by.iter().enumerate() {
21424                    if i > 0 {
21425                        self.write(", ");
21426                    }
21427                    self.generate_ordered(ord)?;
21428                }
21429            }
21430        }
21431        if let Some(ref limit) = f.limit {
21432            self.write_space();
21433            self.write_keyword("LIMIT");
21434            self.write_space();
21435            self.generate_expression(limit)?;
21436        }
21437        self.write(")");
21438        if uses_within_group_order {
21439            if let Some(ref order_by) = f.order_by {
21440                self.write_space();
21441                self.write_keyword("WITHIN GROUP");
21442                self.write(" (");
21443                self.write_keyword("ORDER BY");
21444                self.write_space();
21445                for (i, ord) in order_by.iter().enumerate() {
21446                    if i > 0 {
21447                        self.write(", ");
21448                    }
21449                    self.generate_ordered(ord)?;
21450                }
21451                self.write(")");
21452            }
21453        }
21454        if let Some(ref filter) = f.filter {
21455            self.write_space();
21456            self.write_keyword("FILTER");
21457            self.write("(");
21458            self.write_keyword("WHERE");
21459            self.write_space();
21460            self.generate_expression(filter)?;
21461            self.write(")");
21462        }
21463        Ok(())
21464    }
21465
21466    fn generate_string_agg_separator(&mut self, separator: &Expression) -> Result<()> {
21467        if matches!(
21468            self.config.dialect,
21469            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21470        ) {
21471            if let Some(inner) = Self::tsql_string_agg_literal_separator_cast_inner(separator) {
21472                return self.generate_expression(inner);
21473            }
21474        }
21475
21476        self.generate_expression(separator)
21477    }
21478
21479    fn tsql_string_agg_literal_separator_cast_inner(separator: &Expression) -> Option<&Expression> {
21480        let Expression::Cast(cast) = separator else {
21481            return None;
21482        };
21483
21484        if cast.format.is_some() || cast.default.is_some() {
21485            return None;
21486        }
21487
21488        if !Self::is_string_data_type(&cast.to) {
21489            return None;
21490        }
21491
21492        match &cast.this {
21493            Expression::Literal(literal) if literal.is_string() => Some(&cast.this),
21494            _ => None,
21495        }
21496    }
21497
21498    fn is_string_data_type(data_type: &DataType) -> bool {
21499        matches!(
21500            data_type,
21501            DataType::Char { .. }
21502                | DataType::VarChar { .. }
21503                | DataType::String { .. }
21504                | DataType::Text
21505                | DataType::TextWithLength { .. }
21506        ) || matches!(
21507            data_type,
21508            DataType::Custom { name, .. }
21509                if name.eq_ignore_ascii_case("VARCHAR")
21510                    || name.eq_ignore_ascii_case("NVARCHAR")
21511                    || name.eq_ignore_ascii_case("VARCHAR(MAX)")
21512                    || name.eq_ignore_ascii_case("NVARCHAR(MAX)")
21513        )
21514    }
21515
21516    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
21517        use crate::dialects::DialectType;
21518        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
21519        self.write_keyword("LISTAGG");
21520        self.write("(");
21521        if f.distinct {
21522            self.write_keyword("DISTINCT");
21523            self.write_space();
21524        }
21525        self.generate_expression(&f.this)?;
21526        if let Some(ref sep) = f.separator {
21527            self.write(", ");
21528            self.generate_expression(sep)?;
21529        } else if matches!(
21530            self.config.dialect,
21531            Some(DialectType::Trino) | Some(DialectType::Presto)
21532        ) {
21533            // Trino/Presto require explicit separator; default to ','
21534            self.write(", ','");
21535        }
21536        if let Some(ref overflow) = f.on_overflow {
21537            self.write_space();
21538            self.write_keyword("ON OVERFLOW");
21539            self.write_space();
21540            match overflow {
21541                ListAggOverflow::Error => self.write_keyword("ERROR"),
21542                ListAggOverflow::Truncate { filler, with_count } => {
21543                    self.write_keyword("TRUNCATE");
21544                    if let Some(ref fill) = filler {
21545                        self.write_space();
21546                        self.generate_expression(fill)?;
21547                    }
21548                    if *with_count {
21549                        self.write_space();
21550                        self.write_keyword("WITH COUNT");
21551                    } else {
21552                        self.write_space();
21553                        self.write_keyword("WITHOUT COUNT");
21554                    }
21555                }
21556            }
21557        }
21558        if order_inside_args {
21559            if let Some(ref order_by) = f.order_by {
21560                self.write_space();
21561                self.write_keyword("ORDER BY");
21562                self.write_space();
21563                for (i, ord) in order_by.iter().enumerate() {
21564                    if i > 0 {
21565                        self.write(", ");
21566                    }
21567                    self.generate_ordered(ord)?;
21568                }
21569            }
21570        }
21571        self.write(")");
21572        if !order_inside_args {
21573            if let Some(ref order_by) = f.order_by {
21574                self.write_space();
21575                self.write_keyword("WITHIN GROUP");
21576                self.write(" (");
21577                self.write_keyword("ORDER BY");
21578                self.write_space();
21579                for (i, ord) in order_by.iter().enumerate() {
21580                    if i > 0 {
21581                        self.write(", ");
21582                    }
21583                    self.generate_ordered(ord)?;
21584                }
21585                self.write(")");
21586            }
21587        }
21588        if let Some(ref filter) = f.filter {
21589            self.write_space();
21590            self.write_keyword("FILTER");
21591            self.write("(");
21592            self.write_keyword("WHERE");
21593            self.write_space();
21594            self.generate_expression(filter)?;
21595            self.write(")");
21596        }
21597        Ok(())
21598    }
21599
21600    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
21601        self.write_keyword("SUM_IF");
21602        self.write("(");
21603        self.generate_expression(&f.this)?;
21604        self.write(", ");
21605        self.generate_expression(&f.condition)?;
21606        self.write(")");
21607        if let Some(ref filter) = f.filter {
21608            self.write_space();
21609            self.write_keyword("FILTER");
21610            self.write("(");
21611            self.write_keyword("WHERE");
21612            self.write_space();
21613            self.generate_expression(filter)?;
21614            self.write(")");
21615        }
21616        Ok(())
21617    }
21618
21619    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
21620        self.write_keyword("APPROX_PERCENTILE");
21621        self.write("(");
21622        self.generate_expression(&f.this)?;
21623        self.write(", ");
21624        self.generate_expression(&f.percentile)?;
21625        if let Some(ref acc) = f.accuracy {
21626            self.write(", ");
21627            self.generate_expression(acc)?;
21628        }
21629        self.write(")");
21630        if let Some(ref filter) = f.filter {
21631            self.write_space();
21632            self.write_keyword("FILTER");
21633            self.write("(");
21634            self.write_keyword("WHERE");
21635            self.write_space();
21636            self.generate_expression(filter)?;
21637            self.write(")");
21638        }
21639        Ok(())
21640    }
21641
21642    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
21643        self.write_keyword(name);
21644        self.write("(");
21645        self.generate_expression(&f.percentile)?;
21646        self.write(")");
21647        if let Some(ref order_by) = f.order_by {
21648            self.write_space();
21649            self.write_keyword("WITHIN GROUP");
21650            self.write(" (");
21651            self.write_keyword("ORDER BY");
21652            self.write_space();
21653            self.generate_expression(&f.this)?;
21654            for ord in order_by.iter() {
21655                if ord.desc {
21656                    self.write_space();
21657                    self.write_keyword("DESC");
21658                }
21659            }
21660            self.write(")");
21661        }
21662        if let Some(ref filter) = f.filter {
21663            self.write_space();
21664            self.write_keyword("FILTER");
21665            self.write("(");
21666            self.write_keyword("WHERE");
21667            self.write_space();
21668            self.generate_expression(filter)?;
21669            self.write(")");
21670        }
21671        Ok(())
21672    }
21673
21674    // Window function generators
21675
21676    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
21677        self.write_keyword("NTILE");
21678        self.write("(");
21679        if let Some(num_buckets) = &f.num_buckets {
21680            self.generate_expression(num_buckets)?;
21681        }
21682        if let Some(order_by) = &f.order_by {
21683            self.write_keyword(" ORDER BY ");
21684            for (i, ob) in order_by.iter().enumerate() {
21685                if i > 0 {
21686                    self.write(", ");
21687                }
21688                self.generate_ordered(ob)?;
21689            }
21690        }
21691        self.write(")");
21692        Ok(())
21693    }
21694
21695    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
21696        self.write_keyword(name);
21697        self.write("(");
21698        self.generate_expression(&f.this)?;
21699        if let Some(ref offset) = f.offset {
21700            self.write(", ");
21701            self.generate_expression(offset)?;
21702            if let Some(ref default) = f.default {
21703                self.write(", ");
21704                self.generate_expression(default)?;
21705            }
21706        }
21707        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
21708        if self.config.ignore_nulls_in_func {
21709            match f.ignore_nulls {
21710                Some(true) => {
21711                    self.write_space();
21712                    self.write_keyword("IGNORE NULLS");
21713                }
21714                Some(false) => {
21715                    self.write_space();
21716                    self.write_keyword("RESPECT NULLS");
21717                }
21718                None => {}
21719            }
21720        }
21721        self.write(")");
21722        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21723        if !self.config.ignore_nulls_in_func {
21724            match f.ignore_nulls {
21725                Some(true) => {
21726                    self.write_space();
21727                    self.write_keyword("IGNORE NULLS");
21728                }
21729                Some(false) => {
21730                    self.write_space();
21731                    self.write_keyword("RESPECT NULLS");
21732                }
21733                None => {}
21734            }
21735        }
21736        Ok(())
21737    }
21738
21739    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
21740        self.write_keyword(name);
21741        self.write("(");
21742        self.generate_expression(&f.this)?;
21743        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
21744        if !f.order_by.is_empty() {
21745            self.write_space();
21746            self.write_keyword("ORDER BY");
21747            self.write_space();
21748            for (i, ordered) in f.order_by.iter().enumerate() {
21749                if i > 0 {
21750                    self.write(", ");
21751                }
21752                self.generate_ordered(ordered)?;
21753            }
21754        }
21755        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21756        if self.config.ignore_nulls_in_func {
21757            match f.ignore_nulls {
21758                Some(true) => {
21759                    self.write_space();
21760                    self.write_keyword("IGNORE NULLS");
21761                }
21762                Some(false) => {
21763                    self.write_space();
21764                    self.write_keyword("RESPECT NULLS");
21765                }
21766                None => {}
21767            }
21768        }
21769        self.write(")");
21770        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21771        if !self.config.ignore_nulls_in_func {
21772            match f.ignore_nulls {
21773                Some(true) => {
21774                    self.write_space();
21775                    self.write_keyword("IGNORE NULLS");
21776                }
21777                Some(false) => {
21778                    self.write_space();
21779                    self.write_keyword("RESPECT NULLS");
21780                }
21781                None => {}
21782            }
21783        }
21784        Ok(())
21785    }
21786
21787    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
21788    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
21789    fn generate_value_func_with_ignore_nulls_bool(
21790        &mut self,
21791        name: &str,
21792        f: &ValueFunc,
21793    ) -> Result<()> {
21794        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21795            self.write_keyword(name);
21796            self.write("(");
21797            self.generate_expression(&f.this)?;
21798            self.write(", ");
21799            self.write_keyword("TRUE");
21800            self.write(")");
21801            return Ok(());
21802        }
21803        self.generate_value_func(name, f)
21804    }
21805
21806    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
21807        self.write_keyword("NTH_VALUE");
21808        self.write("(");
21809        self.generate_expression(&f.this)?;
21810        self.write(", ");
21811        self.generate_expression(&f.offset)?;
21812        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21813        if self.config.ignore_nulls_in_func {
21814            match f.ignore_nulls {
21815                Some(true) => {
21816                    self.write_space();
21817                    self.write_keyword("IGNORE NULLS");
21818                }
21819                Some(false) => {
21820                    self.write_space();
21821                    self.write_keyword("RESPECT NULLS");
21822                }
21823                None => {}
21824            }
21825        }
21826        self.write(")");
21827        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
21828        if matches!(
21829            self.config.dialect,
21830            Some(crate::dialects::DialectType::Snowflake)
21831        ) {
21832            match f.from_first {
21833                Some(true) => {
21834                    self.write_space();
21835                    self.write_keyword("FROM FIRST");
21836                }
21837                Some(false) => {
21838                    self.write_space();
21839                    self.write_keyword("FROM LAST");
21840                }
21841                None => {}
21842            }
21843        }
21844        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21845        if !self.config.ignore_nulls_in_func {
21846            match f.ignore_nulls {
21847                Some(true) => {
21848                    self.write_space();
21849                    self.write_keyword("IGNORE NULLS");
21850                }
21851                Some(false) => {
21852                    self.write_space();
21853                    self.write_keyword("RESPECT NULLS");
21854                }
21855                None => {}
21856            }
21857        }
21858        Ok(())
21859    }
21860
21861    // Additional string function generators
21862
21863    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
21864        // Standard syntax: POSITION(substr IN str)
21865        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
21866        if matches!(
21867            self.config.dialect,
21868            Some(crate::dialects::DialectType::ClickHouse)
21869        ) {
21870            self.write_keyword("POSITION");
21871            self.write("(");
21872            self.generate_expression(&f.string)?;
21873            self.write(", ");
21874            self.generate_expression(&f.substring)?;
21875            if let Some(ref start) = f.start {
21876                self.write(", ");
21877                self.generate_expression(start)?;
21878            }
21879            self.write(")");
21880            return Ok(());
21881        }
21882
21883        self.write_keyword("POSITION");
21884        self.write("(");
21885        self.generate_expression(&f.substring)?;
21886        self.write_space();
21887        self.write_keyword("IN");
21888        self.write_space();
21889        self.generate_expression(&f.string)?;
21890        if let Some(ref start) = f.start {
21891            self.write(", ");
21892            self.generate_expression(start)?;
21893        }
21894        self.write(")");
21895        Ok(())
21896    }
21897
21898    // Additional math function generators
21899
21900    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
21901        // Teradata RANDOM(lower, upper)
21902        if f.lower.is_some() || f.upper.is_some() {
21903            self.write_keyword("RANDOM");
21904            self.write("(");
21905            if let Some(ref lower) = f.lower {
21906                self.generate_expression(lower)?;
21907            }
21908            if let Some(ref upper) = f.upper {
21909                self.write(", ");
21910                self.generate_expression(upper)?;
21911            }
21912            self.write(")");
21913            return Ok(());
21914        }
21915        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
21916        let func_name = match self.config.dialect {
21917            Some(crate::dialects::DialectType::Snowflake)
21918            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
21919            _ => "RAND",
21920        };
21921        self.write_keyword(func_name);
21922        self.write("(");
21923        // DuckDB doesn't support seeded RANDOM, so skip the seed
21924        if !matches!(
21925            self.config.dialect,
21926            Some(crate::dialects::DialectType::DuckDB)
21927        ) {
21928            if let Some(ref seed) = f.seed {
21929                self.generate_expression(seed)?;
21930            }
21931        }
21932        self.write(")");
21933        Ok(())
21934    }
21935
21936    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
21937        self.write_keyword("TRUNCATE");
21938        self.write("(");
21939        self.generate_expression(&f.this)?;
21940        if let Some(ref decimals) = f.decimals {
21941            self.write(", ");
21942            self.generate_expression(decimals)?;
21943        }
21944        self.write(")");
21945        Ok(())
21946    }
21947
21948    // Control flow generators
21949
21950    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
21951        self.write_keyword("DECODE");
21952        self.write("(");
21953        self.generate_expression(&f.this)?;
21954        for (search, result) in &f.search_results {
21955            self.write(", ");
21956            self.generate_expression(search)?;
21957            self.write(", ");
21958            self.generate_expression(result)?;
21959        }
21960        if let Some(ref default) = f.default {
21961            self.write(", ");
21962            self.generate_expression(default)?;
21963        }
21964        self.write(")");
21965        Ok(())
21966    }
21967
21968    // Date/time function generators
21969
21970    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
21971        self.write_keyword(name);
21972        self.write("(");
21973        self.generate_expression(&f.this)?;
21974        self.write(", ");
21975        self.generate_expression(&f.format)?;
21976        self.write(")");
21977        Ok(())
21978    }
21979
21980    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
21981        self.write_keyword("FROM_UNIXTIME");
21982        self.write("(");
21983        self.generate_expression(&f.this)?;
21984        if let Some(ref format) = f.format {
21985            self.write(", ");
21986            self.generate_expression(format)?;
21987        }
21988        self.write(")");
21989        Ok(())
21990    }
21991
21992    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
21993        self.write_keyword("UNIX_TIMESTAMP");
21994        self.write("(");
21995        if let Some(ref expr) = f.this {
21996            self.generate_expression(expr)?;
21997            if let Some(ref format) = f.format {
21998                self.write(", ");
21999                self.generate_expression(format)?;
22000            }
22001        } else if matches!(
22002            self.config.dialect,
22003            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
22004        ) {
22005            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
22006            self.write_keyword("CURRENT_TIMESTAMP");
22007            self.write("()");
22008        }
22009        self.write(")");
22010        Ok(())
22011    }
22012
22013    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
22014        self.write_keyword("MAKE_DATE");
22015        self.write("(");
22016        self.generate_expression(&f.year)?;
22017        self.write(", ");
22018        self.generate_expression(&f.month)?;
22019        self.write(", ");
22020        self.generate_expression(&f.day)?;
22021        self.write(")");
22022        Ok(())
22023    }
22024
22025    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
22026        self.write_keyword("MAKE_TIMESTAMP");
22027        self.write("(");
22028        self.generate_expression(&f.year)?;
22029        self.write(", ");
22030        self.generate_expression(&f.month)?;
22031        self.write(", ");
22032        self.generate_expression(&f.day)?;
22033        self.write(", ");
22034        self.generate_expression(&f.hour)?;
22035        self.write(", ");
22036        self.generate_expression(&f.minute)?;
22037        self.write(", ");
22038        self.generate_expression(&f.second)?;
22039        if let Some(ref tz) = f.timezone {
22040            self.write(", ");
22041            self.generate_expression(tz)?;
22042        }
22043        self.write(")");
22044        Ok(())
22045    }
22046
22047    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
22048    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
22049        match expr {
22050            Expression::Struct(s) => {
22051                if s.fields.iter().all(|(name, _)| name.is_some()) {
22052                    Some(
22053                        s.fields
22054                            .iter()
22055                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
22056                            .collect(),
22057                    )
22058                } else {
22059                    None
22060                }
22061            }
22062            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22063                // Check if all args are Alias (named fields)
22064                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
22065                    Some(
22066                        f.args
22067                            .iter()
22068                            .filter_map(|a| {
22069                                if let Expression::Alias(alias) = a {
22070                                    Some(alias.alias.name.clone())
22071                                } else {
22072                                    None
22073                                }
22074                            })
22075                            .collect(),
22076                    )
22077                } else {
22078                    None
22079                }
22080            }
22081            _ => None,
22082        }
22083    }
22084
22085    /// Check if a struct expression has any unnamed fields
22086    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
22087        match expr {
22088            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
22089            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22090                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
22091            }
22092            _ => false,
22093        }
22094    }
22095
22096    /// Get the field count of a struct expression
22097    fn struct_field_count(expr: &Expression) -> usize {
22098        match expr {
22099            Expression::Struct(s) => s.fields.len(),
22100            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
22101            _ => 0,
22102        }
22103    }
22104
22105    /// Apply field names to an unnamed struct expression, producing a new expression with names
22106    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
22107        match expr {
22108            Expression::Struct(s) => {
22109                let mut new_fields = Vec::with_capacity(s.fields.len());
22110                for (i, (name, value)) in s.fields.iter().enumerate() {
22111                    if name.is_none() && i < field_names.len() {
22112                        new_fields.push((Some(field_names[i].clone()), value.clone()));
22113                    } else {
22114                        new_fields.push((name.clone(), value.clone()));
22115                    }
22116                }
22117                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
22118            }
22119            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
22120                let mut new_args = Vec::with_capacity(f.args.len());
22121                for (i, arg) in f.args.iter().enumerate() {
22122                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
22123                        // Wrap the value in an Alias with the inherited name
22124                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
22125                            this: arg.clone(),
22126                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
22127                            column_aliases: Vec::new(),
22128                            alias_explicit_as: false,
22129                            alias_keyword: None,
22130                            pre_alias_comments: Vec::new(),
22131                            trailing_comments: Vec::new(),
22132                            inferred_type: None,
22133                        })));
22134                    } else {
22135                        new_args.push(arg.clone());
22136                    }
22137                }
22138                Expression::Function(Box::new(crate::expressions::Function {
22139                    name: f.name.clone(),
22140                    args: new_args,
22141                    distinct: f.distinct,
22142                    trailing_comments: f.trailing_comments.clone(),
22143                    use_bracket_syntax: f.use_bracket_syntax,
22144                    no_parens: f.no_parens,
22145                    quoted: f.quoted,
22146                    span: None,
22147                    inferred_type: None,
22148                }))
22149            }
22150            _ => expr.clone(),
22151        }
22152    }
22153
22154    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
22155    /// This implements BigQuery's implicit field name inheritance for struct arrays.
22156    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
22157    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
22158        let first = match expressions.first() {
22159            Some(e) => e,
22160            None => return expressions.to_vec(),
22161        };
22162
22163        let field_names = match Self::extract_struct_field_names(first) {
22164            Some(names) if !names.is_empty() => names,
22165            _ => return expressions.to_vec(),
22166        };
22167
22168        let mut result = Vec::with_capacity(expressions.len());
22169        for (idx, expr) in expressions.iter().enumerate() {
22170            if idx == 0 {
22171                result.push(expr.clone());
22172                continue;
22173            }
22174            // Check if this is a struct with unnamed fields that needs name propagation
22175            if Self::struct_field_count(expr) == field_names.len()
22176                && Self::struct_has_unnamed_fields(expr)
22177            {
22178                result.push(Self::apply_struct_field_names(expr, &field_names));
22179            } else {
22180                result.push(expr.clone());
22181            }
22182        }
22183        result
22184    }
22185
22186    // Array function generators
22187
22188    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
22189        // Apply struct name inheritance for target dialects that need it
22190        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
22191        let needs_inheritance = matches!(
22192            self.config.dialect,
22193            Some(DialectType::DuckDB)
22194                | Some(DialectType::Spark)
22195                | Some(DialectType::Databricks)
22196                | Some(DialectType::Hive)
22197                | Some(DialectType::Snowflake)
22198                | Some(DialectType::Presto)
22199                | Some(DialectType::Trino)
22200        );
22201        let propagated: Vec<Expression>;
22202        let expressions = if needs_inheritance && f.expressions.len() > 1 {
22203            propagated = Self::inherit_struct_field_names(&f.expressions);
22204            &propagated
22205        } else {
22206            &f.expressions
22207        };
22208
22209        // Check if elements should be split onto multiple lines (pretty + too wide)
22210        let should_split = if self.config.pretty && !expressions.is_empty() {
22211            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
22212            for expr in expressions {
22213                let mut temp_gen = Generator::with_arc_config(self.config.clone());
22214                Arc::make_mut(&mut temp_gen.config).pretty = false;
22215                temp_gen.generate_expression(expr)?;
22216                expr_strings.push(temp_gen.output);
22217            }
22218            self.too_wide(&expr_strings)
22219        } else {
22220            false
22221        };
22222
22223        if f.bracket_notation {
22224            // For Spark/Databricks, use ARRAY(...) with parens
22225            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
22226            // For others (DuckDB, Snowflake), use bare [...]
22227            let (open, close) = match self.config.dialect {
22228                None
22229                | Some(DialectType::Generic)
22230                | Some(DialectType::Spark)
22231                | Some(DialectType::Databricks)
22232                | Some(DialectType::Hive) => {
22233                    self.write_keyword("ARRAY");
22234                    ("(", ")")
22235                }
22236                Some(DialectType::Presto)
22237                | Some(DialectType::Trino)
22238                | Some(DialectType::PostgreSQL)
22239                | Some(DialectType::Redshift)
22240                | Some(DialectType::Materialize)
22241                | Some(DialectType::RisingWave)
22242                | Some(DialectType::CockroachDB) => {
22243                    self.write_keyword("ARRAY");
22244                    ("[", "]")
22245                }
22246                _ => ("[", "]"),
22247            };
22248            self.write(open);
22249            if should_split {
22250                self.write_newline();
22251                self.indent_level += 1;
22252                for (i, expr) in expressions.iter().enumerate() {
22253                    self.write_indent();
22254                    self.generate_expression(expr)?;
22255                    if i + 1 < expressions.len() {
22256                        self.write(",");
22257                    }
22258                    self.write_newline();
22259                }
22260                self.indent_level -= 1;
22261                self.write_indent();
22262            } else {
22263                for (i, expr) in expressions.iter().enumerate() {
22264                    if i > 0 {
22265                        self.write(", ");
22266                    }
22267                    self.generate_expression(expr)?;
22268                }
22269            }
22270            self.write(close);
22271        } else {
22272            // Use LIST keyword if that was the original syntax (DuckDB)
22273            if f.use_list_keyword {
22274                self.write_keyword("LIST");
22275            } else {
22276                self.write_keyword("ARRAY");
22277            }
22278            // For Spark/Hive, always use ARRAY(...) with parens
22279            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
22280            let has_subquery = expressions
22281                .iter()
22282                .any(|e| matches!(e, Expression::Select(_)));
22283            let (open, close) = if matches!(
22284                self.config.dialect,
22285                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
22286            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
22287                && has_subquery)
22288            {
22289                ("(", ")")
22290            } else {
22291                ("[", "]")
22292            };
22293            self.write(open);
22294            if should_split {
22295                self.write_newline();
22296                self.indent_level += 1;
22297                for (i, expr) in expressions.iter().enumerate() {
22298                    self.write_indent();
22299                    self.generate_expression(expr)?;
22300                    if i + 1 < expressions.len() {
22301                        self.write(",");
22302                    }
22303                    self.write_newline();
22304                }
22305                self.indent_level -= 1;
22306                self.write_indent();
22307            } else {
22308                for (i, expr) in expressions.iter().enumerate() {
22309                    if i > 0 {
22310                        self.write(", ");
22311                    }
22312                    self.generate_expression(expr)?;
22313                }
22314            }
22315            self.write(close);
22316        }
22317        Ok(())
22318    }
22319
22320    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
22321        self.write_keyword("ARRAY_SORT");
22322        self.write("(");
22323        self.generate_expression(&f.this)?;
22324        if let Some(ref comp) = f.comparator {
22325            self.write(", ");
22326            self.generate_expression(comp)?;
22327        }
22328        self.write(")");
22329        Ok(())
22330    }
22331
22332    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
22333        self.write_keyword(name);
22334        self.write("(");
22335        self.generate_expression(&f.this)?;
22336        self.write(", ");
22337        self.generate_expression(&f.separator)?;
22338        if let Some(ref null_rep) = f.null_replacement {
22339            self.write(", ");
22340            self.generate_expression(null_rep)?;
22341        }
22342        self.write(")");
22343        Ok(())
22344    }
22345
22346    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
22347        self.write_keyword("UNNEST");
22348        self.write("(");
22349        self.generate_expression(&f.this)?;
22350        for extra in &f.expressions {
22351            self.write(", ");
22352            self.generate_expression(extra)?;
22353        }
22354        self.write(")");
22355        if f.with_ordinality {
22356            self.write_space();
22357            if self.config.unnest_with_ordinality {
22358                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
22359                self.write_keyword("WITH ORDINALITY");
22360            } else if f.offset_alias.is_some() {
22361                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
22362                // Alias (if any) comes BEFORE WITH OFFSET
22363                if let Some(ref alias) = f.alias {
22364                    self.write_keyword("AS");
22365                    self.write_space();
22366                    self.generate_identifier(alias)?;
22367                    self.write_space();
22368                }
22369                self.write_keyword("WITH OFFSET");
22370                if let Some(ref offset_alias) = f.offset_alias {
22371                    self.write_space();
22372                    self.write_keyword("AS");
22373                    self.write_space();
22374                    self.generate_identifier(offset_alias)?;
22375                }
22376            } else {
22377                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
22378                self.write_keyword("WITH OFFSET");
22379                if f.alias.is_none() {
22380                    self.write(" AS offset");
22381                }
22382            }
22383        }
22384        if let Some(ref alias) = f.alias {
22385            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
22386            let should_add_alias = if !f.with_ordinality {
22387                true
22388            } else if self.config.unnest_with_ordinality {
22389                // Presto/Trino: alias comes after WITH ORDINALITY
22390                true
22391            } else if f.offset_alias.is_some() {
22392                // BigQuery expansion: alias already handled above
22393                false
22394            } else {
22395                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
22396                true
22397            };
22398            if should_add_alias {
22399                self.write_space();
22400                self.write_keyword("AS");
22401                self.write_space();
22402                self.generate_identifier(alias)?;
22403            }
22404        }
22405        Ok(())
22406    }
22407
22408    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
22409        self.write_keyword("FILTER");
22410        self.write("(");
22411        self.generate_expression(&f.this)?;
22412        self.write(", ");
22413        self.generate_expression(&f.filter)?;
22414        self.write(")");
22415        Ok(())
22416    }
22417
22418    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
22419        self.write_keyword("TRANSFORM");
22420        self.write("(");
22421        self.generate_expression(&f.this)?;
22422        self.write(", ");
22423        self.generate_expression(&f.transform)?;
22424        self.write(")");
22425        Ok(())
22426    }
22427
22428    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
22429        self.write_keyword(name);
22430        self.write("(");
22431        self.generate_expression(&f.start)?;
22432        self.write(", ");
22433        self.generate_expression(&f.stop)?;
22434        if let Some(ref step) = f.step {
22435            self.write(", ");
22436            self.generate_expression(step)?;
22437        }
22438        self.write(")");
22439        Ok(())
22440    }
22441
22442    // Struct function generators
22443
22444    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
22445        self.write_keyword("STRUCT");
22446        self.write("(");
22447        for (i, (name, expr)) in f.fields.iter().enumerate() {
22448            if i > 0 {
22449                self.write(", ");
22450            }
22451            if let Some(ref id) = name {
22452                self.generate_identifier(id)?;
22453                self.write(" ");
22454                self.write_keyword("AS");
22455                self.write(" ");
22456            }
22457            self.generate_expression(expr)?;
22458        }
22459        self.write(")");
22460        Ok(())
22461    }
22462
22463    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
22464    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
22465        // Extract named/unnamed fields from function args
22466        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
22467        let mut names: Vec<Option<String>> = Vec::new();
22468        let mut values: Vec<&Expression> = Vec::new();
22469        let mut all_named = true;
22470
22471        for arg in &func.args {
22472            match arg {
22473                Expression::Alias(a) => {
22474                    names.push(Some(a.alias.name.clone()));
22475                    values.push(&a.this);
22476                }
22477                _ => {
22478                    names.push(None);
22479                    values.push(arg);
22480                    all_named = false;
22481                }
22482            }
22483        }
22484
22485        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22486            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
22487            self.write("{");
22488            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22489                if i > 0 {
22490                    self.write(", ");
22491                }
22492                if let Some(n) = name {
22493                    self.write("'");
22494                    self.write(n);
22495                    self.write("'");
22496                } else {
22497                    self.write("'_");
22498                    self.write(&i.to_string());
22499                    self.write("'");
22500                }
22501                self.write(": ");
22502                self.generate_expression(value)?;
22503            }
22504            self.write("}");
22505            return Ok(());
22506        }
22507
22508        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22509            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
22510            self.write_keyword("OBJECT_CONSTRUCT");
22511            self.write("(");
22512            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22513                if i > 0 {
22514                    self.write(", ");
22515                }
22516                if let Some(n) = name {
22517                    self.write("'");
22518                    self.write(n);
22519                    self.write("'");
22520                } else {
22521                    self.write("'_");
22522                    self.write(&i.to_string());
22523                    self.write("'");
22524                }
22525                self.write(", ");
22526                self.generate_expression(value)?;
22527            }
22528            self.write(")");
22529            return Ok(());
22530        }
22531
22532        if matches!(
22533            self.config.dialect,
22534            Some(DialectType::Presto) | Some(DialectType::Trino)
22535        ) {
22536            if all_named && !names.is_empty() {
22537                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
22538                // Need to infer types from values
22539                self.write_keyword("CAST");
22540                self.write("(");
22541                self.write_keyword("ROW");
22542                self.write("(");
22543                for (i, value) in values.iter().enumerate() {
22544                    if i > 0 {
22545                        self.write(", ");
22546                    }
22547                    self.generate_expression(value)?;
22548                }
22549                self.write(")");
22550                self.write(" ");
22551                self.write_keyword("AS");
22552                self.write(" ");
22553                self.write_keyword("ROW");
22554                self.write("(");
22555                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22556                    if i > 0 {
22557                        self.write(", ");
22558                    }
22559                    if let Some(n) = name {
22560                        self.write(n);
22561                    }
22562                    self.write(" ");
22563                    let type_str = Self::infer_sql_type_for_presto(value);
22564                    self.write_keyword(&type_str);
22565                }
22566                self.write(")");
22567                self.write(")");
22568            } else {
22569                // Unnamed: ROW(values...)
22570                self.write_keyword("ROW");
22571                self.write("(");
22572                for (i, value) in values.iter().enumerate() {
22573                    if i > 0 {
22574                        self.write(", ");
22575                    }
22576                    self.generate_expression(value)?;
22577                }
22578                self.write(")");
22579            }
22580            return Ok(());
22581        }
22582
22583        // Default: ROW(values...) for other dialects
22584        self.write_keyword("ROW");
22585        self.write("(");
22586        for (i, value) in values.iter().enumerate() {
22587            if i > 0 {
22588                self.write(", ");
22589            }
22590            self.generate_expression(value)?;
22591        }
22592        self.write(")");
22593        Ok(())
22594    }
22595
22596    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
22597    fn infer_sql_type_for_presto(expr: &Expression) -> String {
22598        match expr {
22599            Expression::Literal(lit)
22600                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
22601            {
22602                "VARCHAR".to_string()
22603            }
22604            Expression::Literal(lit)
22605                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
22606            {
22607                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
22608                    unreachable!()
22609                };
22610                if n.contains('.') {
22611                    "DOUBLE".to_string()
22612                } else {
22613                    "INTEGER".to_string()
22614                }
22615            }
22616            Expression::Boolean(_) => "BOOLEAN".to_string(),
22617            Expression::Literal(lit)
22618                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
22619            {
22620                "DATE".to_string()
22621            }
22622            Expression::Literal(lit)
22623                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
22624            {
22625                "TIMESTAMP".to_string()
22626            }
22627            Expression::Literal(lit)
22628                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
22629            {
22630                "TIMESTAMP".to_string()
22631            }
22632            Expression::Array(_) | Expression::ArrayFunc(_) => {
22633                // Try to infer element type from first element
22634                "ARRAY(VARCHAR)".to_string()
22635            }
22636            // For nested structs - generate a nested ROW type by inspecting fields
22637            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
22638            Expression::Function(f) => {
22639                if f.name.eq_ignore_ascii_case("STRUCT") {
22640                    "ROW".to_string()
22641                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
22642                    "DATE".to_string()
22643                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
22644                    || f.name.eq_ignore_ascii_case("NOW")
22645                {
22646                    "TIMESTAMP".to_string()
22647                } else {
22648                    "VARCHAR".to_string()
22649                }
22650            }
22651            _ => "VARCHAR".to_string(),
22652        }
22653    }
22654
22655    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
22656        // DuckDB uses STRUCT_EXTRACT function syntax
22657        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22658            self.write_keyword("STRUCT_EXTRACT");
22659            self.write("(");
22660            self.generate_expression(&f.this)?;
22661            self.write(", ");
22662            // Output field name as string literal
22663            self.write("'");
22664            self.write(&f.field.name);
22665            self.write("'");
22666            self.write(")");
22667            return Ok(());
22668        }
22669        self.generate_expression(&f.this)?;
22670        self.write(".");
22671        self.generate_identifier(&f.field)
22672    }
22673
22674    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
22675        if matches!(
22676            self.config.dialect,
22677            Some(DialectType::Spark | DialectType::Databricks)
22678        ) {
22679            self.write_keyword("STRUCT");
22680            self.write("(");
22681            for (i, (name, value)) in f.pairs.iter().enumerate() {
22682                if i > 0 {
22683                    self.write(", ");
22684                }
22685                self.generate_expression(value)?;
22686                self.write(" ");
22687                self.write_keyword("AS");
22688                self.write(" ");
22689                if let Expression::Literal(lit) = name {
22690                    if let Literal::String(field_name) = lit.as_ref() {
22691                        self.generate_identifier(&Identifier::new(field_name))?;
22692                    } else {
22693                        self.generate_expression(name)?;
22694                    }
22695                } else {
22696                    self.generate_expression(name)?;
22697                }
22698            }
22699            self.write(")");
22700            return Ok(());
22701        }
22702
22703        self.write_keyword("NAMED_STRUCT");
22704        self.write("(");
22705        for (i, (name, value)) in f.pairs.iter().enumerate() {
22706            if i > 0 {
22707                self.write(", ");
22708            }
22709            self.generate_expression(name)?;
22710            self.write(", ");
22711            self.generate_expression(value)?;
22712        }
22713        self.write(")");
22714        Ok(())
22715    }
22716
22717    // Map function generators
22718
22719    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
22720        if f.curly_brace_syntax {
22721            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
22722            if f.with_map_keyword {
22723                self.write_keyword("MAP");
22724                self.write(" ");
22725            }
22726            self.write("{");
22727            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
22728                if i > 0 {
22729                    self.write(", ");
22730                }
22731                self.generate_expression(key)?;
22732                self.write(": ");
22733                self.generate_expression(val)?;
22734            }
22735            self.write("}");
22736        } else {
22737            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
22738            self.write_keyword("MAP");
22739            self.write("(");
22740            self.write_keyword("ARRAY");
22741            self.write("[");
22742            for (i, key) in f.keys.iter().enumerate() {
22743                if i > 0 {
22744                    self.write(", ");
22745                }
22746                self.generate_expression(key)?;
22747            }
22748            self.write("], ");
22749            self.write_keyword("ARRAY");
22750            self.write("[");
22751            for (i, val) in f.values.iter().enumerate() {
22752                if i > 0 {
22753                    self.write(", ");
22754                }
22755                self.generate_expression(val)?;
22756            }
22757            self.write("])");
22758        }
22759        Ok(())
22760    }
22761
22762    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
22763        self.write_keyword(name);
22764        self.write("(");
22765        self.generate_expression(&f.this)?;
22766        self.write(", ");
22767        self.generate_expression(&f.transform)?;
22768        self.write(")");
22769        Ok(())
22770    }
22771
22772    // JSON function generators
22773
22774    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
22775        use crate::dialects::DialectType;
22776
22777        // Check if we should use arrow syntax (-> or ->>)
22778        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
22779
22780        if use_arrow {
22781            // Output arrow syntax: expr -> path or expr ->> path
22782            self.generate_expression(&f.this)?;
22783            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
22784                self.write(" ->> ");
22785            } else {
22786                self.write(" -> ");
22787            }
22788            self.generate_expression(&f.path)?;
22789            return Ok(());
22790        }
22791
22792        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
22793        if f.hash_arrow_syntax
22794            && matches!(
22795                self.config.dialect,
22796                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22797            )
22798        {
22799            self.generate_expression(&f.this)?;
22800            self.write(" #>> ");
22801            self.generate_expression(&f.path)?;
22802            return Ok(());
22803        }
22804
22805        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
22806        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
22807        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22808            match name {
22809                "JSON_EXTRACT_SCALAR"
22810                | "JSON_EXTRACT_PATH_TEXT"
22811                | "JSON_EXTRACT"
22812                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
22813                _ => name,
22814            }
22815        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
22816            match name {
22817                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
22818                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
22819                _ => name,
22820            }
22821        } else {
22822            name
22823        };
22824
22825        self.write_keyword(func_name);
22826        self.write("(");
22827        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
22828        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22829            if let Expression::Cast(ref cast) = f.this {
22830                if matches!(cast.to, crate::expressions::DataType::Json) {
22831                    self.generate_expression(&cast.this)?;
22832                } else {
22833                    self.generate_expression(&f.this)?;
22834                }
22835            } else {
22836                self.generate_expression(&f.this)?;
22837            }
22838        } else {
22839            self.generate_expression(&f.this)?;
22840        }
22841        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
22842        // decompose JSON path into separate string arguments
22843        if matches!(
22844            self.config.dialect,
22845            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22846        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
22847        {
22848            if let Expression::Literal(ref lit) = f.path {
22849                if let Literal::String(ref s) = lit.as_ref() {
22850                    let parts = Self::decompose_json_path(s);
22851                    for part in &parts {
22852                        self.write(", '");
22853                        self.write(part);
22854                        self.write("'");
22855                    }
22856                }
22857            } else {
22858                self.write(", ");
22859                self.generate_expression(&f.path)?;
22860            }
22861        } else {
22862            self.write(", ");
22863            self.generate_expression(&f.path)?;
22864        }
22865
22866        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
22867        // These go BEFORE the closing parenthesis
22868        if let Some(ref wrapper) = f.wrapper_option {
22869            self.write_space();
22870            self.write_keyword(wrapper);
22871        }
22872        if let Some(ref quotes) = f.quotes_option {
22873            self.write_space();
22874            self.write_keyword(quotes);
22875            if f.on_scalar_string {
22876                self.write_space();
22877                self.write_keyword("ON SCALAR STRING");
22878            }
22879        }
22880        if let Some(ref on_err) = f.on_error {
22881            self.write_space();
22882            self.write_keyword(on_err);
22883        }
22884        if let Some(ref ret_type) = f.returning {
22885            self.write_space();
22886            self.write_keyword("RETURNING");
22887            self.write_space();
22888            self.generate_data_type(ret_type)?;
22889        }
22890
22891        self.write(")");
22892        Ok(())
22893    }
22894
22895    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
22896    fn dialect_supports_json_arrow(&self) -> bool {
22897        use crate::dialects::DialectType;
22898        match self.config.dialect {
22899            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
22900            Some(DialectType::PostgreSQL) => true,
22901            Some(DialectType::MySQL) => true,
22902            Some(DialectType::DuckDB) => true,
22903            Some(DialectType::CockroachDB) => true,
22904            Some(DialectType::StarRocks) => true,
22905            Some(DialectType::SQLite) => true,
22906            // Other dialects use function syntax
22907            _ => false,
22908        }
22909    }
22910
22911    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
22912        use crate::dialects::DialectType;
22913
22914        // PostgreSQL uses #> operator for JSONB path extraction
22915        if matches!(
22916            self.config.dialect,
22917            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22918        ) && name == "JSON_EXTRACT_PATH"
22919        {
22920            self.generate_expression(&f.this)?;
22921            self.write(" #> ");
22922            if f.paths.len() == 1 {
22923                self.generate_expression(&f.paths[0])?;
22924            } else {
22925                // Multiple paths: ARRAY[path1, path2, ...]
22926                self.write_keyword("ARRAY");
22927                self.write("[");
22928                for (i, path) in f.paths.iter().enumerate() {
22929                    if i > 0 {
22930                        self.write(", ");
22931                    }
22932                    self.generate_expression(path)?;
22933                }
22934                self.write("]");
22935            }
22936            return Ok(());
22937        }
22938
22939        self.write_keyword(name);
22940        self.write("(");
22941        self.generate_expression(&f.this)?;
22942        for path in &f.paths {
22943            self.write(", ");
22944            self.generate_expression(path)?;
22945        }
22946        self.write(")");
22947        Ok(())
22948    }
22949
22950    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
22951        use crate::dialects::DialectType;
22952
22953        self.write_keyword("JSON_OBJECT");
22954        self.write("(");
22955        if f.star {
22956            self.write("*");
22957        } else {
22958            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
22959            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
22960            // Also respect the json_key_value_pair_sep config
22961            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
22962                || matches!(
22963                    self.config.dialect,
22964                    Some(DialectType::BigQuery)
22965                        | Some(DialectType::MySQL)
22966                        | Some(DialectType::SQLite)
22967                );
22968
22969            for (i, (key, value)) in f.pairs.iter().enumerate() {
22970                if i > 0 {
22971                    self.write(", ");
22972                }
22973                self.generate_expression(key)?;
22974                if use_comma_syntax {
22975                    self.write(", ");
22976                } else {
22977                    self.write(": ");
22978                }
22979                self.generate_expression(value)?;
22980            }
22981        }
22982        if let Some(null_handling) = f.null_handling {
22983            self.write_space();
22984            match null_handling {
22985                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22986                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22987            }
22988        }
22989        if f.with_unique_keys {
22990            self.write_space();
22991            self.write_keyword("WITH UNIQUE KEYS");
22992        }
22993        if let Some(ref ret_type) = f.returning_type {
22994            self.write_space();
22995            self.write_keyword("RETURNING");
22996            self.write_space();
22997            self.generate_data_type(ret_type)?;
22998            if f.format_json {
22999                self.write_space();
23000                self.write_keyword("FORMAT JSON");
23001            }
23002            if let Some(ref enc) = f.encoding {
23003                self.write_space();
23004                self.write_keyword("ENCODING");
23005                self.write_space();
23006                self.write(enc);
23007            }
23008        }
23009        self.write(")");
23010        Ok(())
23011    }
23012
23013    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
23014        self.write_keyword(name);
23015        self.write("(");
23016        self.generate_expression(&f.this)?;
23017        for (path, value) in &f.path_values {
23018            self.write(", ");
23019            self.generate_expression(path)?;
23020            self.write(", ");
23021            self.generate_expression(value)?;
23022        }
23023        self.write(")");
23024        Ok(())
23025    }
23026
23027    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
23028        self.write_keyword("JSON_ARRAYAGG");
23029        self.write("(");
23030        self.generate_expression(&f.this)?;
23031        if let Some(ref order_by) = f.order_by {
23032            self.write_space();
23033            self.write_keyword("ORDER BY");
23034            self.write_space();
23035            for (i, ord) in order_by.iter().enumerate() {
23036                if i > 0 {
23037                    self.write(", ");
23038                }
23039                self.generate_ordered(ord)?;
23040            }
23041        }
23042        if let Some(null_handling) = f.null_handling {
23043            self.write_space();
23044            match null_handling {
23045                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23046                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23047            }
23048        }
23049        self.write(")");
23050        if let Some(ref filter) = f.filter {
23051            self.write_space();
23052            self.write_keyword("FILTER");
23053            self.write("(");
23054            self.write_keyword("WHERE");
23055            self.write_space();
23056            self.generate_expression(filter)?;
23057            self.write(")");
23058        }
23059        Ok(())
23060    }
23061
23062    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
23063        self.write_keyword("JSON_OBJECTAGG");
23064        self.write("(");
23065        self.generate_expression(&f.key)?;
23066        self.write(": ");
23067        self.generate_expression(&f.value)?;
23068        if let Some(null_handling) = f.null_handling {
23069            self.write_space();
23070            match null_handling {
23071                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
23072                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
23073            }
23074        }
23075        self.write(")");
23076        if let Some(ref filter) = f.filter {
23077            self.write_space();
23078            self.write_keyword("FILTER");
23079            self.write("(");
23080            self.write_keyword("WHERE");
23081            self.write_space();
23082            self.generate_expression(filter)?;
23083            self.write(")");
23084        }
23085        Ok(())
23086    }
23087
23088    // Type casting/conversion generators
23089
23090    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
23091        use crate::dialects::DialectType;
23092
23093        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
23094        if self.config.dialect == Some(DialectType::Redshift) {
23095            self.write_keyword("CAST");
23096            self.write("(");
23097            self.generate_expression(&f.this)?;
23098            self.write_space();
23099            self.write_keyword("AS");
23100            self.write_space();
23101            self.generate_data_type(&f.to)?;
23102            self.write(")");
23103            return Ok(());
23104        }
23105
23106        self.write_keyword("CONVERT");
23107        self.write("(");
23108        self.generate_data_type(&f.to)?;
23109        self.write(", ");
23110        self.generate_expression(&f.this)?;
23111        if let Some(ref style) = f.style {
23112            self.write(", ");
23113            self.generate_expression(style)?;
23114        }
23115        self.write(")");
23116        Ok(())
23117    }
23118
23119    // Additional expression generators
23120
23121    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
23122        if f.colon {
23123            // DuckDB syntax: LAMBDA x : expr
23124            self.write_keyword("LAMBDA");
23125            self.write_space();
23126            for (i, param) in f.parameters.iter().enumerate() {
23127                if i > 0 {
23128                    self.write(", ");
23129                }
23130                self.generate_identifier(param)?;
23131            }
23132            self.write(" : ");
23133        } else {
23134            // Standard syntax: x -> expr or (x, y) -> expr
23135            if f.parameters.len() == 1 {
23136                self.generate_identifier(&f.parameters[0])?;
23137            } else {
23138                self.write("(");
23139                for (i, param) in f.parameters.iter().enumerate() {
23140                    if i > 0 {
23141                        self.write(", ");
23142                    }
23143                    self.generate_identifier(param)?;
23144                }
23145                self.write(")");
23146            }
23147            self.write(" -> ");
23148        }
23149        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23150            if let Expression::Lambda(inner) = &f.body {
23151                self.generate_lambda_with_parenthesized_single_param(inner)?;
23152                return Ok(());
23153            }
23154        }
23155
23156        self.generate_expression(&f.body)
23157    }
23158
23159    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
23160        if f.colon {
23161            return self.generate_lambda(f);
23162        }
23163
23164        self.write("(");
23165        for (i, param) in f.parameters.iter().enumerate() {
23166            if i > 0 {
23167                self.write(", ");
23168            }
23169            self.generate_identifier(param)?;
23170        }
23171        self.write(") -> ");
23172        self.generate_expression(&f.body)
23173    }
23174
23175    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
23176        self.generate_identifier(&f.name)?;
23177        match f.separator {
23178            NamedArgSeparator::DArrow => self.write(" => "),
23179            NamedArgSeparator::ColonEq => self.write(" := "),
23180            NamedArgSeparator::Eq => self.write(" = "),
23181        }
23182        self.generate_expression(&f.value)
23183    }
23184
23185    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
23186        self.write_keyword(&f.prefix);
23187        self.write(" ");
23188        self.generate_expression(&f.this)
23189    }
23190
23191    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
23192        match f.style {
23193            ParameterStyle::Question => self.write("?"),
23194            ParameterStyle::Dollar => {
23195                if let Some(idx) = f.index {
23196                    if matches!(
23197                        self.config.dialect,
23198                        Some(DialectType::TSQL) | Some(DialectType::Fabric)
23199                    ) {
23200                        self.write("@P");
23201                        self.write(&idx.to_string());
23202                    } else {
23203                        self.write("$");
23204                        self.write(&idx.to_string());
23205                    }
23206                } else {
23207                    self.write("$");
23208                    if let Some(ref name) = f.name {
23209                        // Session variable like $x or $query_id
23210                        self.write(name);
23211                    }
23212                }
23213            }
23214            ParameterStyle::DollarBrace => {
23215                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
23216                self.write("${");
23217                if let Some(ref name) = f.name {
23218                    self.write(name);
23219                }
23220                if let Some(ref expr) = f.expression {
23221                    self.write(":");
23222                    self.write(expr);
23223                }
23224                self.write("}");
23225            }
23226            ParameterStyle::Colon => {
23227                self.write(":");
23228                if let Some(idx) = f.index {
23229                    self.write(&idx.to_string());
23230                } else if let Some(ref name) = f.name {
23231                    self.write(name);
23232                }
23233            }
23234            ParameterStyle::At => {
23235                self.write("@");
23236                if let Some(ref name) = f.name {
23237                    if f.string_quoted {
23238                        self.write("'");
23239                        self.write(name);
23240                        self.write("'");
23241                    } else if f.quoted {
23242                        self.write("\"");
23243                        self.write(name);
23244                        self.write("\"");
23245                    } else {
23246                        self.write(name);
23247                    }
23248                }
23249            }
23250            ParameterStyle::DoubleAt => {
23251                self.write("@@");
23252                if let Some(ref name) = f.name {
23253                    self.write(name);
23254                }
23255            }
23256            ParameterStyle::DoubleDollar => {
23257                self.write("$$");
23258                if let Some(ref name) = f.name {
23259                    self.write(name);
23260                }
23261            }
23262            ParameterStyle::Percent => {
23263                if let Some(ref name) = f.name {
23264                    // %(name)s format
23265                    self.write("%(");
23266                    self.write(name);
23267                    self.write(")s");
23268                } else {
23269                    // %s format
23270                    self.write("%s");
23271                }
23272            }
23273            ParameterStyle::Brace => {
23274                // Spark/Databricks widget template variable: {name}
23275                // ClickHouse query parameter may include kind: {name: Type}
23276                self.write("{");
23277                if let Some(ref name) = f.name {
23278                    self.write(name);
23279                }
23280                if let Some(ref expr) = f.expression {
23281                    self.write(": ");
23282                    self.write(expr);
23283                }
23284                self.write("}");
23285            }
23286        }
23287        Ok(())
23288    }
23289
23290    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
23291        self.write("?");
23292        if let Some(idx) = f.index {
23293            self.write(&idx.to_string());
23294        }
23295        Ok(())
23296    }
23297
23298    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
23299        if f.is_block {
23300            self.write("/*");
23301            self.write(&f.text);
23302            self.write("*/");
23303        } else {
23304            self.write("--");
23305            self.write(&f.text);
23306        }
23307        Ok(())
23308    }
23309
23310    // Additional predicate generators
23311
23312    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
23313        self.generate_expression(&f.this)?;
23314        if f.not {
23315            self.write_space();
23316            self.write_keyword("NOT");
23317        }
23318        self.write_space();
23319        self.write_keyword("SIMILAR TO");
23320        self.write_space();
23321        self.generate_expression(&f.pattern)?;
23322        if let Some(ref escape) = f.escape {
23323            self.write_space();
23324            self.write_keyword("ESCAPE");
23325            self.write_space();
23326            self.generate_expression(escape)?;
23327        }
23328        Ok(())
23329    }
23330
23331    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
23332        self.generate_expression(&f.this)?;
23333        self.write_space();
23334        // Output comparison operator if present
23335        if let Some(op) = &f.op {
23336            match op {
23337                QuantifiedOp::Eq => self.write("="),
23338                QuantifiedOp::Neq => self.write("<>"),
23339                QuantifiedOp::Lt => self.write("<"),
23340                QuantifiedOp::Lte => self.write("<="),
23341                QuantifiedOp::Gt => self.write(">"),
23342                QuantifiedOp::Gte => self.write(">="),
23343            }
23344            self.write_space();
23345        }
23346        self.write_keyword(name);
23347
23348        // If the child is a Subquery, it provides its own parens — output with space
23349        if matches!(&f.subquery, Expression::Subquery(_)) {
23350            self.write_space();
23351            self.generate_expression(&f.subquery)?;
23352        } else {
23353            let is_statement = matches!(
23354                &f.subquery,
23355                Expression::Select(_)
23356                    | Expression::Union(_)
23357                    | Expression::Intersect(_)
23358                    | Expression::Except(_)
23359            );
23360            if is_statement
23361                && !self.config.quantified_no_paren_space
23362                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
23363            {
23364                self.write_space();
23365            }
23366            self.write("(");
23367
23368            if self.config.pretty && is_statement {
23369                self.write_newline();
23370                self.indent_level += 1;
23371                self.write_indent();
23372            }
23373            self.generate_expression(&f.subquery)?;
23374            if self.config.pretty && is_statement {
23375                self.write_newline();
23376                self.indent_level -= 1;
23377                self.write_indent();
23378            }
23379            self.write(")");
23380        }
23381        Ok(())
23382    }
23383
23384    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
23385        // Check if this is a simple binary form (this OVERLAPS expression)
23386        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
23387            self.generate_expression(this)?;
23388            self.write_space();
23389            self.write_keyword("OVERLAPS");
23390            self.write_space();
23391            self.generate_expression(expr)?;
23392        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
23393            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
23394        {
23395            // Full ANSI form: (a, b) OVERLAPS (c, d)
23396            self.write("(");
23397            self.generate_expression(ls)?;
23398            self.write(", ");
23399            self.generate_expression(le)?;
23400            self.write(")");
23401            self.write_space();
23402            self.write_keyword("OVERLAPS");
23403            self.write_space();
23404            self.write("(");
23405            self.generate_expression(rs)?;
23406            self.write(", ");
23407            self.generate_expression(re)?;
23408            self.write(")");
23409        }
23410        Ok(())
23411    }
23412
23413    // Type conversion generators
23414
23415    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
23416        use crate::dialects::DialectType;
23417
23418        // SingleStore uses !:> syntax for try cast
23419        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23420            self.generate_expression(&cast.this)?;
23421            self.write(" !:> ");
23422            self.generate_data_type(&cast.to)?;
23423            return Ok(());
23424        }
23425
23426        // Teradata uses TRYCAST (no underscore)
23427        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
23428            self.write_keyword("TRYCAST");
23429            self.write("(");
23430            self.generate_expression(&cast.this)?;
23431            self.write_space();
23432            self.write_keyword("AS");
23433            self.write_space();
23434            self.generate_data_type(&cast.to)?;
23435            self.write(")");
23436            return Ok(());
23437        }
23438
23439        // Dialects without TRY_CAST: generate as regular CAST
23440        let keyword = if matches!(
23441            self.config.dialect,
23442            Some(DialectType::Hive)
23443                | Some(DialectType::MySQL)
23444                | Some(DialectType::SQLite)
23445                | Some(DialectType::Oracle)
23446                | Some(DialectType::ClickHouse)
23447                | Some(DialectType::Redshift)
23448                | Some(DialectType::PostgreSQL)
23449                | Some(DialectType::StarRocks)
23450                | Some(DialectType::Doris)
23451        ) {
23452            "CAST"
23453        } else {
23454            "TRY_CAST"
23455        };
23456
23457        self.write_keyword(keyword);
23458        self.write("(");
23459        self.generate_expression(&cast.this)?;
23460        self.write_space();
23461        self.write_keyword("AS");
23462        self.write_space();
23463        self.generate_data_type(&cast.to)?;
23464
23465        // Output FORMAT clause if present
23466        if let Some(format) = &cast.format {
23467            self.write_space();
23468            self.write_keyword("FORMAT");
23469            self.write_space();
23470            self.generate_expression(format)?;
23471        }
23472
23473        self.write(")");
23474        Ok(())
23475    }
23476
23477    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
23478        self.write_keyword("SAFE_CAST");
23479        self.write("(");
23480        self.generate_expression(&cast.this)?;
23481        self.write_space();
23482        self.write_keyword("AS");
23483        self.write_space();
23484        self.generate_data_type(&cast.to)?;
23485
23486        // Output FORMAT clause if present
23487        if let Some(format) = &cast.format {
23488            self.write_space();
23489            self.write_keyword("FORMAT");
23490            self.write_space();
23491            self.generate_expression(format)?;
23492        }
23493
23494        self.write(")");
23495        Ok(())
23496    }
23497
23498    // Array/struct/map access generators
23499
23500    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
23501        // Wrap the base expression in parentheses when it uses arrow syntax (->)
23502        // which has lower precedence than bracket subscript ([]).
23503        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
23504        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
23505        if needs_parens {
23506            self.write("(");
23507        }
23508        self.generate_expression(&s.this)?;
23509        if needs_parens {
23510            self.write(")");
23511        }
23512        self.write("[");
23513        self.generate_expression(&s.index)?;
23514        self.write("]");
23515        Ok(())
23516    }
23517
23518    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
23519        self.generate_expression(&d.this)?;
23520        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
23521        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
23522        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
23523            && matches!(
23524                &d.this,
23525                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
23526            );
23527        if use_colon {
23528            self.write(":");
23529        } else {
23530            self.write(".");
23531        }
23532        self.generate_identifier(&d.field)
23533    }
23534
23535    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
23536        self.generate_expression(&m.this)?;
23537        self.write(".");
23538        // Method names after a dot should not be quoted based on reserved keywords
23539        // Only quote if explicitly marked as quoted in the AST
23540        if m.method.quoted {
23541            let q = self.config.identifier_quote;
23542            self.write(&format!("{}{}{}", q, m.method.name, q));
23543        } else {
23544            self.write(&m.method.name);
23545        }
23546        self.write("(");
23547        for (i, arg) in m.args.iter().enumerate() {
23548            if i > 0 {
23549                self.write(", ");
23550            }
23551            self.generate_expression(arg)?;
23552        }
23553        self.write(")");
23554        Ok(())
23555    }
23556
23557    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
23558        // Check if we need to wrap the inner expression in parentheses
23559        // JSON arrow expressions have lower precedence than array subscript
23560        let needs_parens = matches!(
23561            &s.this,
23562            Expression::JsonExtract(f) if f.arrow_syntax
23563        ) || matches!(
23564            &s.this,
23565            Expression::JsonExtractScalar(f) if f.arrow_syntax
23566        );
23567
23568        if needs_parens {
23569            self.write("(");
23570        }
23571        self.generate_expression(&s.this)?;
23572        if needs_parens {
23573            self.write(")");
23574        }
23575        self.write("[");
23576        if let Some(start) = &s.start {
23577            self.generate_expression(start)?;
23578        }
23579        self.write(":");
23580        if let Some(end) = &s.end {
23581            self.generate_expression(end)?;
23582        }
23583        self.write("]");
23584        Ok(())
23585    }
23586
23587    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23588        // Generate left expression, but skip trailing comments if they're already in left_comments
23589        // to avoid duplication (comments are captured as both expr.trailing_comments
23590        // and BinaryOp.left_comments during parsing)
23591        match &op.left {
23592            Expression::Column(col) => {
23593                // Generate column with trailing comments but skip them if they're
23594                // already captured in BinaryOp.left_comments to avoid duplication
23595                if let Some(table) = &col.table {
23596                    self.generate_identifier(table)?;
23597                    self.write(".");
23598                }
23599                self.generate_identifier(&col.name)?;
23600                // Oracle-style join marker (+)
23601                if col.join_mark && self.config.supports_column_join_marks {
23602                    self.write(" (+)");
23603                }
23604                // Output column trailing comments if they're not already in left_comments
23605                if op.left_comments.is_empty() {
23606                    for comment in &col.trailing_comments {
23607                        self.write_space();
23608                        self.write_formatted_comment(comment);
23609                    }
23610                }
23611            }
23612            Expression::Add(inner_op)
23613            | Expression::Sub(inner_op)
23614            | Expression::Mul(inner_op)
23615            | Expression::Div(inner_op)
23616            | Expression::Concat(inner_op) => {
23617                // Generate binary op without its trailing comments
23618                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23619                    Expression::Add(_) => "+",
23620                    Expression::Sub(_) => "-",
23621                    Expression::Mul(_) => "*",
23622                    Expression::Div(_) => "/",
23623                    Expression::Concat(_) => "||",
23624                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23625                })?;
23626            }
23627            _ => {
23628                self.generate_expression(&op.left)?;
23629            }
23630        }
23631        // Output comments after left operand
23632        for comment in &op.left_comments {
23633            self.write_space();
23634            self.write_formatted_comment(comment);
23635        }
23636        if self.config.pretty
23637            && matches!(self.config.dialect, Some(DialectType::Snowflake))
23638            && (operator == "AND" || operator == "OR")
23639        {
23640            self.write_newline();
23641            self.write_indent();
23642            self.write_keyword(operator);
23643        } else {
23644            self.write_space();
23645            if operator.chars().all(|c| c.is_alphabetic()) {
23646                self.write_keyword(operator);
23647            } else {
23648                self.write(operator);
23649            }
23650        }
23651        // Output comments after operator (before right operand)
23652        for comment in &op.operator_comments {
23653            self.write_space();
23654            self.write_formatted_comment(comment);
23655        }
23656        self.write_space();
23657        self.generate_expression(&op.right)?;
23658        // Output trailing comments after right operand
23659        for comment in &op.trailing_comments {
23660            self.write_space();
23661            self.write_formatted_comment(comment);
23662        }
23663        Ok(())
23664    }
23665
23666    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
23667        let keyword = connector.keyword();
23668        let Some(terms) = self.flatten_connector_terms(op, connector) else {
23669            return self.generate_binary_op(op, keyword);
23670        };
23671
23672        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
23673            let should_wrap = matches!(connector, ConnectorOperator::Or)
23674                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
23675                && matches!(
23676                    generator.config.source_dialect,
23677                    Some(DialectType::ClickHouse)
23678                )
23679                && matches!(term, Expression::And(_));
23680            if should_wrap {
23681                generator.write("(");
23682                generator.generate_expression(term)?;
23683                generator.write(")");
23684            } else {
23685                generator.generate_expression(term)?;
23686            }
23687            Ok(())
23688        };
23689
23690        wrap_clickhouse_or_term(self, terms[0])?;
23691        for term in terms.iter().skip(1) {
23692            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
23693                self.write_newline();
23694                self.write_indent();
23695                self.write_keyword(keyword);
23696            } else {
23697                self.write_space();
23698                self.write_keyword(keyword);
23699            }
23700            self.write_space();
23701            wrap_clickhouse_or_term(self, term)?;
23702        }
23703
23704        Ok(())
23705    }
23706
23707    fn flatten_connector_terms<'a>(
23708        &self,
23709        root: &'a BinaryOp,
23710        connector: ConnectorOperator,
23711    ) -> Option<Vec<&'a Expression>> {
23712        if !root.left_comments.is_empty()
23713            || !root.operator_comments.is_empty()
23714            || !root.trailing_comments.is_empty()
23715        {
23716            return None;
23717        }
23718
23719        let mut terms = Vec::new();
23720        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
23721
23722        while let Some(expr) = stack.pop() {
23723            match (connector, expr) {
23724                (ConnectorOperator::And, Expression::And(inner))
23725                    if inner.left_comments.is_empty()
23726                        && inner.operator_comments.is_empty()
23727                        && inner.trailing_comments.is_empty() =>
23728                {
23729                    stack.push(&inner.right);
23730                    stack.push(&inner.left);
23731                }
23732                (ConnectorOperator::Or, Expression::Or(inner))
23733                    if inner.left_comments.is_empty()
23734                        && inner.operator_comments.is_empty()
23735                        && inner.trailing_comments.is_empty() =>
23736                {
23737                    stack.push(&inner.right);
23738                    stack.push(&inner.left);
23739                }
23740                _ => terms.push(expr),
23741            }
23742        }
23743
23744        if terms.len() > 1 {
23745            Some(terms)
23746        } else {
23747            None
23748        }
23749    }
23750
23751    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
23752        let mut depth = 0usize;
23753        let mut quote: Option<char> = None;
23754        let mut chars = sql.chars().peekable();
23755
23756        while let Some(ch) = chars.next() {
23757            if let Some(quote_char) = quote {
23758                if ch == '\\' {
23759                    chars.next();
23760                } else if ch == quote_char {
23761                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
23762                        chars.next();
23763                    } else {
23764                        quote = None;
23765                    }
23766                }
23767                continue;
23768            }
23769
23770            match ch {
23771                '\'' | '"' | '`' => quote = Some(ch),
23772                '(' => depth += 1,
23773                ')' => depth = depth.saturating_sub(1),
23774                _ => {}
23775            }
23776        }
23777
23778        depth
23779    }
23780
23781    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
23782    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23783        self.generate_like_op_inner(op, operator, false)
23784    }
23785
23786    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23787        self.generate_like_op_inner(op, operator, true)
23788    }
23789
23790    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
23791        if negated
23792            && matches!(
23793                self.config.dialect,
23794                Some(DialectType::ClickHouse)
23795                    | Some(DialectType::DataFusion)
23796                    | Some(DialectType::TSQL)
23797                    | Some(DialectType::Fabric)
23798            )
23799        {
23800            self.write_keyword("NOT");
23801            self.write_space();
23802            return self.generate_like_op_inner(op, operator, false);
23803        }
23804
23805        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23806            if let Expression::Star(star) = &op.left {
23807                if star
23808                    .except
23809                    .as_ref()
23810                    .is_some_and(|except| !except.is_empty())
23811                {
23812                    if let Some(table) = &star.table {
23813                        self.generate_identifier(table)?;
23814                        self.write(".");
23815                    }
23816                    self.write("*");
23817                    self.write_space();
23818                    self.write_keyword(operator);
23819                    if let Some(quantifier) = &op.quantifier {
23820                        self.write_space();
23821                        self.write_keyword(quantifier);
23822                        self.write_space();
23823                    } else {
23824                        self.write_space();
23825                    }
23826                    self.generate_expression(&op.right)?;
23827                    if let Some(escape) = &op.escape {
23828                        self.write_space();
23829                        self.write_keyword("ESCAPE");
23830                        self.write_space();
23831                        self.generate_expression(escape)?;
23832                    }
23833                    if let Some(except) = &star.except {
23834                        self.write_space();
23835                        self.write_keyword("EXCEPT");
23836                        self.write(" (");
23837                        for (i, col) in except.iter().enumerate() {
23838                            if i > 0 {
23839                                self.write(", ");
23840                            }
23841                            self.generate_identifier(col)?;
23842                        }
23843                        self.write(")");
23844                    }
23845                    return Ok(());
23846                }
23847            }
23848        }
23849
23850        self.generate_expression(&op.left)?;
23851        self.write_space();
23852        if negated {
23853            self.write_keyword("NOT");
23854            self.write_space();
23855        }
23856        // Drill backtick-quotes ILIKE
23857        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
23858            self.write("`ILIKE`");
23859        } else {
23860            self.write_keyword(operator);
23861        }
23862        if let Some(quantifier) = &op.quantifier {
23863            self.write_space();
23864            self.write_keyword(quantifier);
23865            // Match Python sqlglot behavior:
23866            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
23867            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
23868            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
23869            let is_any =
23870                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
23871            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
23872                self.write_space();
23873            }
23874        } else {
23875            self.write_space();
23876        }
23877        self.generate_expression(&op.right)?;
23878        if let Some(escape) = &op.escape {
23879            self.write_space();
23880            self.write_keyword("ESCAPE");
23881            self.write_space();
23882            self.generate_expression(escape)?;
23883        }
23884        Ok(())
23885    }
23886
23887    /// Generate null-safe equality
23888    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
23889    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
23890        use crate::dialects::DialectType;
23891        self.generate_expression(&op.left)?;
23892        self.write_space();
23893        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
23894            self.write("<=>");
23895        } else {
23896            self.write_keyword("IS NOT DISTINCT FROM");
23897        }
23898        self.write_space();
23899        self.generate_expression(&op.right)?;
23900        Ok(())
23901    }
23902
23903    /// Generate IS DISTINCT FROM (null-safe inequality)
23904    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
23905        self.generate_expression(&op.left)?;
23906        self.write_space();
23907        self.write_keyword("IS DISTINCT FROM");
23908        self.write_space();
23909        self.generate_expression(&op.right)?;
23910        Ok(())
23911    }
23912
23913    /// Generate binary op without trailing comments (used when nested inside another binary op)
23914    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23915        // Generate left expression, but skip trailing comments
23916        match &op.left {
23917            Expression::Column(col) => {
23918                if let Some(table) = &col.table {
23919                    self.generate_identifier(table)?;
23920                    self.write(".");
23921                }
23922                self.generate_identifier(&col.name)?;
23923                // Oracle-style join marker (+)
23924                if col.join_mark && self.config.supports_column_join_marks {
23925                    self.write(" (+)");
23926                }
23927            }
23928            Expression::Add(inner_op)
23929            | Expression::Sub(inner_op)
23930            | Expression::Mul(inner_op)
23931            | Expression::Div(inner_op)
23932            | Expression::Concat(inner_op) => {
23933                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23934                    Expression::Add(_) => "+",
23935                    Expression::Sub(_) => "-",
23936                    Expression::Mul(_) => "*",
23937                    Expression::Div(_) => "/",
23938                    Expression::Concat(_) => "||",
23939                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23940                })?;
23941            }
23942            _ => {
23943                self.generate_expression(&op.left)?;
23944            }
23945        }
23946        // Output left_comments
23947        for comment in &op.left_comments {
23948            self.write_space();
23949            self.write_formatted_comment(comment);
23950        }
23951        self.write_space();
23952        if operator.chars().all(|c| c.is_alphabetic()) {
23953            self.write_keyword(operator);
23954        } else {
23955            self.write(operator);
23956        }
23957        // Output operator_comments
23958        for comment in &op.operator_comments {
23959            self.write_space();
23960            self.write_formatted_comment(comment);
23961        }
23962        self.write_space();
23963        // Generate right expression, but skip trailing comments if it's a Column
23964        // (the parent's left_comments will output them)
23965        match &op.right {
23966            Expression::Column(col) => {
23967                if let Some(table) = &col.table {
23968                    self.generate_identifier(table)?;
23969                    self.write(".");
23970                }
23971                self.generate_identifier(&col.name)?;
23972                // Oracle-style join marker (+)
23973                if col.join_mark && self.config.supports_column_join_marks {
23974                    self.write(" (+)");
23975                }
23976            }
23977            _ => {
23978                self.generate_expression(&op.right)?;
23979            }
23980        }
23981        // Skip trailing_comments - parent will handle them via its left_comments
23982        Ok(())
23983    }
23984
23985    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
23986        if operator.chars().all(|c| c.is_alphabetic()) {
23987            self.write_keyword(operator);
23988            self.write_space();
23989        } else {
23990            self.write(operator);
23991            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
23992            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
23993                self.write_space();
23994            }
23995        }
23996        self.generate_expression(&op.this)
23997    }
23998
23999    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
24000        // Generic mode supports two styles for negated IN:
24001        // - Prefix: NOT a IN (...)
24002        // - Infix:  a NOT IN (...)
24003        let is_generic =
24004            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24005        let use_prefix_not =
24006            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
24007        if use_prefix_not {
24008            self.write_keyword("NOT");
24009            self.write_space();
24010        }
24011        self.generate_expression(&in_expr.this)?;
24012        if in_expr.global {
24013            self.write_space();
24014            self.write_keyword("GLOBAL");
24015        }
24016        if in_expr.not && !use_prefix_not {
24017            self.write_space();
24018            self.write_keyword("NOT");
24019        }
24020        self.write_space();
24021        self.write_keyword("IN");
24022
24023        // BigQuery: IN UNNEST(expr)
24024        if let Some(unnest_expr) = &in_expr.unnest {
24025            self.write_space();
24026            self.write_keyword("UNNEST");
24027            self.write("(");
24028            self.generate_expression(unnest_expr)?;
24029            self.write(")");
24030            return Ok(());
24031        }
24032
24033        if let Some(query) = &in_expr.query {
24034            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
24035            // vs a subquery (col IN (SELECT ...))
24036            let is_bare = in_expr.expressions.is_empty()
24037                && !matches!(
24038                    query,
24039                    Expression::Select(_)
24040                        | Expression::Union(_)
24041                        | Expression::Intersect(_)
24042                        | Expression::Except(_)
24043                        | Expression::Subquery(_)
24044                );
24045            if is_bare {
24046                // Bare identifier: no parentheses
24047                self.write_space();
24048                self.generate_expression(query)?;
24049            } else {
24050                // Subquery: with parentheses
24051                self.write(" (");
24052                let is_statement = matches!(
24053                    query,
24054                    Expression::Select(_)
24055                        | Expression::Union(_)
24056                        | Expression::Intersect(_)
24057                        | Expression::Except(_)
24058                        | Expression::Subquery(_)
24059                );
24060                if self.config.pretty && is_statement {
24061                    self.write_newline();
24062                    self.indent_level += 1;
24063                    self.write_indent();
24064                }
24065                self.generate_expression(query)?;
24066                if self.config.pretty && is_statement {
24067                    self.write_newline();
24068                    self.indent_level -= 1;
24069                    self.write_indent();
24070                }
24071                self.write(")");
24072            }
24073        } else {
24074            // DuckDB: IN without parentheses for single expression that is NOT a literal
24075            // (array/list membership like 'red' IN tbl.flags)
24076            // ClickHouse: IN without parentheses for single non-array expressions
24077            let is_duckdb = matches!(
24078                self.config.dialect,
24079                Some(crate::dialects::DialectType::DuckDB)
24080            );
24081            let is_clickhouse = matches!(
24082                self.config.dialect,
24083                Some(crate::dialects::DialectType::ClickHouse)
24084            );
24085            let single_expr = in_expr.expressions.len() == 1;
24086            if is_clickhouse && single_expr {
24087                if let Expression::Array(arr) = &in_expr.expressions[0] {
24088                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
24089                    self.write(" (");
24090                    for (i, expr) in arr.expressions.iter().enumerate() {
24091                        if i > 0 {
24092                            self.write(", ");
24093                        }
24094                        self.generate_expression(expr)?;
24095                    }
24096                    self.write(")");
24097                } else if in_expr.is_field {
24098                    self.write_space();
24099                    self.generate_expression(&in_expr.expressions[0])?;
24100                } else {
24101                    self.write(" (");
24102                    self.generate_expression(&in_expr.expressions[0])?;
24103                    self.write(")");
24104                }
24105            } else {
24106                let is_bare_ref = single_expr
24107                    && matches!(
24108                        &in_expr.expressions[0],
24109                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
24110                    );
24111                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
24112                    // Bare field reference (no parens in source): IN identifier
24113                    // Also DuckDB: IN without parentheses for array/list membership
24114                    self.write_space();
24115                    self.generate_expression(&in_expr.expressions[0])?;
24116                } else {
24117                    // Standard IN (list)
24118                    self.write(" (");
24119                    for (i, expr) in in_expr.expressions.iter().enumerate() {
24120                        if i > 0 {
24121                            self.write(", ");
24122                        }
24123                        self.generate_expression(expr)?;
24124                    }
24125                    self.write(")");
24126                }
24127            }
24128        }
24129
24130        Ok(())
24131    }
24132
24133    fn generate_between(&mut self, between: &Between) -> Result<()> {
24134        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
24135        let use_prefix_not = between.not
24136            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
24137        if use_prefix_not {
24138            self.write_keyword("NOT");
24139            self.write_space();
24140        }
24141        self.generate_expression(&between.this)?;
24142        if between.not && !use_prefix_not {
24143            self.write_space();
24144            self.write_keyword("NOT");
24145        }
24146        self.write_space();
24147        self.write_keyword("BETWEEN");
24148        // Emit SYMMETRIC/ASYMMETRIC if present
24149        if let Some(sym) = between.symmetric {
24150            if sym {
24151                self.write(" SYMMETRIC");
24152            } else {
24153                self.write(" ASYMMETRIC");
24154            }
24155        }
24156        self.write_space();
24157        self.generate_expression(&between.low)?;
24158        self.write_space();
24159        self.write_keyword("AND");
24160        self.write_space();
24161        self.generate_expression(&between.high)
24162    }
24163
24164    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
24165        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
24166        let use_prefix_not = is_null.not
24167            && (self.config.dialect.is_none()
24168                || self.config.dialect == Some(DialectType::Generic)
24169                || is_null.postfix_form);
24170        if use_prefix_not {
24171            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
24172            self.write_keyword("NOT");
24173            self.write_space();
24174            self.generate_expression(&is_null.this)?;
24175            self.write_space();
24176            self.write_keyword("IS");
24177            self.write_space();
24178            self.write_keyword("NULL");
24179        } else {
24180            self.generate_expression(&is_null.this)?;
24181            self.write_space();
24182            self.write_keyword("IS");
24183            if is_null.not {
24184                self.write_space();
24185                self.write_keyword("NOT");
24186            }
24187            self.write_space();
24188            self.write_keyword("NULL");
24189        }
24190        Ok(())
24191    }
24192
24193    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
24194        self.generate_expression(&is_true.this)?;
24195        self.write_space();
24196        self.write_keyword("IS");
24197        if is_true.not {
24198            self.write_space();
24199            self.write_keyword("NOT");
24200        }
24201        self.write_space();
24202        self.write_keyword("TRUE");
24203        Ok(())
24204    }
24205
24206    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
24207        self.generate_expression(&is_false.this)?;
24208        self.write_space();
24209        self.write_keyword("IS");
24210        if is_false.not {
24211            self.write_space();
24212            self.write_keyword("NOT");
24213        }
24214        self.write_space();
24215        self.write_keyword("FALSE");
24216        Ok(())
24217    }
24218
24219    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
24220        self.generate_expression(&is_json.this)?;
24221        self.write_space();
24222        self.write_keyword("IS");
24223        if is_json.negated {
24224            self.write_space();
24225            self.write_keyword("NOT");
24226        }
24227        self.write_space();
24228        self.write_keyword("JSON");
24229
24230        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
24231        if let Some(ref json_type) = is_json.json_type {
24232            self.write_space();
24233            self.write_keyword(json_type);
24234        }
24235
24236        // Output key uniqueness constraint if specified
24237        match &is_json.unique_keys {
24238            Some(JsonUniqueKeys::With) => {
24239                self.write_space();
24240                self.write_keyword("WITH UNIQUE KEYS");
24241            }
24242            Some(JsonUniqueKeys::Without) => {
24243                self.write_space();
24244                self.write_keyword("WITHOUT UNIQUE KEYS");
24245            }
24246            Some(JsonUniqueKeys::Shorthand) => {
24247                self.write_space();
24248                self.write_keyword("UNIQUE KEYS");
24249            }
24250            None => {}
24251        }
24252
24253        Ok(())
24254    }
24255
24256    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
24257        self.generate_expression(&is_expr.left)?;
24258        self.write_space();
24259        self.write_keyword("IS");
24260        self.write_space();
24261        self.generate_expression(&is_expr.right)
24262    }
24263
24264    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
24265        if exists.not {
24266            self.write_keyword("NOT");
24267            self.write_space();
24268        }
24269        self.write_keyword("EXISTS");
24270        self.write("(");
24271        let is_statement = matches!(
24272            &exists.this,
24273            Expression::Select(_)
24274                | Expression::Union(_)
24275                | Expression::Intersect(_)
24276                | Expression::Except(_)
24277        );
24278        if self.config.pretty && is_statement {
24279            self.write_newline();
24280            self.indent_level += 1;
24281            self.write_indent();
24282            self.generate_expression(&exists.this)?;
24283            self.write_newline();
24284            self.indent_level -= 1;
24285            self.write_indent();
24286            self.write(")");
24287        } else {
24288            self.generate_expression(&exists.this)?;
24289            self.write(")");
24290        }
24291        Ok(())
24292    }
24293
24294    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
24295        self.generate_expression(&op.left)?;
24296        self.write_space();
24297        self.write_keyword("MEMBER OF");
24298        self.write("(");
24299        self.generate_expression(&op.right)?;
24300        self.write(")");
24301        Ok(())
24302    }
24303
24304    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
24305        if subquery.lateral {
24306            self.write_keyword("LATERAL");
24307            self.write_space();
24308        }
24309
24310        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
24311        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
24312        // to carry the LIMIT modifier without adding more parens
24313        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
24314            matches!(
24315                &p.this,
24316                Expression::Select(_)
24317                    | Expression::Union(_)
24318                    | Expression::Intersect(_)
24319                    | Expression::Except(_)
24320                    | Expression::Subquery(_)
24321            )
24322        } else {
24323            false
24324        };
24325
24326        // Check if inner expression is a statement for pretty formatting
24327        let is_statement = matches!(
24328            &subquery.this,
24329            Expression::Select(_)
24330                | Expression::Union(_)
24331                | Expression::Intersect(_)
24332                | Expression::Except(_)
24333                | Expression::Merge(_)
24334        );
24335
24336        if !skip_outer_parens {
24337            self.write("(");
24338            if self.config.pretty && is_statement {
24339                self.write_newline();
24340                self.indent_level += 1;
24341                self.write_indent();
24342            }
24343        }
24344        self.generate_expression(&subquery.this)?;
24345
24346        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
24347        if subquery.modifiers_inside {
24348            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
24349            if let Some(order_by) = &subquery.order_by {
24350                self.write_space();
24351                self.write_keyword("ORDER BY");
24352                self.write_space();
24353                for (i, ord) in order_by.expressions.iter().enumerate() {
24354                    if i > 0 {
24355                        self.write(", ");
24356                    }
24357                    self.generate_ordered(ord)?;
24358                }
24359            }
24360
24361            if let Some(limit) = &subquery.limit {
24362                self.write_space();
24363                self.write_keyword("LIMIT");
24364                self.write_space();
24365                self.generate_expression(&limit.this)?;
24366                if limit.percent {
24367                    self.write_space();
24368                    self.write_keyword("PERCENT");
24369                }
24370            }
24371
24372            if let Some(offset) = &subquery.offset {
24373                self.write_space();
24374                self.write_keyword("OFFSET");
24375                self.write_space();
24376                self.generate_expression(&offset.this)?;
24377            }
24378        }
24379
24380        if !skip_outer_parens {
24381            if self.config.pretty && is_statement {
24382                self.write_newline();
24383                self.indent_level -= 1;
24384                self.write_indent();
24385            }
24386            self.write(")");
24387        }
24388
24389        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
24390        if !subquery.modifiers_inside {
24391            if let Some(order_by) = &subquery.order_by {
24392                self.write_space();
24393                self.write_keyword("ORDER BY");
24394                self.write_space();
24395                for (i, ord) in order_by.expressions.iter().enumerate() {
24396                    if i > 0 {
24397                        self.write(", ");
24398                    }
24399                    self.generate_ordered(ord)?;
24400                }
24401            }
24402
24403            if let Some(limit) = &subquery.limit {
24404                self.write_space();
24405                self.write_keyword("LIMIT");
24406                self.write_space();
24407                self.generate_expression(&limit.this)?;
24408                if limit.percent {
24409                    self.write_space();
24410                    self.write_keyword("PERCENT");
24411                }
24412            }
24413
24414            if let Some(offset) = &subquery.offset {
24415                self.write_space();
24416                self.write_keyword("OFFSET");
24417                self.write_space();
24418                self.generate_expression(&offset.this)?;
24419            }
24420
24421            // Generate DISTRIBUTE BY (Hive/Spark)
24422            if let Some(distribute_by) = &subquery.distribute_by {
24423                self.write_space();
24424                self.write_keyword("DISTRIBUTE BY");
24425                self.write_space();
24426                for (i, expr) in distribute_by.expressions.iter().enumerate() {
24427                    if i > 0 {
24428                        self.write(", ");
24429                    }
24430                    self.generate_expression(expr)?;
24431                }
24432            }
24433
24434            // Generate SORT BY (Hive/Spark)
24435            if let Some(sort_by) = &subquery.sort_by {
24436                self.write_space();
24437                self.write_keyword("SORT BY");
24438                self.write_space();
24439                for (i, ord) in sort_by.expressions.iter().enumerate() {
24440                    if i > 0 {
24441                        self.write(", ");
24442                    }
24443                    self.generate_ordered(ord)?;
24444                }
24445            }
24446
24447            // Generate CLUSTER BY (Hive/Spark)
24448            if let Some(cluster_by) = &subquery.cluster_by {
24449                self.write_space();
24450                self.write_keyword("CLUSTER BY");
24451                self.write_space();
24452                for (i, ord) in cluster_by.expressions.iter().enumerate() {
24453                    if i > 0 {
24454                        self.write(", ");
24455                    }
24456                    self.generate_ordered(ord)?;
24457                }
24458            }
24459        }
24460
24461        if let Some(alias) = &subquery.alias {
24462            self.write_space();
24463            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
24464                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
24465                    && !subquery.alias_explicit_as);
24466            if !skip_as {
24467                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24468                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
24469                } else {
24470                    self.write_keyword("AS");
24471                }
24472                self.write_space();
24473            }
24474            self.generate_identifier(alias)?;
24475            if !subquery.column_aliases.is_empty() {
24476                self.write("(");
24477                for (i, col) in subquery.column_aliases.iter().enumerate() {
24478                    if i > 0 {
24479                        self.write(", ");
24480                    }
24481                    self.generate_identifier(col)?;
24482                }
24483                self.write(")");
24484            }
24485        }
24486        // Output trailing comments
24487        for comment in &subquery.trailing_comments {
24488            self.write(" ");
24489            self.write_formatted_comment(comment);
24490        }
24491        Ok(())
24492    }
24493
24494    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
24495        // Generate WITH clause if present
24496        if let Some(ref with) = pivot.with {
24497            self.generate_with(with)?;
24498            self.write_space();
24499        }
24500
24501        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
24502
24503        // Check for Redshift UNPIVOT in FROM clause:
24504        // UNPIVOT expr [AS val AT attr]
24505        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
24506        let is_redshift_unpivot = pivot.unpivot
24507            && pivot.expressions.is_empty()
24508            && pivot.fields.is_empty()
24509            && pivot.using.is_empty()
24510            && pivot.into.is_none()
24511            && !matches!(&pivot.this, Expression::Null(_));
24512
24513        if is_redshift_unpivot {
24514            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
24515            self.write_keyword("UNPIVOT");
24516            self.write_space();
24517            self.generate_expression(&pivot.this)?;
24518            // Alias - for Redshift it can be "val AT attr" format
24519            if let Some(alias) = &pivot.alias {
24520                self.write_space();
24521                self.write_keyword("AS");
24522                self.write_space();
24523                // The alias might contain " AT " for the attr part
24524                self.write(&alias.name);
24525            }
24526            return Ok(());
24527        }
24528
24529        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
24530        let is_simplified = !pivot.using.is_empty()
24531            || pivot.into.is_some()
24532            || (pivot.fields.is_empty()
24533                && !pivot.expressions.is_empty()
24534                && !matches!(&pivot.this, Expression::Null(_)));
24535
24536        if is_simplified {
24537            // DuckDB simplified syntax:
24538            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
24539            //   UNPIVOT table ON cols INTO NAME col VALUE col
24540            self.write_keyword(direction);
24541            self.write_space();
24542            self.generate_expression(&pivot.this)?;
24543
24544            if !pivot.expressions.is_empty() {
24545                self.write_space();
24546                self.write_keyword("ON");
24547                self.write_space();
24548                for (i, expr) in pivot.expressions.iter().enumerate() {
24549                    if i > 0 {
24550                        self.write(", ");
24551                    }
24552                    self.generate_expression(expr)?;
24553                }
24554            }
24555
24556            // INTO (for UNPIVOT)
24557            if let Some(into) = &pivot.into {
24558                self.write_space();
24559                self.write_keyword("INTO");
24560                self.write_space();
24561                self.generate_expression(into)?;
24562            }
24563
24564            // USING (for PIVOT)
24565            if !pivot.using.is_empty() {
24566                self.write_space();
24567                self.write_keyword("USING");
24568                self.write_space();
24569                for (i, expr) in pivot.using.iter().enumerate() {
24570                    if i > 0 {
24571                        self.write(", ");
24572                    }
24573                    self.generate_expression(expr)?;
24574                }
24575            }
24576
24577            // GROUP BY
24578            if let Some(group) = &pivot.group {
24579                self.write_space();
24580                self.generate_expression(group)?;
24581            }
24582        } else {
24583            // Standard syntax:
24584            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
24585            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
24586            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
24587            if !matches!(&pivot.this, Expression::Null(_)) {
24588                self.generate_expression(&pivot.this)?;
24589                self.write_space();
24590            }
24591            self.write_keyword(direction);
24592            self.write("(");
24593
24594            // Aggregation expressions
24595            for (i, expr) in pivot.expressions.iter().enumerate() {
24596                if i > 0 {
24597                    self.write(", ");
24598                }
24599                self.generate_expression(expr)?;
24600            }
24601
24602            // FOR...IN fields
24603            if !pivot.fields.is_empty() {
24604                if !pivot.expressions.is_empty() {
24605                    self.write_space();
24606                }
24607                self.write_keyword("FOR");
24608                self.write_space();
24609                for (i, field) in pivot.fields.iter().enumerate() {
24610                    if i > 0 {
24611                        self.write_space();
24612                    }
24613                    // field is an In expression: column IN (values)
24614                    self.generate_expression(field)?;
24615                }
24616            }
24617
24618            // DEFAULT ON NULL
24619            if let Some(default_val) = &pivot.default_on_null {
24620                self.write_space();
24621                self.write_keyword("DEFAULT ON NULL");
24622                self.write(" (");
24623                self.generate_expression(default_val)?;
24624                self.write(")");
24625            }
24626
24627            // GROUP BY inside PIVOT parens
24628            if let Some(group) = &pivot.group {
24629                self.write_space();
24630                self.generate_expression(group)?;
24631            }
24632
24633            self.write(")");
24634        }
24635
24636        // Alias
24637        if let Some(alias) = &pivot.alias {
24638            self.write_space();
24639            self.write_keyword("AS");
24640            self.write_space();
24641            self.generate_identifier(alias)?;
24642            self.generate_alias_column_list(&pivot.alias_columns)?;
24643        }
24644
24645        Ok(())
24646    }
24647
24648    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
24649        self.generate_expression(&unpivot.this)?;
24650        self.write_space();
24651        self.write_keyword("UNPIVOT");
24652        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
24653        if let Some(include) = unpivot.include_nulls {
24654            self.write_space();
24655            if include {
24656                self.write_keyword("INCLUDE NULLS");
24657            } else {
24658                self.write_keyword("EXCLUDE NULLS");
24659            }
24660            self.write_space();
24661        }
24662        self.write("(");
24663        if unpivot.value_column_parenthesized {
24664            self.write("(");
24665        }
24666        self.generate_identifier(&unpivot.value_column)?;
24667        // Output additional value columns if present
24668        for extra_col in &unpivot.extra_value_columns {
24669            self.write(", ");
24670            self.generate_identifier(extra_col)?;
24671        }
24672        if unpivot.value_column_parenthesized {
24673            self.write(")");
24674        }
24675        self.write_space();
24676        self.write_keyword("FOR");
24677        self.write_space();
24678        self.generate_identifier(&unpivot.name_column)?;
24679        self.write_space();
24680        self.write_keyword("IN");
24681        self.write(" (");
24682        for (i, col) in unpivot.columns.iter().enumerate() {
24683            if i > 0 {
24684                self.write(", ");
24685            }
24686            self.generate_expression(col)?;
24687        }
24688        self.write("))");
24689        if let Some(alias) = &unpivot.alias {
24690            self.write_space();
24691            self.write_keyword("AS");
24692            self.write_space();
24693            self.generate_identifier(alias)?;
24694            self.generate_alias_column_list(&unpivot.alias_columns)?;
24695        }
24696        Ok(())
24697    }
24698
24699    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
24700        if columns.is_empty() {
24701            return Ok(());
24702        }
24703
24704        self.write("(");
24705        for (i, column) in columns.iter().enumerate() {
24706            if i > 0 {
24707                self.write(", ");
24708            }
24709            self.generate_identifier(column)?;
24710        }
24711        self.write(")");
24712        Ok(())
24713    }
24714
24715    fn generate_values(&mut self, values: &Values) -> Result<()> {
24716        self.write_keyword("VALUES");
24717        for (i, row) in values.expressions.iter().enumerate() {
24718            if i > 0 {
24719                self.write(",");
24720            }
24721            self.write(" (");
24722            for (j, expr) in row.expressions.iter().enumerate() {
24723                if j > 0 {
24724                    self.write(", ");
24725                }
24726                self.generate_expression(expr)?;
24727            }
24728            self.write(")");
24729        }
24730        if let Some(alias) = &values.alias {
24731            self.write_space();
24732            self.write_keyword("AS");
24733            self.write_space();
24734            self.generate_identifier(alias)?;
24735            if !values.column_aliases.is_empty() {
24736                self.write("(");
24737                for (i, col) in values.column_aliases.iter().enumerate() {
24738                    if i > 0 {
24739                        self.write(", ");
24740                    }
24741                    self.generate_identifier(col)?;
24742                }
24743                self.write(")");
24744            }
24745        }
24746        Ok(())
24747    }
24748
24749    fn generate_array(&mut self, arr: &Array) -> Result<()> {
24750        // Apply struct name inheritance for target dialects that need it
24751        let needs_inheritance = matches!(
24752            self.config.dialect,
24753            Some(DialectType::DuckDB)
24754                | Some(DialectType::Spark)
24755                | Some(DialectType::Databricks)
24756                | Some(DialectType::Hive)
24757                | Some(DialectType::Snowflake)
24758                | Some(DialectType::Presto)
24759                | Some(DialectType::Trino)
24760        );
24761        let propagated: Vec<Expression>;
24762        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
24763            propagated = Self::inherit_struct_field_names(&arr.expressions);
24764            &propagated
24765        } else {
24766            &arr.expressions
24767        };
24768
24769        // Generic mode: ARRAY(1, 2, 3) with parentheses
24770        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
24771        let use_parens =
24772            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24773        if !self.config.array_bracket_only {
24774            self.write_keyword("ARRAY");
24775        }
24776        if use_parens {
24777            self.write("(");
24778        } else {
24779            self.write("[");
24780        }
24781        for (i, expr) in expressions.iter().enumerate() {
24782            if i > 0 {
24783                self.write(", ");
24784            }
24785            self.generate_expression(expr)?;
24786        }
24787        if use_parens {
24788            self.write(")");
24789        } else {
24790            self.write("]");
24791        }
24792        Ok(())
24793    }
24794
24795    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
24796        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
24797        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
24798        if tuple.expressions.len() == 2 {
24799            if let Expression::TableAlias(_) = &tuple.expressions[1] {
24800                // First element is the function/expression, second is the TableAlias
24801                self.generate_expression(&tuple.expressions[0])?;
24802                self.write_space();
24803                self.write_keyword("AS");
24804                self.write_space();
24805                self.generate_expression(&tuple.expressions[1])?;
24806                return Ok(());
24807            }
24808        }
24809
24810        // In pretty mode, format long tuples with each element on a new line
24811        // Only expand if total width exceeds threshold
24812        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
24813            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
24814            for expr in &tuple.expressions {
24815                expr_strings.push(self.generate_to_string(expr)?);
24816            }
24817            self.too_wide(&expr_strings)
24818        } else {
24819            false
24820        };
24821
24822        if expand_tuple {
24823            self.write("(");
24824            self.write_newline();
24825            self.indent_level += 1;
24826            for (i, expr) in tuple.expressions.iter().enumerate() {
24827                if i > 0 {
24828                    self.write(",");
24829                    self.write_newline();
24830                }
24831                self.write_indent();
24832                self.generate_expression(expr)?;
24833            }
24834            self.indent_level -= 1;
24835            self.write_newline();
24836            self.write_indent();
24837            self.write(")");
24838        } else {
24839            self.write("(");
24840            for (i, expr) in tuple.expressions.iter().enumerate() {
24841                if i > 0 {
24842                    self.write(", ");
24843                }
24844                self.generate_expression(expr)?;
24845            }
24846            self.write(")");
24847        }
24848        Ok(())
24849    }
24850
24851    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
24852        self.generate_expression(&pipe.this)?;
24853        self.write(" |> ");
24854        self.generate_expression(&pipe.expression)?;
24855        Ok(())
24856    }
24857
24858    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
24859        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
24860            && !self.config.null_ordering_supported
24861            && matches!(
24862                self.config.dialect,
24863                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24864            );
24865        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
24866        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
24867            let target_default_nulls_first = !ordered.desc;
24868
24869            unsupported_tsql_null_ordering
24870                && nulls_first != target_default_nulls_first
24871                && !random_ordering
24872        } else {
24873            false
24874        };
24875
24876        if emulate_tsql_null_ordering {
24877            if Self::is_integer_ordering_literal(&ordered.this) {
24878                let nulls_order = if ordered.nulls_first == Some(true) {
24879                    "NULLS FIRST"
24880                } else {
24881                    "NULLS LAST"
24882                };
24883                self.unsupported(format!(
24884                    "'{nulls_order}' translation not supported with positional ordering"
24885                ))?;
24886            } else {
24887                self.write_keyword("CASE WHEN");
24888                self.write_space();
24889                self.generate_expression(&ordered.this)?;
24890                self.write_space();
24891                self.write_keyword("IS NULL THEN 1 ELSE 0 END");
24892                if ordered.nulls_first == Some(true) {
24893                    self.write_space();
24894                    self.write_keyword("DESC");
24895                }
24896                self.write(", ");
24897            }
24898        }
24899
24900        self.generate_expression(&ordered.this)?;
24901        if ordered.desc {
24902            self.write_space();
24903            self.write_keyword("DESC");
24904        } else if ordered.explicit_asc {
24905            self.write_space();
24906            self.write_keyword("ASC");
24907        }
24908        if let Some(nulls_first) = ordered.nulls_first {
24909            if !unsupported_tsql_null_ordering
24910                && (self.config.null_ordering_supported
24911                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
24912            {
24913                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
24914                // for the dialect. Different dialects have different NULL ordering defaults:
24915                //
24916                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
24917                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
24918                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
24919                //
24920                // nulls_are_small (Spark, Hive, BigQuery, most others):
24921                //   - ASC: NULLS FIRST is default
24922                //   - DESC: NULLS LAST is default
24923                //
24924                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
24925                //   - NULLS LAST is always the default regardless of sort direction
24926                let is_asc = !ordered.desc;
24927                let is_nulls_are_large = matches!(
24928                    self.config.dialect,
24929                    Some(DialectType::Oracle)
24930                        | Some(DialectType::PostgreSQL)
24931                        | Some(DialectType::Redshift)
24932                        | Some(DialectType::Snowflake)
24933                );
24934                let is_nulls_are_last = matches!(
24935                    self.config.dialect,
24936                    Some(DialectType::Dremio)
24937                        | Some(DialectType::DuckDB)
24938                        | Some(DialectType::Presto)
24939                        | Some(DialectType::Trino)
24940                        | Some(DialectType::Athena)
24941                        | Some(DialectType::ClickHouse)
24942                        | Some(DialectType::Drill)
24943                        | Some(DialectType::Exasol)
24944                );
24945
24946                // Check if the NULLS ordering matches the default for this dialect
24947                let is_default_nulls = if is_nulls_are_large {
24948                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
24949                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
24950                } else if is_nulls_are_last {
24951                    // For nulls_are_last: NULLS LAST is always default
24952                    !nulls_first
24953                } else {
24954                    false
24955                };
24956
24957                if !is_default_nulls {
24958                    self.write_space();
24959                    self.write_keyword("NULLS");
24960                    self.write_space();
24961                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
24962                }
24963            }
24964        }
24965        // WITH FILL clause (ClickHouse)
24966        if let Some(ref with_fill) = ordered.with_fill {
24967            self.write_space();
24968            self.generate_with_fill(with_fill)?;
24969        }
24970        Ok(())
24971    }
24972
24973    fn is_integer_ordering_literal(expr: &Expression) -> bool {
24974        matches!(
24975            expr,
24976            Expression::Literal(lit)
24977                if matches!(lit.as_ref(), Literal::Number(n) if n.parse::<u64>().is_ok())
24978        )
24979    }
24980
24981    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
24982    fn write_clickhouse_type(&mut self, type_str: &str) {
24983        if self.clickhouse_nullable_depth < 0 {
24984            // Map key context: don't wrap in Nullable
24985            self.write(type_str);
24986        } else {
24987            self.write(&format!("Nullable({})", type_str));
24988        }
24989    }
24990
24991    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
24992        use crate::dialects::DialectType;
24993
24994        match dt {
24995            DataType::Boolean => {
24996                // Dialect-specific boolean type mappings
24997                match self.config.dialect {
24998                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
24999                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
25000                    Some(DialectType::Oracle) => {
25001                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
25002                        self.write_keyword("NUMBER(1)")
25003                    }
25004                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
25005                    _ => self.write_keyword("BOOLEAN"),
25006                }
25007            }
25008            DataType::TinyInt { length } => {
25009                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
25010                // Dremio maps TINYINT to INT
25011                // ClickHouse maps TINYINT to Int8
25012                match self.config.dialect {
25013                    Some(DialectType::PostgreSQL)
25014                    | Some(DialectType::Redshift)
25015                    | Some(DialectType::Oracle)
25016                    | Some(DialectType::Exasol) => {
25017                        self.write_keyword("SMALLINT");
25018                    }
25019                    Some(DialectType::Teradata) => {
25020                        // Teradata uses BYTEINT for smallest integer
25021                        self.write_keyword("BYTEINT");
25022                    }
25023                    Some(DialectType::Dremio) => {
25024                        // Dremio maps TINYINT to INT
25025                        self.write_keyword("INT");
25026                    }
25027                    Some(DialectType::ClickHouse) => {
25028                        self.write_clickhouse_type("Int8");
25029                    }
25030                    _ => {
25031                        self.write_keyword("TINYINT");
25032                    }
25033                }
25034                if let Some(n) = length {
25035                    if !matches!(
25036                        self.config.dialect,
25037                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
25038                    ) {
25039                        self.write(&format!("({})", n));
25040                    }
25041                }
25042            }
25043            DataType::SmallInt { length } => {
25044                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
25045                match self.config.dialect {
25046                    Some(DialectType::Dremio) => {
25047                        self.write_keyword("INT");
25048                    }
25049                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
25050                        self.write_keyword("INTEGER");
25051                    }
25052                    Some(DialectType::BigQuery) => {
25053                        self.write_keyword("INT64");
25054                    }
25055                    Some(DialectType::ClickHouse) => {
25056                        self.write_clickhouse_type("Int16");
25057                    }
25058                    _ => {
25059                        self.write_keyword("SMALLINT");
25060                        if let Some(n) = length {
25061                            self.write(&format!("({})", n));
25062                        }
25063                    }
25064                }
25065            }
25066            DataType::Int {
25067                length,
25068                integer_spelling: _,
25069            } => {
25070                // BigQuery uses INT64 for INT
25071                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
25072                    self.write_keyword("INT64");
25073                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25074                    self.write_clickhouse_type("Int32");
25075                } else {
25076                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
25077                    let use_integer = match self.config.dialect {
25078                        Some(DialectType::TSQL)
25079                        | Some(DialectType::Fabric)
25080                        | Some(DialectType::Presto)
25081                        | Some(DialectType::Trino)
25082                        | Some(DialectType::SQLite)
25083                        | Some(DialectType::Redshift) => true,
25084                        _ => false,
25085                    };
25086                    if use_integer {
25087                        self.write_keyword("INTEGER");
25088                    } else {
25089                        self.write_keyword("INT");
25090                    }
25091                    if let Some(n) = length {
25092                        self.write(&format!("({})", n));
25093                    }
25094                }
25095            }
25096            DataType::BigInt { length } => {
25097                // Dialect-specific bigint type mappings
25098                match self.config.dialect {
25099                    Some(DialectType::Oracle) => {
25100                        // Oracle doesn't have BIGINT, uses INT
25101                        self.write_keyword("INT");
25102                    }
25103                    Some(DialectType::ClickHouse) => {
25104                        self.write_clickhouse_type("Int64");
25105                    }
25106                    _ => {
25107                        self.write_keyword("BIGINT");
25108                        if let Some(n) = length {
25109                            self.write(&format!("({})", n));
25110                        }
25111                    }
25112                }
25113            }
25114            DataType::Float {
25115                precision,
25116                scale,
25117                real_spelling,
25118            } => {
25119                // Dialect-specific float type mappings
25120                // If real_spelling is true, preserve REAL; otherwise use dialect default
25121                // Spark/Hive don't support REAL, always use FLOAT
25122                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25123                    self.write_clickhouse_type("Float32");
25124                } else if *real_spelling
25125                    && !matches!(
25126                        self.config.dialect,
25127                        Some(DialectType::Spark)
25128                            | Some(DialectType::Databricks)
25129                            | Some(DialectType::Hive)
25130                            | Some(DialectType::Snowflake)
25131                            | Some(DialectType::MySQL)
25132                            | Some(DialectType::BigQuery)
25133                    )
25134                {
25135                    self.write_keyword("REAL")
25136                } else {
25137                    match self.config.dialect {
25138                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
25139                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25140                        _ => self.write_keyword("FLOAT"),
25141                    }
25142                }
25143                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
25144                // Spark/Hive don't support FLOAT(precision)
25145                if !matches!(
25146                    self.config.dialect,
25147                    Some(DialectType::Spark)
25148                        | Some(DialectType::Databricks)
25149                        | Some(DialectType::Hive)
25150                        | Some(DialectType::Presto)
25151                        | Some(DialectType::Trino)
25152                ) {
25153                    if let Some(p) = precision {
25154                        self.write(&format!("({}", p));
25155                        if let Some(s) = scale {
25156                            self.write(&format!(", {})", s));
25157                        } else {
25158                            self.write(")");
25159                        }
25160                    }
25161                }
25162            }
25163            DataType::Double { precision, scale } => {
25164                // Dialect-specific double type mappings
25165                match self.config.dialect {
25166                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25167                        self.write_keyword("FLOAT")
25168                    } // SQL Server/Fabric FLOAT is double
25169                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
25170                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
25171                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25172                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
25173                    Some(DialectType::PostgreSQL)
25174                    | Some(DialectType::Redshift)
25175                    | Some(DialectType::Teradata)
25176                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
25177                    _ => self.write_keyword("DOUBLE"),
25178                }
25179                // MySQL supports DOUBLE(precision, scale)
25180                if let Some(p) = precision {
25181                    self.write(&format!("({}", p));
25182                    if let Some(s) = scale {
25183                        self.write(&format!(", {})", s));
25184                    } else {
25185                        self.write(")");
25186                    }
25187                }
25188            }
25189            DataType::Decimal { precision, scale } => {
25190                // Dialect-specific decimal type mappings
25191                match self.config.dialect {
25192                    Some(DialectType::ClickHouse) => {
25193                        self.write("Decimal");
25194                        if let Some(p) = precision {
25195                            self.write(&format!("({}", p));
25196                            if let Some(s) = scale {
25197                                self.write(&format!(", {}", s));
25198                            }
25199                            self.write(")");
25200                        }
25201                    }
25202                    Some(DialectType::Oracle) => {
25203                        // Oracle uses NUMBER instead of DECIMAL
25204                        self.write_keyword("NUMBER");
25205                        if let Some(p) = precision {
25206                            self.write(&format!("({}", p));
25207                            if let Some(s) = scale {
25208                                self.write(&format!(", {}", s));
25209                            }
25210                            self.write(")");
25211                        }
25212                    }
25213                    Some(DialectType::BigQuery) => {
25214                        // BigQuery uses NUMERIC instead of DECIMAL
25215                        self.write_keyword("NUMERIC");
25216                        if let Some(p) = precision {
25217                            self.write(&format!("({}", p));
25218                            if let Some(s) = scale {
25219                                self.write(&format!(", {}", s));
25220                            }
25221                            self.write(")");
25222                        }
25223                    }
25224                    _ => {
25225                        self.write_keyword("DECIMAL");
25226                        if let Some(p) = precision {
25227                            self.write(&format!("({}", p));
25228                            if let Some(s) = scale {
25229                                self.write(&format!(", {}", s));
25230                            }
25231                            self.write(")");
25232                        }
25233                    }
25234                }
25235            }
25236            DataType::Char { length } => {
25237                // Dialect-specific char type mappings
25238                match self.config.dialect {
25239                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25240                        // DuckDB/SQLite maps CHAR to TEXT
25241                        self.write_keyword("TEXT");
25242                    }
25243                    Some(DialectType::Hive)
25244                    | Some(DialectType::Spark)
25245                    | Some(DialectType::Databricks) => {
25246                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
25247                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
25248                        if length.is_some()
25249                            && !matches!(self.config.dialect, Some(DialectType::Hive))
25250                        {
25251                            self.write_keyword("CHAR");
25252                            if let Some(n) = length {
25253                                self.write(&format!("({})", n));
25254                            }
25255                        } else {
25256                            self.write_keyword("STRING");
25257                        }
25258                    }
25259                    Some(DialectType::Dremio) => {
25260                        // Dremio maps CHAR to VARCHAR
25261                        self.write_keyword("VARCHAR");
25262                        if let Some(n) = length {
25263                            self.write(&format!("({})", n));
25264                        }
25265                    }
25266                    _ => {
25267                        self.write_keyword("CHAR");
25268                        if let Some(n) = length {
25269                            self.write(&format!("({})", n));
25270                        }
25271                    }
25272                }
25273            }
25274            DataType::VarChar {
25275                length,
25276                parenthesized_length,
25277            } => {
25278                // Dialect-specific varchar type mappings
25279                match self.config.dialect {
25280                    Some(DialectType::Oracle) => {
25281                        self.write_keyword("VARCHAR2");
25282                        if let Some(n) = length {
25283                            self.write(&format!("({})", n));
25284                        }
25285                    }
25286                    Some(DialectType::DuckDB) => {
25287                        // DuckDB maps VARCHAR to TEXT, preserving length
25288                        self.write_keyword("TEXT");
25289                        if let Some(n) = length {
25290                            self.write(&format!("({})", n));
25291                        }
25292                    }
25293                    Some(DialectType::SQLite) => {
25294                        // SQLite maps VARCHAR to TEXT, preserving length
25295                        self.write_keyword("TEXT");
25296                        if let Some(n) = length {
25297                            self.write(&format!("({})", n));
25298                        }
25299                    }
25300                    Some(DialectType::MySQL) if length.is_none() => {
25301                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
25302                        self.write_keyword("TEXT");
25303                    }
25304                    Some(DialectType::Hive)
25305                    | Some(DialectType::Spark)
25306                    | Some(DialectType::Databricks)
25307                        if length.is_none() =>
25308                    {
25309                        // Hive/Spark/Databricks: VARCHAR without length → STRING
25310                        self.write_keyword("STRING");
25311                    }
25312                    _ => {
25313                        self.write_keyword("VARCHAR");
25314                        if let Some(n) = length {
25315                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
25316                            if *parenthesized_length {
25317                                self.write(&format!("(({}))", n));
25318                            } else {
25319                                self.write(&format!("({})", n));
25320                            }
25321                        }
25322                    }
25323                }
25324            }
25325            DataType::Text => {
25326                // Dialect-specific text type mappings
25327                match self.config.dialect {
25328                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
25329                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25330                        self.write_keyword("VARCHAR(MAX)")
25331                    }
25332                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25333                    Some(DialectType::Snowflake)
25334                    | Some(DialectType::Dremio)
25335                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
25336                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
25337                    Some(DialectType::Presto)
25338                    | Some(DialectType::Trino)
25339                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25340                    Some(DialectType::Spark)
25341                    | Some(DialectType::Databricks)
25342                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25343                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
25344                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
25345                        self.write_keyword("STRING")
25346                    }
25347                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
25348                    _ => self.write_keyword("TEXT"),
25349                }
25350            }
25351            DataType::TextWithLength { length } => {
25352                // TEXT(n) - dialect-specific type with length
25353                match self.config.dialect {
25354                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
25355                    Some(DialectType::Hive)
25356                    | Some(DialectType::Spark)
25357                    | Some(DialectType::Databricks) => {
25358                        self.write(&format!("VARCHAR({})", length));
25359                    }
25360                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
25361                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
25362                    Some(DialectType::Snowflake)
25363                    | Some(DialectType::Presto)
25364                    | Some(DialectType::Trino)
25365                    | Some(DialectType::Athena)
25366                    | Some(DialectType::Drill)
25367                    | Some(DialectType::Dremio) => {
25368                        self.write(&format!("VARCHAR({})", length));
25369                    }
25370                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25371                        self.write(&format!("VARCHAR({})", length))
25372                    }
25373                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
25374                        self.write(&format!("STRING({})", length))
25375                    }
25376                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
25377                    _ => self.write(&format!("TEXT({})", length)),
25378                }
25379            }
25380            DataType::String { length } => {
25381                // STRING type with optional length (BigQuery STRING(n))
25382                match self.config.dialect {
25383                    Some(DialectType::ClickHouse) => {
25384                        // ClickHouse uses String with specific casing
25385                        self.write("String");
25386                        if let Some(n) = length {
25387                            self.write(&format!("({})", n));
25388                        }
25389                    }
25390                    Some(DialectType::BigQuery)
25391                    | Some(DialectType::Hive)
25392                    | Some(DialectType::Spark)
25393                    | Some(DialectType::Databricks)
25394                    | Some(DialectType::StarRocks)
25395                    | Some(DialectType::Doris) => {
25396                        self.write_keyword("STRING");
25397                        if let Some(n) = length {
25398                            self.write(&format!("({})", n));
25399                        }
25400                    }
25401                    Some(DialectType::PostgreSQL) => {
25402                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
25403                        if let Some(n) = length {
25404                            self.write_keyword("VARCHAR");
25405                            self.write(&format!("({})", n));
25406                        } else {
25407                            self.write_keyword("TEXT");
25408                        }
25409                    }
25410                    Some(DialectType::Redshift) => {
25411                        // Redshift: STRING -> VARCHAR(MAX)
25412                        if let Some(n) = length {
25413                            self.write_keyword("VARCHAR");
25414                            self.write(&format!("({})", n));
25415                        } else {
25416                            self.write_keyword("VARCHAR(MAX)");
25417                        }
25418                    }
25419                    Some(DialectType::MySQL) => {
25420                        // MySQL doesn't have STRING - use VARCHAR or TEXT
25421                        if let Some(n) = length {
25422                            self.write_keyword("VARCHAR");
25423                            self.write(&format!("({})", n));
25424                        } else {
25425                            self.write_keyword("TEXT");
25426                        }
25427                    }
25428                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25429                        // TSQL: STRING -> VARCHAR(MAX)
25430                        if let Some(n) = length {
25431                            self.write_keyword("VARCHAR");
25432                            self.write(&format!("({})", n));
25433                        } else {
25434                            self.write_keyword("VARCHAR(MAX)");
25435                        }
25436                    }
25437                    Some(DialectType::Oracle) => {
25438                        // Oracle: STRING -> CLOB
25439                        self.write_keyword("CLOB");
25440                    }
25441                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
25442                        // DuckDB/Materialize uses TEXT for string types
25443                        self.write_keyword("TEXT");
25444                        if let Some(n) = length {
25445                            self.write(&format!("({})", n));
25446                        }
25447                    }
25448                    Some(DialectType::Presto)
25449                    | Some(DialectType::Trino)
25450                    | Some(DialectType::Drill)
25451                    | Some(DialectType::Dremio) => {
25452                        // Presto/Trino/Drill use VARCHAR for string types
25453                        self.write_keyword("VARCHAR");
25454                        if let Some(n) = length {
25455                            self.write(&format!("({})", n));
25456                        }
25457                    }
25458                    Some(DialectType::Snowflake) => {
25459                        // Snowflake: STRING stays as STRING (identity/DDL)
25460                        // CAST context STRING -> VARCHAR is handled in generate_cast
25461                        self.write_keyword("STRING");
25462                        if let Some(n) = length {
25463                            self.write(&format!("({})", n));
25464                        }
25465                    }
25466                    _ => {
25467                        // Default: output STRING with optional length
25468                        self.write_keyword("STRING");
25469                        if let Some(n) = length {
25470                            self.write(&format!("({})", n));
25471                        }
25472                    }
25473                }
25474            }
25475            DataType::Binary { length } => {
25476                // Dialect-specific binary type mappings
25477                match self.config.dialect {
25478                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25479                        self.write_keyword("BYTEA");
25480                        if let Some(n) = length {
25481                            self.write(&format!("({})", n));
25482                        }
25483                    }
25484                    Some(DialectType::Redshift) => {
25485                        self.write_keyword("VARBYTE");
25486                        if let Some(n) = length {
25487                            self.write(&format!("({})", n));
25488                        }
25489                    }
25490                    Some(DialectType::DuckDB)
25491                    | Some(DialectType::SQLite)
25492                    | Some(DialectType::Oracle) => {
25493                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
25494                        self.write_keyword("BLOB");
25495                        if let Some(n) = length {
25496                            self.write(&format!("({})", n));
25497                        }
25498                    }
25499                    Some(DialectType::Presto)
25500                    | Some(DialectType::Trino)
25501                    | Some(DialectType::Athena)
25502                    | Some(DialectType::Drill)
25503                    | Some(DialectType::Dremio) => {
25504                        // These dialects map BINARY to VARBINARY
25505                        self.write_keyword("VARBINARY");
25506                        if let Some(n) = length {
25507                            self.write(&format!("({})", n));
25508                        }
25509                    }
25510                    Some(DialectType::ClickHouse) => {
25511                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
25512                        if self.clickhouse_nullable_depth < 0 {
25513                            self.write("BINARY");
25514                        } else {
25515                            self.write("Nullable(BINARY");
25516                        }
25517                        if let Some(n) = length {
25518                            self.write(&format!("({})", n));
25519                        }
25520                        if self.clickhouse_nullable_depth >= 0 {
25521                            self.write(")");
25522                        }
25523                    }
25524                    _ => {
25525                        self.write_keyword("BINARY");
25526                        if let Some(n) = length {
25527                            self.write(&format!("({})", n));
25528                        }
25529                    }
25530                }
25531            }
25532            DataType::VarBinary { length } => {
25533                // Dialect-specific varbinary type mappings
25534                match self.config.dialect {
25535                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25536                        self.write_keyword("BYTEA");
25537                        if let Some(n) = length {
25538                            self.write(&format!("({})", n));
25539                        }
25540                    }
25541                    Some(DialectType::Redshift) => {
25542                        self.write_keyword("VARBYTE");
25543                        if let Some(n) = length {
25544                            self.write(&format!("({})", n));
25545                        }
25546                    }
25547                    Some(DialectType::DuckDB)
25548                    | Some(DialectType::SQLite)
25549                    | Some(DialectType::Oracle) => {
25550                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
25551                        self.write_keyword("BLOB");
25552                        if let Some(n) = length {
25553                            self.write(&format!("({})", n));
25554                        }
25555                    }
25556                    Some(DialectType::Exasol) => {
25557                        // Exasol maps VARBINARY to VARCHAR
25558                        self.write_keyword("VARCHAR");
25559                    }
25560                    Some(DialectType::Spark)
25561                    | Some(DialectType::Hive)
25562                    | Some(DialectType::Databricks) => {
25563                        // Spark/Hive use BINARY instead of VARBINARY
25564                        self.write_keyword("BINARY");
25565                        if let Some(n) = length {
25566                            self.write(&format!("({})", n));
25567                        }
25568                    }
25569                    Some(DialectType::ClickHouse) => {
25570                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
25571                        self.write_clickhouse_type("String");
25572                    }
25573                    _ => {
25574                        self.write_keyword("VARBINARY");
25575                        if let Some(n) = length {
25576                            self.write(&format!("({})", n));
25577                        }
25578                    }
25579                }
25580            }
25581            DataType::Blob => {
25582                // Dialect-specific blob type mappings
25583                match self.config.dialect {
25584                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
25585                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
25586                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25587                        self.write_keyword("VARBINARY")
25588                    }
25589                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25590                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
25591                    Some(DialectType::Presto)
25592                    | Some(DialectType::Trino)
25593                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25594                    Some(DialectType::DuckDB) => {
25595                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
25596                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
25597                        self.write_keyword("VARBINARY");
25598                    }
25599                    Some(DialectType::Spark)
25600                    | Some(DialectType::Databricks)
25601                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
25602                    Some(DialectType::ClickHouse) => {
25603                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
25604                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
25605                        // This matches Python sqlglot behavior.
25606                        self.write("Nullable(String)");
25607                    }
25608                    _ => self.write_keyword("BLOB"),
25609                }
25610            }
25611            DataType::Bit { length } => {
25612                // Dialect-specific bit type mappings
25613                match self.config.dialect {
25614                    Some(DialectType::Dremio)
25615                    | Some(DialectType::Spark)
25616                    | Some(DialectType::Databricks)
25617                    | Some(DialectType::Hive)
25618                    | Some(DialectType::Snowflake)
25619                    | Some(DialectType::BigQuery)
25620                    | Some(DialectType::Presto)
25621                    | Some(DialectType::Trino)
25622                    | Some(DialectType::ClickHouse)
25623                    | Some(DialectType::Redshift) => {
25624                        // These dialects don't support BIT type, use BOOLEAN
25625                        self.write_keyword("BOOLEAN");
25626                    }
25627                    _ => {
25628                        self.write_keyword("BIT");
25629                        if let Some(n) = length {
25630                            self.write(&format!("({})", n));
25631                        }
25632                    }
25633                }
25634            }
25635            DataType::VarBit { length } => {
25636                self.write_keyword("VARBIT");
25637                if let Some(n) = length {
25638                    self.write(&format!("({})", n));
25639                }
25640            }
25641            DataType::Date => self.write_keyword("DATE"),
25642            DataType::Time {
25643                precision,
25644                timezone,
25645            } => {
25646                if *timezone {
25647                    // Dialect-specific TIME WITH TIME ZONE output
25648                    match self.config.dialect {
25649                        Some(DialectType::DuckDB) => {
25650                            // DuckDB: TIMETZ (drops precision)
25651                            self.write_keyword("TIMETZ");
25652                        }
25653                        Some(DialectType::PostgreSQL) => {
25654                            // PostgreSQL: TIMETZ or TIMETZ(p)
25655                            self.write_keyword("TIMETZ");
25656                            if let Some(p) = precision {
25657                                self.write(&format!("({})", p));
25658                            }
25659                        }
25660                        _ => {
25661                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
25662                            self.write_keyword("TIME");
25663                            if let Some(p) = precision {
25664                                self.write(&format!("({})", p));
25665                            }
25666                            self.write_keyword(" WITH TIME ZONE");
25667                        }
25668                    }
25669                } else {
25670                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
25671                    if matches!(
25672                        self.config.dialect,
25673                        Some(DialectType::Spark)
25674                            | Some(DialectType::Databricks)
25675                            | Some(DialectType::Hive)
25676                    ) {
25677                        self.write_keyword("TIMESTAMP");
25678                    } else {
25679                        self.write_keyword("TIME");
25680                        if let Some(p) = precision {
25681                            self.write(&format!("({})", p));
25682                        }
25683                    }
25684                }
25685            }
25686            DataType::Timestamp {
25687                precision,
25688                timezone,
25689            } => {
25690                // Dialect-specific timestamp type mappings
25691                match self.config.dialect {
25692                    Some(DialectType::Snowflake) if *timezone => {
25693                        self.write_keyword("TIMESTAMPTZ");
25694                        if let Some(p) = precision {
25695                            self.write(&format!("({})", p));
25696                        }
25697                    }
25698                    Some(DialectType::ClickHouse) => {
25699                        self.write("DateTime");
25700                        if let Some(p) = precision {
25701                            self.write(&format!("({})", p));
25702                        }
25703                    }
25704                    Some(DialectType::TSQL) => {
25705                        if *timezone {
25706                            self.write_keyword("DATETIMEOFFSET");
25707                        } else {
25708                            self.write_keyword("DATETIME2");
25709                        }
25710                        if let Some(p) = precision {
25711                            self.write(&format!("({})", p));
25712                        }
25713                    }
25714                    Some(DialectType::MySQL) => {
25715                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
25716                        self.write_keyword("TIMESTAMP");
25717                        if let Some(p) = precision {
25718                            self.write(&format!("({})", p));
25719                        }
25720                    }
25721                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
25722                        // Doris/StarRocks: TIMESTAMP -> DATETIME
25723                        self.write_keyword("DATETIME");
25724                        if let Some(p) = precision {
25725                            self.write(&format!("({})", p));
25726                        }
25727                    }
25728                    Some(DialectType::BigQuery) => {
25729                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
25730                        if *timezone {
25731                            self.write_keyword("TIMESTAMP");
25732                        } else {
25733                            self.write_keyword("DATETIME");
25734                        }
25735                    }
25736                    Some(DialectType::DuckDB) => {
25737                        // DuckDB: TIMESTAMPTZ shorthand
25738                        if *timezone {
25739                            self.write_keyword("TIMESTAMPTZ");
25740                        } else {
25741                            self.write_keyword("TIMESTAMP");
25742                            if let Some(p) = precision {
25743                                self.write(&format!("({})", p));
25744                            }
25745                        }
25746                    }
25747                    _ => {
25748                        if *timezone && !self.config.tz_to_with_time_zone {
25749                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
25750                            self.write_keyword("TIMESTAMPTZ");
25751                            if let Some(p) = precision {
25752                                self.write(&format!("({})", p));
25753                            }
25754                        } else {
25755                            self.write_keyword("TIMESTAMP");
25756                            if let Some(p) = precision {
25757                                self.write(&format!("({})", p));
25758                            }
25759                            if *timezone {
25760                                self.write_space();
25761                                self.write_keyword("WITH TIME ZONE");
25762                            }
25763                        }
25764                    }
25765                }
25766            }
25767            DataType::Interval { unit, to } => {
25768                self.write_keyword("INTERVAL");
25769                if let Some(u) = unit {
25770                    self.write_space();
25771                    self.write_keyword(u);
25772                }
25773                // Handle range intervals like DAY TO HOUR
25774                if let Some(t) = to {
25775                    self.write_space();
25776                    self.write_keyword("TO");
25777                    self.write_space();
25778                    self.write_keyword(t);
25779                }
25780            }
25781            DataType::Json => {
25782                // Dialect-specific JSON type mappings
25783                match self.config.dialect {
25784                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
25785                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
25786                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
25787                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
25788                    _ => self.write_keyword("JSON"),
25789                }
25790            }
25791            DataType::JsonB => {
25792                // JSONB is PostgreSQL specific, but Doris also supports it
25793                match self.config.dialect {
25794                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
25795                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
25796                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
25797                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
25798                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
25799                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
25800                }
25801            }
25802            DataType::Uuid => {
25803                // Dialect-specific UUID type mappings
25804                match self.config.dialect {
25805                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
25806                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
25807                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
25808                    Some(DialectType::BigQuery)
25809                    | Some(DialectType::Spark)
25810                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
25811                    _ => self.write_keyword("UUID"),
25812                }
25813            }
25814            DataType::Array {
25815                element_type,
25816                dimension,
25817            } => {
25818                // Dialect-specific array syntax
25819                match self.config.dialect {
25820                    Some(DialectType::PostgreSQL)
25821                    | Some(DialectType::Redshift)
25822                    | Some(DialectType::DuckDB) => {
25823                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
25824                        self.generate_data_type(element_type)?;
25825                        if let Some(dim) = dimension {
25826                            self.write(&format!("[{}]", dim));
25827                        } else {
25828                            self.write("[]");
25829                        }
25830                    }
25831                    Some(DialectType::BigQuery) => {
25832                        self.write_keyword("ARRAY<");
25833                        self.generate_data_type(element_type)?;
25834                        self.write(">");
25835                    }
25836                    Some(DialectType::Snowflake)
25837                    | Some(DialectType::Presto)
25838                    | Some(DialectType::Trino)
25839                    | Some(DialectType::ClickHouse) => {
25840                        // These dialects use Array(TYPE) parentheses syntax
25841                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25842                            self.write("Array(");
25843                        } else {
25844                            self.write_keyword("ARRAY(");
25845                        }
25846                        self.generate_data_type(element_type)?;
25847                        self.write(")");
25848                    }
25849                    Some(DialectType::TSQL)
25850                    | Some(DialectType::MySQL)
25851                    | Some(DialectType::Oracle) => {
25852                        // These dialects don't have native array types
25853                        // Fall back to JSON or use native workarounds
25854                        match self.config.dialect {
25855                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
25856                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
25857                            _ => self.write_keyword("JSON"),
25858                        }
25859                    }
25860                    _ => {
25861                        // Default: use angle bracket syntax (ARRAY<T>)
25862                        self.write_keyword("ARRAY<");
25863                        self.generate_data_type(element_type)?;
25864                        self.write(">");
25865                    }
25866                }
25867            }
25868            DataType::List { element_type } => {
25869                // Materialize: element_type LIST (postfix syntax)
25870                self.generate_data_type(element_type)?;
25871                self.write_keyword(" LIST");
25872            }
25873            DataType::Map {
25874                key_type,
25875                value_type,
25876            } => {
25877                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
25878                match self.config.dialect {
25879                    Some(DialectType::Materialize) => {
25880                        // Materialize: MAP[key_type => value_type]
25881                        self.write_keyword("MAP[");
25882                        self.generate_data_type(key_type)?;
25883                        self.write(" => ");
25884                        self.generate_data_type(value_type)?;
25885                        self.write("]");
25886                    }
25887                    Some(DialectType::Snowflake)
25888                    | Some(DialectType::RisingWave)
25889                    | Some(DialectType::DuckDB)
25890                    | Some(DialectType::Presto)
25891                    | Some(DialectType::Trino)
25892                    | Some(DialectType::Athena) => {
25893                        self.write_keyword("MAP(");
25894                        self.generate_data_type(key_type)?;
25895                        self.write(", ");
25896                        self.generate_data_type(value_type)?;
25897                        self.write(")");
25898                    }
25899                    Some(DialectType::ClickHouse) => {
25900                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
25901                        // Key types must NOT be wrapped in Nullable
25902                        self.write("Map(");
25903                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
25904                        self.generate_data_type(key_type)?;
25905                        self.clickhouse_nullable_depth = 0;
25906                        self.write(", ");
25907                        self.generate_data_type(value_type)?;
25908                        self.write(")");
25909                    }
25910                    _ => {
25911                        self.write_keyword("MAP<");
25912                        self.generate_data_type(key_type)?;
25913                        self.write(", ");
25914                        self.generate_data_type(value_type)?;
25915                        self.write(">");
25916                    }
25917                }
25918            }
25919            DataType::Vector {
25920                element_type,
25921                dimension,
25922            } => {
25923                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
25924                    // SingleStore format: VECTOR(dimension, type_alias)
25925                    self.write_keyword("VECTOR(");
25926                    if let Some(dim) = dimension {
25927                        self.write(&dim.to_string());
25928                    }
25929                    // Map type back to SingleStore alias
25930                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
25931                        DataType::TinyInt { .. } => Some("I8"),
25932                        DataType::SmallInt { .. } => Some("I16"),
25933                        DataType::Int { .. } => Some("I32"),
25934                        DataType::BigInt { .. } => Some("I64"),
25935                        DataType::Float { .. } => Some("F32"),
25936                        DataType::Double { .. } => Some("F64"),
25937                        _ => None,
25938                    });
25939                    if let Some(alias) = type_alias {
25940                        if dimension.is_some() {
25941                            self.write(", ");
25942                        }
25943                        self.write(alias);
25944                    }
25945                    self.write(")");
25946                } else {
25947                    // Snowflake format: VECTOR(type, dimension)
25948                    self.write_keyword("VECTOR(");
25949                    if let Some(ref et) = element_type {
25950                        self.generate_data_type(et)?;
25951                        if dimension.is_some() {
25952                            self.write(", ");
25953                        }
25954                    }
25955                    if let Some(dim) = dimension {
25956                        self.write(&dim.to_string());
25957                    }
25958                    self.write(")");
25959                }
25960            }
25961            DataType::Object { fields, modifier } => {
25962                self.write_keyword("OBJECT(");
25963                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
25964                    if i > 0 {
25965                        self.write(", ");
25966                    }
25967                    self.write(name);
25968                    self.write(" ");
25969                    self.generate_data_type(dt)?;
25970                    if *not_null {
25971                        self.write_keyword(" NOT NULL");
25972                    }
25973                }
25974                self.write(")");
25975                if let Some(mod_str) = modifier {
25976                    self.write(" ");
25977                    self.write_keyword(mod_str);
25978                }
25979            }
25980            DataType::Struct { fields, nested } => {
25981                // Dialect-specific struct type mappings
25982                match self.config.dialect {
25983                    Some(DialectType::Snowflake) => {
25984                        // Snowflake maps STRUCT to OBJECT
25985                        self.write_keyword("OBJECT(");
25986                        for (i, field) in fields.iter().enumerate() {
25987                            if i > 0 {
25988                                self.write(", ");
25989                            }
25990                            if !field.name.is_empty() {
25991                                self.write(&field.name);
25992                                self.write(" ");
25993                            }
25994                            self.generate_data_type(&field.data_type)?;
25995                        }
25996                        self.write(")");
25997                    }
25998                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
25999                        // Presto/Trino use ROW(name TYPE, ...) syntax
26000                        self.write_keyword("ROW(");
26001                        for (i, field) in fields.iter().enumerate() {
26002                            if i > 0 {
26003                                self.write(", ");
26004                            }
26005                            if !field.name.is_empty() {
26006                                self.write(&field.name);
26007                                self.write(" ");
26008                            }
26009                            self.generate_data_type(&field.data_type)?;
26010                        }
26011                        self.write(")");
26012                    }
26013                    Some(DialectType::DuckDB) => {
26014                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
26015                        self.write_keyword("STRUCT(");
26016                        for (i, field) in fields.iter().enumerate() {
26017                            if i > 0 {
26018                                self.write(", ");
26019                            }
26020                            if !field.name.is_empty() {
26021                                self.write(&field.name);
26022                                self.write(" ");
26023                            }
26024                            self.generate_data_type(&field.data_type)?;
26025                        }
26026                        self.write(")");
26027                    }
26028                    Some(DialectType::ClickHouse) => {
26029                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
26030                        self.write("Tuple(");
26031                        for (i, field) in fields.iter().enumerate() {
26032                            if i > 0 {
26033                                self.write(", ");
26034                            }
26035                            if !field.name.is_empty() {
26036                                self.write(&field.name);
26037                                self.write(" ");
26038                            }
26039                            self.generate_data_type(&field.data_type)?;
26040                        }
26041                        self.write(")");
26042                    }
26043                    Some(DialectType::SingleStore) => {
26044                        // SingleStore uses RECORD(name TYPE, ...) for struct types
26045                        self.write_keyword("RECORD(");
26046                        for (i, field) in fields.iter().enumerate() {
26047                            if i > 0 {
26048                                self.write(", ");
26049                            }
26050                            if !field.name.is_empty() {
26051                                self.write(&field.name);
26052                                self.write(" ");
26053                            }
26054                            self.generate_data_type(&field.data_type)?;
26055                        }
26056                        self.write(")");
26057                    }
26058                    _ => {
26059                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
26060                        let force_angle_brackets = matches!(
26061                            self.config.dialect,
26062                            Some(DialectType::Hive)
26063                                | Some(DialectType::Spark)
26064                                | Some(DialectType::Databricks)
26065                        );
26066                        if *nested && !force_angle_brackets {
26067                            self.write_keyword("STRUCT(");
26068                            for (i, field) in fields.iter().enumerate() {
26069                                if i > 0 {
26070                                    self.write(", ");
26071                                }
26072                                if !field.name.is_empty() {
26073                                    self.write(&field.name);
26074                                    self.write(" ");
26075                                }
26076                                self.generate_data_type(&field.data_type)?;
26077                            }
26078                            self.write(")");
26079                        } else {
26080                            self.write_keyword("STRUCT<");
26081                            for (i, field) in fields.iter().enumerate() {
26082                                if i > 0 {
26083                                    self.write(", ");
26084                                }
26085                                if !field.name.is_empty() {
26086                                    // Named field: name TYPE (with configurable separator for Hive)
26087                                    self.write(&field.name);
26088                                    self.write(self.config.struct_field_sep);
26089                                }
26090                                // For anonymous fields, just output the type
26091                                self.generate_data_type(&field.data_type)?;
26092                                // Spark/Databricks: Output COMMENT clause if present
26093                                if let Some(comment) = &field.comment {
26094                                    self.write(" COMMENT '");
26095                                    self.write(comment);
26096                                    self.write("'");
26097                                }
26098                                // BigQuery: Output OPTIONS clause if present
26099                                if !field.options.is_empty() {
26100                                    self.write(" ");
26101                                    self.generate_options_clause(&field.options)?;
26102                                }
26103                            }
26104                            self.write(">");
26105                        }
26106                    }
26107                }
26108            }
26109            DataType::Enum {
26110                values,
26111                assignments,
26112            } => {
26113                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
26114                // ClickHouse: Enum('hello' = 1, 'world' = 2)
26115                if self.config.dialect == Some(DialectType::ClickHouse) {
26116                    self.write("Enum(");
26117                } else {
26118                    self.write_keyword("ENUM(");
26119                }
26120                for (i, val) in values.iter().enumerate() {
26121                    if i > 0 {
26122                        self.write(", ");
26123                    }
26124                    self.write("'");
26125                    self.write(val);
26126                    self.write("'");
26127                    if let Some(Some(assignment)) = assignments.get(i) {
26128                        self.write(" = ");
26129                        self.write(assignment);
26130                    }
26131                }
26132                self.write(")");
26133            }
26134            DataType::Set { values } => {
26135                // MySQL SET type: SET('a', 'b', 'c')
26136                self.write_keyword("SET(");
26137                for (i, val) in values.iter().enumerate() {
26138                    if i > 0 {
26139                        self.write(", ");
26140                    }
26141                    self.write("'");
26142                    self.write(val);
26143                    self.write("'");
26144                }
26145                self.write(")");
26146            }
26147            DataType::Union { fields } => {
26148                // DuckDB UNION type: UNION(num INT, str TEXT)
26149                self.write_keyword("UNION(");
26150                for (i, (name, dt)) in fields.iter().enumerate() {
26151                    if i > 0 {
26152                        self.write(", ");
26153                    }
26154                    if !name.is_empty() {
26155                        self.write(name);
26156                        self.write(" ");
26157                    }
26158                    self.generate_data_type(dt)?;
26159                }
26160                self.write(")");
26161            }
26162            DataType::Nullable { inner } => {
26163                // ClickHouse: Nullable(T), other dialects: just the inner type
26164                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26165                    self.write("Nullable(");
26166                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
26167                    let saved_depth = self.clickhouse_nullable_depth;
26168                    self.clickhouse_nullable_depth = -1;
26169                    self.generate_data_type(inner)?;
26170                    self.clickhouse_nullable_depth = saved_depth;
26171                    self.write(")");
26172                } else {
26173                    // Map ClickHouse-specific custom type names to standard types
26174                    match inner.as_ref() {
26175                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
26176                            self.generate_data_type(&DataType::Timestamp {
26177                                precision: None,
26178                                timezone: false,
26179                            })?;
26180                        }
26181                        _ => {
26182                            self.generate_data_type(inner)?;
26183                        }
26184                    }
26185                }
26186            }
26187            DataType::Custom { name } => {
26188                // Handle dialect-specific type transformations
26189                let name_upper = name.to_ascii_uppercase();
26190                match self.config.dialect {
26191                    Some(DialectType::ClickHouse) => {
26192                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26193                            (name_upper[..idx].to_string(), &name[idx..])
26194                        } else {
26195                            (name_upper.clone(), "")
26196                        };
26197                        let mapped = match base_upper.as_str() {
26198                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
26199                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
26200                            "DATETIME64" => "DateTime64",
26201                            "DATE32" => "Date32",
26202                            "INT" => "Int32",
26203                            "MEDIUMINT" => "Int32",
26204                            "INT8" => "Int8",
26205                            "INT16" => "Int16",
26206                            "INT32" => "Int32",
26207                            "INT64" => "Int64",
26208                            "INT128" => "Int128",
26209                            "INT256" => "Int256",
26210                            "UINT8" => "UInt8",
26211                            "UINT16" => "UInt16",
26212                            "UINT32" => "UInt32",
26213                            "UINT64" => "UInt64",
26214                            "UINT128" => "UInt128",
26215                            "UINT256" => "UInt256",
26216                            "FLOAT32" => "Float32",
26217                            "FLOAT64" => "Float64",
26218                            "DECIMAL32" => "Decimal32",
26219                            "DECIMAL64" => "Decimal64",
26220                            "DECIMAL128" => "Decimal128",
26221                            "DECIMAL256" => "Decimal256",
26222                            "ENUM" => "Enum",
26223                            "ENUM8" => "Enum8",
26224                            "ENUM16" => "Enum16",
26225                            "FIXEDSTRING" => "FixedString",
26226                            "NESTED" => "Nested",
26227                            "LOWCARDINALITY" => "LowCardinality",
26228                            "NULLABLE" => "Nullable",
26229                            "IPV4" => "IPv4",
26230                            "IPV6" => "IPv6",
26231                            "POINT" => "Point",
26232                            "RING" => "Ring",
26233                            "LINESTRING" => "LineString",
26234                            "MULTILINESTRING" => "MultiLineString",
26235                            "POLYGON" => "Polygon",
26236                            "MULTIPOLYGON" => "MultiPolygon",
26237                            "AGGREGATEFUNCTION" => "AggregateFunction",
26238                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
26239                            "DYNAMIC" => "Dynamic",
26240                            _ => "",
26241                        };
26242                        if mapped.is_empty() {
26243                            self.write(name);
26244                        } else {
26245                            self.write(mapped);
26246                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
26247                                && !suffix.is_empty()
26248                            {
26249                                let escaped_suffix = suffix
26250                                    .replace('\\', "\\\\")
26251                                    .replace('\t', "\\t")
26252                                    .replace('\n', "\\n")
26253                                    .replace('\r', "\\r");
26254                                self.write(&escaped_suffix);
26255                            } else {
26256                                self.write(suffix);
26257                            }
26258                        }
26259                    }
26260                    Some(DialectType::MySQL)
26261                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
26262                    {
26263                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
26264                        self.write_keyword("TIMESTAMP");
26265                    }
26266                    Some(DialectType::Snowflake) => {
26267                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26268                            (name_upper[..idx].to_string(), &name[idx..])
26269                        } else {
26270                            (name_upper.clone(), "")
26271                        };
26272
26273                        match base_upper.as_str() {
26274                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
26275                                self.write_keyword("TIMESTAMPNTZ");
26276                                self.write(suffix);
26277                            }
26278                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
26279                                self.write_keyword("TIMESTAMPLTZ");
26280                                self.write(suffix);
26281                            }
26282                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
26283                                self.write_keyword("TIMESTAMPTZ");
26284                                self.write(suffix);
26285                            }
26286                            _ => self.write(name),
26287                        }
26288                    }
26289                    Some(DialectType::Fabric) => {
26290                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
26291                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26292                        } else {
26293                            (name_upper.clone(), None)
26294                        };
26295
26296                        match base_upper.as_str() {
26297                            "NVARCHAR" => {
26298                                self.write_keyword("VARCHAR");
26299                                if let Some(args) = args_str {
26300                                    self.write(args);
26301                                }
26302                            }
26303                            "NCHAR" => {
26304                                self.write_keyword("CHAR");
26305                                if let Some(args) = args_str {
26306                                    self.write(args);
26307                                }
26308                            }
26309                            _ => self.write(name),
26310                        }
26311                    }
26312                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
26313                        self.write_keyword("SQL_VARIANT");
26314                    }
26315                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
26316                        self.write_keyword("DECIMAL(38, 5)");
26317                    }
26318                    Some(DialectType::Exasol) => {
26319                        // Exasol type mappings for custom types
26320                        match name_upper.as_str() {
26321                            // Binary types → VARCHAR
26322                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
26323                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
26324                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
26325                            // Integer types
26326                            "MEDIUMINT" => self.write_keyword("INT"),
26327                            // Decimal types → DECIMAL
26328                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
26329                                self.write_keyword("DECIMAL")
26330                            }
26331                            // Timestamp types
26332                            "DATETIME" => self.write_keyword("TIMESTAMP"),
26333                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
26334                            _ => self.write(name),
26335                        }
26336                    }
26337                    Some(DialectType::Dremio) => {
26338                        // Dremio type mappings for custom types
26339                        match name_upper.as_str() {
26340                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
26341                            "ARRAY" => self.write_keyword("LIST"),
26342                            "NCHAR" => self.write_keyword("VARCHAR"),
26343                            _ => self.write(name),
26344                        }
26345                    }
26346                    // Map dialect-specific custom types to standard SQL types for other dialects
26347                    _ => {
26348                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
26349                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
26350                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26351                        } else {
26352                            (name_upper.clone(), None)
26353                        };
26354
26355                        match base_upper.as_str() {
26356                            "INT64"
26357                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26358                            {
26359                                self.write_keyword("BIGINT");
26360                            }
26361                            "FLOAT64"
26362                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26363                            {
26364                                self.write_keyword("DOUBLE");
26365                            }
26366                            "BOOL"
26367                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26368                            {
26369                                self.write_keyword("BOOLEAN");
26370                            }
26371                            "BYTES"
26372                                if matches!(
26373                                    self.config.dialect,
26374                                    Some(DialectType::Spark)
26375                                        | Some(DialectType::Hive)
26376                                        | Some(DialectType::Databricks)
26377                                ) =>
26378                            {
26379                                self.write_keyword("BINARY");
26380                            }
26381                            "BYTES"
26382                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26383                            {
26384                                self.write_keyword("VARBINARY");
26385                            }
26386                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
26387                            "DATETIME2" | "SMALLDATETIME"
26388                                if !matches!(
26389                                    self.config.dialect,
26390                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26391                                ) =>
26392                            {
26393                                // PostgreSQL preserves precision, others don't
26394                                if matches!(
26395                                    self.config.dialect,
26396                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
26397                                ) {
26398                                    self.write_keyword("TIMESTAMP");
26399                                    if let Some(args) = _args_str {
26400                                        self.write(args);
26401                                    }
26402                                } else {
26403                                    self.write_keyword("TIMESTAMP");
26404                                }
26405                            }
26406                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
26407                            "DATETIMEOFFSET"
26408                                if !matches!(
26409                                    self.config.dialect,
26410                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26411                                ) =>
26412                            {
26413                                if matches!(
26414                                    self.config.dialect,
26415                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
26416                                ) {
26417                                    self.write_keyword("TIMESTAMPTZ");
26418                                    if let Some(args) = _args_str {
26419                                        self.write(args);
26420                                    }
26421                                } else {
26422                                    self.write_keyword("TIMESTAMPTZ");
26423                                }
26424                            }
26425                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
26426                            "UNIQUEIDENTIFIER"
26427                                if !matches!(
26428                                    self.config.dialect,
26429                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26430                                ) =>
26431                            {
26432                                match self.config.dialect {
26433                                    Some(DialectType::Spark)
26434                                    | Some(DialectType::Databricks)
26435                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
26436                                    _ => self.write_keyword("UUID"),
26437                                }
26438                            }
26439                            // TSQL BIT -> BOOLEAN for most dialects
26440                            "BIT"
26441                                if !matches!(
26442                                    self.config.dialect,
26443                                    Some(DialectType::TSQL)
26444                                        | Some(DialectType::Fabric)
26445                                        | Some(DialectType::PostgreSQL)
26446                                        | Some(DialectType::MySQL)
26447                                        | Some(DialectType::DuckDB)
26448                                ) =>
26449                            {
26450                                self.write_keyword("BOOLEAN");
26451                            }
26452                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
26453                            "NVARCHAR"
26454                                if !matches!(
26455                                    self.config.dialect,
26456                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26457                                ) =>
26458                            {
26459                                match self.config.dialect {
26460                                    Some(DialectType::Oracle) => {
26461                                        // Oracle: NVARCHAR -> NVARCHAR2
26462                                        self.write_keyword("NVARCHAR2");
26463                                        if let Some(args) = _args_str {
26464                                            self.write(args);
26465                                        }
26466                                    }
26467                                    Some(DialectType::BigQuery) => {
26468                                        // BigQuery: NVARCHAR -> STRING
26469                                        self.write_keyword("STRING");
26470                                    }
26471                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
26472                                        self.write_keyword("TEXT");
26473                                        if let Some(args) = _args_str {
26474                                            self.write(args);
26475                                        }
26476                                    }
26477                                    Some(DialectType::Hive) => {
26478                                        // Hive: NVARCHAR -> STRING
26479                                        self.write_keyword("STRING");
26480                                    }
26481                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
26482                                        if _args_str.is_some() {
26483                                            self.write_keyword("VARCHAR");
26484                                            self.write(_args_str.unwrap());
26485                                        } else {
26486                                            self.write_keyword("STRING");
26487                                        }
26488                                    }
26489                                    _ => {
26490                                        self.write_keyword("VARCHAR");
26491                                        if let Some(args) = _args_str {
26492                                            self.write(args);
26493                                        }
26494                                    }
26495                                }
26496                            }
26497                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
26498                            "NCHAR"
26499                                if !matches!(
26500                                    self.config.dialect,
26501                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26502                                ) =>
26503                            {
26504                                match self.config.dialect {
26505                                    Some(DialectType::Oracle) => {
26506                                        // Oracle natively supports NCHAR
26507                                        self.write_keyword("NCHAR");
26508                                        if let Some(args) = _args_str {
26509                                            self.write(args);
26510                                        }
26511                                    }
26512                                    Some(DialectType::BigQuery) => {
26513                                        // BigQuery: NCHAR -> STRING
26514                                        self.write_keyword("STRING");
26515                                    }
26516                                    Some(DialectType::Hive) => {
26517                                        // Hive: NCHAR -> STRING
26518                                        self.write_keyword("STRING");
26519                                    }
26520                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
26521                                        self.write_keyword("TEXT");
26522                                        if let Some(args) = _args_str {
26523                                            self.write(args);
26524                                        }
26525                                    }
26526                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
26527                                        if _args_str.is_some() {
26528                                            self.write_keyword("CHAR");
26529                                            self.write(_args_str.unwrap());
26530                                        } else {
26531                                            self.write_keyword("STRING");
26532                                        }
26533                                    }
26534                                    _ => {
26535                                        self.write_keyword("CHAR");
26536                                        if let Some(args) = _args_str {
26537                                            self.write(args);
26538                                        }
26539                                    }
26540                                }
26541                            }
26542                            // MySQL text variant types -> map to appropriate target type
26543                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
26544                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
26545                                Some(DialectType::MySQL)
26546                                | Some(DialectType::SingleStore)
26547                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
26548                                Some(DialectType::Spark)
26549                                | Some(DialectType::Databricks)
26550                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
26551                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
26552                                Some(DialectType::Presto)
26553                                | Some(DialectType::Trino)
26554                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
26555                                Some(DialectType::Snowflake)
26556                                | Some(DialectType::Redshift)
26557                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
26558                                _ => self.write_keyword("TEXT"),
26559                            },
26560                            // MySQL blob variant types -> map to appropriate target type
26561                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
26562                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
26563                                Some(DialectType::MySQL)
26564                                | Some(DialectType::SingleStore)
26565                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
26566                                Some(DialectType::Spark)
26567                                | Some(DialectType::Databricks)
26568                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
26569                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
26570                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
26571                                Some(DialectType::Presto)
26572                                | Some(DialectType::Trino)
26573                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
26574                                Some(DialectType::Snowflake)
26575                                | Some(DialectType::Redshift)
26576                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
26577                                _ => self.write_keyword("BLOB"),
26578                            },
26579                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
26580                            "LONGVARCHAR" => match self.config.dialect {
26581                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
26582                                _ => self.write_keyword("VARCHAR"),
26583                            },
26584                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
26585                            "DATETIME" => {
26586                                match self.config.dialect {
26587                                    Some(DialectType::MySQL)
26588                                    | Some(DialectType::Doris)
26589                                    | Some(DialectType::StarRocks)
26590                                    | Some(DialectType::TSQL)
26591                                    | Some(DialectType::Fabric)
26592                                    | Some(DialectType::BigQuery)
26593                                    | Some(DialectType::SQLite)
26594                                    | Some(DialectType::Snowflake) => {
26595                                        self.write_keyword("DATETIME");
26596                                        if let Some(args) = _args_str {
26597                                            self.write(args);
26598                                        }
26599                                    }
26600                                    Some(_) => {
26601                                        // Only map to TIMESTAMP when we have a specific target dialect
26602                                        self.write_keyword("TIMESTAMP");
26603                                        if let Some(args) = _args_str {
26604                                            self.write(args);
26605                                        }
26606                                    }
26607                                    None => {
26608                                        // No dialect - preserve original
26609                                        self.write(name);
26610                                    }
26611                                }
26612                            }
26613                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
26614                            "VARCHAR2"
26615                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
26616                            {
26617                                match self.config.dialect {
26618                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
26619                                        self.write_keyword("TEXT");
26620                                    }
26621                                    Some(DialectType::Hive)
26622                                    | Some(DialectType::Spark)
26623                                    | Some(DialectType::Databricks)
26624                                    | Some(DialectType::BigQuery)
26625                                    | Some(DialectType::ClickHouse)
26626                                    | Some(DialectType::StarRocks)
26627                                    | Some(DialectType::Doris) => {
26628                                        self.write_keyword("STRING");
26629                                    }
26630                                    _ => {
26631                                        self.write_keyword("VARCHAR");
26632                                        if let Some(args) = _args_str {
26633                                            self.write(args);
26634                                        }
26635                                    }
26636                                }
26637                            }
26638                            "NVARCHAR2"
26639                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
26640                            {
26641                                match self.config.dialect {
26642                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
26643                                        self.write_keyword("TEXT");
26644                                    }
26645                                    Some(DialectType::Hive)
26646                                    | Some(DialectType::Spark)
26647                                    | Some(DialectType::Databricks)
26648                                    | Some(DialectType::BigQuery)
26649                                    | Some(DialectType::ClickHouse)
26650                                    | Some(DialectType::StarRocks)
26651                                    | Some(DialectType::Doris) => {
26652                                        self.write_keyword("STRING");
26653                                    }
26654                                    _ => {
26655                                        self.write_keyword("VARCHAR");
26656                                        if let Some(args) = _args_str {
26657                                            self.write(args);
26658                                        }
26659                                    }
26660                                }
26661                            }
26662                            _ => self.write(name),
26663                        }
26664                    }
26665                }
26666            }
26667            DataType::Geometry { subtype, srid } => {
26668                // Dialect-specific geometry type mappings
26669                match self.config.dialect {
26670                    Some(DialectType::MySQL) => {
26671                        // MySQL uses POINT SRID 4326 syntax for specific types
26672                        if let Some(sub) = subtype {
26673                            self.write_keyword(sub);
26674                            if let Some(s) = srid {
26675                                self.write(" SRID ");
26676                                self.write(&s.to_string());
26677                            }
26678                        } else {
26679                            self.write_keyword("GEOMETRY");
26680                        }
26681                    }
26682                    Some(DialectType::BigQuery) => {
26683                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
26684                        self.write_keyword("GEOGRAPHY");
26685                    }
26686                    Some(DialectType::Teradata) => {
26687                        // Teradata uses ST_GEOMETRY
26688                        self.write_keyword("ST_GEOMETRY");
26689                        if subtype.is_some() || srid.is_some() {
26690                            self.write("(");
26691                            if let Some(sub) = subtype {
26692                                self.write_keyword(sub);
26693                            }
26694                            if let Some(s) = srid {
26695                                if subtype.is_some() {
26696                                    self.write(", ");
26697                                }
26698                                self.write(&s.to_string());
26699                            }
26700                            self.write(")");
26701                        }
26702                    }
26703                    _ => {
26704                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
26705                        self.write_keyword("GEOMETRY");
26706                        if subtype.is_some() || srid.is_some() {
26707                            self.write("(");
26708                            if let Some(sub) = subtype {
26709                                self.write_keyword(sub);
26710                            }
26711                            if let Some(s) = srid {
26712                                if subtype.is_some() {
26713                                    self.write(", ");
26714                                }
26715                                self.write(&s.to_string());
26716                            }
26717                            self.write(")");
26718                        }
26719                    }
26720                }
26721            }
26722            DataType::Geography { subtype, srid } => {
26723                // Dialect-specific geography type mappings
26724                match self.config.dialect {
26725                    Some(DialectType::MySQL) => {
26726                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
26727                        if let Some(sub) = subtype {
26728                            self.write_keyword(sub);
26729                        } else {
26730                            self.write_keyword("GEOMETRY");
26731                        }
26732                        // Geography implies SRID 4326 (WGS84)
26733                        let effective_srid = srid.unwrap_or(4326);
26734                        self.write(" SRID ");
26735                        self.write(&effective_srid.to_string());
26736                    }
26737                    Some(DialectType::BigQuery) => {
26738                        // BigQuery uses simple GEOGRAPHY without parameters
26739                        self.write_keyword("GEOGRAPHY");
26740                    }
26741                    Some(DialectType::Snowflake) => {
26742                        // Snowflake uses GEOGRAPHY without parameters
26743                        self.write_keyword("GEOGRAPHY");
26744                    }
26745                    _ => {
26746                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
26747                        self.write_keyword("GEOGRAPHY");
26748                        if subtype.is_some() || srid.is_some() {
26749                            self.write("(");
26750                            if let Some(sub) = subtype {
26751                                self.write_keyword(sub);
26752                            }
26753                            if let Some(s) = srid {
26754                                if subtype.is_some() {
26755                                    self.write(", ");
26756                                }
26757                                self.write(&s.to_string());
26758                            }
26759                            self.write(")");
26760                        }
26761                    }
26762                }
26763            }
26764            DataType::CharacterSet { name } => {
26765                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
26766                self.write_keyword("CHAR CHARACTER SET ");
26767                self.write(name);
26768            }
26769            _ => self.write("UNKNOWN"),
26770        }
26771        Ok(())
26772    }
26773
26774    // === Helper methods ===
26775
26776    #[inline]
26777    fn write(&mut self, s: &str) {
26778        self.output.push_str(s);
26779    }
26780
26781    #[inline]
26782    fn write_space(&mut self) {
26783        self.output.push(' ');
26784    }
26785
26786    #[inline]
26787    fn write_keyword(&mut self, keyword: &str) {
26788        if self.config.uppercase_keywords {
26789            self.output.push_str(keyword);
26790        } else {
26791            for b in keyword.bytes() {
26792                self.output.push(b.to_ascii_lowercase() as char);
26793            }
26794        }
26795    }
26796
26797    /// Write a function name respecting the normalize_functions config setting
26798    fn write_func_name(&mut self, name: &str) {
26799        let normalized = self.normalize_func_name(name);
26800        self.output.push_str(normalized.as_ref());
26801    }
26802
26803    /// Convert strptime format string to Exasol format string
26804    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
26805    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
26806    fn convert_strptime_to_exasol_format(format: &str) -> String {
26807        let mut result = String::new();
26808        let chars: Vec<char> = format.chars().collect();
26809        let mut i = 0;
26810        while i < chars.len() {
26811            if chars[i] == '%' && i + 1 < chars.len() {
26812                let spec = chars[i + 1];
26813                let exasol_spec = match spec {
26814                    'Y' => "YYYY",
26815                    'y' => "YY",
26816                    'm' => "MM",
26817                    'd' => "DD",
26818                    'H' => "HH",
26819                    'M' => "MI",
26820                    'S' => "SS",
26821                    'a' => "DY",    // abbreviated weekday name
26822                    'A' => "DAY",   // full weekday name
26823                    'b' => "MON",   // abbreviated month name
26824                    'B' => "MONTH", // full month name
26825                    'I' => "H12",   // 12-hour format
26826                    'u' => "ID",    // ISO weekday (1-7)
26827                    'V' => "IW",    // ISO week number
26828                    'G' => "IYYY",  // ISO year
26829                    'W' => "UW",    // Week number (Monday as first day)
26830                    'U' => "UW",    // Week number (Sunday as first day)
26831                    'z' => "Z",     // timezone offset
26832                    _ => {
26833                        // Unknown specifier, keep as-is
26834                        result.push('%');
26835                        result.push(spec);
26836                        i += 2;
26837                        continue;
26838                    }
26839                };
26840                result.push_str(exasol_spec);
26841                i += 2;
26842            } else {
26843                result.push(chars[i]);
26844                i += 1;
26845            }
26846        }
26847        result
26848    }
26849
26850    /// Convert strptime format string to PostgreSQL/Redshift format string
26851    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
26852    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
26853    fn convert_strptime_to_postgres_format(format: &str) -> String {
26854        let mut result = String::new();
26855        let chars: Vec<char> = format.chars().collect();
26856        let mut i = 0;
26857        while i < chars.len() {
26858            if chars[i] == '%' && i + 1 < chars.len() {
26859                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
26860                if chars[i + 1] == '-' && i + 2 < chars.len() {
26861                    let spec = chars[i + 2];
26862                    let pg_spec = match spec {
26863                        'd' => "FMDD",
26864                        'm' => "FMMM",
26865                        'H' => "FMHH24",
26866                        'M' => "FMMI",
26867                        'S' => "FMSS",
26868                        _ => {
26869                            result.push('%');
26870                            result.push('-');
26871                            result.push(spec);
26872                            i += 3;
26873                            continue;
26874                        }
26875                    };
26876                    result.push_str(pg_spec);
26877                    i += 3;
26878                    continue;
26879                }
26880                let spec = chars[i + 1];
26881                let pg_spec = match spec {
26882                    'Y' => "YYYY",
26883                    'y' => "YY",
26884                    'm' => "MM",
26885                    'd' => "DD",
26886                    'H' => "HH24",
26887                    'I' => "HH12",
26888                    'M' => "MI",
26889                    'S' => "SS",
26890                    'f' => "US",      // microseconds
26891                    'u' => "D",       // day of week (1=Monday)
26892                    'j' => "DDD",     // day of year
26893                    'z' => "OF",      // UTC offset
26894                    'Z' => "TZ",      // timezone name
26895                    'A' => "TMDay",   // full weekday name
26896                    'a' => "TMDy",    // abbreviated weekday name
26897                    'b' => "TMMon",   // abbreviated month name
26898                    'B' => "TMMonth", // full month name
26899                    'U' => "WW",      // week number
26900                    _ => {
26901                        // Unknown specifier, keep as-is
26902                        result.push('%');
26903                        result.push(spec);
26904                        i += 2;
26905                        continue;
26906                    }
26907                };
26908                result.push_str(pg_spec);
26909                i += 2;
26910            } else {
26911                result.push(chars[i]);
26912                i += 1;
26913            }
26914        }
26915        result
26916    }
26917
26918    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
26919    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
26920        if self.config.limit_only_literals {
26921            if let Some(value) = Self::try_evaluate_constant(expr) {
26922                self.write(&value.to_string());
26923                return Ok(());
26924            }
26925        }
26926        self.generate_expression(expr)
26927    }
26928
26929    fn is_noop_limit_expr(expr: &Expression) -> bool {
26930        match expr {
26931            Expression::Null(_) => true,
26932            Expression::Identifier(identifier) => identifier.name.eq_ignore_ascii_case("ALL"),
26933            Expression::Column(column) => {
26934                column.table.is_none() && column.name.name.eq_ignore_ascii_case("ALL")
26935            }
26936            Expression::Var(var) => var.this.eq_ignore_ascii_case("ALL"),
26937            Expression::Paren(paren) => Self::is_noop_limit_expr(&paren.this),
26938            _ => false,
26939        }
26940    }
26941
26942    /// Format a comment with proper spacing.
26943    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
26944    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
26945    fn write_formatted_comment(&mut self, comment: &str) {
26946        // Normalize all comments to block comment format /* ... */
26947        // This matches Python sqlglot behavior which always outputs block comments
26948        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
26949            // Already block comment - extract inner content
26950            // Preserve internal whitespace, but ensure at least one space padding
26951            &comment[2..comment.len() - 2]
26952        } else if comment.starts_with("--") {
26953            // Line comment - extract content after --
26954            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
26955            &comment[2..]
26956        } else {
26957            // Raw content (no delimiters)
26958            comment
26959        };
26960        // Skip empty comments (e.g., bare "--" with no content)
26961        if content.trim().is_empty() {
26962            return;
26963        }
26964        // Escape nested block comment markers to prevent premature closure or unintended nesting.
26965        // This matches Python sqlglot's sanitize_comment behavior.
26966        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
26967        let content = &sanitized;
26968        // Ensure at least one space after /* and before */
26969        self.output.push_str("/*");
26970        if !content.starts_with(' ') {
26971            self.output.push(' ');
26972        }
26973        self.output.push_str(content);
26974        if !content.ends_with(' ') {
26975            self.output.push(' ');
26976        }
26977        self.output.push_str("*/");
26978    }
26979
26980    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
26981    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
26982    fn escape_block_for_single_quote(&self, block: &str) -> String {
26983        let escape_backslash = matches!(
26984            self.config.dialect,
26985            Some(crate::dialects::DialectType::Snowflake)
26986        );
26987        let mut escaped = String::with_capacity(block.len() + 4);
26988        for ch in block.chars() {
26989            if ch == '\'' {
26990                escaped.push('\\');
26991                escaped.push('\'');
26992            } else if escape_backslash && ch == '\\' {
26993                escaped.push('\\');
26994                escaped.push('\\');
26995            } else {
26996                escaped.push(ch);
26997            }
26998        }
26999        escaped
27000    }
27001
27002    fn write_newline(&mut self) {
27003        self.output.push('\n');
27004    }
27005
27006    fn write_indent(&mut self) {
27007        for _ in 0..self.indent_level {
27008            self.output.push_str(self.config.indent);
27009        }
27010    }
27011
27012    // === SQLGlot-style pretty printing helpers ===
27013
27014    /// Returns the separator string for pretty printing.
27015    /// Check if the total length of arguments exceeds max_text_width.
27016    /// Used for dynamic line breaking in expressions() formatting.
27017    fn too_wide(&self, args: &[String]) -> bool {
27018        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
27019    }
27020
27021    /// Generate an expression to a string using a temporary non-pretty generator.
27022    /// Useful for width calculations before deciding on formatting.
27023    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
27024        let config = GeneratorConfig {
27025            pretty: false,
27026            dialect: self.config.dialect,
27027            ..Default::default()
27028        };
27029        let mut gen = Generator::with_config(config);
27030        gen.generate_expression(expr)?;
27031        Ok(gen.output)
27032    }
27033
27034    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
27035    /// In pretty mode: newline + indented keyword + newline + indented condition
27036    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
27037        if self.config.pretty {
27038            self.write_newline();
27039            self.write_indent();
27040            self.write_keyword(keyword);
27041            self.write_newline();
27042            self.indent_level += 1;
27043            self.write_indent();
27044            self.generate_expression(condition)?;
27045            self.indent_level -= 1;
27046        } else {
27047            self.write_space();
27048            self.write_keyword(keyword);
27049            self.write_space();
27050            self.generate_expression(condition)?;
27051        }
27052        Ok(())
27053    }
27054
27055    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
27056    /// In pretty mode: each expression on new line with indentation
27057    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
27058        if exprs.is_empty() {
27059            return Ok(());
27060        }
27061
27062        if self.config.pretty {
27063            self.write_newline();
27064            self.write_indent();
27065            self.write_keyword(keyword);
27066            self.write_newline();
27067            self.indent_level += 1;
27068            for (i, expr) in exprs.iter().enumerate() {
27069                if i > 0 {
27070                    self.write(",");
27071                    self.write_newline();
27072                }
27073                self.write_indent();
27074                self.generate_expression(expr)?;
27075            }
27076            self.indent_level -= 1;
27077        } else {
27078            self.write_space();
27079            self.write_keyword(keyword);
27080            self.write_space();
27081            for (i, expr) in exprs.iter().enumerate() {
27082                if i > 0 {
27083                    self.write(", ");
27084                }
27085                self.generate_expression(expr)?;
27086            }
27087        }
27088        Ok(())
27089    }
27090
27091    /// Writes ORDER BY / SORT BY clause with Ordered expressions
27092    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
27093        if orderings.is_empty() {
27094            return Ok(());
27095        }
27096
27097        if self.config.pretty {
27098            self.write_newline();
27099            self.write_indent();
27100            self.write_keyword(keyword);
27101            self.write_newline();
27102            self.indent_level += 1;
27103            for (i, ordered) in orderings.iter().enumerate() {
27104                if i > 0 {
27105                    self.write(",");
27106                    self.write_newline();
27107                }
27108                self.write_indent();
27109                self.generate_ordered(ordered)?;
27110            }
27111            self.indent_level -= 1;
27112        } else {
27113            self.write_space();
27114            self.write_keyword(keyword);
27115            self.write_space();
27116            for (i, ordered) in orderings.iter().enumerate() {
27117                if i > 0 {
27118                    self.write(", ");
27119                }
27120                self.generate_ordered(ordered)?;
27121            }
27122        }
27123        Ok(())
27124    }
27125
27126    /// Writes WINDOW clause with named window definitions
27127    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
27128        if windows.is_empty() {
27129            return Ok(());
27130        }
27131
27132        if self.config.pretty {
27133            self.write_newline();
27134            self.write_indent();
27135            self.write_keyword("WINDOW");
27136            self.write_newline();
27137            self.indent_level += 1;
27138            for (i, named_window) in windows.iter().enumerate() {
27139                if i > 0 {
27140                    self.write(",");
27141                    self.write_newline();
27142                }
27143                self.write_indent();
27144                self.generate_identifier(&named_window.name)?;
27145                self.write_space();
27146                self.write_keyword("AS");
27147                self.write(" (");
27148                self.generate_over(&named_window.spec)?;
27149                self.write(")");
27150            }
27151            self.indent_level -= 1;
27152        } else {
27153            self.write_space();
27154            self.write_keyword("WINDOW");
27155            self.write_space();
27156            for (i, named_window) in windows.iter().enumerate() {
27157                if i > 0 {
27158                    self.write(", ");
27159                }
27160                self.generate_identifier(&named_window.name)?;
27161                self.write_space();
27162                self.write_keyword("AS");
27163                self.write(" (");
27164                self.generate_over(&named_window.spec)?;
27165                self.write(")");
27166            }
27167        }
27168        Ok(())
27169    }
27170
27171    // === BATCH-GENERATED STUB METHODS (481 variants) ===
27172    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
27173        // AI_AGG(this, expression)
27174        self.write_keyword("AI_AGG");
27175        self.write("(");
27176        self.generate_expression(&e.this)?;
27177        self.write(", ");
27178        self.generate_expression(&e.expression)?;
27179        self.write(")");
27180        Ok(())
27181    }
27182
27183    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
27184        // AI_CLASSIFY(input, [categories], [config])
27185        self.write_keyword("AI_CLASSIFY");
27186        self.write("(");
27187        self.generate_expression(&e.this)?;
27188        if let Some(categories) = &e.categories {
27189            self.write(", ");
27190            self.generate_expression(categories)?;
27191        }
27192        if let Some(config) = &e.config {
27193            self.write(", ");
27194            self.generate_expression(config)?;
27195        }
27196        self.write(")");
27197        Ok(())
27198    }
27199
27200    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
27201        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
27202        self.write_keyword("ADD");
27203        self.write_space();
27204        if e.exists {
27205            self.write_keyword("IF NOT EXISTS");
27206            self.write_space();
27207        }
27208        self.generate_expression(&e.this)?;
27209        if let Some(location) = &e.location {
27210            self.write_space();
27211            self.generate_expression(location)?;
27212        }
27213        Ok(())
27214    }
27215
27216    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
27217        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
27218        self.write_keyword("ALGORITHM");
27219        self.write("=");
27220        self.generate_expression(&e.this)?;
27221        Ok(())
27222    }
27223
27224    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
27225        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
27226        self.generate_expression(&e.this)?;
27227        self.write_space();
27228        self.write_keyword("AS");
27229        self.write(" (");
27230        for (i, expr) in e.expressions.iter().enumerate() {
27231            if i > 0 {
27232                self.write(", ");
27233            }
27234            self.generate_expression(expr)?;
27235        }
27236        self.write(")");
27237        Ok(())
27238    }
27239
27240    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
27241        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
27242        self.write_keyword("ALLOWED_VALUES");
27243        self.write_space();
27244        for (i, expr) in e.expressions.iter().enumerate() {
27245            if i > 0 {
27246                self.write(", ");
27247            }
27248            self.generate_expression(expr)?;
27249        }
27250        Ok(())
27251    }
27252
27253    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
27254        // Python: complex logic based on dtype, default, comment, visible, etc.
27255        self.write_keyword("ALTER COLUMN");
27256        self.write_space();
27257        self.generate_expression(&e.this)?;
27258
27259        if let Some(dtype) = &e.dtype {
27260            self.write_space();
27261            self.write_keyword("SET DATA TYPE");
27262            self.write_space();
27263            self.generate_expression(dtype)?;
27264            if let Some(collate) = &e.collate {
27265                self.write_space();
27266                self.write_keyword("COLLATE");
27267                self.write_space();
27268                self.generate_expression(collate)?;
27269            }
27270            if let Some(using) = &e.using {
27271                self.write_space();
27272                self.write_keyword("USING");
27273                self.write_space();
27274                self.generate_expression(using)?;
27275            }
27276        } else if let Some(default) = &e.default {
27277            self.write_space();
27278            self.write_keyword("SET DEFAULT");
27279            self.write_space();
27280            self.generate_expression(default)?;
27281        } else if let Some(comment) = &e.comment {
27282            self.write_space();
27283            self.write_keyword("COMMENT");
27284            self.write_space();
27285            self.generate_expression(comment)?;
27286        } else if let Some(drop) = &e.drop {
27287            self.write_space();
27288            self.write_keyword("DROP");
27289            self.write_space();
27290            self.generate_expression(drop)?;
27291        } else if let Some(visible) = &e.visible {
27292            self.write_space();
27293            self.generate_expression(visible)?;
27294        } else if let Some(rename_to) = &e.rename_to {
27295            self.write_space();
27296            self.write_keyword("RENAME TO");
27297            self.write_space();
27298            self.generate_expression(rename_to)?;
27299        } else if let Some(allow_null) = &e.allow_null {
27300            self.write_space();
27301            self.generate_expression(allow_null)?;
27302        }
27303        Ok(())
27304    }
27305
27306    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
27307        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
27308        self.write_keyword("ALTER SESSION");
27309        self.write_space();
27310        if e.unset.is_some() {
27311            self.write_keyword("UNSET");
27312        } else {
27313            self.write_keyword("SET");
27314        }
27315        self.write_space();
27316        for (i, expr) in e.expressions.iter().enumerate() {
27317            if i > 0 {
27318                self.write(", ");
27319            }
27320            self.generate_expression(expr)?;
27321        }
27322        Ok(())
27323    }
27324
27325    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
27326        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
27327        self.write_keyword("SET");
27328
27329        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
27330        if let Some(opt) = &e.option {
27331            self.write_space();
27332            self.generate_expression(opt)?;
27333        }
27334
27335        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
27336        // Check if expressions look like property assignments
27337        if !e.expressions.is_empty() {
27338            // Check if this looks like property assignments (for SET PROPERTIES)
27339            let is_properties = e
27340                .expressions
27341                .iter()
27342                .any(|expr| matches!(expr, Expression::Eq(_)));
27343            if is_properties && e.option.is_none() {
27344                self.write_space();
27345                self.write_keyword("PROPERTIES");
27346            }
27347            self.write_space();
27348            for (i, expr) in e.expressions.iter().enumerate() {
27349                if i > 0 {
27350                    self.write(", ");
27351                }
27352                self.generate_expression(expr)?;
27353            }
27354        }
27355
27356        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
27357        if let Some(file_format) = &e.file_format {
27358            self.write(" ");
27359            self.write_keyword("STAGE_FILE_FORMAT");
27360            self.write(" = (");
27361            self.generate_space_separated_properties(file_format)?;
27362            self.write(")");
27363        }
27364
27365        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
27366        if let Some(copy_options) = &e.copy_options {
27367            self.write(" ");
27368            self.write_keyword("STAGE_COPY_OPTIONS");
27369            self.write(" = (");
27370            self.generate_space_separated_properties(copy_options)?;
27371            self.write(")");
27372        }
27373
27374        // Generate TAG ...
27375        if let Some(tag) = &e.tag {
27376            self.write(" ");
27377            self.write_keyword("TAG");
27378            self.write(" ");
27379            self.generate_expression(tag)?;
27380        }
27381
27382        Ok(())
27383    }
27384
27385    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
27386    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
27387        match expr {
27388            Expression::Tuple(t) => {
27389                for (i, prop) in t.expressions.iter().enumerate() {
27390                    if i > 0 {
27391                        self.write(" ");
27392                    }
27393                    self.generate_expression(prop)?;
27394                }
27395            }
27396            _ => {
27397                self.generate_expression(expr)?;
27398            }
27399        }
27400        Ok(())
27401    }
27402
27403    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
27404        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
27405        self.write_keyword("ALTER");
27406        if e.compound.is_some() {
27407            self.write_space();
27408            self.write_keyword("COMPOUND");
27409        }
27410        self.write_space();
27411        self.write_keyword("SORTKEY");
27412        self.write_space();
27413        if let Some(this) = &e.this {
27414            self.generate_expression(this)?;
27415        } else if !e.expressions.is_empty() {
27416            self.write("(");
27417            for (i, expr) in e.expressions.iter().enumerate() {
27418                if i > 0 {
27419                    self.write(", ");
27420                }
27421                self.generate_expression(expr)?;
27422            }
27423            self.write(")");
27424        }
27425        Ok(())
27426    }
27427
27428    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
27429        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
27430        self.write_keyword("ANALYZE");
27431        if !e.options.is_empty() {
27432            self.write_space();
27433            for (i, opt) in e.options.iter().enumerate() {
27434                if i > 0 {
27435                    self.write_space();
27436                }
27437                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
27438                if let Expression::Identifier(id) = opt {
27439                    self.write_keyword(&id.name);
27440                } else {
27441                    self.generate_expression(opt)?;
27442                }
27443            }
27444        }
27445        if let Some(kind) = &e.kind {
27446            self.write_space();
27447            self.write_keyword(kind);
27448        }
27449        if let Some(this) = &e.this {
27450            self.write_space();
27451            self.generate_expression(this)?;
27452        }
27453        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
27454        if !e.columns.is_empty() {
27455            self.write("(");
27456            for (i, col) in e.columns.iter().enumerate() {
27457                if i > 0 {
27458                    self.write(", ");
27459                }
27460                self.write(col);
27461            }
27462            self.write(")");
27463        }
27464        if let Some(partition) = &e.partition {
27465            self.write_space();
27466            self.generate_expression(partition)?;
27467        }
27468        if let Some(mode) = &e.mode {
27469            self.write_space();
27470            self.generate_expression(mode)?;
27471        }
27472        if let Some(expression) = &e.expression {
27473            self.write_space();
27474            self.generate_expression(expression)?;
27475        }
27476        if !e.properties.is_empty() {
27477            self.write_space();
27478            self.write_keyword(self.config.with_properties_prefix);
27479            self.write(" (");
27480            for (i, prop) in e.properties.iter().enumerate() {
27481                if i > 0 {
27482                    self.write(", ");
27483                }
27484                self.generate_expression(prop)?;
27485            }
27486            self.write(")");
27487        }
27488        Ok(())
27489    }
27490
27491    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
27492        // Python: return f"DELETE{kind} STATISTICS"
27493        self.write_keyword("DELETE");
27494        if let Some(kind) = &e.kind {
27495            self.write_space();
27496            self.write_keyword(kind);
27497        }
27498        self.write_space();
27499        self.write_keyword("STATISTICS");
27500        Ok(())
27501    }
27502
27503    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
27504        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
27505        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
27506        if let Expression::Identifier(id) = e.this.as_ref() {
27507            self.write_keyword(&id.name);
27508        } else {
27509            self.generate_expression(&e.this)?;
27510        }
27511        self.write_space();
27512        self.write_keyword("HISTOGRAM ON");
27513        self.write_space();
27514        for (i, expr) in e.expressions.iter().enumerate() {
27515            if i > 0 {
27516                self.write(", ");
27517            }
27518            self.generate_expression(expr)?;
27519        }
27520        if let Some(expression) = &e.expression {
27521            self.write_space();
27522            self.generate_expression(expression)?;
27523        }
27524        if let Some(update_options) = &e.update_options {
27525            self.write_space();
27526            self.generate_expression(update_options)?;
27527            self.write_space();
27528            self.write_keyword("UPDATE");
27529        }
27530        Ok(())
27531    }
27532
27533    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
27534        // Python: return f"LIST CHAINED ROWS{inner_expression}"
27535        self.write_keyword("LIST CHAINED ROWS");
27536        if let Some(expression) = &e.expression {
27537            self.write_space();
27538            self.write_keyword("INTO");
27539            self.write_space();
27540            self.generate_expression(expression)?;
27541        }
27542        Ok(())
27543    }
27544
27545    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
27546        // Python: return f"SAMPLE {sample} {kind}"
27547        self.write_keyword("SAMPLE");
27548        self.write_space();
27549        if let Some(sample) = &e.sample {
27550            self.generate_expression(sample)?;
27551            self.write_space();
27552        }
27553        self.write_keyword(&e.kind);
27554        Ok(())
27555    }
27556
27557    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
27558        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
27559        self.write_keyword(&e.kind);
27560        if let Some(option) = &e.option {
27561            self.write_space();
27562            self.generate_expression(option)?;
27563        }
27564        self.write_space();
27565        self.write_keyword("STATISTICS");
27566        if let Some(this) = &e.this {
27567            self.write_space();
27568            self.generate_expression(this)?;
27569        }
27570        if !e.expressions.is_empty() {
27571            self.write_space();
27572            for (i, expr) in e.expressions.iter().enumerate() {
27573                if i > 0 {
27574                    self.write(", ");
27575                }
27576                self.generate_expression(expr)?;
27577            }
27578        }
27579        Ok(())
27580    }
27581
27582    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
27583        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
27584        self.write_keyword("VALIDATE");
27585        self.write_space();
27586        self.write_keyword(&e.kind);
27587        if let Some(this) = &e.this {
27588            self.write_space();
27589            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
27590            if let Expression::Identifier(id) = this.as_ref() {
27591                self.write_keyword(&id.name);
27592            } else {
27593                self.generate_expression(this)?;
27594            }
27595        }
27596        if let Some(expression) = &e.expression {
27597            self.write_space();
27598            self.write_keyword("INTO");
27599            self.write_space();
27600            self.generate_expression(expression)?;
27601        }
27602        Ok(())
27603    }
27604
27605    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
27606        // Python: return f"WITH {expressions}"
27607        self.write_keyword("WITH");
27608        self.write_space();
27609        for (i, expr) in e.expressions.iter().enumerate() {
27610            if i > 0 {
27611                self.write(", ");
27612            }
27613            self.generate_expression(expr)?;
27614        }
27615        Ok(())
27616    }
27617
27618    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
27619        // Anonymous represents a generic function call: FUNC_NAME(args...)
27620        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
27621        self.generate_expression(&e.this)?;
27622        self.write("(");
27623        for (i, arg) in e.expressions.iter().enumerate() {
27624            if i > 0 {
27625                self.write(", ");
27626            }
27627            self.generate_expression(arg)?;
27628        }
27629        self.write(")");
27630        Ok(())
27631    }
27632
27633    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
27634        // Same as Anonymous but for aggregate functions
27635        self.generate_expression(&e.this)?;
27636        self.write("(");
27637        for (i, arg) in e.expressions.iter().enumerate() {
27638            if i > 0 {
27639                self.write(", ");
27640            }
27641            self.generate_expression(arg)?;
27642        }
27643        self.write(")");
27644        Ok(())
27645    }
27646
27647    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
27648        // Python: return f"{this} APPLY({expr})"
27649        self.generate_expression(&e.this)?;
27650        self.write_space();
27651        self.write_keyword("APPLY");
27652        self.write("(");
27653        self.generate_expression(&e.expression)?;
27654        self.write(")");
27655        Ok(())
27656    }
27657
27658    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
27659        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
27660        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
27661        self.write("(");
27662        self.generate_expression(&e.this)?;
27663        if let Some(percentile) = &e.percentile {
27664            self.write(", ");
27665            self.generate_expression(percentile)?;
27666        }
27667        self.write(")");
27668        Ok(())
27669    }
27670
27671    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
27672        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
27673        self.write_keyword("APPROX_QUANTILE");
27674        self.write("(");
27675        self.generate_expression(&e.this)?;
27676        if let Some(quantile) = &e.quantile {
27677            self.write(", ");
27678            self.generate_expression(quantile)?;
27679        }
27680        if let Some(accuracy) = &e.accuracy {
27681            self.write(", ");
27682            self.generate_expression(accuracy)?;
27683        }
27684        if let Some(weight) = &e.weight {
27685            self.write(", ");
27686            self.generate_expression(weight)?;
27687        }
27688        self.write(")");
27689        Ok(())
27690    }
27691
27692    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
27693        // APPROX_QUANTILES(this, expression)
27694        self.write_keyword("APPROX_QUANTILES");
27695        self.write("(");
27696        self.generate_expression(&e.this)?;
27697        if let Some(expression) = &e.expression {
27698            self.write(", ");
27699            self.generate_expression(expression)?;
27700        }
27701        self.write(")");
27702        Ok(())
27703    }
27704
27705    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
27706        // APPROX_TOP_K(this[, expression][, counters])
27707        self.write_keyword("APPROX_TOP_K");
27708        self.write("(");
27709        self.generate_expression(&e.this)?;
27710        if let Some(expression) = &e.expression {
27711            self.write(", ");
27712            self.generate_expression(expression)?;
27713        }
27714        if let Some(counters) = &e.counters {
27715            self.write(", ");
27716            self.generate_expression(counters)?;
27717        }
27718        self.write(")");
27719        Ok(())
27720    }
27721
27722    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
27723        // APPROX_TOP_K_ACCUMULATE(this[, expression])
27724        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
27725        self.write("(");
27726        self.generate_expression(&e.this)?;
27727        if let Some(expression) = &e.expression {
27728            self.write(", ");
27729            self.generate_expression(expression)?;
27730        }
27731        self.write(")");
27732        Ok(())
27733    }
27734
27735    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
27736        // APPROX_TOP_K_COMBINE(this[, expression])
27737        self.write_keyword("APPROX_TOP_K_COMBINE");
27738        self.write("(");
27739        self.generate_expression(&e.this)?;
27740        if let Some(expression) = &e.expression {
27741            self.write(", ");
27742            self.generate_expression(expression)?;
27743        }
27744        self.write(")");
27745        Ok(())
27746    }
27747
27748    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
27749        // APPROX_TOP_K_ESTIMATE(this[, expression])
27750        self.write_keyword("APPROX_TOP_K_ESTIMATE");
27751        self.write("(");
27752        self.generate_expression(&e.this)?;
27753        if let Some(expression) = &e.expression {
27754            self.write(", ");
27755            self.generate_expression(expression)?;
27756        }
27757        self.write(")");
27758        Ok(())
27759    }
27760
27761    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
27762        // APPROX_TOP_SUM(this, expression[, count])
27763        self.write_keyword("APPROX_TOP_SUM");
27764        self.write("(");
27765        self.generate_expression(&e.this)?;
27766        self.write(", ");
27767        self.generate_expression(&e.expression)?;
27768        if let Some(count) = &e.count {
27769            self.write(", ");
27770            self.generate_expression(count)?;
27771        }
27772        self.write(")");
27773        Ok(())
27774    }
27775
27776    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
27777        // ARG_MAX(this, expression[, count])
27778        self.write_keyword("ARG_MAX");
27779        self.write("(");
27780        self.generate_expression(&e.this)?;
27781        self.write(", ");
27782        self.generate_expression(&e.expression)?;
27783        if let Some(count) = &e.count {
27784            self.write(", ");
27785            self.generate_expression(count)?;
27786        }
27787        self.write(")");
27788        Ok(())
27789    }
27790
27791    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
27792        // ARG_MIN(this, expression[, count])
27793        self.write_keyword("ARG_MIN");
27794        self.write("(");
27795        self.generate_expression(&e.this)?;
27796        self.write(", ");
27797        self.generate_expression(&e.expression)?;
27798        if let Some(count) = &e.count {
27799            self.write(", ");
27800            self.generate_expression(count)?;
27801        }
27802        self.write(")");
27803        Ok(())
27804    }
27805
27806    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
27807        // ARRAY_ALL(this, expression)
27808        self.write_keyword("ARRAY_ALL");
27809        self.write("(");
27810        self.generate_expression(&e.this)?;
27811        self.write(", ");
27812        self.generate_expression(&e.expression)?;
27813        self.write(")");
27814        Ok(())
27815    }
27816
27817    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
27818        // ARRAY_ANY(this, expression) - fallback implementation
27819        self.write_keyword("ARRAY_ANY");
27820        self.write("(");
27821        self.generate_expression(&e.this)?;
27822        self.write(", ");
27823        self.generate_expression(&e.expression)?;
27824        self.write(")");
27825        Ok(())
27826    }
27827
27828    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
27829        // ARRAY_CONSTRUCT_COMPACT(expressions...)
27830        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
27831        self.write("(");
27832        for (i, expr) in e.expressions.iter().enumerate() {
27833            if i > 0 {
27834                self.write(", ");
27835            }
27836            self.generate_expression(expr)?;
27837        }
27838        self.write(")");
27839        Ok(())
27840    }
27841
27842    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
27843        // ARRAY_SUM(this[, expression])
27844        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27845            self.write("arraySum");
27846        } else {
27847            self.write_keyword("ARRAY_SUM");
27848        }
27849        self.write("(");
27850        self.generate_expression(&e.this)?;
27851        if let Some(expression) = &e.expression {
27852            self.write(", ");
27853            self.generate_expression(expression)?;
27854        }
27855        self.write(")");
27856        Ok(())
27857    }
27858
27859    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
27860        // Python: return f"{this} AT {index}"
27861        self.generate_expression(&e.this)?;
27862        self.write_space();
27863        self.write_keyword("AT");
27864        self.write_space();
27865        self.generate_expression(&e.expression)?;
27866        Ok(())
27867    }
27868
27869    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
27870        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
27871        self.write_keyword("ATTACH");
27872        if e.exists {
27873            self.write_space();
27874            self.write_keyword("IF NOT EXISTS");
27875        }
27876        self.write_space();
27877        self.generate_expression(&e.this)?;
27878        if !e.expressions.is_empty() {
27879            self.write(" (");
27880            for (i, expr) in e.expressions.iter().enumerate() {
27881                if i > 0 {
27882                    self.write(", ");
27883                }
27884                self.generate_expression(expr)?;
27885            }
27886            self.write(")");
27887        }
27888        Ok(())
27889    }
27890
27891    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
27892        // AttachOption: this [expression]
27893        // Python sqlglot: no equals sign, just space-separated
27894        self.generate_expression(&e.this)?;
27895        if let Some(expression) = &e.expression {
27896            self.write_space();
27897            self.generate_expression(expression)?;
27898        }
27899        Ok(())
27900    }
27901
27902    /// Generate the auto_increment keyword and options for a column definition.
27903    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
27904    /// GENERATED AS IDENTITY, etc.
27905    fn generate_auto_increment_keyword(
27906        &mut self,
27907        col: &crate::expressions::ColumnDef,
27908    ) -> Result<()> {
27909        use crate::dialects::DialectType;
27910        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
27911            self.write_keyword("IDENTITY");
27912            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27913                self.write("(");
27914                if let Some(ref start) = col.auto_increment_start {
27915                    self.generate_expression(start)?;
27916                } else {
27917                    self.write("0");
27918                }
27919                self.write(", ");
27920                if let Some(ref inc) = col.auto_increment_increment {
27921                    self.generate_expression(inc)?;
27922                } else {
27923                    self.write("1");
27924                }
27925                self.write(")");
27926            }
27927        } else if matches!(
27928            self.config.dialect,
27929            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
27930        ) {
27931            self.write_keyword("AUTOINCREMENT");
27932            if let Some(ref start) = col.auto_increment_start {
27933                self.write_space();
27934                self.write_keyword("START");
27935                self.write_space();
27936                self.generate_expression(start)?;
27937            }
27938            if let Some(ref inc) = col.auto_increment_increment {
27939                self.write_space();
27940                self.write_keyword("INCREMENT");
27941                self.write_space();
27942                self.generate_expression(inc)?;
27943            }
27944            if let Some(order) = col.auto_increment_order {
27945                self.write_space();
27946                if order {
27947                    self.write_keyword("ORDER");
27948                } else {
27949                    self.write_keyword("NOORDER");
27950                }
27951            }
27952        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
27953            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
27954            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27955                self.write(" (");
27956                let mut first = true;
27957                if let Some(ref start) = col.auto_increment_start {
27958                    self.write_keyword("START WITH");
27959                    self.write_space();
27960                    self.generate_expression(start)?;
27961                    first = false;
27962                }
27963                if let Some(ref inc) = col.auto_increment_increment {
27964                    if !first {
27965                        self.write_space();
27966                    }
27967                    self.write_keyword("INCREMENT BY");
27968                    self.write_space();
27969                    self.generate_expression(inc)?;
27970                }
27971                self.write(")");
27972            }
27973        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
27974            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
27975            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
27976            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27977                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
27978            } else {
27979                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
27980            }
27981            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27982                self.write(" (");
27983                let mut first = true;
27984                if let Some(ref start) = col.auto_increment_start {
27985                    self.write_keyword("START WITH");
27986                    self.write_space();
27987                    self.generate_expression(start)?;
27988                    first = false;
27989                }
27990                if let Some(ref inc) = col.auto_increment_increment {
27991                    if !first {
27992                        self.write_space();
27993                    }
27994                    self.write_keyword("INCREMENT BY");
27995                    self.write_space();
27996                    self.generate_expression(inc)?;
27997                }
27998                self.write(")");
27999            }
28000        } else if matches!(
28001            self.config.dialect,
28002            Some(DialectType::TSQL) | Some(DialectType::Fabric)
28003        ) {
28004            self.write_keyword("IDENTITY");
28005            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
28006                self.write("(");
28007                if let Some(ref start) = col.auto_increment_start {
28008                    self.generate_expression(start)?;
28009                } else {
28010                    self.write("0");
28011                }
28012                self.write(", ");
28013                if let Some(ref inc) = col.auto_increment_increment {
28014                    self.generate_expression(inc)?;
28015                } else {
28016                    self.write("1");
28017                }
28018                self.write(")");
28019            }
28020        } else {
28021            self.write_keyword("AUTO_INCREMENT");
28022            if let Some(ref start) = col.auto_increment_start {
28023                self.write_space();
28024                self.write_keyword("START");
28025                self.write_space();
28026                self.generate_expression(start)?;
28027            }
28028            if let Some(ref inc) = col.auto_increment_increment {
28029                self.write_space();
28030                self.write_keyword("INCREMENT");
28031                self.write_space();
28032                self.generate_expression(inc)?;
28033            }
28034            if let Some(order) = col.auto_increment_order {
28035                self.write_space();
28036                if order {
28037                    self.write_keyword("ORDER");
28038                } else {
28039                    self.write_keyword("NOORDER");
28040                }
28041            }
28042        }
28043        Ok(())
28044    }
28045
28046    fn generate_tidb_auto_random(&mut self, auto_random: &TiDBAutoRandom) -> Result<()> {
28047        let is_tidb = matches!(self.config.dialect, Some(DialectType::TiDB));
28048        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
28049
28050        if !is_tidb && !is_mysql {
28051            return self
28052                .write_unsupported_comment("AUTO_RANDOM is only supported by the TiDB dialect");
28053        }
28054        if is_mysql {
28055            self.unsupported("MySQL ignores TiDB AUTO_RANDOM executable comments")?;
28056        }
28057
28058        let use_comment = auto_random.executable_comment || is_mysql;
28059        if use_comment {
28060            self.write("/*T![auto_rand] ");
28061        }
28062        self.write_keyword("AUTO_RANDOM");
28063        if let Some(shard_bits) = auto_random.shard_bits {
28064            self.write("(");
28065            self.write(&shard_bits.to_string());
28066            if let Some(range_bits) = auto_random.range_bits {
28067                self.write(", ");
28068                self.write(&range_bits.to_string());
28069            }
28070            self.write(")");
28071        }
28072        if use_comment {
28073            self.write(" */");
28074        }
28075        Ok(())
28076    }
28077
28078    fn generate_tidb_table_option(&mut self, option: &TiDBTableOption, force: bool) -> Result<()> {
28079        let is_tidb = matches!(self.config.dialect, Some(DialectType::TiDB));
28080        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
28081        if !is_tidb && !is_mysql {
28082            return self.write_unsupported_comment(
28083                "TiDB table options are not supported by the target dialect",
28084            );
28085        }
28086        if is_mysql {
28087            self.unsupported("MySQL ignores TiDB table-option executable comments")?;
28088        }
28089
28090        let use_comment = option.executable_comment || is_mysql;
28091        if use_comment {
28092            match option.kind {
28093                TiDBTableOptionKind::Ttl { .. }
28094                | TiDBTableOptionKind::TtlEnable { .. }
28095                | TiDBTableOptionKind::TtlJobInterval { .. } => self.write("/*T![ttl] "),
28096                TiDBTableOptionKind::PlacementPolicy { .. } => self.write("/*T![placement] "),
28097                _ => self.write("/*T! "),
28098            }
28099        }
28100        if force {
28101            self.write_keyword("FORCE");
28102            self.write_space();
28103        }
28104        match &option.kind {
28105            TiDBTableOptionKind::ShardRowIdBits { bits } => {
28106                self.write_keyword("SHARD_ROW_ID_BITS");
28107                self.write("=");
28108                self.write(&bits.to_string());
28109            }
28110            TiDBTableOptionKind::PreSplitRegions { regions } => {
28111                self.write_keyword("PRE_SPLIT_REGIONS");
28112                self.write("=");
28113                self.write(&regions.to_string());
28114            }
28115            TiDBTableOptionKind::AutoRandomBase { value } => {
28116                self.write_keyword("AUTO_RANDOM_BASE");
28117                self.write("=");
28118                self.write(&value.to_string());
28119            }
28120            TiDBTableOptionKind::PlacementPolicy { policy } => {
28121                self.write_keyword("PLACEMENT POLICY");
28122                self.write("=");
28123                if let Some(policy) = policy {
28124                    self.generate_identifier(policy)?;
28125                } else {
28126                    self.write_keyword("DEFAULT");
28127                }
28128            }
28129            TiDBTableOptionKind::Ttl {
28130                column,
28131                interval,
28132                enabled,
28133            } => {
28134                self.write_keyword("TTL");
28135                self.write("=");
28136                self.generate_identifier(column)?;
28137                self.write(" + ");
28138                self.generate_tidb_ttl_interval(interval)?;
28139                if let Some(enabled) = enabled {
28140                    self.write_space();
28141                    self.write_keyword("TTL_ENABLE");
28142                    self.write("='");
28143                    self.write(if *enabled { "ON" } else { "OFF" });
28144                    self.write("'");
28145                }
28146            }
28147            TiDBTableOptionKind::TtlEnable { enabled } => {
28148                self.write_keyword("TTL_ENABLE");
28149                self.write("='");
28150                self.write(if *enabled { "ON" } else { "OFF" });
28151                self.write("'");
28152            }
28153            TiDBTableOptionKind::TtlJobInterval { interval } => {
28154                self.write_keyword("TTL_JOB_INTERVAL");
28155                self.write("=");
28156                self.generate_string_literal(interval)?;
28157            }
28158        }
28159        if use_comment {
28160            self.write(" */");
28161        }
28162        Ok(())
28163    }
28164
28165    fn generate_tidb_ttl_interval(&mut self, interval: &Interval) -> Result<()> {
28166        self.write_keyword("INTERVAL");
28167        if let Some(value) = &interval.this {
28168            self.write_space();
28169            if let Expression::Literal(literal) = value {
28170                if let Literal::String(value) = literal.as_ref() {
28171                    if value.parse::<f64>().is_ok() {
28172                        self.write(value);
28173                    } else {
28174                        self.generate_string_literal(value)?;
28175                    }
28176                } else {
28177                    self.generate_expression(value)?;
28178                }
28179            } else {
28180                self.generate_expression(value)?;
28181            }
28182        }
28183        if let Some(unit) = &interval.unit {
28184            self.write_space();
28185            self.write_interval_unit_spec(unit)?;
28186        }
28187        Ok(())
28188    }
28189
28190    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
28191        // AUTO_INCREMENT=value
28192        self.write_keyword("AUTO_INCREMENT");
28193        self.write("=");
28194        self.generate_expression(&e.this)?;
28195        Ok(())
28196    }
28197
28198    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
28199        // AUTO_REFRESH=value
28200        self.write_keyword("AUTO_REFRESH");
28201        self.write("=");
28202        self.generate_expression(&e.this)?;
28203        Ok(())
28204    }
28205
28206    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
28207        // BACKUP YES|NO (Redshift syntax uses space, not equals)
28208        self.write_keyword("BACKUP");
28209        self.write_space();
28210        self.generate_expression(&e.this)?;
28211        Ok(())
28212    }
28213
28214    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
28215        // BASE64_DECODE_BINARY(this[, alphabet])
28216        self.write_keyword("BASE64_DECODE_BINARY");
28217        self.write("(");
28218        self.generate_expression(&e.this)?;
28219        if let Some(alphabet) = &e.alphabet {
28220            self.write(", ");
28221            self.generate_expression(alphabet)?;
28222        }
28223        self.write(")");
28224        Ok(())
28225    }
28226
28227    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
28228        // BASE64_DECODE_STRING(this[, alphabet])
28229        self.write_keyword("BASE64_DECODE_STRING");
28230        self.write("(");
28231        self.generate_expression(&e.this)?;
28232        if let Some(alphabet) = &e.alphabet {
28233            self.write(", ");
28234            self.generate_expression(alphabet)?;
28235        }
28236        self.write(")");
28237        Ok(())
28238    }
28239
28240    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
28241        // BASE64_ENCODE(this[, max_line_length][, alphabet])
28242        self.write_keyword("BASE64_ENCODE");
28243        self.write("(");
28244        self.generate_expression(&e.this)?;
28245        if let Some(max_line_length) = &e.max_line_length {
28246            self.write(", ");
28247            self.generate_expression(max_line_length)?;
28248        }
28249        if let Some(alphabet) = &e.alphabet {
28250            self.write(", ");
28251            self.generate_expression(alphabet)?;
28252        }
28253        self.write(")");
28254        Ok(())
28255    }
28256
28257    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
28258        // BLOCKCOMPRESSION=... (complex Teradata property)
28259        self.write_keyword("BLOCKCOMPRESSION");
28260        self.write("=");
28261        if let Some(autotemp) = &e.autotemp {
28262            self.write_keyword("AUTOTEMP");
28263            self.write("(");
28264            self.generate_expression(autotemp)?;
28265            self.write(")");
28266        }
28267        if let Some(always) = &e.always {
28268            self.generate_expression(always)?;
28269        }
28270        if let Some(default) = &e.default {
28271            self.generate_expression(default)?;
28272        }
28273        if let Some(manual) = &e.manual {
28274            self.generate_expression(manual)?;
28275        }
28276        if let Some(never) = &e.never {
28277            self.generate_expression(never)?;
28278        }
28279        Ok(())
28280    }
28281
28282    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
28283        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
28284        self.write("((");
28285        self.generate_expression(&e.this)?;
28286        self.write(") ");
28287        self.write_keyword("AND");
28288        self.write(" (");
28289        self.generate_expression(&e.expression)?;
28290        self.write("))");
28291        Ok(())
28292    }
28293
28294    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
28295        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
28296        self.write("((");
28297        self.generate_expression(&e.this)?;
28298        self.write(") ");
28299        self.write_keyword("OR");
28300        self.write(" (");
28301        self.generate_expression(&e.expression)?;
28302        self.write("))");
28303        Ok(())
28304    }
28305
28306    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
28307        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
28308        self.write_keyword("BUILD");
28309        self.write_space();
28310        self.generate_expression(&e.this)?;
28311        Ok(())
28312    }
28313
28314    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
28315        // Byte string literal like B'...' or X'...'
28316        self.generate_expression(&e.this)?;
28317        Ok(())
28318    }
28319
28320    fn generate_case_specific_column_constraint(
28321        &mut self,
28322        e: &CaseSpecificColumnConstraint,
28323    ) -> Result<()> {
28324        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
28325        if e.not_.is_some() {
28326            self.write_keyword("NOT");
28327            self.write_space();
28328        }
28329        self.write_keyword("CASESPECIFIC");
28330        Ok(())
28331    }
28332
28333    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
28334        // Cast to string type (dialect-specific)
28335        self.write_keyword("CAST");
28336        self.write("(");
28337        self.generate_expression(&e.this)?;
28338        if self.config.dialect == Some(DialectType::ClickHouse) {
28339            // ClickHouse: CAST(expr, 'type_string')
28340            self.write(", ");
28341        } else {
28342            self.write_space();
28343            self.write_keyword("AS");
28344            self.write_space();
28345        }
28346        if let Some(to) = &e.to {
28347            self.generate_expression(to)?;
28348        }
28349        self.write(")");
28350        Ok(())
28351    }
28352
28353    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
28354        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
28355        // Python: f"CHANGES ({information}){at_before}{end}"
28356        self.write_keyword("CHANGES");
28357        self.write(" (");
28358        if let Some(information) = &e.information {
28359            self.write_keyword("INFORMATION");
28360            self.write(" => ");
28361            self.generate_expression(information)?;
28362        }
28363        self.write(")");
28364        // at_before and end are HistoricalData expressions that generate their own keywords
28365        if let Some(at_before) = &e.at_before {
28366            self.write(" ");
28367            self.generate_expression(at_before)?;
28368        }
28369        if let Some(end) = &e.end {
28370            self.write(" ");
28371            self.generate_expression(end)?;
28372        }
28373        Ok(())
28374    }
28375
28376    fn generate_character_set_column_constraint(
28377        &mut self,
28378        e: &CharacterSetColumnConstraint,
28379    ) -> Result<()> {
28380        // CHARACTER SET charset_name
28381        self.write_keyword("CHARACTER SET");
28382        self.write_space();
28383        self.generate_expression(&e.this)?;
28384        Ok(())
28385    }
28386
28387    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
28388        // [DEFAULT] CHARACTER SET=value
28389        if e.default.is_some() {
28390            self.write_keyword("DEFAULT");
28391            self.write_space();
28392        }
28393        self.write_keyword("CHARACTER SET");
28394        self.write("=");
28395        self.generate_expression(&e.this)?;
28396        Ok(())
28397    }
28398
28399    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
28400        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
28401        self.write_keyword("CHECK");
28402        self.write(" (");
28403        self.generate_expression(&e.this)?;
28404        self.write(")");
28405        if e.enforced.is_some() {
28406            self.write_space();
28407            self.write_keyword("ENFORCED");
28408        }
28409        Ok(())
28410    }
28411
28412    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
28413        // Python: return f"ASSUME ({self.sql(e, 'this')})"
28414        self.write_keyword("ASSUME");
28415        self.write(" (");
28416        self.generate_expression(&e.this)?;
28417        self.write(")");
28418        Ok(())
28419    }
28420
28421    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
28422        // CHECK_JSON(this)
28423        self.write_keyword("CHECK_JSON");
28424        self.write("(");
28425        self.generate_expression(&e.this)?;
28426        self.write(")");
28427        Ok(())
28428    }
28429
28430    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
28431        // CHECK_XML(this)
28432        self.write_keyword("CHECK_XML");
28433        self.write("(");
28434        self.generate_expression(&e.this)?;
28435        self.write(")");
28436        Ok(())
28437    }
28438
28439    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
28440        // CHECKSUM=[ON|OFF|DEFAULT]
28441        self.write_keyword("CHECKSUM");
28442        self.write("=");
28443        if e.on.is_some() {
28444            self.write_keyword("ON");
28445        } else if e.default.is_some() {
28446            self.write_keyword("DEFAULT");
28447        } else {
28448            self.write_keyword("OFF");
28449        }
28450        Ok(())
28451    }
28452
28453    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
28454        // Python: return f"{shallow}{keyword} {this}"
28455        if e.shallow.is_some() {
28456            self.write_keyword("SHALLOW");
28457            self.write_space();
28458        }
28459        if e.copy.is_some() {
28460            self.write_keyword("COPY");
28461        } else {
28462            self.write_keyword("CLONE");
28463        }
28464        self.write_space();
28465        self.generate_expression(&e.this)?;
28466        Ok(())
28467    }
28468
28469    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
28470        // CLUSTER BY (expressions)
28471        self.write_keyword("CLUSTER BY");
28472        self.write(" (");
28473        for (i, ord) in e.expressions.iter().enumerate() {
28474            if i > 0 {
28475                self.write(", ");
28476            }
28477            self.generate_ordered(ord)?;
28478        }
28479        self.write(")");
28480        Ok(())
28481    }
28482
28483    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
28484        // BigQuery table property: CLUSTER BY col1, col2
28485        self.write_keyword("CLUSTER BY");
28486        self.write_space();
28487        for (i, col) in e.columns.iter().enumerate() {
28488            if i > 0 {
28489                self.write(", ");
28490            }
28491            self.generate_identifier(col)?;
28492        }
28493        Ok(())
28494    }
28495
28496    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
28497        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
28498        self.write_keyword("CLUSTERED BY");
28499        self.write(" (");
28500        for (i, expr) in e.expressions.iter().enumerate() {
28501            if i > 0 {
28502                self.write(", ");
28503            }
28504            self.generate_expression(expr)?;
28505        }
28506        self.write(")");
28507        if let Some(sorted_by) = &e.sorted_by {
28508            self.write_space();
28509            self.write_keyword("SORTED BY");
28510            self.write(" (");
28511            // Unwrap Tuple to avoid double parentheses
28512            if let Expression::Tuple(t) = sorted_by.as_ref() {
28513                for (i, expr) in t.expressions.iter().enumerate() {
28514                    if i > 0 {
28515                        self.write(", ");
28516                    }
28517                    self.generate_expression(expr)?;
28518                }
28519            } else {
28520                self.generate_expression(sorted_by)?;
28521            }
28522            self.write(")");
28523        }
28524        if let Some(buckets) = &e.buckets {
28525            self.write_space();
28526            self.write_keyword("INTO");
28527            self.write_space();
28528            self.generate_expression(buckets)?;
28529            self.write_space();
28530            self.write_keyword("BUCKETS");
28531        }
28532        Ok(())
28533    }
28534
28535    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
28536        // [DEFAULT] COLLATE [=] value
28537        // BigQuery uses space: DEFAULT COLLATE 'en'
28538        // Others use equals: COLLATE='en'
28539        if e.default.is_some() {
28540            self.write_keyword("DEFAULT");
28541            self.write_space();
28542        }
28543        self.write_keyword("COLLATE");
28544        // BigQuery uses space between COLLATE and value
28545        match self.config.dialect {
28546            Some(DialectType::BigQuery) => self.write_space(),
28547            _ => self.write("="),
28548        }
28549        self.generate_expression(&e.this)?;
28550        Ok(())
28551    }
28552
28553    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
28554        // ColumnConstraint is an enum
28555        match e {
28556            ColumnConstraint::NotNull => {
28557                self.write_keyword("NOT NULL");
28558            }
28559            ColumnConstraint::Null => {
28560                self.write_keyword("NULL");
28561            }
28562            ColumnConstraint::Unique => {
28563                self.write_keyword("UNIQUE");
28564            }
28565            ColumnConstraint::PrimaryKey => {
28566                self.write_keyword("PRIMARY KEY");
28567            }
28568            ColumnConstraint::Default(expr) => {
28569                self.write_keyword("DEFAULT");
28570                self.write_space();
28571                self.generate_expression(expr)?;
28572            }
28573            ColumnConstraint::Check(expr) => {
28574                self.write_keyword("CHECK");
28575                self.write(" (");
28576                self.generate_expression(expr)?;
28577                self.write(")");
28578            }
28579            ColumnConstraint::References(fk_ref) => {
28580                if fk_ref.has_foreign_key_keywords {
28581                    self.write_keyword("FOREIGN KEY");
28582                    self.write_space();
28583                }
28584                self.write_keyword("REFERENCES");
28585                self.write_space();
28586                self.generate_table(&fk_ref.table)?;
28587                if !fk_ref.columns.is_empty() {
28588                    self.write(" (");
28589                    for (i, col) in fk_ref.columns.iter().enumerate() {
28590                        if i > 0 {
28591                            self.write(", ");
28592                        }
28593                        self.generate_identifier(col)?;
28594                    }
28595                    self.write(")");
28596                }
28597            }
28598            ColumnConstraint::GeneratedAsIdentity(gen) => {
28599                self.write_keyword("GENERATED");
28600                self.write_space();
28601                if gen.always {
28602                    self.write_keyword("ALWAYS");
28603                } else {
28604                    self.write_keyword("BY DEFAULT");
28605                    if gen.on_null {
28606                        self.write_space();
28607                        self.write_keyword("ON NULL");
28608                    }
28609                }
28610                self.write_space();
28611                self.write_keyword("AS IDENTITY");
28612            }
28613            ColumnConstraint::Collate(collation) => {
28614                self.write_keyword("COLLATE");
28615                self.write_space();
28616                self.generate_identifier(collation)?;
28617            }
28618            ColumnConstraint::Comment(comment) => {
28619                self.write_keyword("COMMENT");
28620                self.write(" '");
28621                self.write(comment);
28622                self.write("'");
28623            }
28624            ColumnConstraint::ComputedColumn(cc) => {
28625                self.generate_computed_column_inline(cc)?;
28626            }
28627            ColumnConstraint::GeneratedAsRow(gar) => {
28628                self.generate_generated_as_row_inline(gar)?;
28629            }
28630            ColumnConstraint::Tags(tags) => {
28631                self.write_keyword("TAG");
28632                self.write(" (");
28633                for (i, expr) in tags.expressions.iter().enumerate() {
28634                    if i > 0 {
28635                        self.write(", ");
28636                    }
28637                    self.generate_expression(expr)?;
28638                }
28639                self.write(")");
28640            }
28641            ColumnConstraint::Path(path_expr) => {
28642                self.write_keyword("PATH");
28643                self.write_space();
28644                self.generate_expression(path_expr)?;
28645            }
28646        }
28647        Ok(())
28648    }
28649
28650    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
28651        // ColumnPosition is an enum
28652        match e {
28653            ColumnPosition::First => {
28654                self.write_keyword("FIRST");
28655            }
28656            ColumnPosition::After(ident) => {
28657                self.write_keyword("AFTER");
28658                self.write_space();
28659                self.generate_identifier(ident)?;
28660            }
28661        }
28662        Ok(())
28663    }
28664
28665    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
28666        // column(prefix)
28667        self.generate_expression(&e.this)?;
28668        self.write("(");
28669        self.generate_expression(&e.expression)?;
28670        self.write(")");
28671        Ok(())
28672    }
28673
28674    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
28675        // If unpack is true, this came from * COLUMNS(pattern)
28676        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
28677        if let Some(ref unpack) = e.unpack {
28678            if let Expression::Boolean(b) = unpack.as_ref() {
28679                if b.value {
28680                    self.write("*");
28681                }
28682            }
28683        }
28684        self.write_keyword("COLUMNS");
28685        self.write("(");
28686        self.generate_expression(&e.this)?;
28687        self.write(")");
28688        Ok(())
28689    }
28690
28691    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
28692        // Combined aggregate: FUNC(args) combined
28693        self.generate_expression(&e.this)?;
28694        self.write("(");
28695        for (i, expr) in e.expressions.iter().enumerate() {
28696            if i > 0 {
28697                self.write(", ");
28698            }
28699            self.generate_expression(expr)?;
28700        }
28701        self.write(")");
28702        Ok(())
28703    }
28704
28705    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
28706        // Combined parameterized aggregate: FUNC(params)(expressions)
28707        self.generate_expression(&e.this)?;
28708        self.write("(");
28709        for (i, param) in e.params.iter().enumerate() {
28710            if i > 0 {
28711                self.write(", ");
28712            }
28713            self.generate_expression(param)?;
28714        }
28715        self.write(")(");
28716        for (i, expr) in e.expressions.iter().enumerate() {
28717            if i > 0 {
28718                self.write(", ");
28719            }
28720            self.generate_expression(expr)?;
28721        }
28722        self.write(")");
28723        Ok(())
28724    }
28725
28726    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
28727        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
28728        self.write_keyword("COMMIT");
28729
28730        // TSQL always uses COMMIT TRANSACTION
28731        if e.this.is_none()
28732            && matches!(
28733                self.config.dialect,
28734                Some(DialectType::TSQL) | Some(DialectType::Fabric)
28735            )
28736        {
28737            self.write_space();
28738            self.write_keyword("TRANSACTION");
28739        }
28740
28741        // Check if this has TRANSACTION keyword or transaction name
28742        if let Some(this) = &e.this {
28743            // Check if it's just the "TRANSACTION" marker or an actual transaction name
28744            let is_transaction_marker = matches!(
28745                this.as_ref(),
28746                Expression::Identifier(id) if id.name == "TRANSACTION"
28747            );
28748
28749            self.write_space();
28750            self.write_keyword("TRANSACTION");
28751
28752            // If it's a real transaction name, output it
28753            if !is_transaction_marker {
28754                self.write_space();
28755                self.generate_expression(this)?;
28756            }
28757        }
28758
28759        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
28760        if let Some(durability) = &e.durability {
28761            self.write_space();
28762            self.write_keyword("WITH");
28763            self.write(" (");
28764            self.write_keyword("DELAYED_DURABILITY");
28765            self.write(" = ");
28766            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
28767                self.write_keyword("ON");
28768            } else {
28769                self.write_keyword("OFF");
28770            }
28771            self.write(")");
28772        }
28773
28774        // Output AND [NO] CHAIN
28775        if let Some(chain) = &e.chain {
28776            self.write_space();
28777            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
28778                self.write_keyword("AND NO CHAIN");
28779            } else {
28780                self.write_keyword("AND CHAIN");
28781            }
28782        }
28783        Ok(())
28784    }
28785
28786    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
28787        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
28788        self.write("[");
28789        self.generate_expression(&e.this)?;
28790        self.write_space();
28791        self.write_keyword("FOR");
28792        self.write_space();
28793        self.generate_expression(&e.expression)?;
28794        // Handle optional position variable (for enumerate-like syntax)
28795        if let Some(pos) = &e.position {
28796            self.write(", ");
28797            self.generate_expression(pos)?;
28798        }
28799        if let Some(iterator) = &e.iterator {
28800            self.write_space();
28801            self.write_keyword("IN");
28802            self.write_space();
28803            self.generate_expression(iterator)?;
28804        }
28805        if let Some(condition) = &e.condition {
28806            self.write_space();
28807            self.write_keyword("IF");
28808            self.write_space();
28809            self.generate_expression(condition)?;
28810        }
28811        self.write("]");
28812        Ok(())
28813    }
28814
28815    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
28816        // COMPRESS(this[, method])
28817        self.write_keyword("COMPRESS");
28818        self.write("(");
28819        self.generate_expression(&e.this)?;
28820        if let Some(method) = &e.method {
28821            self.write(", '");
28822            self.write(method);
28823            self.write("'");
28824        }
28825        self.write(")");
28826        Ok(())
28827    }
28828
28829    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
28830        // Python: return f"COMPRESS {this}"
28831        self.write_keyword("COMPRESS");
28832        if let Some(this) = &e.this {
28833            self.write_space();
28834            self.generate_expression(this)?;
28835        }
28836        Ok(())
28837    }
28838
28839    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
28840        // Python: return f"AS {this}{persisted}"
28841        self.write_keyword("AS");
28842        self.write_space();
28843        self.generate_expression(&e.this)?;
28844        if e.not_null.is_some() {
28845            self.write_space();
28846            self.write_keyword("PERSISTED NOT NULL");
28847        } else if e.persisted.is_some() {
28848            self.write_space();
28849            self.write_keyword("PERSISTED");
28850        }
28851        Ok(())
28852    }
28853
28854    /// Generate a ComputedColumn constraint inline within a column definition.
28855    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
28856    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
28857    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
28858        let computed_expr = if matches!(
28859            self.config.dialect,
28860            Some(DialectType::TSQL) | Some(DialectType::Fabric)
28861        ) {
28862            match &*cc.expression {
28863                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
28864                {
28865                    let wrapped = Expression::Cast(Box::new(Cast {
28866                        this: y.this.clone(),
28867                        to: DataType::Date,
28868                        trailing_comments: Vec::new(),
28869                        double_colon_syntax: false,
28870                        format: None,
28871                        default: None,
28872                        inferred_type: None,
28873                    }));
28874                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
28875                }
28876                Expression::Function(f)
28877                    if f.name.eq_ignore_ascii_case("YEAR")
28878                        && f.args.len() == 1
28879                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
28880                {
28881                    let wrapped = Expression::Cast(Box::new(Cast {
28882                        this: f.args[0].clone(),
28883                        to: DataType::Date,
28884                        trailing_comments: Vec::new(),
28885                        double_colon_syntax: false,
28886                        format: None,
28887                        default: None,
28888                        inferred_type: None,
28889                    }));
28890                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
28891                }
28892                _ => *cc.expression.clone(),
28893            }
28894        } else {
28895            *cc.expression.clone()
28896        };
28897
28898        match cc.persistence_kind.as_deref() {
28899            Some("STORED") | Some("VIRTUAL") => {
28900                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
28901                self.write_keyword("GENERATED ALWAYS AS");
28902                self.write(" (");
28903                self.generate_expression(&computed_expr)?;
28904                self.write(")");
28905                self.write_space();
28906                if cc.persisted {
28907                    self.write_keyword("STORED");
28908                } else {
28909                    self.write_keyword("VIRTUAL");
28910                }
28911            }
28912            Some("PERSISTED") => {
28913                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
28914                self.write_keyword("AS");
28915                self.write(" (");
28916                self.generate_expression(&computed_expr)?;
28917                self.write(")");
28918                self.write_space();
28919                self.write_keyword("PERSISTED");
28920                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
28921                if let Some(ref dt) = cc.data_type {
28922                    self.write_space();
28923                    self.generate_data_type(dt)?;
28924                }
28925                if cc.not_null {
28926                    self.write_space();
28927                    self.write_keyword("NOT NULL");
28928                }
28929            }
28930            _ => {
28931                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
28932                // TSQL computed column without PERSISTED: AS (expr)
28933                if matches!(
28934                    self.config.dialect,
28935                    Some(DialectType::Spark)
28936                        | Some(DialectType::Databricks)
28937                        | Some(DialectType::Hive)
28938                ) {
28939                    self.write_keyword("GENERATED ALWAYS AS");
28940                    self.write(" (");
28941                    self.generate_expression(&computed_expr)?;
28942                    self.write(")");
28943                } else if matches!(
28944                    self.config.dialect,
28945                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
28946                ) {
28947                    self.write_keyword("AS");
28948                    let omit_parens = matches!(computed_expr, Expression::Year(_))
28949                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
28950                    if omit_parens {
28951                        self.write_space();
28952                        self.generate_expression(&computed_expr)?;
28953                    } else {
28954                        self.write(" (");
28955                        self.generate_expression(&computed_expr)?;
28956                        self.write(")");
28957                    }
28958                } else {
28959                    self.write_keyword("AS");
28960                    self.write(" (");
28961                    self.generate_expression(&computed_expr)?;
28962                    self.write(")");
28963                }
28964            }
28965        }
28966        Ok(())
28967    }
28968
28969    /// Generate a GeneratedAsRow constraint inline within a column definition.
28970    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28971    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
28972        self.write_keyword("GENERATED ALWAYS AS ROW ");
28973        if gar.start {
28974            self.write_keyword("START");
28975        } else {
28976            self.write_keyword("END");
28977        }
28978        if gar.hidden {
28979            self.write_space();
28980            self.write_keyword("HIDDEN");
28981        }
28982        Ok(())
28983    }
28984
28985    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
28986    fn generate_system_versioning_content(
28987        &mut self,
28988        e: &WithSystemVersioningProperty,
28989    ) -> Result<()> {
28990        let mut parts = Vec::new();
28991
28992        if let Some(this) = &e.this {
28993            let mut s = String::from("HISTORY_TABLE=");
28994            let mut gen = Generator::with_arc_config(self.config.clone());
28995            gen.generate_expression(this)?;
28996            s.push_str(&gen.output);
28997            parts.push(s);
28998        }
28999
29000        if let Some(data_consistency) = &e.data_consistency {
29001            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
29002            let mut gen = Generator::with_arc_config(self.config.clone());
29003            gen.generate_expression(data_consistency)?;
29004            s.push_str(&gen.output);
29005            parts.push(s);
29006        }
29007
29008        if let Some(retention_period) = &e.retention_period {
29009            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
29010            let mut gen = Generator::with_arc_config(self.config.clone());
29011            gen.generate_expression(retention_period)?;
29012            s.push_str(&gen.output);
29013            parts.push(s);
29014        }
29015
29016        self.write_keyword("SYSTEM_VERSIONING");
29017        self.write("=");
29018
29019        if !parts.is_empty() {
29020            self.write_keyword("ON");
29021            self.write("(");
29022            self.write(&parts.join(", "));
29023            self.write(")");
29024        } else if e.on.is_some() {
29025            self.write_keyword("ON");
29026        } else {
29027            self.write_keyword("OFF");
29028        }
29029
29030        Ok(())
29031    }
29032
29033    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
29034        // Conditional INSERT for multi-table inserts
29035        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
29036        if e.else_.is_some() {
29037            self.write_keyword("ELSE");
29038            self.write_space();
29039        } else if let Some(expression) = &e.expression {
29040            self.write_keyword("WHEN");
29041            self.write_space();
29042            self.generate_expression(expression)?;
29043            self.write_space();
29044            self.write_keyword("THEN");
29045            self.write_space();
29046        }
29047
29048        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
29049        // without the "INSERT " prefix
29050        if let Expression::Insert(insert) = e.this.as_ref() {
29051            self.write_keyword("INTO");
29052            self.write_space();
29053            self.generate_table(&insert.table)?;
29054
29055            // Optional column list
29056            if !insert.columns.is_empty() {
29057                self.write(" (");
29058                for (i, col) in insert.columns.iter().enumerate() {
29059                    if i > 0 {
29060                        self.write(", ");
29061                    }
29062                    self.generate_identifier(col)?;
29063                }
29064                self.write(")");
29065            }
29066
29067            // Optional VALUES clause
29068            if !insert.values.is_empty() {
29069                self.write_space();
29070                self.write_keyword("VALUES");
29071                for (row_idx, row) in insert.values.iter().enumerate() {
29072                    if row_idx > 0 {
29073                        self.write(", ");
29074                    }
29075                    self.write(" (");
29076                    for (i, val) in row.iter().enumerate() {
29077                        if i > 0 {
29078                            self.write(", ");
29079                        }
29080                        self.generate_expression(val)?;
29081                    }
29082                    self.write(")");
29083                }
29084            }
29085        } else {
29086            // Fallback for non-Insert expressions
29087            self.generate_expression(&e.this)?;
29088        }
29089        Ok(())
29090    }
29091
29092    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
29093        // Python: return f"CONSTRAINT {this} {expressions}"
29094        self.write_keyword("CONSTRAINT");
29095        self.write_space();
29096        self.generate_expression(&e.this)?;
29097        if !e.expressions.is_empty() {
29098            self.write_space();
29099            for (i, expr) in e.expressions.iter().enumerate() {
29100                if i > 0 {
29101                    self.write_space();
29102                }
29103                self.generate_expression(expr)?;
29104            }
29105        }
29106        Ok(())
29107    }
29108
29109    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
29110        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
29111        self.write_keyword("CONVERT_TIMEZONE");
29112        self.write("(");
29113        let mut first = true;
29114        if let Some(source_tz) = &e.source_tz {
29115            self.generate_expression(source_tz)?;
29116            first = false;
29117        }
29118        if let Some(target_tz) = &e.target_tz {
29119            if !first {
29120                self.write(", ");
29121            }
29122            self.generate_expression(target_tz)?;
29123            first = false;
29124        }
29125        if let Some(timestamp) = &e.timestamp {
29126            if !first {
29127                self.write(", ");
29128            }
29129            self.generate_expression(timestamp)?;
29130        }
29131        self.write(")");
29132        Ok(())
29133    }
29134
29135    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
29136        // CONVERT(this USING dest)
29137        self.write_keyword("CONVERT");
29138        self.write("(");
29139        self.generate_expression(&e.this)?;
29140        if let Some(dest) = &e.dest {
29141            self.write_space();
29142            self.write_keyword("USING");
29143            self.write_space();
29144            self.generate_expression(dest)?;
29145        }
29146        self.write(")");
29147        Ok(())
29148    }
29149
29150    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
29151        self.write_keyword("COPY");
29152        if e.is_into {
29153            self.write_space();
29154            self.write_keyword("INTO");
29155        }
29156        self.write_space();
29157
29158        // Generate target table or query (or stage for COPY INTO @stage)
29159        if let Expression::Literal(lit) = &e.this {
29160            if let Literal::String(s) = lit.as_ref() {
29161                if s.starts_with('@') {
29162                    self.write(s);
29163                } else {
29164                    self.generate_expression(&e.this)?;
29165                }
29166            }
29167        } else {
29168            self.generate_expression(&e.this)?;
29169        }
29170
29171        // FROM or TO based on kind
29172        if e.kind {
29173            // kind=true means FROM (loading into table)
29174            if self.config.pretty {
29175                self.write_newline();
29176            } else {
29177                self.write_space();
29178            }
29179            self.write_keyword("FROM");
29180            self.write_space();
29181        } else if !e.files.is_empty() {
29182            // kind=false means TO (exporting)
29183            if self.config.pretty {
29184                self.write_newline();
29185            } else {
29186                self.write_space();
29187            }
29188            self.write_keyword("TO");
29189            self.write_space();
29190        }
29191
29192        // Generate source/destination files
29193        for (i, file) in e.files.iter().enumerate() {
29194            if i > 0 {
29195                self.write_space();
29196            }
29197            // For stage references (strings starting with @), output without quotes
29198            if let Expression::Literal(lit) = file {
29199                if let Literal::String(s) = lit.as_ref() {
29200                    if s.starts_with('@') {
29201                        self.write(s);
29202                    } else {
29203                        self.generate_expression(file)?;
29204                    }
29205                }
29206            } else if let Expression::Identifier(id) = file {
29207                // Backtick-quoted file path (Databricks style: `s3://link`)
29208                if id.quoted {
29209                    self.write("`");
29210                    self.write(&id.name);
29211                    self.write("`");
29212                } else {
29213                    self.generate_expression(file)?;
29214                }
29215            } else {
29216                self.generate_expression(file)?;
29217            }
29218        }
29219
29220        // Generate credentials if present (Snowflake style - not wrapped in WITH)
29221        if !e.with_wrapped {
29222            if let Some(ref creds) = e.credentials {
29223                if let Some(ref storage) = creds.storage {
29224                    if self.config.pretty {
29225                        self.write_newline();
29226                    } else {
29227                        self.write_space();
29228                    }
29229                    self.write_keyword("STORAGE_INTEGRATION");
29230                    self.write(" = ");
29231                    self.write(storage);
29232                }
29233                if creds.credentials.is_empty() {
29234                    // Empty credentials: CREDENTIALS = ()
29235                    if self.config.pretty {
29236                        self.write_newline();
29237                    } else {
29238                        self.write_space();
29239                    }
29240                    self.write_keyword("CREDENTIALS");
29241                    self.write(" = ()");
29242                } else {
29243                    if self.config.pretty {
29244                        self.write_newline();
29245                    } else {
29246                        self.write_space();
29247                    }
29248                    self.write_keyword("CREDENTIALS");
29249                    // Check if this is Redshift-style (single value with empty key)
29250                    // vs Snowflake-style (multiple key=value pairs)
29251                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
29252                        // Redshift style: CREDENTIALS 'value'
29253                        self.write(" '");
29254                        self.write(&creds.credentials[0].1);
29255                        self.write("'");
29256                    } else {
29257                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
29258                        self.write(" = (");
29259                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
29260                            if i > 0 {
29261                                self.write_space();
29262                            }
29263                            self.write(k);
29264                            self.write("='");
29265                            self.write(v);
29266                            self.write("'");
29267                        }
29268                        self.write(")");
29269                    }
29270                }
29271                if let Some(ref encryption) = creds.encryption {
29272                    self.write_space();
29273                    self.write_keyword("ENCRYPTION");
29274                    self.write(" = ");
29275                    self.write(encryption);
29276                }
29277            }
29278        }
29279
29280        // Generate parameters
29281        if !e.params.is_empty() {
29282            if e.with_wrapped {
29283                // DuckDB/PostgreSQL/TSQL WITH (...) format
29284                self.write_space();
29285                self.write_keyword("WITH");
29286                self.write(" (");
29287                for (i, param) in e.params.iter().enumerate() {
29288                    if i > 0 {
29289                        self.write(", ");
29290                    }
29291                    self.generate_copy_param_with_format(param)?;
29292                }
29293                self.write(")");
29294            } else {
29295                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
29296                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
29297                // For Snowflake: KEY = VALUE
29298                for param in &e.params {
29299                    if self.config.pretty {
29300                        self.write_newline();
29301                    } else {
29302                        self.write_space();
29303                    }
29304                    // Preserve original case of parameter name (important for Redshift COPY options)
29305                    self.write(&param.name);
29306                    if let Some(ref value) = param.value {
29307                        // Use = only if it was present in the original (param.eq)
29308                        if param.eq {
29309                            self.write(" = ");
29310                        } else {
29311                            self.write(" ");
29312                        }
29313                        if !param.values.is_empty() {
29314                            self.write("(");
29315                            for (i, v) in param.values.iter().enumerate() {
29316                                if i > 0 {
29317                                    self.write_space();
29318                                }
29319                                self.generate_copy_nested_param(v)?;
29320                            }
29321                            self.write(")");
29322                        } else {
29323                            // For COPY parameter values, output identifiers without quoting
29324                            self.generate_copy_param_value(value)?;
29325                        }
29326                    } else if !param.values.is_empty() {
29327                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
29328                        if param.eq {
29329                            self.write(" = (");
29330                        } else {
29331                            self.write(" (");
29332                        }
29333                        // Determine separator for values inside parentheses:
29334                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
29335                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
29336                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
29337                        let is_key_value_pairs = param
29338                            .values
29339                            .first()
29340                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
29341                        let sep = if is_key_value_pairs && param.eq {
29342                            " "
29343                        } else {
29344                            ", "
29345                        };
29346                        for (i, v) in param.values.iter().enumerate() {
29347                            if i > 0 {
29348                                self.write(sep);
29349                            }
29350                            self.generate_copy_nested_param(v)?;
29351                        }
29352                        self.write(")");
29353                    }
29354                }
29355            }
29356        }
29357
29358        Ok(())
29359    }
29360
29361    /// Generate a COPY parameter in WITH (...) format
29362    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
29363    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
29364        self.write_keyword(&param.name);
29365        if !param.values.is_empty() {
29366            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
29367            self.write(" = (");
29368            for (i, v) in param.values.iter().enumerate() {
29369                if i > 0 {
29370                    self.write(", ");
29371                }
29372                self.generate_copy_nested_param(v)?;
29373            }
29374            self.write(")");
29375        } else if let Some(ref value) = param.value {
29376            if param.eq {
29377                self.write(" = ");
29378            } else {
29379                self.write(" ");
29380            }
29381            self.generate_expression(value)?;
29382        }
29383        Ok(())
29384    }
29385
29386    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
29387    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
29388        match expr {
29389            Expression::Eq(eq) => {
29390                // Generate key
29391                match &eq.left {
29392                    Expression::Column(c) => self.write(&c.name.name),
29393                    _ => self.generate_expression(&eq.left)?,
29394                }
29395                self.write("=");
29396                // Generate value
29397                match &eq.right {
29398                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29399                        let Literal::String(s) = lit.as_ref() else {
29400                            unreachable!()
29401                        };
29402                        self.write("'");
29403                        self.write(s);
29404                        self.write("'");
29405                    }
29406                    Expression::Tuple(t) => {
29407                        // For lists like NULL_IF=('', 'str1')
29408                        self.write("(");
29409                        if self.config.pretty {
29410                            self.write_newline();
29411                            self.indent_level += 1;
29412                            for (i, item) in t.expressions.iter().enumerate() {
29413                                if i > 0 {
29414                                    self.write(", ");
29415                                }
29416                                self.write_indent();
29417                                self.generate_expression(item)?;
29418                            }
29419                            self.write_newline();
29420                            self.indent_level -= 1;
29421                        } else {
29422                            for (i, item) in t.expressions.iter().enumerate() {
29423                                if i > 0 {
29424                                    self.write(", ");
29425                                }
29426                                self.generate_expression(item)?;
29427                            }
29428                        }
29429                        self.write(")");
29430                    }
29431                    _ => self.generate_expression(&eq.right)?,
29432                }
29433                Ok(())
29434            }
29435            Expression::Column(c) => {
29436                // Standalone keyword like COMPRESSION
29437                self.write(&c.name.name);
29438                Ok(())
29439            }
29440            _ => self.generate_expression(expr),
29441        }
29442    }
29443
29444    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
29445    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
29446    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
29447        match expr {
29448            Expression::Column(c) => {
29449                // Output identifier, preserving quotes if originally quoted
29450                if c.name.quoted {
29451                    self.write("\"");
29452                    self.write(&c.name.name);
29453                    self.write("\"");
29454                } else {
29455                    self.write(&c.name.name);
29456                }
29457                Ok(())
29458            }
29459            Expression::Identifier(id) => {
29460                // Output identifier, preserving quotes if originally quoted
29461                if id.quoted {
29462                    self.write("\"");
29463                    self.write(&id.name);
29464                    self.write("\"");
29465                } else {
29466                    self.write(&id.name);
29467                }
29468                Ok(())
29469            }
29470            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29471                let Literal::String(s) = lit.as_ref() else {
29472                    unreachable!()
29473                };
29474                // Output string with quotes
29475                self.write("'");
29476                self.write(s);
29477                self.write("'");
29478                Ok(())
29479            }
29480            _ => self.generate_expression(expr),
29481        }
29482    }
29483
29484    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
29485        self.write_keyword(&e.name);
29486        if let Some(ref value) = e.value {
29487            if e.eq {
29488                self.write(" = ");
29489            } else {
29490                self.write(" ");
29491            }
29492            self.generate_expression(value)?;
29493        }
29494        if !e.values.is_empty() {
29495            if e.eq {
29496                self.write(" = ");
29497            } else {
29498                self.write(" ");
29499            }
29500            self.write("(");
29501            for (i, v) in e.values.iter().enumerate() {
29502                if i > 0 {
29503                    self.write(", ");
29504                }
29505                self.generate_expression(v)?;
29506            }
29507            self.write(")");
29508        }
29509        Ok(())
29510    }
29511
29512    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
29513        // CORR(this, expression)
29514        self.write_keyword("CORR");
29515        self.write("(");
29516        self.generate_expression(&e.this)?;
29517        self.write(", ");
29518        self.generate_expression(&e.expression)?;
29519        self.write(")");
29520        Ok(())
29521    }
29522
29523    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
29524        // COSINE_DISTANCE(this, expression)
29525        self.write_keyword("COSINE_DISTANCE");
29526        self.write("(");
29527        self.generate_expression(&e.this)?;
29528        self.write(", ");
29529        self.generate_expression(&e.expression)?;
29530        self.write(")");
29531        Ok(())
29532    }
29533
29534    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
29535        // COVAR_POP(this, expression)
29536        self.write_keyword("COVAR_POP");
29537        self.write("(");
29538        self.generate_expression(&e.this)?;
29539        self.write(", ");
29540        self.generate_expression(&e.expression)?;
29541        self.write(")");
29542        Ok(())
29543    }
29544
29545    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
29546        // COVAR_SAMP(this, expression)
29547        self.write_keyword("COVAR_SAMP");
29548        self.write("(");
29549        self.generate_expression(&e.this)?;
29550        self.write(", ");
29551        self.generate_expression(&e.expression)?;
29552        self.write(")");
29553        Ok(())
29554    }
29555
29556    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
29557        // CREDENTIALS (key1='value1', key2='value2')
29558        self.write_keyword("CREDENTIALS");
29559        self.write(" (");
29560        for (i, (key, value)) in e.credentials.iter().enumerate() {
29561            if i > 0 {
29562                self.write(", ");
29563            }
29564            self.write(key);
29565            self.write("='");
29566            self.write(value);
29567            self.write("'");
29568        }
29569        self.write(")");
29570        Ok(())
29571    }
29572
29573    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
29574        // CREDENTIALS=(expressions)
29575        self.write_keyword("CREDENTIALS");
29576        self.write("=(");
29577        for (i, expr) in e.expressions.iter().enumerate() {
29578            if i > 0 {
29579                self.write(", ");
29580            }
29581            self.generate_expression(expr)?;
29582        }
29583        self.write(")");
29584        Ok(())
29585    }
29586
29587    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
29588        use crate::dialects::DialectType;
29589
29590        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
29591        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
29592        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
29593            self.generate_expression(&e.this)?;
29594            self.write_space();
29595            self.write_keyword("AS");
29596            self.write_space();
29597            self.generate_identifier(&e.alias)?;
29598            return Ok(());
29599        }
29600        self.write(&e.alias.name);
29601
29602        // BigQuery doesn't support column aliases in CTE definitions
29603        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
29604
29605        if !e.columns.is_empty() && !skip_cte_columns {
29606            self.write("(");
29607            for (i, col) in e.columns.iter().enumerate() {
29608                if i > 0 {
29609                    self.write(", ");
29610                }
29611                self.write(&col.name);
29612            }
29613            self.write(")");
29614        }
29615        // USING KEY (columns) for DuckDB recursive CTEs
29616        if !e.key_expressions.is_empty() {
29617            self.write_space();
29618            self.write_keyword("USING KEY");
29619            self.write(" (");
29620            for (i, key) in e.key_expressions.iter().enumerate() {
29621                if i > 0 {
29622                    self.write(", ");
29623                }
29624                self.write(&key.name);
29625            }
29626            self.write(")");
29627        }
29628        self.write_space();
29629        self.write_keyword("AS");
29630        self.write_space();
29631        if let Some(materialized) = e.materialized {
29632            if materialized {
29633                self.write_keyword("MATERIALIZED");
29634            } else {
29635                self.write_keyword("NOT MATERIALIZED");
29636            }
29637            self.write_space();
29638        }
29639        self.write("(");
29640        self.generate_expression(&e.this)?;
29641        self.write(")");
29642        Ok(())
29643    }
29644
29645    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
29646        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
29647        if e.expressions.is_empty() {
29648            self.write_keyword("WITH CUBE");
29649        } else {
29650            self.write_keyword("CUBE");
29651            self.write("(");
29652            for (i, expr) in e.expressions.iter().enumerate() {
29653                if i > 0 {
29654                    self.write(", ");
29655                }
29656                self.generate_expression(expr)?;
29657            }
29658            self.write(")");
29659        }
29660        Ok(())
29661    }
29662
29663    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
29664        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
29665        self.write_keyword("CURRENT_DATETIME");
29666        if let Some(this) = &e.this {
29667            self.write("(");
29668            self.generate_expression(this)?;
29669            self.write(")");
29670        }
29671        Ok(())
29672    }
29673
29674    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
29675        // CURRENT_SCHEMA - no arguments
29676        self.write_keyword("CURRENT_SCHEMA");
29677        Ok(())
29678    }
29679
29680    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
29681        // CURRENT_SCHEMAS(include_implicit)
29682        self.write_keyword("CURRENT_SCHEMAS");
29683        self.write("(");
29684        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
29685        if !matches!(
29686            self.config.dialect,
29687            Some(crate::dialects::DialectType::Snowflake)
29688        ) {
29689            if let Some(this) = &e.this {
29690                self.generate_expression(this)?;
29691            }
29692        }
29693        self.write(")");
29694        Ok(())
29695    }
29696
29697    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
29698        // CURRENT_USER or CURRENT_USER()
29699        self.write_keyword("CURRENT_USER");
29700        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
29701        let needs_parens = e.this.is_some()
29702            || matches!(
29703                self.config.dialect,
29704                Some(DialectType::Snowflake)
29705                    | Some(DialectType::Spark)
29706                    | Some(DialectType::Hive)
29707                    | Some(DialectType::DuckDB)
29708                    | Some(DialectType::BigQuery)
29709                    | Some(DialectType::MySQL)
29710                    | Some(DialectType::Databricks)
29711            );
29712        if needs_parens {
29713            self.write("()");
29714        }
29715        Ok(())
29716    }
29717
29718    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
29719        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
29720        if self.config.dialect == Some(DialectType::Solr) {
29721            self.generate_expression(&e.this)?;
29722            self.write(" ");
29723            self.write_keyword("OR");
29724            self.write(" ");
29725            self.generate_expression(&e.expression)?;
29726        } else if self.config.dialect == Some(DialectType::MySQL) {
29727            self.generate_mysql_concat_from_dpipe(e)?;
29728        } else {
29729            // String concatenation: this || expression
29730            self.generate_expression(&e.this)?;
29731            self.write(" || ");
29732            self.generate_expression(&e.expression)?;
29733        }
29734        Ok(())
29735    }
29736
29737    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
29738        // DATABLOCKSIZE=... (Teradata)
29739        self.write_keyword("DATABLOCKSIZE");
29740        self.write("=");
29741        if let Some(size) = e.size {
29742            self.write(&size.to_string());
29743            if let Some(units) = &e.units {
29744                self.write_space();
29745                self.generate_expression(units)?;
29746            }
29747        } else if e.minimum.is_some() {
29748            self.write_keyword("MINIMUM");
29749        } else if e.maximum.is_some() {
29750            self.write_keyword("MAXIMUM");
29751        } else if e.default.is_some() {
29752            self.write_keyword("DEFAULT");
29753        }
29754        Ok(())
29755    }
29756
29757    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
29758        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
29759        self.write_keyword("DATA_DELETION");
29760        self.write("=");
29761
29762        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
29763        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
29764
29765        if is_on {
29766            self.write_keyword("ON");
29767            if has_options {
29768                self.write("(");
29769                let mut first = true;
29770                if let Some(filter_column) = &e.filter_column {
29771                    self.write_keyword("FILTER_COLUMN");
29772                    self.write("=");
29773                    self.generate_expression(filter_column)?;
29774                    first = false;
29775                }
29776                if let Some(retention_period) = &e.retention_period {
29777                    if !first {
29778                        self.write(", ");
29779                    }
29780                    self.write_keyword("RETENTION_PERIOD");
29781                    self.write("=");
29782                    self.generate_expression(retention_period)?;
29783                }
29784                self.write(")");
29785            }
29786        } else {
29787            self.write_keyword("OFF");
29788        }
29789        Ok(())
29790    }
29791
29792    /// Generate a Date function expression
29793    /// For Exasol: {d'value'} -> TO_DATE('value')
29794    /// For other dialects: DATE('value')
29795    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
29796        use crate::dialects::DialectType;
29797        use crate::expressions::Literal;
29798
29799        match self.config.dialect {
29800            // Exasol uses TO_DATE for Date expressions
29801            Some(DialectType::Exasol) => {
29802                self.write_keyword("TO_DATE");
29803                self.write("(");
29804                // Extract the string value from the expression if it's a string literal
29805                match &e.this {
29806                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29807                        let Literal::String(s) = lit.as_ref() else {
29808                            unreachable!()
29809                        };
29810                        self.write("'");
29811                        self.write(s);
29812                        self.write("'");
29813                    }
29814                    _ => {
29815                        self.generate_expression(&e.this)?;
29816                    }
29817                }
29818                self.write(")");
29819            }
29820            // Standard: DATE(value)
29821            _ => {
29822                self.write_keyword("DATE");
29823                self.write("(");
29824                self.generate_expression(&e.this)?;
29825                self.write(")");
29826            }
29827        }
29828        Ok(())
29829    }
29830
29831    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
29832        // DATE_BIN(interval, timestamp[, origin])
29833        self.write_keyword("DATE_BIN");
29834        self.write("(");
29835        self.generate_expression(&e.this)?;
29836        self.write(", ");
29837        self.generate_expression(&e.expression)?;
29838        if let Some(origin) = &e.origin {
29839            self.write(", ");
29840            self.generate_expression(origin)?;
29841        }
29842        self.write(")");
29843        Ok(())
29844    }
29845
29846    fn generate_date_format_column_constraint(
29847        &mut self,
29848        e: &DateFormatColumnConstraint,
29849    ) -> Result<()> {
29850        // FORMAT 'format_string' (Teradata)
29851        self.write_keyword("FORMAT");
29852        self.write_space();
29853        self.generate_expression(&e.this)?;
29854        Ok(())
29855    }
29856
29857    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
29858        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
29859        self.write_keyword("DATE_FROM_PARTS");
29860        self.write("(");
29861        let mut first = true;
29862        if let Some(year) = &e.year {
29863            self.generate_expression(year)?;
29864            first = false;
29865        }
29866        if let Some(month) = &e.month {
29867            if !first {
29868                self.write(", ");
29869            }
29870            self.generate_expression(month)?;
29871            first = false;
29872        }
29873        if let Some(day) = &e.day {
29874            if !first {
29875                self.write(", ");
29876            }
29877            self.generate_expression(day)?;
29878        }
29879        self.write(")");
29880        Ok(())
29881    }
29882
29883    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
29884        // DATETIME(this) or DATETIME(this, expression)
29885        self.write_keyword("DATETIME");
29886        self.write("(");
29887        self.generate_expression(&e.this)?;
29888        if let Some(expr) = &e.expression {
29889            self.write(", ");
29890            self.generate_expression(expr)?;
29891        }
29892        self.write(")");
29893        Ok(())
29894    }
29895
29896    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
29897        // DATETIME_ADD(this, expression, unit)
29898        self.write_keyword("DATETIME_ADD");
29899        self.write("(");
29900        self.generate_expression(&e.this)?;
29901        self.write(", ");
29902        self.generate_expression(&e.expression)?;
29903        if let Some(unit) = &e.unit {
29904            self.write(", ");
29905            self.write_keyword(unit);
29906        }
29907        self.write(")");
29908        Ok(())
29909    }
29910
29911    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
29912        // DATETIME_DIFF(this, expression, unit)
29913        self.write_keyword("DATETIME_DIFF");
29914        self.write("(");
29915        self.generate_expression(&e.this)?;
29916        self.write(", ");
29917        self.generate_expression(&e.expression)?;
29918        if let Some(unit) = &e.unit {
29919            self.write(", ");
29920            self.write_keyword(unit);
29921        }
29922        self.write(")");
29923        Ok(())
29924    }
29925
29926    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
29927        // DATETIME_SUB(this, expression, unit)
29928        self.write_keyword("DATETIME_SUB");
29929        self.write("(");
29930        self.generate_expression(&e.this)?;
29931        self.write(", ");
29932        self.generate_expression(&e.expression)?;
29933        if let Some(unit) = &e.unit {
29934            self.write(", ");
29935            self.write_keyword(unit);
29936        }
29937        self.write(")");
29938        Ok(())
29939    }
29940
29941    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
29942        // DATETIME_TRUNC(this, unit, zone)
29943        self.write_keyword("DATETIME_TRUNC");
29944        self.write("(");
29945        self.generate_expression(&e.this)?;
29946        self.write(", ");
29947        self.write_keyword(&e.unit);
29948        if let Some(zone) = &e.zone {
29949            self.write(", ");
29950            self.generate_expression(zone)?;
29951        }
29952        self.write(")");
29953        Ok(())
29954    }
29955
29956    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
29957        // DAYNAME(this)
29958        self.write_keyword("DAYNAME");
29959        self.write("(");
29960        self.generate_expression(&e.this)?;
29961        self.write(")");
29962        Ok(())
29963    }
29964
29965    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
29966        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
29967        self.write_keyword("DECLARE");
29968        self.write_space();
29969        if e.replace {
29970            self.write_keyword("OR");
29971            self.write_space();
29972            self.write_keyword("REPLACE");
29973            self.write_space();
29974        }
29975        for (i, expr) in e.expressions.iter().enumerate() {
29976            if i > 0 {
29977                self.write(", ");
29978            }
29979            self.generate_expression(expr)?;
29980        }
29981        Ok(())
29982    }
29983
29984    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
29985        use crate::dialects::DialectType;
29986
29987        // variable TYPE [DEFAULT default]
29988        self.generate_expression(&e.this)?;
29989        // BigQuery multi-variable: DECLARE X, Y, Z INT64
29990        for name in &e.additional_names {
29991            self.write(", ");
29992            self.generate_expression(name)?;
29993        }
29994        if let Some(kind) = &e.kind {
29995            self.write_space();
29996            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
29997            // TSQL: Always includes AS (normalization)
29998            // Others: Include AS if present in original
29999            match self.config.dialect {
30000                Some(DialectType::BigQuery) => {
30001                    self.write(kind);
30002                }
30003                Some(DialectType::TSQL) => {
30004                    // TSQL DECLARE: no AS keyword (sqlglot convention)
30005                    // Normalize INT to INTEGER for simple declarations
30006                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
30007                    let is_complex_table = kind.starts_with("TABLE")
30008                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
30009                    if is_complex_table {
30010                        self.write(kind);
30011                    } else if kind == "INT" {
30012                        self.write("INTEGER");
30013                    } else if kind.starts_with("TABLE") {
30014                        // Normalize INT to INTEGER inside simple TABLE column definitions
30015                        let normalized = kind
30016                            .replace(" INT ", " INTEGER ")
30017                            .replace(" INT,", " INTEGER,")
30018                            .replace(" INT)", " INTEGER)")
30019                            .replace("(INT ", "(INTEGER ");
30020                        self.write(&normalized);
30021                    } else {
30022                        self.write(kind);
30023                    }
30024                }
30025                _ => {
30026                    if e.has_as {
30027                        self.write_keyword("AS");
30028                        self.write_space();
30029                    }
30030                    self.write(kind);
30031                }
30032            }
30033        }
30034        if let Some(default) = &e.default {
30035            // BigQuery uses DEFAULT, others use =
30036            match self.config.dialect {
30037                Some(DialectType::BigQuery) => {
30038                    self.write_space();
30039                    self.write_keyword("DEFAULT");
30040                    self.write_space();
30041                }
30042                _ => {
30043                    self.write(" = ");
30044                }
30045            }
30046            self.generate_expression(default)?;
30047        }
30048        Ok(())
30049    }
30050
30051    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
30052        // DECODE(expr, search1, result1, search2, result2, ..., default)
30053        self.write_keyword("DECODE");
30054        self.write("(");
30055        for (i, expr) in e.expressions.iter().enumerate() {
30056            if i > 0 {
30057                self.write(", ");
30058            }
30059            self.generate_expression(expr)?;
30060        }
30061        self.write(")");
30062        Ok(())
30063    }
30064
30065    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
30066        // DECOMPRESS(expr, 'method')
30067        self.write_keyword("DECOMPRESS");
30068        self.write("(");
30069        self.generate_expression(&e.this)?;
30070        self.write(", '");
30071        self.write(&e.method);
30072        self.write("')");
30073        Ok(())
30074    }
30075
30076    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
30077        // DECOMPRESS(expr, 'method')
30078        self.write_keyword("DECOMPRESS");
30079        self.write("(");
30080        self.generate_expression(&e.this)?;
30081        self.write(", '");
30082        self.write(&e.method);
30083        self.write("')");
30084        Ok(())
30085    }
30086
30087    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
30088        // DECRYPT(value, passphrase [, aad [, algorithm]])
30089        self.write_keyword("DECRYPT");
30090        self.write("(");
30091        self.generate_expression(&e.this)?;
30092        if let Some(passphrase) = &e.passphrase {
30093            self.write(", ");
30094            self.generate_expression(passphrase)?;
30095        }
30096        if let Some(aad) = &e.aad {
30097            self.write(", ");
30098            self.generate_expression(aad)?;
30099        }
30100        if let Some(method) = &e.encryption_method {
30101            self.write(", ");
30102            self.generate_expression(method)?;
30103        }
30104        self.write(")");
30105        Ok(())
30106    }
30107
30108    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
30109        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
30110        self.write_keyword("DECRYPT_RAW");
30111        self.write("(");
30112        self.generate_expression(&e.this)?;
30113        if let Some(key) = &e.key {
30114            self.write(", ");
30115            self.generate_expression(key)?;
30116        }
30117        if let Some(iv) = &e.iv {
30118            self.write(", ");
30119            self.generate_expression(iv)?;
30120        }
30121        if let Some(aad) = &e.aad {
30122            self.write(", ");
30123            self.generate_expression(aad)?;
30124        }
30125        if let Some(method) = &e.encryption_method {
30126            self.write(", ");
30127            self.generate_expression(method)?;
30128        }
30129        self.write(")");
30130        Ok(())
30131    }
30132
30133    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
30134        // DEFINER = user
30135        self.write_keyword("DEFINER");
30136        self.write(" = ");
30137        self.generate_expression(&e.this)?;
30138        Ok(())
30139    }
30140
30141    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
30142        // Python: DETACH[DATABASE IF EXISTS] this
30143        self.write_keyword("DETACH");
30144        if e.exists {
30145            self.write_keyword(" DATABASE IF EXISTS");
30146        }
30147        self.write_space();
30148        self.generate_expression(&e.this)?;
30149        Ok(())
30150    }
30151
30152    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
30153        let property_name = match e.this.as_ref() {
30154            Expression::Identifier(id) => id.name.as_str(),
30155            Expression::Var(v) => v.this.as_str(),
30156            _ => "DICTIONARY",
30157        };
30158        self.write_keyword(property_name);
30159        self.write("(");
30160        self.write(&e.kind);
30161        if let Some(settings) = &e.settings {
30162            self.write("(");
30163            if let Expression::Tuple(t) = settings.as_ref() {
30164                if self.config.pretty && !t.expressions.is_empty() {
30165                    self.write_newline();
30166                    self.indent_level += 1;
30167                    for (i, pair) in t.expressions.iter().enumerate() {
30168                        if i > 0 {
30169                            self.write(",");
30170                            self.write_newline();
30171                        }
30172                        self.write_indent();
30173                        if let Expression::Tuple(pair_tuple) = pair {
30174                            if let Some(k) = pair_tuple.expressions.first() {
30175                                self.generate_expression(k)?;
30176                            }
30177                            if let Some(v) = pair_tuple.expressions.get(1) {
30178                                self.write(" ");
30179                                self.generate_expression(v)?;
30180                            }
30181                        } else {
30182                            self.generate_expression(pair)?;
30183                        }
30184                    }
30185                    self.indent_level -= 1;
30186                    self.write_newline();
30187                    self.write_indent();
30188                } else {
30189                    for (i, pair) in t.expressions.iter().enumerate() {
30190                        if i > 0 {
30191                            // ClickHouse dict properties are space-separated, not comma-separated
30192                            self.write(" ");
30193                        }
30194                        if let Expression::Tuple(pair_tuple) = pair {
30195                            if let Some(k) = pair_tuple.expressions.first() {
30196                                self.generate_expression(k)?;
30197                            }
30198                            if let Some(v) = pair_tuple.expressions.get(1) {
30199                                self.write(" ");
30200                                self.generate_expression(v)?;
30201                            }
30202                        } else {
30203                            self.generate_expression(pair)?;
30204                        }
30205                    }
30206                }
30207            } else {
30208                self.generate_expression(settings)?;
30209            }
30210            self.write(")");
30211        } else {
30212            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
30213            self.write("()");
30214        }
30215        self.write(")");
30216        Ok(())
30217    }
30218
30219    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
30220        let property_name = match e.this.as_ref() {
30221            Expression::Identifier(id) => id.name.as_str(),
30222            Expression::Var(v) => v.this.as_str(),
30223            _ => "RANGE",
30224        };
30225        self.write_keyword(property_name);
30226        self.write("(");
30227        if let Some(min) = &e.min {
30228            self.write_keyword("MIN");
30229            self.write_space();
30230            self.generate_expression(min)?;
30231        }
30232        if let Some(max) = &e.max {
30233            self.write_space();
30234            self.write_keyword("MAX");
30235            self.write_space();
30236            self.generate_expression(max)?;
30237        }
30238        self.write(")");
30239        Ok(())
30240    }
30241
30242    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
30243        // Python: {local}DIRECTORY {this}{row_format}
30244        if e.local.is_some() {
30245            self.write_keyword("LOCAL ");
30246        }
30247        self.write_keyword("DIRECTORY");
30248        self.write_space();
30249        self.generate_expression(&e.this)?;
30250        if let Some(row_format) = &e.row_format {
30251            self.write_space();
30252            self.generate_expression(row_format)?;
30253        }
30254        Ok(())
30255    }
30256
30257    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
30258        // Redshift: DISTKEY(column)
30259        self.write_keyword("DISTKEY");
30260        self.write("(");
30261        self.generate_expression(&e.this)?;
30262        self.write(")");
30263        Ok(())
30264    }
30265
30266    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
30267        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
30268        self.write_keyword("DISTSTYLE");
30269        self.write_space();
30270        self.generate_expression(&e.this)?;
30271        Ok(())
30272    }
30273
30274    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
30275        // Python: "DISTRIBUTE BY" expressions
30276        self.write_keyword("DISTRIBUTE BY");
30277        self.write_space();
30278        for (i, expr) in e.expressions.iter().enumerate() {
30279            if i > 0 {
30280                self.write(", ");
30281            }
30282            self.generate_expression(expr)?;
30283        }
30284        Ok(())
30285    }
30286
30287    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
30288        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
30289        self.write_keyword("DISTRIBUTED BY");
30290        self.write_space();
30291        self.write(&e.kind);
30292        if !e.expressions.is_empty() {
30293            self.write(" (");
30294            for (i, expr) in e.expressions.iter().enumerate() {
30295                if i > 0 {
30296                    self.write(", ");
30297                }
30298                self.generate_expression(expr)?;
30299            }
30300            self.write(")");
30301        }
30302        if let Some(buckets) = &e.buckets {
30303            self.write_space();
30304            self.write_keyword("BUCKETS");
30305            self.write_space();
30306            self.generate_expression(buckets)?;
30307        }
30308        if let Some(order) = &e.order {
30309            self.write_space();
30310            self.generate_expression(order)?;
30311        }
30312        Ok(())
30313    }
30314
30315    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
30316        // DOT_PRODUCT(vector1, vector2)
30317        self.write_keyword("DOT_PRODUCT");
30318        self.write("(");
30319        self.generate_expression(&e.this)?;
30320        self.write(", ");
30321        self.generate_expression(&e.expression)?;
30322        self.write(")");
30323        Ok(())
30324    }
30325
30326    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
30327        // Python: DROP{IF EXISTS }expressions
30328        self.write_keyword("DROP");
30329        if e.exists {
30330            self.write_keyword(" IF EXISTS ");
30331        } else {
30332            self.write_space();
30333        }
30334        for (i, expr) in e.expressions.iter().enumerate() {
30335            if i > 0 {
30336                self.write(", ");
30337            }
30338            self.generate_expression(expr)?;
30339        }
30340        Ok(())
30341    }
30342
30343    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
30344        // Python: DUPLICATE KEY (expressions)
30345        self.write_keyword("DUPLICATE KEY");
30346        self.write(" (");
30347        for (i, expr) in e.expressions.iter().enumerate() {
30348            if i > 0 {
30349                self.write(", ");
30350            }
30351            self.generate_expression(expr)?;
30352        }
30353        self.write(")");
30354        Ok(())
30355    }
30356
30357    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
30358        // ELT(index, str1, str2, ...)
30359        self.write_keyword("ELT");
30360        self.write("(");
30361        self.generate_expression(&e.this)?;
30362        for expr in &e.expressions {
30363            self.write(", ");
30364            self.generate_expression(expr)?;
30365        }
30366        self.write(")");
30367        Ok(())
30368    }
30369
30370    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
30371        // ENCODE(string, charset)
30372        self.write_keyword("ENCODE");
30373        self.write("(");
30374        self.generate_expression(&e.this)?;
30375        if let Some(charset) = &e.charset {
30376            self.write(", ");
30377            self.generate_expression(charset)?;
30378        }
30379        self.write(")");
30380        Ok(())
30381    }
30382
30383    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
30384        // Python: [KEY ]ENCODE this [properties]
30385        if e.key.is_some() {
30386            self.write_keyword("KEY ");
30387        }
30388        self.write_keyword("ENCODE");
30389        self.write_space();
30390        self.generate_expression(&e.this)?;
30391        if !e.properties.is_empty() {
30392            self.write(" (");
30393            for (i, prop) in e.properties.iter().enumerate() {
30394                if i > 0 {
30395                    self.write(", ");
30396                }
30397                self.generate_expression(prop)?;
30398            }
30399            self.write(")");
30400        }
30401        Ok(())
30402    }
30403
30404    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
30405        // ENCRYPT(value, passphrase [, aad [, algorithm]])
30406        self.write_keyword("ENCRYPT");
30407        self.write("(");
30408        self.generate_expression(&e.this)?;
30409        if let Some(passphrase) = &e.passphrase {
30410            self.write(", ");
30411            self.generate_expression(passphrase)?;
30412        }
30413        if let Some(aad) = &e.aad {
30414            self.write(", ");
30415            self.generate_expression(aad)?;
30416        }
30417        if let Some(method) = &e.encryption_method {
30418            self.write(", ");
30419            self.generate_expression(method)?;
30420        }
30421        self.write(")");
30422        Ok(())
30423    }
30424
30425    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
30426        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
30427        self.write_keyword("ENCRYPT_RAW");
30428        self.write("(");
30429        self.generate_expression(&e.this)?;
30430        if let Some(key) = &e.key {
30431            self.write(", ");
30432            self.generate_expression(key)?;
30433        }
30434        if let Some(iv) = &e.iv {
30435            self.write(", ");
30436            self.generate_expression(iv)?;
30437        }
30438        if let Some(aad) = &e.aad {
30439            self.write(", ");
30440            self.generate_expression(aad)?;
30441        }
30442        if let Some(method) = &e.encryption_method {
30443            self.write(", ");
30444            self.generate_expression(method)?;
30445        }
30446        self.write(")");
30447        Ok(())
30448    }
30449
30450    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
30451        // MySQL: ENGINE = InnoDB
30452        self.write_keyword("ENGINE");
30453        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
30454            self.write("=");
30455        } else {
30456            self.write(" = ");
30457        }
30458        self.generate_expression(&e.this)?;
30459        Ok(())
30460    }
30461
30462    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
30463        // ENVIRONMENT (expressions)
30464        self.write_keyword("ENVIRONMENT");
30465        self.write(" (");
30466        for (i, expr) in e.expressions.iter().enumerate() {
30467            if i > 0 {
30468                self.write(", ");
30469            }
30470            self.generate_expression(expr)?;
30471        }
30472        self.write(")");
30473        Ok(())
30474    }
30475
30476    fn generate_ephemeral_column_constraint(
30477        &mut self,
30478        e: &EphemeralColumnConstraint,
30479    ) -> Result<()> {
30480        // MySQL: EPHEMERAL [expr]
30481        self.write_keyword("EPHEMERAL");
30482        if let Some(this) = &e.this {
30483            self.write_space();
30484            self.generate_expression(this)?;
30485        }
30486        Ok(())
30487    }
30488
30489    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
30490        // Snowflake: EQUAL_NULL(a, b)
30491        self.write_keyword("EQUAL_NULL");
30492        self.write("(");
30493        self.generate_expression(&e.this)?;
30494        self.write(", ");
30495        self.generate_expression(&e.expression)?;
30496        self.write(")");
30497        Ok(())
30498    }
30499
30500    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
30501        use crate::dialects::DialectType;
30502
30503        // PostgreSQL uses <-> operator syntax
30504        match self.config.dialect {
30505            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
30506                self.generate_expression(&e.this)?;
30507                self.write(" <-> ");
30508                self.generate_expression(&e.expression)?;
30509            }
30510            _ => {
30511                // Other dialects use EUCLIDEAN_DISTANCE function
30512                self.write_keyword("EUCLIDEAN_DISTANCE");
30513                self.write("(");
30514                self.generate_expression(&e.this)?;
30515                self.write(", ");
30516                self.generate_expression(&e.expression)?;
30517                self.write(")");
30518            }
30519        }
30520        Ok(())
30521    }
30522
30523    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
30524        // EXECUTE AS CALLER|OWNER|user
30525        self.write_keyword("EXECUTE AS");
30526        self.write_space();
30527        self.generate_expression(&e.this)?;
30528        Ok(())
30529    }
30530
30531    fn generate_export(&mut self, e: &Export) -> Result<()> {
30532        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
30533        self.write_keyword("EXPORT DATA");
30534        if let Some(connection) = &e.connection {
30535            self.write_space();
30536            self.write_keyword("WITH CONNECTION");
30537            self.write_space();
30538            self.generate_expression(connection)?;
30539        }
30540        if !e.options.is_empty() {
30541            self.write_space();
30542            self.generate_options_clause(&e.options)?;
30543        }
30544        self.write_space();
30545        self.write_keyword("AS");
30546        self.write_space();
30547        self.generate_expression(&e.this)?;
30548        Ok(())
30549    }
30550
30551    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
30552        // EXTERNAL [this]
30553        self.write_keyword("EXTERNAL");
30554        if let Some(this) = &e.this {
30555            self.write_space();
30556            self.generate_expression(this)?;
30557        }
30558        Ok(())
30559    }
30560
30561    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
30562        // Python: {no}FALLBACK{protection}
30563        if e.no.is_some() {
30564            self.write_keyword("NO ");
30565        }
30566        self.write_keyword("FALLBACK");
30567        if e.protection.is_some() {
30568            self.write_keyword(" PROTECTION");
30569        }
30570        Ok(())
30571    }
30572
30573    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
30574        // BigQuery: FARM_FINGERPRINT(value)
30575        self.write_keyword("FARM_FINGERPRINT");
30576        self.write("(");
30577        for (i, expr) in e.expressions.iter().enumerate() {
30578            if i > 0 {
30579                self.write(", ");
30580            }
30581            self.generate_expression(expr)?;
30582        }
30583        self.write(")");
30584        Ok(())
30585    }
30586
30587    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
30588        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
30589        self.write_keyword("FEATURES_AT_TIME");
30590        self.write("(");
30591        self.generate_expression(&e.this)?;
30592        if let Some(time) = &e.time {
30593            self.write(", ");
30594            self.generate_expression(time)?;
30595        }
30596        if let Some(num_rows) = &e.num_rows {
30597            self.write(", ");
30598            self.generate_expression(num_rows)?;
30599        }
30600        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
30601            self.write(", ");
30602            self.generate_expression(ignore_nulls)?;
30603        }
30604        self.write(")");
30605        Ok(())
30606    }
30607
30608    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
30609        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
30610        let use_limit = !e.percent
30611            && !e.with_ties
30612            && e.count.is_some()
30613            && matches!(
30614                self.config.dialect,
30615                Some(DialectType::Spark)
30616                    | Some(DialectType::Hive)
30617                    | Some(DialectType::DuckDB)
30618                    | Some(DialectType::SQLite)
30619                    | Some(DialectType::MySQL)
30620                    | Some(DialectType::BigQuery)
30621                    | Some(DialectType::Databricks)
30622                    | Some(DialectType::StarRocks)
30623                    | Some(DialectType::Doris)
30624                    | Some(DialectType::Athena)
30625                    | Some(DialectType::ClickHouse)
30626            );
30627
30628        if use_limit {
30629            self.write_keyword("LIMIT");
30630            self.write_space();
30631            self.generate_expression(e.count.as_ref().unwrap())?;
30632            return Ok(());
30633        }
30634
30635        // Python: FETCH direction count limit_options
30636        self.write_keyword("FETCH");
30637        if !e.direction.is_empty() {
30638            self.write_space();
30639            self.write_keyword(&e.direction);
30640        }
30641        if let Some(count) = &e.count {
30642            self.write_space();
30643            self.generate_expression(count)?;
30644        }
30645        // Generate PERCENT, ROWS, WITH TIES/ONLY
30646        if e.percent {
30647            self.write_keyword(" PERCENT");
30648        }
30649        if e.rows {
30650            self.write_keyword(" ROWS");
30651        }
30652        if e.with_ties {
30653            self.write_keyword(" WITH TIES");
30654        } else if e.rows {
30655            self.write_keyword(" ONLY");
30656        } else {
30657            self.write_keyword(" ROWS ONLY");
30658        }
30659        Ok(())
30660    }
30661
30662    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
30663        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
30664        // For Spark/Databricks without hive_format: USING this
30665        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
30666        if e.hive_format.is_some() {
30667            // Hive format: STORED AS ...
30668            self.write_keyword("STORED AS");
30669            self.write_space();
30670            if let Some(this) = &e.this {
30671                // Uppercase the format name (e.g., parquet -> PARQUET)
30672                if let Expression::Identifier(id) = this.as_ref() {
30673                    self.write_keyword(&id.name.to_ascii_uppercase());
30674                } else {
30675                    self.generate_expression(this)?;
30676                }
30677            }
30678        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
30679            // Hive: STORED AS format
30680            self.write_keyword("STORED AS");
30681            self.write_space();
30682            if let Some(this) = &e.this {
30683                if let Expression::Identifier(id) = this.as_ref() {
30684                    self.write_keyword(&id.name.to_ascii_uppercase());
30685                } else {
30686                    self.generate_expression(this)?;
30687                }
30688            }
30689        } else if matches!(
30690            self.config.dialect,
30691            Some(DialectType::Spark) | Some(DialectType::Databricks)
30692        ) {
30693            // Spark/Databricks: USING format (e.g., USING DELTA)
30694            self.write_keyword("USING");
30695            self.write_space();
30696            if let Some(this) = &e.this {
30697                self.generate_expression(this)?;
30698            }
30699        } else {
30700            // Snowflake/standard format
30701            self.write_keyword("FILE_FORMAT");
30702            self.write(" = ");
30703            if let Some(this) = &e.this {
30704                self.generate_expression(this)?;
30705            } else if !e.expressions.is_empty() {
30706                self.write("(");
30707                for (i, expr) in e.expressions.iter().enumerate() {
30708                    if i > 0 {
30709                        self.write(", ");
30710                    }
30711                    self.generate_expression(expr)?;
30712                }
30713                self.write(")");
30714            }
30715        }
30716        Ok(())
30717    }
30718
30719    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
30720        // agg_func FILTER(WHERE condition)
30721        self.generate_expression(&e.this)?;
30722        self.write_space();
30723        self.write_keyword("FILTER");
30724        self.write("(");
30725        self.write_keyword("WHERE");
30726        self.write_space();
30727        self.generate_expression(&e.expression)?;
30728        self.write(")");
30729        Ok(())
30730    }
30731
30732    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
30733        // FLOAT64(this) or FLOAT64(this, expression)
30734        self.write_keyword("FLOAT64");
30735        self.write("(");
30736        self.generate_expression(&e.this)?;
30737        if let Some(expr) = &e.expression {
30738            self.write(", ");
30739            self.generate_expression(expr)?;
30740        }
30741        self.write(")");
30742        Ok(())
30743    }
30744
30745    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
30746        // FOR this DO expression
30747        self.write_keyword("FOR");
30748        self.write_space();
30749        self.generate_expression(&e.this)?;
30750        self.write_space();
30751        self.write_keyword("DO");
30752        self.write_space();
30753        self.generate_expression(&e.expression)?;
30754        Ok(())
30755    }
30756
30757    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
30758        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
30759        self.write_keyword("FOREIGN KEY");
30760        if !e.expressions.is_empty() {
30761            self.write(" (");
30762            for (i, expr) in e.expressions.iter().enumerate() {
30763                if i > 0 {
30764                    self.write(", ");
30765                }
30766                self.generate_expression(expr)?;
30767            }
30768            self.write(")");
30769        }
30770        if let Some(reference) = &e.reference {
30771            self.write_space();
30772            self.generate_expression(reference)?;
30773        }
30774        if let Some(delete) = &e.delete {
30775            self.write_space();
30776            self.write_keyword("ON DELETE");
30777            self.write_space();
30778            self.generate_expression(delete)?;
30779        }
30780        if let Some(update) = &e.update {
30781            self.write_space();
30782            self.write_keyword("ON UPDATE");
30783            self.write_space();
30784            self.generate_expression(update)?;
30785        }
30786        if !e.options.is_empty() {
30787            self.write_space();
30788            for (i, opt) in e.options.iter().enumerate() {
30789                if i > 0 {
30790                    self.write_space();
30791                }
30792                self.generate_expression(opt)?;
30793            }
30794        }
30795        Ok(())
30796    }
30797
30798    fn generate_format(&mut self, e: &Format) -> Result<()> {
30799        // FORMAT(this, expressions...)
30800        self.write_keyword("FORMAT");
30801        self.write("(");
30802        self.generate_expression(&e.this)?;
30803        for expr in &e.expressions {
30804            self.write(", ");
30805            self.generate_expression(expr)?;
30806        }
30807        self.write(")");
30808        Ok(())
30809    }
30810
30811    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
30812        // Teradata: column (FORMAT 'format_string')
30813        self.generate_expression(&e.this)?;
30814        self.write(" (");
30815        self.write_keyword("FORMAT");
30816        self.write(" '");
30817        self.write(&e.format);
30818        self.write("')");
30819        Ok(())
30820    }
30821
30822    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
30823        // Python: FREESPACE=this[PERCENT]
30824        self.write_keyword("FREESPACE");
30825        self.write("=");
30826        self.generate_expression(&e.this)?;
30827        if e.percent.is_some() {
30828            self.write_keyword(" PERCENT");
30829        }
30830        Ok(())
30831    }
30832
30833    fn generate_from(&mut self, e: &From) -> Result<()> {
30834        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
30835        self.write_keyword("FROM");
30836        self.write_space();
30837
30838        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
30839        // But keep commas when TABLESAMPLE is present
30840        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
30841        use crate::dialects::DialectType;
30842        let has_tablesample = e
30843            .expressions
30844            .iter()
30845            .any(|expr| matches!(expr, Expression::TableSample(_)));
30846        let is_cross_join_dialect = matches!(
30847            self.config.dialect,
30848            Some(DialectType::BigQuery)
30849                | Some(DialectType::Hive)
30850                | Some(DialectType::Spark)
30851                | Some(DialectType::Databricks)
30852                | Some(DialectType::SQLite)
30853                | Some(DialectType::ClickHouse)
30854        );
30855        let source_is_same_as_target2 = self.config.source_dialect.is_some()
30856            && self.config.source_dialect == self.config.dialect;
30857        let source_is_cross_join_dialect2 = matches!(
30858            self.config.source_dialect,
30859            Some(DialectType::BigQuery)
30860                | Some(DialectType::Hive)
30861                | Some(DialectType::Spark)
30862                | Some(DialectType::Databricks)
30863                | Some(DialectType::SQLite)
30864                | Some(DialectType::ClickHouse)
30865        );
30866        let use_cross_join = !has_tablesample
30867            && is_cross_join_dialect
30868            && (source_is_same_as_target2
30869                || source_is_cross_join_dialect2
30870                || self.config.source_dialect.is_none());
30871
30872        // Snowflake wraps standalone VALUES in FROM clause with parentheses
30873        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
30874
30875        for (i, expr) in e.expressions.iter().enumerate() {
30876            if i > 0 {
30877                if use_cross_join {
30878                    self.write(" CROSS JOIN ");
30879                } else {
30880                    self.write(", ");
30881                }
30882            }
30883            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
30884                self.write("(");
30885                self.generate_expression(expr)?;
30886                self.write(")");
30887            } else {
30888                self.generate_expression(expr)?;
30889            }
30890            // Output leading comments that were on the table name before FROM
30891            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
30892            let leading = Self::extract_table_leading_comments(expr);
30893            for comment in &leading {
30894                self.write_space();
30895                self.write_formatted_comment(comment);
30896            }
30897        }
30898        Ok(())
30899    }
30900
30901    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
30902    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
30903        match expr {
30904            Expression::Table(t) => t.leading_comments.clone(),
30905            Expression::Pivot(p) => {
30906                if let Expression::Table(t) = &p.this {
30907                    t.leading_comments.clone()
30908                } else {
30909                    Vec::new()
30910                }
30911            }
30912            _ => Vec::new(),
30913        }
30914    }
30915
30916    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
30917        // FROM_BASE(this, expression) - convert from base N
30918        self.write_keyword("FROM_BASE");
30919        self.write("(");
30920        self.generate_expression(&e.this)?;
30921        self.write(", ");
30922        self.generate_expression(&e.expression)?;
30923        self.write(")");
30924        Ok(())
30925    }
30926
30927    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
30928        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
30929        self.generate_expression(&e.this)?;
30930        if let Some(zone) = &e.zone {
30931            self.write_space();
30932            self.write_keyword("AT TIME ZONE");
30933            self.write_space();
30934            self.generate_expression(zone)?;
30935            self.write_space();
30936            self.write_keyword("AT TIME ZONE");
30937            self.write(" 'UTC'");
30938        }
30939        Ok(())
30940    }
30941
30942    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
30943        // GAP_FILL(this, ts_column, bucket_width, ...)
30944        self.write_keyword("GAP_FILL");
30945        self.write("(");
30946        self.generate_expression(&e.this)?;
30947        if let Some(ts_column) = &e.ts_column {
30948            self.write(", ");
30949            self.generate_expression(ts_column)?;
30950        }
30951        if let Some(bucket_width) = &e.bucket_width {
30952            self.write(", ");
30953            self.generate_expression(bucket_width)?;
30954        }
30955        if let Some(partitioning_columns) = &e.partitioning_columns {
30956            self.write(", ");
30957            self.generate_expression(partitioning_columns)?;
30958        }
30959        if let Some(value_columns) = &e.value_columns {
30960            self.write(", ");
30961            self.generate_expression(value_columns)?;
30962        }
30963        self.write(")");
30964        Ok(())
30965    }
30966
30967    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
30968        // GENERATE_DATE_ARRAY(start, end, step)
30969        self.write_keyword("GENERATE_DATE_ARRAY");
30970        self.write("(");
30971        let mut first = true;
30972        if let Some(start) = &e.start {
30973            self.generate_expression(start)?;
30974            first = false;
30975        }
30976        if let Some(end) = &e.end {
30977            if !first {
30978                self.write(", ");
30979            }
30980            self.generate_expression(end)?;
30981            first = false;
30982        }
30983        if let Some(step) = &e.step {
30984            if !first {
30985                self.write(", ");
30986            }
30987            self.generate_expression(step)?;
30988        }
30989        self.write(")");
30990        Ok(())
30991    }
30992
30993    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
30994        // ML.GENERATE_EMBEDDING(model, content, params)
30995        self.write_keyword("ML.GENERATE_EMBEDDING");
30996        self.write("(");
30997        self.generate_expression(&e.this)?;
30998        self.write(", ");
30999        self.generate_expression(&e.expression)?;
31000        if let Some(params) = &e.params_struct {
31001            self.write(", ");
31002            self.generate_expression(params)?;
31003        }
31004        self.write(")");
31005        Ok(())
31006    }
31007
31008    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
31009        // Dialect-specific function name
31010        let fn_name = match self.config.dialect {
31011            Some(DialectType::Presto)
31012            | Some(DialectType::Trino)
31013            | Some(DialectType::Athena)
31014            | Some(DialectType::Spark)
31015            | Some(DialectType::Databricks)
31016            | Some(DialectType::Hive) => "SEQUENCE",
31017            _ => "GENERATE_SERIES",
31018        };
31019        self.write_keyword(fn_name);
31020        self.write("(");
31021        let mut first = true;
31022        if let Some(start) = &e.start {
31023            self.generate_expression(start)?;
31024            first = false;
31025        }
31026        if let Some(end) = &e.end {
31027            if !first {
31028                self.write(", ");
31029            }
31030            self.generate_expression(end)?;
31031            first = false;
31032        }
31033        if let Some(step) = &e.step {
31034            if !first {
31035                self.write(", ");
31036            }
31037            // For Presto/Trino: convert WEEK intervals to DAY multiples
31038            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
31039            if matches!(
31040                self.config.dialect,
31041                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
31042            ) {
31043                if let Some(converted) = self.convert_week_interval_to_day(step) {
31044                    self.generate_expression(&converted)?;
31045                } else {
31046                    self.generate_expression(step)?;
31047                }
31048            } else {
31049                self.generate_expression(step)?;
31050            }
31051        }
31052        self.write(")");
31053        Ok(())
31054    }
31055
31056    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
31057    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
31058    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
31059        use crate::expressions::*;
31060        if let Expression::Interval(ref iv) = expr {
31061            // Check for structured WEEK unit
31062            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
31063                unit: IntervalUnit::Week,
31064                ..
31065            }) = &iv.unit
31066            {
31067                // Value is in iv.this
31068                let count = match &iv.this {
31069                    Some(Expression::Literal(lit)) => match lit.as_ref() {
31070                        Literal::String(s) | Literal::Number(s) => s.clone(),
31071                        _ => return None,
31072                    },
31073                    _ => return None,
31074                };
31075                (true, count)
31076            } else if iv.unit.is_none() {
31077                // Check for string-encoded interval like "1 WEEK"
31078                if let Some(Expression::Literal(lit)) = &iv.this {
31079                    if let Literal::String(s) = lit.as_ref() {
31080                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
31081                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
31082                            (true, parts[0].to_string())
31083                        } else {
31084                            (false, String::new())
31085                        }
31086                    } else {
31087                        (false, String::new())
31088                    }
31089                } else {
31090                    (false, String::new())
31091                }
31092            } else {
31093                (false, String::new())
31094            };
31095
31096            if is_week {
31097                // Build: (N * INTERVAL '7' DAY)
31098                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
31099                let day_interval = Expression::Interval(Box::new(Interval {
31100                    this: Some(Expression::Literal(Box::new(Literal::String(
31101                        "7".to_string(),
31102                    )))),
31103                    unit: Some(IntervalUnitSpec::Simple {
31104                        unit: IntervalUnit::Day,
31105                        use_plural: false,
31106                    }),
31107                }));
31108                let mul = Expression::Mul(Box::new(BinaryOp {
31109                    left: count_expr,
31110                    right: day_interval,
31111                    left_comments: vec![],
31112                    operator_comments: vec![],
31113                    trailing_comments: vec![],
31114                    inferred_type: None,
31115                }));
31116                return Some(Expression::Paren(Box::new(Paren {
31117                    this: mul,
31118                    trailing_comments: vec![],
31119                })));
31120            }
31121        }
31122        None
31123    }
31124
31125    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
31126        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
31127        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
31128        self.write("(");
31129        let mut first = true;
31130        if let Some(start) = &e.start {
31131            self.generate_expression(start)?;
31132            first = false;
31133        }
31134        if let Some(end) = &e.end {
31135            if !first {
31136                self.write(", ");
31137            }
31138            self.generate_expression(end)?;
31139            first = false;
31140        }
31141        if let Some(step) = &e.step {
31142            if !first {
31143                self.write(", ");
31144            }
31145            self.generate_expression(step)?;
31146        }
31147        self.write(")");
31148        Ok(())
31149    }
31150
31151    fn generate_generated_as_identity_column_constraint(
31152        &mut self,
31153        e: &GeneratedAsIdentityColumnConstraint,
31154    ) -> Result<()> {
31155        use crate::dialects::DialectType;
31156
31157        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
31158        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31159            self.write_keyword("AUTOINCREMENT");
31160            if let Some(start) = &e.start {
31161                self.write_keyword(" START ");
31162                self.generate_expression(start)?;
31163            }
31164            if let Some(increment) = &e.increment {
31165                self.write_keyword(" INCREMENT ");
31166                self.generate_expression(increment)?;
31167            }
31168            return Ok(());
31169        }
31170
31171        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
31172        self.write_keyword("GENERATED");
31173        if let Some(this) = &e.this {
31174            // Check if it's a truthy boolean expression
31175            if let Expression::Boolean(b) = this.as_ref() {
31176                if b.value {
31177                    self.write_keyword(" ALWAYS");
31178                } else {
31179                    self.write_keyword(" BY DEFAULT");
31180                    if e.on_null.is_some() {
31181                        self.write_keyword(" ON NULL");
31182                    }
31183                }
31184            } else {
31185                self.write_keyword(" ALWAYS");
31186            }
31187        }
31188        self.write_keyword(" AS IDENTITY");
31189        // Add sequence options if any
31190        let has_options = e.start.is_some()
31191            || e.increment.is_some()
31192            || e.minvalue.is_some()
31193            || e.maxvalue.is_some();
31194        if has_options {
31195            self.write(" (");
31196            let mut first = true;
31197            if let Some(start) = &e.start {
31198                self.write_keyword("START WITH ");
31199                self.generate_expression(start)?;
31200                first = false;
31201            }
31202            if let Some(increment) = &e.increment {
31203                if !first {
31204                    self.write(" ");
31205                }
31206                self.write_keyword("INCREMENT BY ");
31207                self.generate_expression(increment)?;
31208                first = false;
31209            }
31210            if let Some(minvalue) = &e.minvalue {
31211                if !first {
31212                    self.write(" ");
31213                }
31214                self.write_keyword("MINVALUE ");
31215                self.generate_expression(minvalue)?;
31216                first = false;
31217            }
31218            if let Some(maxvalue) = &e.maxvalue {
31219                if !first {
31220                    self.write(" ");
31221                }
31222                self.write_keyword("MAXVALUE ");
31223                self.generate_expression(maxvalue)?;
31224            }
31225            self.write(")");
31226        }
31227        Ok(())
31228    }
31229
31230    fn generate_generated_as_row_column_constraint(
31231        &mut self,
31232        e: &GeneratedAsRowColumnConstraint,
31233    ) -> Result<()> {
31234        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
31235        self.write_keyword("GENERATED ALWAYS AS ROW ");
31236        if e.start.is_some() {
31237            self.write_keyword("START");
31238        } else {
31239            self.write_keyword("END");
31240        }
31241        if e.hidden.is_some() {
31242            self.write_keyword(" HIDDEN");
31243        }
31244        Ok(())
31245    }
31246
31247    fn generate_get(&mut self, e: &Get) -> Result<()> {
31248        // GET this target properties
31249        self.write_keyword("GET");
31250        self.write_space();
31251        self.generate_expression(&e.this)?;
31252        if let Some(target) = &e.target {
31253            self.write_space();
31254            self.generate_expression(target)?;
31255        }
31256        for prop in &e.properties {
31257            self.write_space();
31258            self.generate_expression(prop)?;
31259        }
31260        Ok(())
31261    }
31262
31263    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
31264        // GetExtract generates bracket access: this[expression]
31265        self.generate_expression(&e.this)?;
31266        self.write("[");
31267        self.generate_expression(&e.expression)?;
31268        self.write("]");
31269        Ok(())
31270    }
31271
31272    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
31273        // GETBIT(this, expression) or GET_BIT(this, expression)
31274        self.write_keyword("GETBIT");
31275        self.write("(");
31276        self.generate_expression(&e.this)?;
31277        self.write(", ");
31278        self.generate_expression(&e.expression)?;
31279        self.write(")");
31280        Ok(())
31281    }
31282
31283    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
31284        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
31285        if e.is_role {
31286            self.write_keyword("ROLE");
31287            self.write_space();
31288        } else if e.is_group {
31289            self.write_keyword("GROUP");
31290            self.write_space();
31291        } else if e.is_share {
31292            self.write_keyword("SHARE");
31293            self.write_space();
31294        }
31295        self.write(&e.name.name);
31296        Ok(())
31297    }
31298
31299    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
31300        // privilege(columns) or just privilege
31301        self.generate_expression(&e.this)?;
31302        if !e.expressions.is_empty() {
31303            self.write("(");
31304            for (i, expr) in e.expressions.iter().enumerate() {
31305                if i > 0 {
31306                    self.write(", ");
31307                }
31308                self.generate_expression(expr)?;
31309            }
31310            self.write(")");
31311        }
31312        Ok(())
31313    }
31314
31315    fn generate_group(&mut self, e: &Group) -> Result<()> {
31316        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
31317        self.write_keyword("GROUP BY");
31318        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
31319        match e.all {
31320            Some(true) => {
31321                self.write_space();
31322                self.write_keyword("ALL");
31323            }
31324            Some(false) => {
31325                self.write_space();
31326                self.write_keyword("DISTINCT");
31327            }
31328            None => {}
31329        }
31330        if !e.expressions.is_empty() {
31331            self.write_space();
31332            for (i, expr) in e.expressions.iter().enumerate() {
31333                if i > 0 {
31334                    self.write(", ");
31335                }
31336                self.generate_expression(expr)?;
31337            }
31338        }
31339        // Handle CUBE, ROLLUP, GROUPING SETS
31340        if let Some(cube) = &e.cube {
31341            if !e.expressions.is_empty() {
31342                self.write(", ");
31343            } else {
31344                self.write_space();
31345            }
31346            self.generate_expression(cube)?;
31347        }
31348        if let Some(rollup) = &e.rollup {
31349            if !e.expressions.is_empty() || e.cube.is_some() {
31350                self.write(", ");
31351            } else {
31352                self.write_space();
31353            }
31354            self.generate_expression(rollup)?;
31355        }
31356        if let Some(grouping_sets) = &e.grouping_sets {
31357            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
31358                self.write(", ");
31359            } else {
31360                self.write_space();
31361            }
31362            self.generate_expression(grouping_sets)?;
31363        }
31364        if let Some(totals) = &e.totals {
31365            self.write_space();
31366            self.write_keyword("WITH TOTALS");
31367            self.generate_expression(totals)?;
31368        }
31369        Ok(())
31370    }
31371
31372    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
31373        // GROUP BY expressions
31374        self.write_keyword("GROUP BY");
31375        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
31376        match e.all {
31377            Some(true) => {
31378                self.write_space();
31379                self.write_keyword("ALL");
31380            }
31381            Some(false) => {
31382                self.write_space();
31383                self.write_keyword("DISTINCT");
31384            }
31385            None => {}
31386        }
31387
31388        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
31389        // These are represented as Cube/Rollup expressions with empty expressions at the end
31390        let mut trailing_cube = false;
31391        let mut trailing_rollup = false;
31392        let mut regular_expressions: Vec<&Expression> = Vec::new();
31393
31394        for expr in &e.expressions {
31395            match expr {
31396                Expression::Cube(c) if c.expressions.is_empty() => {
31397                    trailing_cube = true;
31398                }
31399                Expression::Rollup(r) if r.expressions.is_empty() => {
31400                    trailing_rollup = true;
31401                }
31402                _ => {
31403                    regular_expressions.push(expr);
31404                }
31405            }
31406        }
31407
31408        // In pretty mode, put columns on separate lines
31409        if self.config.pretty {
31410            self.write_newline();
31411            self.indent_level += 1;
31412            for (i, expr) in regular_expressions.iter().enumerate() {
31413                if i > 0 {
31414                    self.write(",");
31415                    self.write_newline();
31416                }
31417                self.write_indent();
31418                self.generate_expression(expr)?;
31419            }
31420            self.indent_level -= 1;
31421        } else {
31422            self.write_space();
31423            for (i, expr) in regular_expressions.iter().enumerate() {
31424                if i > 0 {
31425                    self.write(", ");
31426                }
31427                self.generate_expression(expr)?;
31428            }
31429        }
31430
31431        // Output trailing WITH CUBE or WITH ROLLUP
31432        if trailing_cube {
31433            self.write_space();
31434            self.write_keyword("WITH CUBE");
31435        } else if trailing_rollup {
31436            self.write_space();
31437            self.write_keyword("WITH ROLLUP");
31438        }
31439
31440        // ClickHouse: WITH TOTALS
31441        if e.totals {
31442            self.write_space();
31443            self.write_keyword("WITH TOTALS");
31444        }
31445
31446        Ok(())
31447    }
31448
31449    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
31450        let function_name = if e.expressions.len() > 1
31451            && matches!(
31452                self.config.dialect,
31453                Some(DialectType::TSQL | DialectType::Fabric)
31454            ) {
31455            "GROUPING_ID"
31456        } else {
31457            "GROUPING"
31458        };
31459        self.write_keyword(function_name);
31460        self.write("(");
31461        for (i, expr) in e.expressions.iter().enumerate() {
31462            if i > 0 {
31463                self.write(", ");
31464            }
31465            self.generate_expression(expr)?;
31466        }
31467        self.write(")");
31468        Ok(())
31469    }
31470
31471    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
31472        // GROUPING_ID(col1, col2, ...)
31473        self.write_keyword("GROUPING_ID");
31474        self.write("(");
31475        for (i, expr) in e.expressions.iter().enumerate() {
31476            if i > 0 {
31477                self.write(", ");
31478            }
31479            self.generate_expression(expr)?;
31480        }
31481        self.write(")");
31482        Ok(())
31483    }
31484
31485    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
31486        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
31487        self.write_keyword("GROUPING SETS");
31488        self.write(" (");
31489        for (i, expr) in e.expressions.iter().enumerate() {
31490            if i > 0 {
31491                self.write(", ");
31492            }
31493            self.generate_expression(expr)?;
31494        }
31495        self.write(")");
31496        Ok(())
31497    }
31498
31499    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
31500        // HASH_AGG(this, expressions...)
31501        self.write_keyword("HASH_AGG");
31502        self.write("(");
31503        self.generate_expression(&e.this)?;
31504        for expr in &e.expressions {
31505            self.write(", ");
31506            self.generate_expression(expr)?;
31507        }
31508        self.write(")");
31509        Ok(())
31510    }
31511
31512    fn generate_having(&mut self, e: &Having) -> Result<()> {
31513        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
31514        self.write_keyword("HAVING");
31515        self.write_space();
31516        self.generate_expression(&e.this)?;
31517        Ok(())
31518    }
31519
31520    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
31521        // Python: this HAVING MAX|MIN expression
31522        self.generate_expression(&e.this)?;
31523        self.write_space();
31524        self.write_keyword("HAVING");
31525        self.write_space();
31526        if e.max.is_some() {
31527            self.write_keyword("MAX");
31528        } else {
31529            self.write_keyword("MIN");
31530        }
31531        self.write_space();
31532        self.generate_expression(&e.expression)?;
31533        Ok(())
31534    }
31535
31536    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
31537        use crate::dialects::DialectType;
31538        // DuckDB: convert dollar-tagged strings to single-quoted
31539        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
31540            // Extract the string content and output as single-quoted
31541            if let Expression::Literal(ref lit) = *e.this {
31542                if let Literal::String(ref s) = lit.as_ref() {
31543                    return self.generate_string_literal(s);
31544                }
31545            }
31546        }
31547        // PostgreSQL: preserve dollar-quoting
31548        if matches!(
31549            self.config.dialect,
31550            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31551        ) {
31552            self.write("$");
31553            if let Some(tag) = &e.tag {
31554                self.generate_expression(tag)?;
31555            }
31556            self.write("$");
31557            self.generate_expression(&e.this)?;
31558            self.write("$");
31559            if let Some(tag) = &e.tag {
31560                self.generate_expression(tag)?;
31561            }
31562            self.write("$");
31563            return Ok(());
31564        }
31565        // Default: output as dollar-tagged
31566        self.write("$");
31567        if let Some(tag) = &e.tag {
31568            self.generate_expression(tag)?;
31569        }
31570        self.write("$");
31571        self.generate_expression(&e.this)?;
31572        self.write("$");
31573        if let Some(tag) = &e.tag {
31574            self.generate_expression(tag)?;
31575        }
31576        self.write("$");
31577        Ok(())
31578    }
31579
31580    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
31581        // HEX_ENCODE(this)
31582        self.write_keyword("HEX_ENCODE");
31583        self.write("(");
31584        self.generate_expression(&e.this)?;
31585        self.write(")");
31586        Ok(())
31587    }
31588
31589    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
31590        // Python: this (kind => expression)
31591        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
31592        match e.this.as_ref() {
31593            Expression::Identifier(id) => self.write(&id.name),
31594            other => self.generate_expression(other)?,
31595        }
31596        self.write(" (");
31597        self.write(&e.kind);
31598        self.write(" => ");
31599        self.generate_expression(&e.expression)?;
31600        self.write(")");
31601        Ok(())
31602    }
31603
31604    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
31605        // HLL(this, expressions...)
31606        self.write_keyword("HLL");
31607        self.write("(");
31608        self.generate_expression(&e.this)?;
31609        for expr in &e.expressions {
31610            self.write(", ");
31611            self.generate_expression(expr)?;
31612        }
31613        self.write(")");
31614        Ok(())
31615    }
31616
31617    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
31618        // Python: IN|OUT|IN OUT
31619        if e.input_.is_some() && e.output.is_some() {
31620            self.write_keyword("IN OUT");
31621        } else if e.input_.is_some() {
31622            self.write_keyword("IN");
31623        } else if e.output.is_some() {
31624            self.write_keyword("OUT");
31625        }
31626        Ok(())
31627    }
31628
31629    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
31630        // Python: INCLUDE this [column_def] [AS alias]
31631        self.write_keyword("INCLUDE");
31632        self.write_space();
31633        self.generate_expression(&e.this)?;
31634        if let Some(column_def) = &e.column_def {
31635            self.write_space();
31636            self.generate_expression(column_def)?;
31637        }
31638        if let Some(alias) = &e.alias {
31639            self.write_space();
31640            self.write_keyword("AS");
31641            self.write_space();
31642            self.write(alias);
31643        }
31644        Ok(())
31645    }
31646
31647    fn generate_index(&mut self, e: &Index) -> Result<()> {
31648        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
31649        if e.unique {
31650            self.write_keyword("UNIQUE");
31651            self.write_space();
31652        }
31653        if e.primary.is_some() {
31654            self.write_keyword("PRIMARY");
31655            self.write_space();
31656        }
31657        if e.amp.is_some() {
31658            self.write_keyword("AMP");
31659            self.write_space();
31660        }
31661        if e.table.is_none() {
31662            self.write_keyword("INDEX");
31663            self.write_space();
31664        }
31665        if let Some(name) = &e.this {
31666            self.generate_expression(name)?;
31667            self.write_space();
31668        }
31669        if let Some(table) = &e.table {
31670            self.write_keyword("ON");
31671            self.write_space();
31672            self.generate_expression(table)?;
31673        }
31674        if !e.params.is_empty() {
31675            self.write("(");
31676            for (i, param) in e.params.iter().enumerate() {
31677                if i > 0 {
31678                    self.write(", ");
31679                }
31680                self.generate_expression(param)?;
31681            }
31682            self.write(")");
31683        }
31684        Ok(())
31685    }
31686
31687    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
31688        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
31689        if let Some(kind) = &e.kind {
31690            self.write(kind);
31691            self.write_space();
31692        }
31693        self.write_keyword("INDEX");
31694        if let Some(this) = &e.this {
31695            self.write_space();
31696            self.generate_expression(this)?;
31697        }
31698        if let Some(index_type) = &e.index_type {
31699            self.write_space();
31700            self.write_keyword("USING");
31701            self.write_space();
31702            self.generate_expression(index_type)?;
31703        }
31704        if !e.expressions.is_empty() {
31705            self.write(" (");
31706            for (i, expr) in e.expressions.iter().enumerate() {
31707                if i > 0 {
31708                    self.write(", ");
31709                }
31710                self.generate_expression(expr)?;
31711            }
31712            self.write(")");
31713        }
31714        for opt in &e.options {
31715            self.write_space();
31716            self.generate_expression(opt)?;
31717        }
31718        Ok(())
31719    }
31720
31721    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
31722        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
31723        if let Some(key_block_size) = &e.key_block_size {
31724            self.write_keyword("KEY_BLOCK_SIZE");
31725            self.write(" = ");
31726            self.generate_expression(key_block_size)?;
31727        } else if let Some(using) = &e.using {
31728            self.write_keyword("USING");
31729            self.write_space();
31730            self.generate_expression(using)?;
31731        } else if let Some(parser) = &e.parser {
31732            self.write_keyword("WITH PARSER");
31733            self.write_space();
31734            self.generate_expression(parser)?;
31735        } else if let Some(comment) = &e.comment {
31736            self.write_keyword("COMMENT");
31737            self.write_space();
31738            self.generate_expression(comment)?;
31739        } else if let Some(visible) = &e.visible {
31740            self.generate_expression(visible)?;
31741        } else if let Some(engine_attr) = &e.engine_attr {
31742            self.write_keyword("ENGINE_ATTRIBUTE");
31743            self.write(" = ");
31744            self.generate_expression(engine_attr)?;
31745        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
31746            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
31747            self.write(" = ");
31748            self.generate_expression(secondary_engine_attr)?;
31749        }
31750        Ok(())
31751    }
31752
31753    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
31754        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
31755        if let Some(using) = &e.using {
31756            self.write_keyword("USING");
31757            self.write_space();
31758            self.generate_expression(using)?;
31759        }
31760        if !e.columns.is_empty() {
31761            self.write("(");
31762            for (i, col) in e.columns.iter().enumerate() {
31763                if i > 0 {
31764                    self.write(", ");
31765                }
31766                self.generate_expression(col)?;
31767            }
31768            self.write(")");
31769        }
31770        if let Some(partition_by) = &e.partition_by {
31771            self.write_space();
31772            self.write_keyword("PARTITION BY");
31773            self.write_space();
31774            self.generate_expression(partition_by)?;
31775        }
31776        if let Some(where_) = &e.where_ {
31777            self.write_space();
31778            self.generate_expression(where_)?;
31779        }
31780        if let Some(include) = &e.include {
31781            self.write_space();
31782            self.write_keyword("INCLUDE");
31783            self.write(" (");
31784            self.generate_expression(include)?;
31785            self.write(")");
31786        }
31787        if let Some(with_storage) = &e.with_storage {
31788            self.write_space();
31789            self.write_keyword("WITH");
31790            self.write(" (");
31791            self.generate_expression(with_storage)?;
31792            self.write(")");
31793        }
31794        if let Some(tablespace) = &e.tablespace {
31795            self.write_space();
31796            self.write_keyword("USING INDEX TABLESPACE");
31797            self.write_space();
31798            self.generate_expression(tablespace)?;
31799        }
31800        Ok(())
31801    }
31802
31803    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
31804        // Python: this INDEX [FOR target] (expressions)
31805        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
31806        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
31807        if let Expression::Identifier(id) = &*e.this {
31808            self.write_keyword(&id.name);
31809        } else {
31810            self.generate_expression(&e.this)?;
31811        }
31812        self.write_space();
31813        self.write_keyword("INDEX");
31814        if let Some(target) = &e.target {
31815            self.write_space();
31816            self.write_keyword("FOR");
31817            self.write_space();
31818            if let Expression::Identifier(id) = &**target {
31819                self.write_keyword(&id.name);
31820            } else {
31821                self.generate_expression(target)?;
31822            }
31823        }
31824        // Always output parentheses (even if empty, e.g. USE INDEX ())
31825        self.write(" (");
31826        for (i, expr) in e.expressions.iter().enumerate() {
31827            if i > 0 {
31828                self.write(", ");
31829            }
31830            self.generate_expression(expr)?;
31831        }
31832        self.write(")");
31833        Ok(())
31834    }
31835
31836    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
31837        // INHERITS (table1, table2, ...)
31838        self.write_keyword("INHERITS");
31839        self.write(" (");
31840        for (i, expr) in e.expressions.iter().enumerate() {
31841            if i > 0 {
31842                self.write(", ");
31843            }
31844            self.generate_expression(expr)?;
31845        }
31846        self.write(")");
31847        Ok(())
31848    }
31849
31850    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
31851        // INPUT(model)
31852        self.write_keyword("INPUT");
31853        self.write("(");
31854        self.generate_expression(&e.this)?;
31855        self.write(")");
31856        Ok(())
31857    }
31858
31859    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
31860        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
31861        if let Some(input_format) = &e.input_format {
31862            self.write_keyword("INPUTFORMAT");
31863            self.write_space();
31864            self.generate_expression(input_format)?;
31865        }
31866        if let Some(output_format) = &e.output_format {
31867            if e.input_format.is_some() {
31868                self.write(" ");
31869            }
31870            self.write_keyword("OUTPUTFORMAT");
31871            self.write_space();
31872            self.generate_expression(output_format)?;
31873        }
31874        Ok(())
31875    }
31876
31877    fn generate_install(&mut self, e: &Install) -> Result<()> {
31878        // [FORCE] INSTALL extension [FROM source]
31879        if e.force.is_some() {
31880            self.write_keyword("FORCE");
31881            self.write_space();
31882        }
31883        self.write_keyword("INSTALL");
31884        self.write_space();
31885        self.generate_expression(&e.this)?;
31886        if let Some(from) = &e.from_ {
31887            self.write_space();
31888            self.write_keyword("FROM");
31889            self.write_space();
31890            self.generate_expression(from)?;
31891        }
31892        Ok(())
31893    }
31894
31895    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
31896        // INTERVAL 'expression' unit
31897        self.write_keyword("INTERVAL");
31898        self.write_space();
31899        // When a unit is specified and the expression is a number,
31900        self.generate_expression(&e.expression)?;
31901        if let Some(unit) = &e.unit {
31902            self.write_space();
31903            self.write(unit);
31904        }
31905        Ok(())
31906    }
31907
31908    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
31909        // unit TO unit (e.g., HOUR TO SECOND)
31910        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
31911        self.write_space();
31912        self.write_keyword("TO");
31913        self.write_space();
31914        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
31915        Ok(())
31916    }
31917
31918    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
31919        // INTO [TEMPORARY|UNLOGGED] table
31920        self.write_keyword("INTO");
31921        if e.temporary {
31922            self.write_keyword(" TEMPORARY");
31923        }
31924        if e.unlogged.is_some() {
31925            self.write_keyword(" UNLOGGED");
31926        }
31927        if let Some(this) = &e.this {
31928            self.write_space();
31929            self.generate_expression(this)?;
31930        }
31931        if !e.expressions.is_empty() {
31932            self.write(" (");
31933            for (i, expr) in e.expressions.iter().enumerate() {
31934                if i > 0 {
31935                    self.write(", ");
31936                }
31937                self.generate_expression(expr)?;
31938            }
31939            self.write(")");
31940        }
31941        Ok(())
31942    }
31943
31944    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
31945        // Python: this expression (e.g., _utf8 'string')
31946        self.generate_expression(&e.this)?;
31947        self.write_space();
31948        self.generate_expression(&e.expression)?;
31949        Ok(())
31950    }
31951
31952    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
31953        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
31954        self.write_keyword("WITH");
31955        if e.no.is_some() {
31956            self.write_keyword(" NO");
31957        }
31958        if e.concurrent.is_some() {
31959            self.write_keyword(" CONCURRENT");
31960        }
31961        self.write_keyword(" ISOLATED LOADING");
31962        if let Some(target) = &e.target {
31963            self.write_space();
31964            self.generate_expression(target)?;
31965        }
31966        Ok(())
31967    }
31968
31969    fn generate_json(&mut self, e: &JSON) -> Result<()> {
31970        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
31971        self.write_keyword("JSON");
31972        if let Some(this) = &e.this {
31973            self.write_space();
31974            self.generate_expression(this)?;
31975        }
31976        if let Some(with_) = &e.with_ {
31977            // Check if it's a truthy boolean
31978            if let Expression::Boolean(b) = with_.as_ref() {
31979                if b.value {
31980                    self.write_keyword(" WITH");
31981                } else {
31982                    self.write_keyword(" WITHOUT");
31983                }
31984            }
31985        }
31986        if e.unique {
31987            self.write_keyword(" UNIQUE KEYS");
31988        }
31989        Ok(())
31990    }
31991
31992    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
31993        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
31994        self.write_keyword("JSON_ARRAY");
31995        self.write("(");
31996        for (i, expr) in e.expressions.iter().enumerate() {
31997            if i > 0 {
31998                self.write(", ");
31999            }
32000            self.generate_expression(expr)?;
32001        }
32002        if let Some(null_handling) = &e.null_handling {
32003            self.write_space();
32004            self.generate_expression(null_handling)?;
32005        }
32006        if let Some(return_type) = &e.return_type {
32007            self.write_space();
32008            self.write_keyword("RETURNING");
32009            self.write_space();
32010            self.generate_expression(return_type)?;
32011        }
32012        if e.strict.is_some() {
32013            self.write_space();
32014            self.write_keyword("STRICT");
32015        }
32016        self.write(")");
32017        Ok(())
32018    }
32019
32020    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
32021        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
32022        self.write_keyword("JSON_ARRAYAGG");
32023        self.write("(");
32024        self.generate_expression(&e.this)?;
32025        if let Some(order) = &e.order {
32026            self.write_space();
32027            // Order is stored as an OrderBy expression
32028            if let Expression::OrderBy(ob) = order.as_ref() {
32029                self.write_keyword("ORDER BY");
32030                self.write_space();
32031                for (i, ord) in ob.expressions.iter().enumerate() {
32032                    if i > 0 {
32033                        self.write(", ");
32034                    }
32035                    self.generate_ordered(ord)?;
32036                }
32037            } else {
32038                // Fallback: generate the expression directly
32039                self.generate_expression(order)?;
32040            }
32041        }
32042        if let Some(null_handling) = &e.null_handling {
32043            self.write_space();
32044            self.generate_expression(null_handling)?;
32045        }
32046        if let Some(return_type) = &e.return_type {
32047            self.write_space();
32048            self.write_keyword("RETURNING");
32049            self.write_space();
32050            self.generate_expression(return_type)?;
32051        }
32052        if e.strict.is_some() {
32053            self.write_space();
32054            self.write_keyword("STRICT");
32055        }
32056        self.write(")");
32057        Ok(())
32058    }
32059
32060    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
32061        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
32062        self.write_keyword("JSON_OBJECTAGG");
32063        self.write("(");
32064        for (i, expr) in e.expressions.iter().enumerate() {
32065            if i > 0 {
32066                self.write(", ");
32067            }
32068            self.generate_expression(expr)?;
32069        }
32070        if let Some(null_handling) = &e.null_handling {
32071            self.write_space();
32072            self.generate_expression(null_handling)?;
32073        }
32074        if let Some(unique_keys) = &e.unique_keys {
32075            self.write_space();
32076            if let Expression::Boolean(b) = unique_keys.as_ref() {
32077                if b.value {
32078                    self.write_keyword("WITH UNIQUE KEYS");
32079                } else {
32080                    self.write_keyword("WITHOUT UNIQUE KEYS");
32081                }
32082            }
32083        }
32084        if let Some(return_type) = &e.return_type {
32085            self.write_space();
32086            self.write_keyword("RETURNING");
32087            self.write_space();
32088            self.generate_expression(return_type)?;
32089        }
32090        self.write(")");
32091        Ok(())
32092    }
32093
32094    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
32095        // JSON_ARRAY_APPEND(this, path, value, ...)
32096        self.write_keyword("JSON_ARRAY_APPEND");
32097        self.write("(");
32098        self.generate_expression(&e.this)?;
32099        for expr in &e.expressions {
32100            self.write(", ");
32101            self.generate_expression(expr)?;
32102        }
32103        self.write(")");
32104        Ok(())
32105    }
32106
32107    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
32108        // JSON_ARRAY_CONTAINS(this, expression)
32109        self.write_keyword("JSON_ARRAY_CONTAINS");
32110        self.write("(");
32111        self.generate_expression(&e.this)?;
32112        self.write(", ");
32113        self.generate_expression(&e.expression)?;
32114        self.write(")");
32115        Ok(())
32116    }
32117
32118    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
32119        // JSON_ARRAY_INSERT(this, path, value, ...)
32120        self.write_keyword("JSON_ARRAY_INSERT");
32121        self.write("(");
32122        self.generate_expression(&e.this)?;
32123        for expr in &e.expressions {
32124            self.write(", ");
32125            self.generate_expression(expr)?;
32126        }
32127        self.write(")");
32128        Ok(())
32129    }
32130
32131    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
32132        // JSONB_EXISTS(this, path)
32133        self.write_keyword("JSONB_EXISTS");
32134        self.write("(");
32135        self.generate_expression(&e.this)?;
32136        if let Some(path) = &e.path {
32137            self.write(", ");
32138            self.generate_expression(path)?;
32139        }
32140        self.write(")");
32141        Ok(())
32142    }
32143
32144    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
32145        // JSONB_EXTRACT_SCALAR(this, expression)
32146        self.write_keyword("JSONB_EXTRACT_SCALAR");
32147        self.write("(");
32148        self.generate_expression(&e.this)?;
32149        self.write(", ");
32150        self.generate_expression(&e.expression)?;
32151        self.write(")");
32152        Ok(())
32153    }
32154
32155    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
32156        // JSONB_OBJECT_AGG(this, expression)
32157        self.write_keyword("JSONB_OBJECT_AGG");
32158        self.write("(");
32159        self.generate_expression(&e.this)?;
32160        self.write(", ");
32161        self.generate_expression(&e.expression)?;
32162        self.write(")");
32163        Ok(())
32164    }
32165
32166    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
32167        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
32168        if let Some(nested_schema) = &e.nested_schema {
32169            self.write_keyword("NESTED");
32170            if let Some(path) = &e.path {
32171                self.write_space();
32172                self.write_keyword("PATH");
32173                self.write_space();
32174                self.generate_expression(path)?;
32175            }
32176            self.write_space();
32177            self.generate_expression(nested_schema)?;
32178        } else {
32179            if let Some(this) = &e.this {
32180                self.generate_expression(this)?;
32181            }
32182            if let Some(kind) = &e.kind {
32183                self.write_space();
32184                self.write(kind);
32185            }
32186            if e.format_json {
32187                self.write_space();
32188                self.write_keyword("FORMAT JSON");
32189            }
32190            if let Some(path) = &e.path {
32191                self.write_space();
32192                self.write_keyword("PATH");
32193                self.write_space();
32194                self.generate_expression(path)?;
32195            }
32196            if e.ordinality.is_some() {
32197                self.write_keyword(" FOR ORDINALITY");
32198            }
32199        }
32200        Ok(())
32201    }
32202
32203    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
32204        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
32205        self.write_keyword("JSON_EXISTS");
32206        self.write("(");
32207        self.generate_expression(&e.this)?;
32208        if let Some(path) = &e.path {
32209            self.write(", ");
32210            self.generate_expression(path)?;
32211        }
32212        if let Some(passing) = &e.passing {
32213            self.write_space();
32214            self.write_keyword("PASSING");
32215            self.write_space();
32216            self.generate_expression(passing)?;
32217        }
32218        if let Some(on_condition) = &e.on_condition {
32219            self.write_space();
32220            self.generate_expression(on_condition)?;
32221        }
32222        self.write(")");
32223        Ok(())
32224    }
32225
32226    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
32227        self.generate_expression(&e.this)?;
32228        self.write(".:");
32229        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
32230        // wrap the entire type string in double quotes.
32231        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
32232        if Self::data_type_has_nested_expressions(&e.to) {
32233            // Generate the data type to a temporary string buffer, then wrap in quotes
32234            let saved = std::mem::take(&mut self.output);
32235            self.generate_data_type(&e.to)?;
32236            let type_sql = std::mem::replace(&mut self.output, saved);
32237            self.write("\"");
32238            self.write(&type_sql);
32239            self.write("\"");
32240        } else {
32241            self.generate_data_type(&e.to)?;
32242        }
32243        Ok(())
32244    }
32245
32246    /// Check if a DataType has nested type expressions (sub-types).
32247    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
32248    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
32249        matches!(
32250            dt,
32251            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
32252        )
32253    }
32254
32255    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
32256        // JSON_EXTRACT_ARRAY(this, expression)
32257        self.write_keyword("JSON_EXTRACT_ARRAY");
32258        self.write("(");
32259        self.generate_expression(&e.this)?;
32260        if let Some(expr) = &e.expression {
32261            self.write(", ");
32262            self.generate_expression(expr)?;
32263        }
32264        self.write(")");
32265        Ok(())
32266    }
32267
32268    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
32269        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
32270        if let Some(option) = &e.option {
32271            self.generate_expression(option)?;
32272            self.write_space();
32273        }
32274        self.write_keyword("QUOTES");
32275        if e.scalar.is_some() {
32276            self.write_keyword(" SCALAR_ONLY");
32277        }
32278        Ok(())
32279    }
32280
32281    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
32282        // JSON_EXTRACT_SCALAR(this, expression)
32283        self.write_keyword("JSON_EXTRACT_SCALAR");
32284        self.write("(");
32285        self.generate_expression(&e.this)?;
32286        self.write(", ");
32287        self.generate_expression(&e.expression)?;
32288        self.write(")");
32289        Ok(())
32290    }
32291
32292    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
32293        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
32294        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
32295        // Otherwise output JSON_EXTRACT(this, expression)
32296        if e.variant_extract.is_some() {
32297            use crate::dialects::DialectType;
32298            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
32299                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
32300                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
32301                // bracket notation: c:["x-y"] instead of c:x-y
32302                self.generate_expression(&e.this)?;
32303                self.write(":");
32304                match e.expression.as_ref() {
32305                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32306                        let Literal::String(s) = lit.as_ref() else {
32307                            unreachable!()
32308                        };
32309                        self.write_databricks_json_path(s);
32310                    }
32311                    _ => {
32312                        // Fallback: generate as-is (shouldn't happen in typical cases)
32313                        self.generate_expression(&e.expression)?;
32314                    }
32315                }
32316            } else {
32317                // Snowflake and others: use GET_PATH(col, 'path')
32318                self.write_keyword("GET_PATH");
32319                self.write("(");
32320                self.generate_expression(&e.this)?;
32321                self.write(", ");
32322                self.generate_expression(&e.expression)?;
32323                self.write(")");
32324            }
32325        } else {
32326            self.write_keyword("JSON_EXTRACT");
32327            self.write("(");
32328            self.generate_expression(&e.this)?;
32329            self.write(", ");
32330            self.generate_expression(&e.expression)?;
32331            for expr in &e.expressions {
32332                self.write(", ");
32333                self.generate_expression(expr)?;
32334            }
32335            self.write(")");
32336        }
32337        Ok(())
32338    }
32339
32340    /// Write a Databricks JSON colon-path, using bracket notation for keys
32341    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
32342    /// Safe identifier regex: ^[_a-zA-Z]\w*$
32343    fn write_databricks_json_path(&mut self, path: &str) {
32344        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
32345        // it was already formatted by the parser - output as-is
32346        if path.starts_with("[\"") || path.starts_with("['") {
32347            self.write(path);
32348            return;
32349        }
32350        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
32351        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
32352        // e.g., "x-y" -> ["x-y"]
32353        let mut first = true;
32354        for segment in path.split('.') {
32355            if !first {
32356                self.write(".");
32357            }
32358            first = false;
32359            // Check if there's a bracket subscript in this segment: "items[0]"
32360            if let Some(bracket_pos) = segment.find('[') {
32361                let key = &segment[..bracket_pos];
32362                let subscript = &segment[bracket_pos..];
32363                if key.is_empty() {
32364                    // Bracket notation at start of segment (e.g., already formatted)
32365                    self.write(segment);
32366                } else if Self::is_safe_json_path_key(key) {
32367                    self.write(key);
32368                    self.write(subscript);
32369                } else {
32370                    self.write("[\"");
32371                    self.write(key);
32372                    self.write("\"]");
32373                    self.write(subscript);
32374                }
32375            } else if Self::is_safe_json_path_key(segment) {
32376                self.write(segment);
32377            } else {
32378                self.write("[\"");
32379                self.write(segment);
32380                self.write("\"]");
32381            }
32382        }
32383    }
32384
32385    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
32386    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
32387    fn is_safe_json_path_key(key: &str) -> bool {
32388        if key.is_empty() {
32389            return false;
32390        }
32391        let mut chars = key.chars();
32392        let first = chars.next().unwrap();
32393        if first != '_' && !first.is_ascii_alphabetic() {
32394            return false;
32395        }
32396        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
32397    }
32398
32399    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
32400        // Output: {expr} FORMAT JSON
32401        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
32402        if let Some(this) = &e.this {
32403            self.generate_expression(this)?;
32404            self.write_space();
32405        }
32406        self.write_keyword("FORMAT JSON");
32407        Ok(())
32408    }
32409
32410    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
32411        // key: value (for JSON objects)
32412        self.generate_expression(&e.this)?;
32413        self.write(": ");
32414        self.generate_expression(&e.expression)?;
32415        Ok(())
32416    }
32417
32418    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
32419        // JSON_KEYS(this, expression, expressions...)
32420        self.write_keyword("JSON_KEYS");
32421        self.write("(");
32422        self.generate_expression(&e.this)?;
32423        if let Some(expr) = &e.expression {
32424            self.write(", ");
32425            self.generate_expression(expr)?;
32426        }
32427        for expr in &e.expressions {
32428            self.write(", ");
32429            self.generate_expression(expr)?;
32430        }
32431        self.write(")");
32432        Ok(())
32433    }
32434
32435    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
32436        // JSON_KEYS(this, expression)
32437        self.write_keyword("JSON_KEYS");
32438        self.write("(");
32439        self.generate_expression(&e.this)?;
32440        if let Some(expr) = &e.expression {
32441            self.write(", ");
32442            self.generate_expression(expr)?;
32443        }
32444        self.write(")");
32445        Ok(())
32446    }
32447
32448    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
32449        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
32450        // The path components are concatenated without spaces
32451        let mut path_str = String::new();
32452        for expr in &e.expressions {
32453            match expr {
32454                Expression::JSONPathRoot(_) => {
32455                    path_str.push('$');
32456                }
32457                Expression::JSONPathKey(k) => {
32458                    // .key or ."key" (quote if key has special characters)
32459                    if let Expression::Literal(lit) = k.this.as_ref() {
32460                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
32461                            path_str.push('.');
32462                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
32463                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
32464                            if needs_quoting {
32465                                path_str.push('"');
32466                                path_str.push_str(s);
32467                                path_str.push('"');
32468                            } else {
32469                                path_str.push_str(s);
32470                            }
32471                        }
32472                    }
32473                }
32474                Expression::JSONPathSubscript(s) => {
32475                    // [index]
32476                    if let Expression::Literal(lit) = s.this.as_ref() {
32477                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
32478                            path_str.push('[');
32479                            path_str.push_str(n);
32480                            path_str.push(']');
32481                        }
32482                    }
32483                }
32484                _ => {
32485                    // For other path parts, try to generate them
32486                    let mut temp_gen = Self::with_arc_config(self.config.clone());
32487                    temp_gen.generate_expression(expr)?;
32488                    path_str.push_str(&temp_gen.output);
32489                }
32490            }
32491        }
32492        // Output as quoted string
32493        self.write("'");
32494        self.write(&path_str);
32495        self.write("'");
32496        Ok(())
32497    }
32498
32499    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
32500        // JSON path filter: ?(predicate)
32501        self.write("?(");
32502        self.generate_expression(&e.this)?;
32503        self.write(")");
32504        Ok(())
32505    }
32506
32507    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
32508        // JSON path key: .key or ["key"]
32509        self.write(".");
32510        self.generate_expression(&e.this)?;
32511        Ok(())
32512    }
32513
32514    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
32515        // JSON path recursive descent: ..
32516        self.write("..");
32517        if let Some(this) = &e.this {
32518            self.generate_expression(this)?;
32519        }
32520        Ok(())
32521    }
32522
32523    fn generate_json_path_root(&mut self) -> Result<()> {
32524        // JSON path root: $
32525        self.write("$");
32526        Ok(())
32527    }
32528
32529    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
32530        // JSON path script: (expression)
32531        self.write("(");
32532        self.generate_expression(&e.this)?;
32533        self.write(")");
32534        Ok(())
32535    }
32536
32537    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
32538        // JSON path selector: *
32539        self.generate_expression(&e.this)?;
32540        Ok(())
32541    }
32542
32543    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
32544        // JSON path slice: [start:end:step]
32545        self.write("[");
32546        if let Some(start) = &e.start {
32547            self.generate_expression(start)?;
32548        }
32549        self.write(":");
32550        if let Some(end) = &e.end {
32551            self.generate_expression(end)?;
32552        }
32553        if let Some(step) = &e.step {
32554            self.write(":");
32555            self.generate_expression(step)?;
32556        }
32557        self.write("]");
32558        Ok(())
32559    }
32560
32561    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
32562        // JSON path subscript: [index] or [*]
32563        self.write("[");
32564        self.generate_expression(&e.this)?;
32565        self.write("]");
32566        Ok(())
32567    }
32568
32569    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
32570        // JSON path union: [key1, key2, ...]
32571        self.write("[");
32572        for (i, expr) in e.expressions.iter().enumerate() {
32573            if i > 0 {
32574                self.write(", ");
32575            }
32576            self.generate_expression(expr)?;
32577        }
32578        self.write("]");
32579        Ok(())
32580    }
32581
32582    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
32583        // JSON_REMOVE(this, path1, path2, ...)
32584        self.write_keyword("JSON_REMOVE");
32585        self.write("(");
32586        self.generate_expression(&e.this)?;
32587        for expr in &e.expressions {
32588            self.write(", ");
32589            self.generate_expression(expr)?;
32590        }
32591        self.write(")");
32592        Ok(())
32593    }
32594
32595    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
32596        // COLUMNS(col1 type, col2 type, ...)
32597        // When pretty printing and content is too wide, format with each column on a separate line
32598        self.write_keyword("COLUMNS");
32599        self.write("(");
32600
32601        if self.config.pretty && !e.expressions.is_empty() {
32602            // First, generate all expressions into strings to check width
32603            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
32604            for expr in &e.expressions {
32605                let mut temp_gen = Generator::with_arc_config(self.config.clone());
32606                temp_gen.generate_expression(expr)?;
32607                expr_strings.push(temp_gen.output);
32608            }
32609
32610            // Check if total width exceeds max_text_width
32611            if self.too_wide(&expr_strings) {
32612                // Pretty print: each column on its own line
32613                self.write_newline();
32614                self.indent_level += 1;
32615                for (i, expr_str) in expr_strings.iter().enumerate() {
32616                    if i > 0 {
32617                        self.write(",");
32618                        self.write_newline();
32619                    }
32620                    self.write_indent();
32621                    self.write(expr_str);
32622                }
32623                self.write_newline();
32624                self.indent_level -= 1;
32625                self.write_indent();
32626            } else {
32627                // Compact: all on one line
32628                for (i, expr_str) in expr_strings.iter().enumerate() {
32629                    if i > 0 {
32630                        self.write(", ");
32631                    }
32632                    self.write(expr_str);
32633                }
32634            }
32635        } else {
32636            // Non-pretty mode: compact format
32637            for (i, expr) in e.expressions.iter().enumerate() {
32638                if i > 0 {
32639                    self.write(", ");
32640                }
32641                self.generate_expression(expr)?;
32642            }
32643        }
32644        self.write(")");
32645        Ok(())
32646    }
32647
32648    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
32649        // JSON_SET(this, path, value, ...)
32650        self.write_keyword("JSON_SET");
32651        self.write("(");
32652        self.generate_expression(&e.this)?;
32653        for expr in &e.expressions {
32654            self.write(", ");
32655            self.generate_expression(expr)?;
32656        }
32657        self.write(")");
32658        Ok(())
32659    }
32660
32661    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
32662        // JSON_STRIP_NULLS(this, expression)
32663        self.write_keyword("JSON_STRIP_NULLS");
32664        self.write("(");
32665        self.generate_expression(&e.this)?;
32666        if let Some(expr) = &e.expression {
32667            self.write(", ");
32668            self.generate_expression(expr)?;
32669        }
32670        self.write(")");
32671        Ok(())
32672    }
32673
32674    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
32675        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
32676        self.write_keyword("JSON_TABLE");
32677        self.write("(");
32678        self.generate_expression(&e.this)?;
32679        if let Some(path) = &e.path {
32680            self.write(", ");
32681            self.generate_expression(path)?;
32682        }
32683        if let Some(error_handling) = &e.error_handling {
32684            self.write_space();
32685            self.generate_expression(error_handling)?;
32686        }
32687        if let Some(empty_handling) = &e.empty_handling {
32688            self.write_space();
32689            self.generate_expression(empty_handling)?;
32690        }
32691        if let Some(schema) = &e.schema {
32692            self.write_space();
32693            self.generate_expression(schema)?;
32694        }
32695        self.write(")");
32696        Ok(())
32697    }
32698
32699    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
32700        // JSON_TYPE(this)
32701        self.write_keyword("JSON_TYPE");
32702        self.write("(");
32703        self.generate_expression(&e.this)?;
32704        self.write(")");
32705        Ok(())
32706    }
32707
32708    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
32709        // JSON_VALUE(this, path RETURNING type ON condition)
32710        self.write_keyword("JSON_VALUE");
32711        self.write("(");
32712        self.generate_expression(&e.this)?;
32713        if let Some(path) = &e.path {
32714            self.write(", ");
32715            self.generate_expression(path)?;
32716        }
32717        if let Some(returning) = &e.returning {
32718            self.write_space();
32719            self.write_keyword("RETURNING");
32720            self.write_space();
32721            self.generate_expression(returning)?;
32722        }
32723        if let Some(on_condition) = &e.on_condition {
32724            self.write_space();
32725            self.generate_expression(on_condition)?;
32726        }
32727        self.write(")");
32728        Ok(())
32729    }
32730
32731    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
32732        // JSON_VALUE_ARRAY(this)
32733        self.write_keyword("JSON_VALUE_ARRAY");
32734        self.write("(");
32735        self.generate_expression(&e.this)?;
32736        self.write(")");
32737        Ok(())
32738    }
32739
32740    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
32741        // JAROWINKLER_SIMILARITY(str1, str2)
32742        self.write_keyword("JAROWINKLER_SIMILARITY");
32743        self.write("(");
32744        self.generate_expression(&e.this)?;
32745        self.write(", ");
32746        self.generate_expression(&e.expression)?;
32747        self.write(")");
32748        Ok(())
32749    }
32750
32751    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
32752        // Python: this(expressions)
32753        self.generate_expression(&e.this)?;
32754        self.write("(");
32755        for (i, expr) in e.expressions.iter().enumerate() {
32756            if i > 0 {
32757                self.write(", ");
32758            }
32759            self.generate_expression(expr)?;
32760        }
32761        self.write(")");
32762        Ok(())
32763    }
32764
32765    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
32766        // Python: {no}{local}{dual}{before}{after}JOURNAL
32767        if e.no.is_some() {
32768            self.write_keyword("NO ");
32769        }
32770        if let Some(local) = &e.local {
32771            self.generate_expression(local)?;
32772            self.write_space();
32773        }
32774        if e.dual.is_some() {
32775            self.write_keyword("DUAL ");
32776        }
32777        if e.before.is_some() {
32778            self.write_keyword("BEFORE ");
32779        }
32780        if e.after.is_some() {
32781            self.write_keyword("AFTER ");
32782        }
32783        self.write_keyword("JOURNAL");
32784        Ok(())
32785    }
32786
32787    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
32788        // LANGUAGE language_name
32789        self.write_keyword("LANGUAGE");
32790        self.write_space();
32791        self.generate_expression(&e.this)?;
32792        Ok(())
32793    }
32794
32795    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
32796        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
32797        if e.view.is_some() {
32798            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
32799            self.write_keyword("LATERAL VIEW");
32800            if e.outer.is_some() {
32801                self.write_space();
32802                self.write_keyword("OUTER");
32803            }
32804            self.write_space();
32805            self.generate_expression(&e.this)?;
32806            if let Some(alias) = &e.alias {
32807                self.write_space();
32808                self.write(alias);
32809            }
32810        } else {
32811            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
32812            self.write_keyword("LATERAL");
32813            self.write_space();
32814            self.generate_expression(&e.this)?;
32815            if e.ordinality.is_some() {
32816                self.write_space();
32817                self.write_keyword("WITH ORDINALITY");
32818            }
32819            if let Some(alias) = &e.alias {
32820                self.write_space();
32821                self.write_keyword("AS");
32822                self.write_space();
32823                self.write(alias);
32824                if !e.column_aliases.is_empty() {
32825                    self.write("(");
32826                    for (i, col) in e.column_aliases.iter().enumerate() {
32827                        if i > 0 {
32828                            self.write(", ");
32829                        }
32830                        self.write(col);
32831                    }
32832                    self.write(")");
32833                }
32834            }
32835        }
32836        Ok(())
32837    }
32838
32839    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
32840        // Python: LIKE this [options]
32841        self.write_keyword("LIKE");
32842        self.write_space();
32843        self.generate_expression(&e.this)?;
32844        for expr in &e.expressions {
32845            self.write_space();
32846            self.generate_expression(expr)?;
32847        }
32848        Ok(())
32849    }
32850
32851    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
32852        self.write_keyword("LIMIT");
32853        self.write_space();
32854        self.write_limit_expr(&e.this)?;
32855        if e.percent {
32856            self.write_space();
32857            self.write_keyword("PERCENT");
32858        }
32859        // Emit any comments that were captured from before the LIMIT keyword
32860        for comment in &e.comments {
32861            self.write(" ");
32862            self.write_formatted_comment(comment);
32863        }
32864        Ok(())
32865    }
32866
32867    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
32868        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
32869        if e.percent.is_some() {
32870            self.write_keyword(" PERCENT");
32871        }
32872        if e.rows.is_some() {
32873            self.write_keyword(" ROWS");
32874        }
32875        if e.with_ties.is_some() {
32876            self.write_keyword(" WITH TIES");
32877        } else if e.rows.is_some() {
32878            self.write_keyword(" ONLY");
32879        }
32880        Ok(())
32881    }
32882
32883    fn generate_list(&mut self, e: &List) -> Result<()> {
32884        use crate::dialects::DialectType;
32885        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
32886
32887        // Check if this is a subquery-based list (LIST(SELECT ...))
32888        if e.expressions.len() == 1 {
32889            if let Expression::Select(_) = &e.expressions[0] {
32890                self.write_keyword("LIST");
32891                self.write("(");
32892                self.generate_expression(&e.expressions[0])?;
32893                self.write(")");
32894                return Ok(());
32895            }
32896        }
32897
32898        // For Materialize, output as LIST[expr, expr, ...]
32899        if is_materialize {
32900            self.write_keyword("LIST");
32901            self.write("[");
32902            for (i, expr) in e.expressions.iter().enumerate() {
32903                if i > 0 {
32904                    self.write(", ");
32905                }
32906                self.generate_expression(expr)?;
32907            }
32908            self.write("]");
32909        } else {
32910            // For other dialects, output as LIST(expr, expr, ...)
32911            self.write_keyword("LIST");
32912            self.write("(");
32913            for (i, expr) in e.expressions.iter().enumerate() {
32914                if i > 0 {
32915                    self.write(", ");
32916                }
32917                self.generate_expression(expr)?;
32918            }
32919            self.write(")");
32920        }
32921        Ok(())
32922    }
32923
32924    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
32925        // Check if this is a subquery-based map (MAP(SELECT ...))
32926        if let Expression::Select(_) = &*e.this {
32927            self.write_keyword("MAP");
32928            self.write("(");
32929            self.generate_expression(&e.this)?;
32930            self.write(")");
32931            return Ok(());
32932        }
32933
32934        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
32935
32936        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
32937        self.write_keyword("MAP");
32938        if is_duckdb {
32939            self.write(" {");
32940        } else {
32941            self.write("[");
32942        }
32943        if let Expression::Struct(s) = &*e.this {
32944            for (i, (_, expr)) in s.fields.iter().enumerate() {
32945                if i > 0 {
32946                    self.write(", ");
32947                }
32948                if let Expression::PropertyEQ(op) = expr {
32949                    self.generate_expression(&op.left)?;
32950                    if is_duckdb {
32951                        self.write(": ");
32952                    } else {
32953                        self.write(" => ");
32954                    }
32955                    self.generate_expression(&op.right)?;
32956                } else {
32957                    self.generate_expression(expr)?;
32958                }
32959            }
32960        }
32961        if is_duckdb {
32962            self.write("}");
32963        } else {
32964            self.write("]");
32965        }
32966        Ok(())
32967    }
32968
32969    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
32970        // Python: LOCALTIME or LOCALTIME(precision)
32971        self.write_keyword("LOCALTIME");
32972        if let Some(precision) = &e.this {
32973            self.write("(");
32974            self.generate_expression(precision)?;
32975            self.write(")");
32976        }
32977        Ok(())
32978    }
32979
32980    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
32981        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
32982        self.write_keyword("LOCALTIMESTAMP");
32983        if let Some(precision) = &e.this {
32984            self.write("(");
32985            self.generate_expression(precision)?;
32986            self.write(")");
32987        }
32988        Ok(())
32989    }
32990
32991    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
32992        // LOCATION 'path'
32993        self.write_keyword("LOCATION");
32994        self.write_space();
32995        self.generate_expression(&e.this)?;
32996        Ok(())
32997    }
32998
32999    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
33000        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
33001        if e.update.is_some() {
33002            if e.key.is_some() {
33003                self.write_keyword("FOR NO KEY UPDATE");
33004            } else {
33005                self.write_keyword("FOR UPDATE");
33006            }
33007        } else {
33008            if e.key.is_some() {
33009                self.write_keyword("FOR KEY SHARE");
33010            } else {
33011                self.write_keyword("FOR SHARE");
33012            }
33013        }
33014        if !e.expressions.is_empty() {
33015            self.write_keyword(" OF ");
33016            for (i, expr) in e.expressions.iter().enumerate() {
33017                if i > 0 {
33018                    self.write(", ");
33019                }
33020                self.generate_expression(expr)?;
33021            }
33022        }
33023        // Handle wait option following Python sqlglot convention:
33024        // - Boolean(true) -> NOWAIT
33025        // - Boolean(false) -> SKIP LOCKED
33026        // - Literal (number) -> WAIT n
33027        if let Some(wait) = &e.wait {
33028            match wait.as_ref() {
33029                Expression::Boolean(b) => {
33030                    if b.value {
33031                        self.write_keyword(" NOWAIT");
33032                    } else {
33033                        self.write_keyword(" SKIP LOCKED");
33034                    }
33035                }
33036                _ => {
33037                    // It's a literal (number), output WAIT n
33038                    self.write_keyword(" WAIT ");
33039                    self.generate_expression(wait)?;
33040                }
33041            }
33042        }
33043        Ok(())
33044    }
33045
33046    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
33047        // LOCK property
33048        self.write_keyword("LOCK");
33049        self.write_space();
33050        self.generate_expression(&e.this)?;
33051        Ok(())
33052    }
33053
33054    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
33055        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
33056        self.write_keyword("LOCKING");
33057        self.write_space();
33058        self.write(&e.kind);
33059        if let Some(this) = &e.this {
33060            self.write_space();
33061            self.generate_expression(this)?;
33062        }
33063        if let Some(for_or_in) = &e.for_or_in {
33064            self.write_space();
33065            self.generate_expression(for_or_in)?;
33066        }
33067        if let Some(lock_type) = &e.lock_type {
33068            self.write_space();
33069            self.generate_expression(lock_type)?;
33070        }
33071        if e.override_.is_some() {
33072            self.write_keyword(" OVERRIDE");
33073        }
33074        Ok(())
33075    }
33076
33077    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
33078        // this expression
33079        self.generate_expression(&e.this)?;
33080        self.write_space();
33081        self.generate_expression(&e.expression)?;
33082        Ok(())
33083    }
33084
33085    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
33086        // [NO] LOG
33087        if e.no.is_some() {
33088            self.write_keyword("NO ");
33089        }
33090        self.write_keyword("LOG");
33091        Ok(())
33092    }
33093
33094    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
33095        // MD5(this, expressions...)
33096        self.write_keyword("MD5");
33097        self.write("(");
33098        self.generate_expression(&e.this)?;
33099        for expr in &e.expressions {
33100            self.write(", ");
33101            self.generate_expression(expr)?;
33102        }
33103        self.write(")");
33104        Ok(())
33105    }
33106
33107    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
33108        // ML.FORECAST(model, [params])
33109        self.write_keyword("ML.FORECAST");
33110        self.write("(");
33111        self.generate_expression(&e.this)?;
33112        if let Some(expression) = &e.expression {
33113            self.write(", ");
33114            self.generate_expression(expression)?;
33115        }
33116        if let Some(params) = &e.params_struct {
33117            self.write(", ");
33118            self.generate_expression(params)?;
33119        }
33120        self.write(")");
33121        Ok(())
33122    }
33123
33124    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
33125        // ML.TRANSLATE(model, input, [params])
33126        self.write_keyword("ML.TRANSLATE");
33127        self.write("(");
33128        self.generate_expression(&e.this)?;
33129        self.write(", ");
33130        self.generate_expression(&e.expression)?;
33131        if let Some(params) = &e.params_struct {
33132            self.write(", ");
33133            self.generate_expression(params)?;
33134        }
33135        self.write(")");
33136        Ok(())
33137    }
33138
33139    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
33140        // MAKE_INTERVAL(years => x, months => y, ...)
33141        self.write_keyword("MAKE_INTERVAL");
33142        self.write("(");
33143        let mut first = true;
33144        if let Some(year) = &e.year {
33145            self.write("years => ");
33146            self.generate_expression(year)?;
33147            first = false;
33148        }
33149        if let Some(month) = &e.month {
33150            if !first {
33151                self.write(", ");
33152            }
33153            self.write("months => ");
33154            self.generate_expression(month)?;
33155            first = false;
33156        }
33157        if let Some(week) = &e.week {
33158            if !first {
33159                self.write(", ");
33160            }
33161            self.write("weeks => ");
33162            self.generate_expression(week)?;
33163            first = false;
33164        }
33165        if let Some(day) = &e.day {
33166            if !first {
33167                self.write(", ");
33168            }
33169            self.write("days => ");
33170            self.generate_expression(day)?;
33171            first = false;
33172        }
33173        if let Some(hour) = &e.hour {
33174            if !first {
33175                self.write(", ");
33176            }
33177            self.write("hours => ");
33178            self.generate_expression(hour)?;
33179            first = false;
33180        }
33181        if let Some(minute) = &e.minute {
33182            if !first {
33183                self.write(", ");
33184            }
33185            self.write("mins => ");
33186            self.generate_expression(minute)?;
33187            first = false;
33188        }
33189        if let Some(second) = &e.second {
33190            if !first {
33191                self.write(", ");
33192            }
33193            self.write("secs => ");
33194            self.generate_expression(second)?;
33195        }
33196        self.write(")");
33197        Ok(())
33198    }
33199
33200    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
33201        // MANHATTAN_DISTANCE(vector1, vector2)
33202        self.write_keyword("MANHATTAN_DISTANCE");
33203        self.write("(");
33204        self.generate_expression(&e.this)?;
33205        self.write(", ");
33206        self.generate_expression(&e.expression)?;
33207        self.write(")");
33208        Ok(())
33209    }
33210
33211    fn generate_map(&mut self, e: &Map) -> Result<()> {
33212        // MAP(key1, value1, key2, value2, ...)
33213        self.write_keyword("MAP");
33214        self.write("(");
33215        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
33216            if i > 0 {
33217                self.write(", ");
33218            }
33219            self.generate_expression(key)?;
33220            self.write(", ");
33221            self.generate_expression(value)?;
33222        }
33223        self.write(")");
33224        Ok(())
33225    }
33226
33227    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
33228        // MAP_CAT(map1, map2)
33229        self.write_keyword("MAP_CAT");
33230        self.write("(");
33231        self.generate_expression(&e.this)?;
33232        self.write(", ");
33233        self.generate_expression(&e.expression)?;
33234        self.write(")");
33235        Ok(())
33236    }
33237
33238    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
33239        // MAP_DELETE(map, key1, key2, ...)
33240        self.write_keyword("MAP_DELETE");
33241        self.write("(");
33242        self.generate_expression(&e.this)?;
33243        for expr in &e.expressions {
33244            self.write(", ");
33245            self.generate_expression(expr)?;
33246        }
33247        self.write(")");
33248        Ok(())
33249    }
33250
33251    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
33252        // MAP_INSERT(map, key, value, [update_flag])
33253        self.write_keyword("MAP_INSERT");
33254        self.write("(");
33255        self.generate_expression(&e.this)?;
33256        if let Some(key) = &e.key {
33257            self.write(", ");
33258            self.generate_expression(key)?;
33259        }
33260        if let Some(value) = &e.value {
33261            self.write(", ");
33262            self.generate_expression(value)?;
33263        }
33264        if let Some(update_flag) = &e.update_flag {
33265            self.write(", ");
33266            self.generate_expression(update_flag)?;
33267        }
33268        self.write(")");
33269        Ok(())
33270    }
33271
33272    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
33273        // MAP_PICK(map, key1, key2, ...)
33274        self.write_keyword("MAP_PICK");
33275        self.write("(");
33276        self.generate_expression(&e.this)?;
33277        for expr in &e.expressions {
33278            self.write(", ");
33279            self.generate_expression(expr)?;
33280        }
33281        self.write(")");
33282        Ok(())
33283    }
33284
33285    fn generate_masking_policy_column_constraint(
33286        &mut self,
33287        e: &MaskingPolicyColumnConstraint,
33288    ) -> Result<()> {
33289        // Python: MASKING POLICY name [USING (cols)]
33290        self.write_keyword("MASKING POLICY");
33291        self.write_space();
33292        self.generate_expression(&e.this)?;
33293        if !e.expressions.is_empty() {
33294            self.write_keyword(" USING");
33295            self.write(" (");
33296            for (i, expr) in e.expressions.iter().enumerate() {
33297                if i > 0 {
33298                    self.write(", ");
33299                }
33300                self.generate_expression(expr)?;
33301            }
33302            self.write(")");
33303        }
33304        Ok(())
33305    }
33306
33307    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
33308        if matches!(
33309            self.config.dialect,
33310            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33311        ) {
33312            if e.expressions.len() > 1 {
33313                self.write("(");
33314            }
33315            for (i, expr) in e.expressions.iter().enumerate() {
33316                if i > 0 {
33317                    self.write_keyword(" OR ");
33318                }
33319                self.generate_expression(expr)?;
33320                self.write_space();
33321                self.write("@@");
33322                self.write_space();
33323                self.generate_expression(&e.this)?;
33324            }
33325            if e.expressions.len() > 1 {
33326                self.write(")");
33327            }
33328            return Ok(());
33329        }
33330
33331        // MATCH(columns) AGAINST(expr [modifier])
33332        self.write_keyword("MATCH");
33333        self.write("(");
33334        for (i, expr) in e.expressions.iter().enumerate() {
33335            if i > 0 {
33336                self.write(", ");
33337            }
33338            self.generate_expression(expr)?;
33339        }
33340        self.write(")");
33341        self.write_keyword(" AGAINST");
33342        self.write("(");
33343        self.generate_expression(&e.this)?;
33344        if let Some(modifier) = &e.modifier {
33345            self.write_space();
33346            self.generate_expression(modifier)?;
33347        }
33348        self.write(")");
33349        Ok(())
33350    }
33351
33352    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
33353        // Python: [window_frame] this
33354        if let Some(window_frame) = &e.window_frame {
33355            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
33356            self.write_space();
33357        }
33358        self.generate_expression(&e.this)?;
33359        Ok(())
33360    }
33361
33362    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
33363        // MATERIALIZED [this]
33364        self.write_keyword("MATERIALIZED");
33365        if let Some(this) = &e.this {
33366            self.write_space();
33367            self.generate_expression(this)?;
33368        }
33369        Ok(())
33370    }
33371
33372    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
33373        // MERGE INTO target USING source ON condition WHEN ...
33374        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
33375        if let Some(with_) = &e.with_ {
33376            if let Expression::With(with_clause) = with_.as_ref() {
33377                self.generate_with(with_clause)?;
33378                self.write_space();
33379            } else {
33380                self.generate_expression(with_)?;
33381                self.write_space();
33382            }
33383        }
33384        self.write_keyword("MERGE INTO");
33385        self.write_space();
33386        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
33387            if let Expression::Alias(alias) = e.this.as_ref() {
33388                self.generate_expression(&alias.this)?;
33389                self.write_space();
33390                self.generate_identifier(&alias.alias)?;
33391            } else {
33392                self.generate_expression(&e.this)?;
33393            }
33394        } else {
33395            self.generate_expression(&e.this)?;
33396        }
33397
33398        // USING clause - newline before in pretty mode
33399        if self.config.pretty {
33400            self.write_newline();
33401            self.write_indent();
33402        } else {
33403            self.write_space();
33404        }
33405        self.write_keyword("USING");
33406        self.write_space();
33407        self.generate_expression(&e.using)?;
33408
33409        // ON clause - newline before in pretty mode
33410        if let Some(on) = &e.on {
33411            if self.config.pretty {
33412                self.write_newline();
33413                self.write_indent();
33414            } else {
33415                self.write_space();
33416            }
33417            self.write_keyword("ON");
33418            self.write_space();
33419            self.generate_expression(on)?;
33420        }
33421        // DuckDB USING (key_columns) clause
33422        if let Some(using_cond) = &e.using_cond {
33423            self.write_space();
33424            self.write_keyword("USING");
33425            self.write_space();
33426            self.write("(");
33427            // using_cond is a Tuple containing the column identifiers
33428            if let Expression::Tuple(tuple) = using_cond.as_ref() {
33429                for (i, col) in tuple.expressions.iter().enumerate() {
33430                    if i > 0 {
33431                        self.write(", ");
33432                    }
33433                    self.generate_expression(col)?;
33434                }
33435            } else {
33436                self.generate_expression(using_cond)?;
33437            }
33438            self.write(")");
33439        }
33440        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
33441        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
33442        if matches!(
33443            self.config.dialect,
33444            Some(crate::DialectType::PostgreSQL)
33445                | Some(crate::DialectType::Redshift)
33446                | Some(crate::DialectType::Trino)
33447                | Some(crate::DialectType::Presto)
33448                | Some(crate::DialectType::Athena)
33449        ) {
33450            let mut names = Vec::new();
33451            match e.this.as_ref() {
33452                Expression::Alias(a) => {
33453                    // e.g., "x AS z" -> strip both "x" and "z"
33454                    if let Expression::Table(t) = &a.this {
33455                        names.push(t.name.name.clone());
33456                    } else if let Expression::Identifier(id) = &a.this {
33457                        names.push(id.name.clone());
33458                    }
33459                    names.push(a.alias.name.clone());
33460                }
33461                Expression::Table(t) => {
33462                    names.push(t.name.name.clone());
33463                }
33464                Expression::Identifier(id) => {
33465                    names.push(id.name.clone());
33466                }
33467                _ => {}
33468            }
33469            self.merge_strip_qualifiers = names;
33470        }
33471
33472        // WHEN clauses - newline before each in pretty mode
33473        if let Some(whens) = &e.whens {
33474            if self.config.pretty {
33475                self.write_newline();
33476                self.write_indent();
33477            } else {
33478                self.write_space();
33479            }
33480            self.generate_expression(whens)?;
33481        }
33482
33483        // Restore merge_strip_qualifiers
33484        self.merge_strip_qualifiers = saved_merge_strip;
33485
33486        // OUTPUT/RETURNING clause - newline before in pretty mode
33487        if let Some(returning) = &e.returning {
33488            if self.config.pretty {
33489                self.write_newline();
33490                self.write_indent();
33491            } else {
33492                self.write_space();
33493            }
33494            self.generate_expression(returning)?;
33495        }
33496        Ok(())
33497    }
33498
33499    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
33500        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
33501        if e.no.is_some() {
33502            self.write_keyword("NO MERGEBLOCKRATIO");
33503        } else if e.default.is_some() {
33504            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
33505        } else {
33506            self.write_keyword("MERGEBLOCKRATIO");
33507            self.write("=");
33508            if let Some(this) = &e.this {
33509                self.generate_expression(this)?;
33510            }
33511            if e.percent.is_some() {
33512                self.write_keyword(" PERCENT");
33513            }
33514        }
33515        Ok(())
33516    }
33517
33518    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
33519        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
33520        self.write_keyword("TTL");
33521        let pretty_clickhouse = self.config.pretty
33522            && matches!(
33523                self.config.dialect,
33524                Some(crate::dialects::DialectType::ClickHouse)
33525            );
33526
33527        if pretty_clickhouse {
33528            self.write_newline();
33529            self.indent_level += 1;
33530            for (i, expr) in e.expressions.iter().enumerate() {
33531                if i > 0 {
33532                    self.write(",");
33533                    self.write_newline();
33534                }
33535                self.write_indent();
33536                self.generate_expression(expr)?;
33537            }
33538            self.indent_level -= 1;
33539        } else {
33540            self.write_space();
33541            for (i, expr) in e.expressions.iter().enumerate() {
33542                if i > 0 {
33543                    self.write(", ");
33544                }
33545                self.generate_expression(expr)?;
33546            }
33547        }
33548
33549        if let Some(where_) = &e.where_ {
33550            if pretty_clickhouse {
33551                self.write_newline();
33552                if let Expression::Where(w) = where_.as_ref() {
33553                    self.write_indent();
33554                    self.write_keyword("WHERE");
33555                    self.write_newline();
33556                    self.indent_level += 1;
33557                    self.write_indent();
33558                    self.generate_expression(&w.this)?;
33559                    self.indent_level -= 1;
33560                } else {
33561                    self.write_indent();
33562                    self.generate_expression(where_)?;
33563                }
33564            } else {
33565                self.write_space();
33566                self.generate_expression(where_)?;
33567            }
33568        }
33569        if let Some(group) = &e.group {
33570            if pretty_clickhouse {
33571                self.write_newline();
33572                if let Expression::Group(g) = group.as_ref() {
33573                    self.write_indent();
33574                    self.write_keyword("GROUP BY");
33575                    self.write_newline();
33576                    self.indent_level += 1;
33577                    for (i, expr) in g.expressions.iter().enumerate() {
33578                        if i > 0 {
33579                            self.write(",");
33580                            self.write_newline();
33581                        }
33582                        self.write_indent();
33583                        self.generate_expression(expr)?;
33584                    }
33585                    self.indent_level -= 1;
33586                } else {
33587                    self.write_indent();
33588                    self.generate_expression(group)?;
33589                }
33590            } else {
33591                self.write_space();
33592                self.generate_expression(group)?;
33593            }
33594        }
33595        if let Some(aggregates) = &e.aggregates {
33596            if pretty_clickhouse {
33597                self.write_newline();
33598                self.write_indent();
33599                self.write_keyword("SET");
33600                self.write_newline();
33601                self.indent_level += 1;
33602                if let Expression::Tuple(t) = aggregates.as_ref() {
33603                    for (i, agg) in t.expressions.iter().enumerate() {
33604                        if i > 0 {
33605                            self.write(",");
33606                            self.write_newline();
33607                        }
33608                        self.write_indent();
33609                        self.generate_expression(agg)?;
33610                    }
33611                } else {
33612                    self.write_indent();
33613                    self.generate_expression(aggregates)?;
33614                }
33615                self.indent_level -= 1;
33616            } else {
33617                self.write_space();
33618                self.write_keyword("SET");
33619                self.write_space();
33620                if let Expression::Tuple(t) = aggregates.as_ref() {
33621                    for (i, agg) in t.expressions.iter().enumerate() {
33622                        if i > 0 {
33623                            self.write(", ");
33624                        }
33625                        self.generate_expression(agg)?;
33626                    }
33627                } else {
33628                    self.generate_expression(aggregates)?;
33629                }
33630            }
33631        }
33632        Ok(())
33633    }
33634
33635    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
33636        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
33637        self.generate_expression(&e.this)?;
33638        if e.delete.is_some() {
33639            self.write_keyword(" DELETE");
33640        }
33641        if let Some(recompress) = &e.recompress {
33642            self.write_keyword(" RECOMPRESS ");
33643            self.generate_expression(recompress)?;
33644        }
33645        if let Some(to_disk) = &e.to_disk {
33646            self.write_keyword(" TO DISK ");
33647            self.generate_expression(to_disk)?;
33648        }
33649        if let Some(to_volume) = &e.to_volume {
33650            self.write_keyword(" TO VOLUME ");
33651            self.generate_expression(to_volume)?;
33652        }
33653        Ok(())
33654    }
33655
33656    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
33657        // MINHASH(this, expressions...)
33658        self.write_keyword("MINHASH");
33659        self.write("(");
33660        self.generate_expression(&e.this)?;
33661        for expr in &e.expressions {
33662            self.write(", ");
33663            self.generate_expression(expr)?;
33664        }
33665        self.write(")");
33666        Ok(())
33667    }
33668
33669    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
33670        // model!attribute - Snowflake syntax
33671        self.generate_expression(&e.this)?;
33672        self.write("!");
33673        self.generate_expression(&e.expression)?;
33674        Ok(())
33675    }
33676
33677    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
33678        // MONTHNAME(this)
33679        self.write_keyword("MONTHNAME");
33680        self.write("(");
33681        self.generate_expression(&e.this)?;
33682        self.write(")");
33683        Ok(())
33684    }
33685
33686    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
33687        // Output leading comments
33688        for comment in &e.leading_comments {
33689            self.write_formatted_comment(comment);
33690            if self.config.pretty {
33691                self.write_newline();
33692                self.write_indent();
33693            } else {
33694                self.write_space();
33695            }
33696        }
33697        // Python: INSERT [OVERWRITE] kind expressions source
33698        self.write_keyword("INSERT");
33699        if e.overwrite {
33700            self.write_space();
33701            self.write_keyword("OVERWRITE");
33702        }
33703        self.write_space();
33704        self.write(&e.kind);
33705        if self.config.pretty {
33706            self.indent_level += 1;
33707            for expr in &e.expressions {
33708                self.write_newline();
33709                self.write_indent();
33710                self.generate_expression(expr)?;
33711            }
33712            self.indent_level -= 1;
33713        } else {
33714            for expr in &e.expressions {
33715                self.write_space();
33716                self.generate_expression(expr)?;
33717            }
33718        }
33719        if let Some(source) = &e.source {
33720            if self.config.pretty {
33721                self.write_newline();
33722                self.write_indent();
33723            } else {
33724                self.write_space();
33725            }
33726            self.generate_expression(source)?;
33727        }
33728        Ok(())
33729    }
33730
33731    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
33732        // Python: NEXT VALUE FOR this [OVER (order)]
33733        self.write_keyword("NEXT VALUE FOR");
33734        self.write_space();
33735        self.generate_expression(&e.this)?;
33736        if let Some(order) = &e.order {
33737            self.write_space();
33738            self.write_keyword("OVER");
33739            self.write(" (");
33740            self.generate_expression(order)?;
33741            self.write(")");
33742        }
33743        Ok(())
33744    }
33745
33746    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
33747        // NORMAL(mean, stddev, gen)
33748        self.write_keyword("NORMAL");
33749        self.write("(");
33750        self.generate_expression(&e.this)?;
33751        if let Some(stddev) = &e.stddev {
33752            self.write(", ");
33753            self.generate_expression(stddev)?;
33754        }
33755        if let Some(gen) = &e.gen {
33756            self.write(", ");
33757            self.generate_expression(gen)?;
33758        }
33759        self.write(")");
33760        Ok(())
33761    }
33762
33763    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
33764        // NORMALIZE(this, form) or CASEFOLD version
33765        if e.is_casefold.is_some() {
33766            self.write_keyword("NORMALIZE_AND_CASEFOLD");
33767        } else {
33768            self.write_keyword("NORMALIZE");
33769        }
33770        self.write("(");
33771        self.generate_expression(&e.this)?;
33772        if let Some(form) = &e.form {
33773            self.write(", ");
33774            self.generate_expression(form)?;
33775        }
33776        self.write(")");
33777        Ok(())
33778    }
33779
33780    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
33781        // Python: [NOT ]NULL
33782        if e.allow_null.is_none() {
33783            self.write_keyword("NOT ");
33784        }
33785        self.write_keyword("NULL");
33786        Ok(())
33787    }
33788
33789    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
33790        // NULLIF(this, expression)
33791        self.write_keyword("NULLIF");
33792        self.write("(");
33793        self.generate_expression(&e.this)?;
33794        self.write(", ");
33795        self.generate_expression(&e.expression)?;
33796        self.write(")");
33797        Ok(())
33798    }
33799
33800    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
33801        // FORMAT(this, format, culture)
33802        self.write_keyword("FORMAT");
33803        self.write("(");
33804        self.generate_expression(&e.this)?;
33805        self.write(", '");
33806        self.write(&e.format);
33807        self.write("'");
33808        if let Some(culture) = &e.culture {
33809            self.write(", ");
33810            self.generate_expression(culture)?;
33811        }
33812        self.write(")");
33813        Ok(())
33814    }
33815
33816    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
33817        // OBJECT_AGG(key, value)
33818        self.write_keyword("OBJECT_AGG");
33819        self.write("(");
33820        self.generate_expression(&e.this)?;
33821        self.write(", ");
33822        self.generate_expression(&e.expression)?;
33823        self.write(")");
33824        Ok(())
33825    }
33826
33827    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
33828        // Python: Just returns the name
33829        self.generate_expression(&e.this)?;
33830        Ok(())
33831    }
33832
33833    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
33834        // OBJECT_INSERT(obj, key, value, [update_flag])
33835        self.write_keyword("OBJECT_INSERT");
33836        self.write("(");
33837        self.generate_expression(&e.this)?;
33838        if let Some(key) = &e.key {
33839            self.write(", ");
33840            self.generate_expression(key)?;
33841        }
33842        if let Some(value) = &e.value {
33843            self.write(", ");
33844            self.generate_expression(value)?;
33845        }
33846        if let Some(update_flag) = &e.update_flag {
33847            self.write(", ");
33848            self.generate_expression(update_flag)?;
33849        }
33850        self.write(")");
33851        Ok(())
33852    }
33853
33854    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
33855        // OFFSET value [ROW|ROWS]
33856        self.write_keyword("OFFSET");
33857        self.write_space();
33858        self.generate_expression(&e.this)?;
33859        // Output ROWS keyword only for TSQL/Oracle targets
33860        if e.rows == Some(true)
33861            && matches!(
33862                self.config.dialect,
33863                Some(crate::dialects::DialectType::TSQL)
33864                    | Some(crate::dialects::DialectType::Oracle)
33865            )
33866        {
33867            self.write_space();
33868            self.write_keyword("ROWS");
33869        }
33870        Ok(())
33871    }
33872
33873    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
33874        // QUALIFY condition (Snowflake/BigQuery)
33875        self.write_keyword("QUALIFY");
33876        self.write_space();
33877        self.generate_expression(&e.this)?;
33878        Ok(())
33879    }
33880
33881    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
33882        // ON CLUSTER cluster_name
33883        self.write_keyword("ON CLUSTER");
33884        self.write_space();
33885        self.generate_expression(&e.this)?;
33886        Ok(())
33887    }
33888
33889    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
33890        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
33891        self.write_keyword("ON COMMIT");
33892        if e.delete.is_some() {
33893            self.write_keyword(" DELETE ROWS");
33894        } else {
33895            self.write_keyword(" PRESERVE ROWS");
33896        }
33897        Ok(())
33898    }
33899
33900    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
33901        // Python: error/empty/null handling
33902        if let Some(empty) = &e.empty {
33903            self.generate_expression(empty)?;
33904            self.write_keyword(" ON EMPTY");
33905        }
33906        if let Some(error) = &e.error {
33907            if e.empty.is_some() {
33908                self.write_space();
33909            }
33910            self.generate_expression(error)?;
33911            self.write_keyword(" ON ERROR");
33912        }
33913        if let Some(null) = &e.null {
33914            if e.empty.is_some() || e.error.is_some() {
33915                self.write_space();
33916            }
33917            self.generate_expression(null)?;
33918            self.write_keyword(" ON NULL");
33919        }
33920        Ok(())
33921    }
33922
33923    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
33924        // Materialize doesn't support ON CONFLICT - skip entirely
33925        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
33926            return Ok(());
33927        }
33928        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
33929        if e.duplicate.is_some() {
33930            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
33931            self.write_keyword("ON DUPLICATE KEY UPDATE");
33932            for (i, expr) in e.expressions.iter().enumerate() {
33933                if i > 0 {
33934                    self.write(",");
33935                }
33936                self.write_space();
33937                self.generate_expression(expr)?;
33938            }
33939            return Ok(());
33940        } else {
33941            self.write_keyword("ON CONFLICT");
33942        }
33943        if let Some(constraint) = &e.constraint {
33944            self.write_keyword(" ON CONSTRAINT ");
33945            self.generate_expression(constraint)?;
33946        }
33947        if let Some(conflict_keys) = &e.conflict_keys {
33948            // conflict_keys can be a Tuple containing expressions
33949            if let Expression::Tuple(t) = conflict_keys.as_ref() {
33950                self.write("(");
33951                for (i, expr) in t.expressions.iter().enumerate() {
33952                    if i > 0 {
33953                        self.write(", ");
33954                    }
33955                    self.generate_expression(expr)?;
33956                }
33957                self.write(")");
33958            } else {
33959                self.write("(");
33960                self.generate_expression(conflict_keys)?;
33961                self.write(")");
33962            }
33963        }
33964        if let Some(index_predicate) = &e.index_predicate {
33965            self.write_keyword(" WHERE ");
33966            self.generate_expression(index_predicate)?;
33967        }
33968        if let Some(action) = &e.action {
33969            // Check if action is "NOTHING" or an UPDATE set
33970            if let Expression::Identifier(id) = action.as_ref() {
33971                if id.name.eq_ignore_ascii_case("NOTHING") {
33972                    self.write_keyword(" DO NOTHING");
33973                } else {
33974                    self.write_keyword(" DO ");
33975                    self.generate_expression(action)?;
33976                }
33977            } else if let Expression::Tuple(t) = action.as_ref() {
33978                // DO UPDATE SET col1 = val1, col2 = val2
33979                self.write_keyword(" DO UPDATE SET ");
33980                for (i, expr) in t.expressions.iter().enumerate() {
33981                    if i > 0 {
33982                        self.write(", ");
33983                    }
33984                    self.generate_expression(expr)?;
33985                }
33986            } else {
33987                self.write_keyword(" DO ");
33988                self.generate_expression(action)?;
33989            }
33990        }
33991        // WHERE clause for the UPDATE action
33992        if let Some(where_) = &e.where_ {
33993            self.write_keyword(" WHERE ");
33994            self.generate_expression(where_)?;
33995        }
33996        Ok(())
33997    }
33998
33999    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
34000        // ON property_value
34001        self.write_keyword("ON");
34002        self.write_space();
34003        self.generate_expression(&e.this)?;
34004        Ok(())
34005    }
34006
34007    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
34008        // Python: this expression (e.g., column opclass)
34009        self.generate_expression(&e.this)?;
34010        self.write_space();
34011        self.generate_expression(&e.expression)?;
34012        Ok(())
34013    }
34014
34015    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
34016        // Python: OPENJSON(this[, path]) [WITH (columns)]
34017        self.write_keyword("OPENJSON");
34018        self.write("(");
34019        self.generate_expression(&e.this)?;
34020        if let Some(path) = &e.path {
34021            self.write(", ");
34022            self.generate_expression(path)?;
34023        }
34024        self.write(")");
34025        if !e.expressions.is_empty() {
34026            self.write_keyword(" WITH");
34027            if self.config.pretty {
34028                self.write(" (\n");
34029                self.indent_level += 2;
34030                for (i, expr) in e.expressions.iter().enumerate() {
34031                    if i > 0 {
34032                        self.write(",\n");
34033                    }
34034                    self.write_indent();
34035                    self.generate_expression(expr)?;
34036                }
34037                self.write("\n");
34038                self.indent_level -= 2;
34039                self.write(")");
34040            } else {
34041                self.write(" (");
34042                for (i, expr) in e.expressions.iter().enumerate() {
34043                    if i > 0 {
34044                        self.write(", ");
34045                    }
34046                    self.generate_expression(expr)?;
34047                }
34048                self.write(")");
34049            }
34050        }
34051        Ok(())
34052    }
34053
34054    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
34055        // Python: this kind [path] [AS JSON]
34056        self.generate_expression(&e.this)?;
34057        self.write_space();
34058        // Use parsed data_type if available, otherwise fall back to kind string
34059        if let Some(ref dt) = e.data_type {
34060            self.generate_data_type(dt)?;
34061        } else if !e.kind.is_empty() {
34062            self.write(&e.kind);
34063        }
34064        if let Some(path) = &e.path {
34065            self.write_space();
34066            self.generate_expression(path)?;
34067        }
34068        if e.as_json.is_some() {
34069            self.write_keyword(" AS JSON");
34070        }
34071        Ok(())
34072    }
34073
34074    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
34075        // this OPERATOR(op) expression
34076        self.generate_expression(&e.this)?;
34077        self.write_space();
34078        if let Some(op) = &e.operator {
34079            self.write_keyword("OPERATOR");
34080            self.write("(");
34081            self.generate_expression(op)?;
34082            self.write(")");
34083        }
34084        // Emit inline comments between OPERATOR() and the RHS
34085        for comment in &e.comments {
34086            self.write_space();
34087            self.write_formatted_comment(comment);
34088        }
34089        self.write_space();
34090        self.generate_expression(&e.expression)?;
34091        Ok(())
34092    }
34093
34094    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
34095        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
34096        self.write_keyword("ORDER BY");
34097        let pretty_clickhouse_single_paren = self.config.pretty
34098            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
34099            && e.expressions.len() == 1
34100            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
34101        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
34102            && e.expressions.len() == 1
34103            && matches!(e.expressions[0].this, Expression::Tuple(_))
34104            && !e.expressions[0].desc
34105            && e.expressions[0].nulls_first.is_none();
34106
34107        if pretty_clickhouse_single_paren {
34108            self.write_space();
34109            if let Expression::Paren(p) = &e.expressions[0].this {
34110                self.write("(");
34111                self.write_newline();
34112                self.indent_level += 1;
34113                self.write_indent();
34114                self.generate_expression(&p.this)?;
34115                self.indent_level -= 1;
34116                self.write_newline();
34117                self.write(")");
34118            }
34119            return Ok(());
34120        }
34121
34122        if clickhouse_single_tuple {
34123            self.write_space();
34124            if let Expression::Tuple(t) = &e.expressions[0].this {
34125                self.write("(");
34126                for (i, expr) in t.expressions.iter().enumerate() {
34127                    if i > 0 {
34128                        self.write(", ");
34129                    }
34130                    self.generate_expression(expr)?;
34131                }
34132                self.write(")");
34133            }
34134            return Ok(());
34135        }
34136
34137        self.write_space();
34138        for (i, ordered) in e.expressions.iter().enumerate() {
34139            if i > 0 {
34140                self.write(", ");
34141            }
34142            self.generate_expression(&ordered.this)?;
34143            if ordered.desc {
34144                self.write_space();
34145                self.write_keyword("DESC");
34146            } else if ordered.explicit_asc {
34147                self.write_space();
34148                self.write_keyword("ASC");
34149            }
34150            if let Some(nulls_first) = ordered.nulls_first {
34151                // In Dremio, NULLS LAST is the default, so skip generating it
34152                let skip_nulls_last =
34153                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
34154                if !skip_nulls_last {
34155                    self.write_space();
34156                    self.write_keyword("NULLS");
34157                    self.write_space();
34158                    if nulls_first {
34159                        self.write_keyword("FIRST");
34160                    } else {
34161                        self.write_keyword("LAST");
34162                    }
34163                }
34164            }
34165        }
34166        Ok(())
34167    }
34168
34169    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
34170        // OUTPUT(model)
34171        self.write_keyword("OUTPUT");
34172        self.write("(");
34173        if self.config.pretty {
34174            self.indent_level += 1;
34175            self.write_newline();
34176            self.write_indent();
34177            self.generate_expression(&e.this)?;
34178            self.indent_level -= 1;
34179            self.write_newline();
34180        } else {
34181            self.generate_expression(&e.this)?;
34182        }
34183        self.write(")");
34184        Ok(())
34185    }
34186
34187    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
34188        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
34189        self.write_keyword("TRUNCATE");
34190        if let Some(this) = &e.this {
34191            self.write_space();
34192            self.generate_expression(this)?;
34193        }
34194        if e.with_count.is_some() {
34195            self.write_keyword(" WITH COUNT");
34196        } else {
34197            self.write_keyword(" WITHOUT COUNT");
34198        }
34199        Ok(())
34200    }
34201
34202    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
34203        // Python: name(expressions)(params)
34204        self.generate_expression(&e.this)?;
34205        self.write("(");
34206        for (i, expr) in e.expressions.iter().enumerate() {
34207            if i > 0 {
34208                self.write(", ");
34209            }
34210            self.generate_expression(expr)?;
34211        }
34212        self.write(")(");
34213        for (i, param) in e.params.iter().enumerate() {
34214            if i > 0 {
34215                self.write(", ");
34216            }
34217            self.generate_expression(param)?;
34218        }
34219        self.write(")");
34220        Ok(())
34221    }
34222
34223    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
34224        // PARSE_DATETIME(format, this) or similar
34225        self.write_keyword("PARSE_DATETIME");
34226        self.write("(");
34227        if let Some(format) = &e.format {
34228            self.write("'");
34229            self.write(format);
34230            self.write("', ");
34231        }
34232        self.generate_expression(&e.this)?;
34233        if let Some(zone) = &e.zone {
34234            self.write(", ");
34235            self.generate_expression(zone)?;
34236        }
34237        self.write(")");
34238        Ok(())
34239    }
34240
34241    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
34242        // PARSE_IP(this, type, permissive)
34243        self.write_keyword("PARSE_IP");
34244        self.write("(");
34245        self.generate_expression(&e.this)?;
34246        if let Some(type_) = &e.type_ {
34247            self.write(", ");
34248            self.generate_expression(type_)?;
34249        }
34250        if let Some(permissive) = &e.permissive {
34251            self.write(", ");
34252            self.generate_expression(permissive)?;
34253        }
34254        self.write(")");
34255        Ok(())
34256    }
34257
34258    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
34259        // PARSE_JSON(this, [expression])
34260        self.write_keyword("PARSE_JSON");
34261        self.write("(");
34262        self.generate_expression(&e.this)?;
34263        if let Some(expression) = &e.expression {
34264            self.write(", ");
34265            self.generate_expression(expression)?;
34266        }
34267        self.write(")");
34268        Ok(())
34269    }
34270
34271    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
34272        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
34273        self.write_keyword("PARSE_TIME");
34274        self.write("(");
34275        self.write(&format!("'{}'", e.format));
34276        self.write(", ");
34277        self.generate_expression(&e.this)?;
34278        self.write(")");
34279        Ok(())
34280    }
34281
34282    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
34283        // PARSE_URL(this, [part_to_extract], [key], [permissive])
34284        self.write_keyword("PARSE_URL");
34285        self.write("(");
34286        self.generate_expression(&e.this)?;
34287        if let Some(part) = &e.part_to_extract {
34288            self.write(", ");
34289            self.generate_expression(part)?;
34290        }
34291        if let Some(key) = &e.key {
34292            self.write(", ");
34293            self.generate_expression(key)?;
34294        }
34295        if let Some(permissive) = &e.permissive {
34296            self.write(", ");
34297            self.generate_expression(permissive)?;
34298        }
34299        self.write(")");
34300        Ok(())
34301    }
34302
34303    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
34304        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
34305        if e.subpartition {
34306            self.write_keyword("SUBPARTITION");
34307        } else {
34308            self.write_keyword("PARTITION");
34309        }
34310        self.write("(");
34311        for (i, expr) in e.expressions.iter().enumerate() {
34312            if i > 0 {
34313                self.write(", ");
34314            }
34315            self.generate_expression(expr)?;
34316        }
34317        self.write(")");
34318        Ok(())
34319    }
34320
34321    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
34322        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
34323        if let Some(this) = &e.this {
34324            if let Some(expression) = &e.expression {
34325                // WITH (MODULUS this, REMAINDER expression)
34326                self.write_keyword("WITH");
34327                self.write(" (");
34328                self.write_keyword("MODULUS");
34329                self.write_space();
34330                self.generate_expression(this)?;
34331                self.write(", ");
34332                self.write_keyword("REMAINDER");
34333                self.write_space();
34334                self.generate_expression(expression)?;
34335                self.write(")");
34336            } else {
34337                // IN (this) - this could be a list
34338                self.write_keyword("IN");
34339                self.write(" (");
34340                self.generate_partition_bound_values(this)?;
34341                self.write(")");
34342            }
34343        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
34344            // FROM (from_expressions) TO (to_expressions)
34345            self.write_keyword("FROM");
34346            self.write(" (");
34347            self.generate_partition_bound_values(from)?;
34348            self.write(") ");
34349            self.write_keyword("TO");
34350            self.write(" (");
34351            self.generate_partition_bound_values(to)?;
34352            self.write(")");
34353        }
34354        Ok(())
34355    }
34356
34357    /// Generate partition bound values - handles Tuple expressions by outputting
34358    /// contents without wrapping parens (since caller provides the parens)
34359    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
34360        if let Expression::Tuple(t) = expr {
34361            for (i, e) in t.expressions.iter().enumerate() {
34362                if i > 0 {
34363                    self.write(", ");
34364                }
34365                self.generate_expression(e)?;
34366            }
34367            Ok(())
34368        } else {
34369            self.generate_expression(expr)
34370        }
34371    }
34372
34373    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
34374        // PARTITION BY LIST (partition_expressions) (create_expressions)
34375        self.write_keyword("PARTITION BY LIST");
34376        if let Some(partition_exprs) = &e.partition_expressions {
34377            self.write(" (");
34378            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
34379            self.generate_doris_partition_expressions(partition_exprs)?;
34380            self.write(")");
34381        }
34382        if let Some(create_exprs) = &e.create_expressions {
34383            self.write(" (");
34384            // Unwrap Tuple for partition definitions
34385            self.generate_doris_partition_definitions(create_exprs)?;
34386            self.write(")");
34387        }
34388        Ok(())
34389    }
34390
34391    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
34392        // PARTITION BY RANGE (partition_expressions) (create_expressions)
34393        self.write_keyword("PARTITION BY RANGE");
34394        if let Some(partition_exprs) = &e.partition_expressions {
34395            self.write(" (");
34396            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
34397            self.generate_doris_partition_expressions(partition_exprs)?;
34398            self.write(")");
34399        }
34400        if let Some(create_exprs) = &e.create_expressions {
34401            self.write(" (");
34402            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
34403            self.generate_doris_partition_definitions(create_exprs)?;
34404            self.write(")");
34405        }
34406        Ok(())
34407    }
34408
34409    /// Generate Doris partition column expressions (unwrap Tuple)
34410    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
34411        if let Expression::Tuple(t) = expr {
34412            for (i, e) in t.expressions.iter().enumerate() {
34413                if i > 0 {
34414                    self.write(", ");
34415                }
34416                self.generate_expression(e)?;
34417            }
34418        } else {
34419            self.generate_expression(expr)?;
34420        }
34421        Ok(())
34422    }
34423
34424    /// Generate Doris partition definitions (comma-separated Partition expressions)
34425    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
34426        match expr {
34427            Expression::Tuple(t) => {
34428                // Multiple partitions, comma-separated
34429                for (i, part) in t.expressions.iter().enumerate() {
34430                    if i > 0 {
34431                        self.write(", ");
34432                    }
34433                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
34434                    if let Expression::Partition(p) = part {
34435                        for (j, inner) in p.expressions.iter().enumerate() {
34436                            if j > 0 {
34437                                self.write(", ");
34438                            }
34439                            self.generate_expression(inner)?;
34440                        }
34441                    } else {
34442                        self.generate_expression(part)?;
34443                    }
34444                }
34445            }
34446            Expression::PartitionByRangePropertyDynamic(_) => {
34447                // Dynamic partition - FROM/TO/INTERVAL
34448                self.generate_expression(expr)?;
34449            }
34450            _ => {
34451                self.generate_expression(expr)?;
34452            }
34453        }
34454        Ok(())
34455    }
34456
34457    fn generate_partition_by_range_property_dynamic(
34458        &mut self,
34459        e: &PartitionByRangePropertyDynamic,
34460    ) -> Result<()> {
34461        if e.use_start_end {
34462            // StarRocks: START ('val') END ('val') EVERY (expr)
34463            if let Some(start) = &e.start {
34464                self.write_keyword("START");
34465                self.write(" (");
34466                self.generate_expression(start)?;
34467                self.write(")");
34468            }
34469            if let Some(end) = &e.end {
34470                self.write_space();
34471                self.write_keyword("END");
34472                self.write(" (");
34473                self.generate_expression(end)?;
34474                self.write(")");
34475            }
34476            if let Some(every) = &e.every {
34477                self.write_space();
34478                self.write_keyword("EVERY");
34479                self.write(" (");
34480                // Use unquoted interval format for StarRocks
34481                self.generate_doris_interval(every)?;
34482                self.write(")");
34483            }
34484        } else {
34485            // Doris: FROM (start) TO (end) INTERVAL n UNIT
34486            if let Some(start) = &e.start {
34487                self.write_keyword("FROM");
34488                self.write(" (");
34489                self.generate_expression(start)?;
34490                self.write(")");
34491            }
34492            if let Some(end) = &e.end {
34493                self.write_space();
34494                self.write_keyword("TO");
34495                self.write(" (");
34496                self.generate_expression(end)?;
34497                self.write(")");
34498            }
34499            if let Some(every) = &e.every {
34500                self.write_space();
34501                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
34502                self.generate_doris_interval(every)?;
34503            }
34504        }
34505        Ok(())
34506    }
34507
34508    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
34509    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
34510        if let Expression::Interval(interval) = expr {
34511            self.write_keyword("INTERVAL");
34512            if let Some(ref value) = interval.this {
34513                self.write_space();
34514                // If the value is a string literal that looks like a number,
34515                // output it without quotes (matching Python sqlglot's
34516                // partitionbyrangepropertydynamic_sql which converts back to number)
34517                match value {
34518                    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()) => {
34519                        if let Literal::String(s) = lit.as_ref() {
34520                            self.write(s);
34521                        }
34522                    }
34523                    _ => {
34524                        self.generate_expression(value)?;
34525                    }
34526                }
34527            }
34528            if let Some(ref unit_spec) = interval.unit {
34529                self.write_space();
34530                self.write_interval_unit_spec(unit_spec)?;
34531            }
34532            Ok(())
34533        } else {
34534            self.generate_expression(expr)
34535        }
34536    }
34537
34538    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
34539        // TRUNCATE(expression, this)
34540        self.write_keyword("TRUNCATE");
34541        self.write("(");
34542        self.generate_expression(&e.expression)?;
34543        self.write(", ");
34544        self.generate_expression(&e.this)?;
34545        self.write(")");
34546        Ok(())
34547    }
34548
34549    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
34550        // Doris: PARTITION name VALUES IN (val1, val2)
34551        self.write_keyword("PARTITION");
34552        self.write_space();
34553        self.generate_expression(&e.this)?;
34554        self.write_space();
34555        self.write_keyword("VALUES IN");
34556        self.write(" (");
34557        for (i, expr) in e.expressions.iter().enumerate() {
34558            if i > 0 {
34559                self.write(", ");
34560            }
34561            self.generate_expression(expr)?;
34562        }
34563        self.write(")");
34564        Ok(())
34565    }
34566
34567    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
34568        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
34569        // TSQL ranges have no expressions and just use `this TO expression`
34570        if e.expressions.is_empty() && e.expression.is_some() {
34571            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
34572            self.generate_expression(&e.this)?;
34573            self.write_space();
34574            self.write_keyword("TO");
34575            self.write_space();
34576            self.generate_expression(e.expression.as_ref().unwrap())?;
34577            return Ok(());
34578        }
34579
34580        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
34581        self.write_keyword("PARTITION");
34582        self.write_space();
34583        self.generate_expression(&e.this)?;
34584        self.write_space();
34585
34586        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
34587        if e.expressions.len() == 1 {
34588            // Single value: VALUES LESS THAN (val)
34589            self.write_keyword("VALUES LESS THAN");
34590            self.write(" (");
34591            self.generate_expression(&e.expressions[0])?;
34592            self.write(")");
34593        } else if !e.expressions.is_empty() {
34594            // Multiple values with Tuple: VALUES [(val1), (val2))
34595            self.write_keyword("VALUES");
34596            self.write(" [");
34597            for (i, expr) in e.expressions.iter().enumerate() {
34598                if i > 0 {
34599                    self.write(", ");
34600                }
34601                // If the expr is a Tuple, generate its contents wrapped in parens
34602                if let Expression::Tuple(t) = expr {
34603                    self.write("(");
34604                    for (j, inner) in t.expressions.iter().enumerate() {
34605                        if j > 0 {
34606                            self.write(", ");
34607                        }
34608                        self.generate_expression(inner)?;
34609                    }
34610                    self.write(")");
34611                } else {
34612                    self.write("(");
34613                    self.generate_expression(expr)?;
34614                    self.write(")");
34615                }
34616            }
34617            self.write(")");
34618        }
34619        Ok(())
34620    }
34621
34622    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
34623        // BUCKET(this, expression)
34624        self.write_keyword("BUCKET");
34625        self.write("(");
34626        self.generate_expression(&e.this)?;
34627        self.write(", ");
34628        self.generate_expression(&e.expression)?;
34629        self.write(")");
34630        Ok(())
34631    }
34632
34633    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
34634        // BigQuery table property: PARTITION BY expression [, expression ...]
34635        self.write_keyword("PARTITION BY");
34636        self.write_space();
34637        for (i, expr) in e.expressions.iter().enumerate() {
34638            if i > 0 {
34639                self.write(", ");
34640            }
34641            self.generate_expression(expr)?;
34642        }
34643        Ok(())
34644    }
34645
34646    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
34647        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
34648        if matches!(
34649            self.config.dialect,
34650            Some(crate::dialects::DialectType::Teradata)
34651                | Some(crate::dialects::DialectType::ClickHouse)
34652        ) {
34653            self.write_keyword("PARTITION BY");
34654        } else {
34655            self.write_keyword("PARTITIONED BY");
34656        }
34657        self.write_space();
34658        // In pretty mode, always use multiline tuple format for PARTITIONED BY
34659        if self.config.pretty {
34660            if let Expression::Tuple(ref tuple) = *e.this {
34661                self.write("(");
34662                self.write_newline();
34663                self.indent_level += 1;
34664                for (i, expr) in tuple.expressions.iter().enumerate() {
34665                    if i > 0 {
34666                        self.write(",");
34667                        self.write_newline();
34668                    }
34669                    self.write_indent();
34670                    self.generate_expression(expr)?;
34671                }
34672                self.indent_level -= 1;
34673                self.write_newline();
34674                self.write(")");
34675            } else {
34676                self.generate_expression(&e.this)?;
34677            }
34678        } else {
34679            self.generate_expression(&e.this)?;
34680        }
34681        Ok(())
34682    }
34683
34684    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
34685        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
34686        self.write_keyword("PARTITION OF");
34687        self.write_space();
34688        self.generate_expression(&e.this)?;
34689        // Check if expression is a PartitionBoundSpec
34690        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
34691            self.write_space();
34692            self.write_keyword("FOR VALUES");
34693            self.write_space();
34694            self.generate_expression(&e.expression)?;
34695        } else {
34696            self.write_space();
34697            self.write_keyword("DEFAULT");
34698        }
34699        Ok(())
34700    }
34701
34702    fn generate_period_for_system_time_constraint(
34703        &mut self,
34704        e: &PeriodForSystemTimeConstraint,
34705    ) -> Result<()> {
34706        // PERIOD FOR SYSTEM_TIME (this, expression)
34707        self.write_keyword("PERIOD FOR SYSTEM_TIME");
34708        self.write(" (");
34709        self.generate_expression(&e.this)?;
34710        self.write(", ");
34711        self.generate_expression(&e.expression)?;
34712        self.write(")");
34713        Ok(())
34714    }
34715
34716    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
34717        // value AS alias
34718        // The alias can be an identifier or an expression (e.g., string concatenation)
34719        self.generate_expression(&e.this)?;
34720        self.write_space();
34721        self.write_keyword("AS");
34722        self.write_space();
34723        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
34724        if self.config.unpivot_aliases_are_identifiers {
34725            match &e.alias {
34726                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
34727                    let Literal::String(s) = lit.as_ref() else {
34728                        unreachable!()
34729                    };
34730                    // Convert string literal to identifier
34731                    self.generate_identifier(&Identifier::new(s.clone()))?;
34732                }
34733                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
34734                    let Literal::Number(n) = lit.as_ref() else {
34735                        unreachable!()
34736                    };
34737                    // Convert number literal to quoted identifier
34738                    let mut id = Identifier::new(n.clone());
34739                    id.quoted = true;
34740                    self.generate_identifier(&id)?;
34741                }
34742                other => {
34743                    self.generate_expression(other)?;
34744                }
34745            }
34746        } else {
34747            self.generate_expression(&e.alias)?;
34748        }
34749        Ok(())
34750    }
34751
34752    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
34753        // ANY or ANY [expression]
34754        self.write_keyword("ANY");
34755        if let Some(this) = &e.this {
34756            self.write_space();
34757            self.generate_expression(this)?;
34758        }
34759        Ok(())
34760    }
34761
34762    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
34763        // ML.PREDICT(MODEL this, expression, [params_struct])
34764        self.write_keyword("ML.PREDICT");
34765        self.write("(");
34766        self.write_keyword("MODEL");
34767        self.write_space();
34768        self.generate_expression(&e.this)?;
34769        self.write(", ");
34770        self.generate_expression(&e.expression)?;
34771        if let Some(params) = &e.params_struct {
34772            self.write(", ");
34773            self.generate_expression(params)?;
34774        }
34775        self.write(")");
34776        Ok(())
34777    }
34778
34779    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
34780        // PREVIOUS_DAY(this, expression)
34781        self.write_keyword("PREVIOUS_DAY");
34782        self.write("(");
34783        self.generate_expression(&e.this)?;
34784        self.write(", ");
34785        self.generate_expression(&e.expression)?;
34786        self.write(")");
34787        Ok(())
34788    }
34789
34790    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
34791        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
34792        self.write_keyword("PRIMARY KEY");
34793        if let Some(name) = &e.this {
34794            self.write_space();
34795            self.generate_expression(name)?;
34796        }
34797        if !e.expressions.is_empty() {
34798            self.write(" (");
34799            for (i, expr) in e.expressions.iter().enumerate() {
34800                if i > 0 {
34801                    self.write(", ");
34802                }
34803                self.generate_expression(expr)?;
34804            }
34805            self.write(")");
34806        }
34807        if let Some(include) = &e.include {
34808            self.write_space();
34809            self.generate_expression(include)?;
34810        }
34811        if !e.options.is_empty() {
34812            self.write_space();
34813            for (i, opt) in e.options.iter().enumerate() {
34814                if i > 0 {
34815                    self.write_space();
34816                }
34817                self.generate_expression(opt)?;
34818            }
34819        }
34820        Ok(())
34821    }
34822
34823    fn generate_primary_key_column_constraint(
34824        &mut self,
34825        _e: &PrimaryKeyColumnConstraint,
34826    ) -> Result<()> {
34827        // PRIMARY KEY constraint at column level
34828        self.write_keyword("PRIMARY KEY");
34829        Ok(())
34830    }
34831
34832    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
34833        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
34834        self.write_keyword("PATH");
34835        self.write_space();
34836        self.generate_expression(&e.this)?;
34837        Ok(())
34838    }
34839
34840    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
34841        // PROJECTION this (expression)
34842        self.write_keyword("PROJECTION");
34843        self.write_space();
34844        self.generate_expression(&e.this)?;
34845        self.write(" (");
34846        self.generate_expression(&e.expression)?;
34847        self.write(")");
34848        Ok(())
34849    }
34850
34851    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
34852        // Properties list
34853        for (i, prop) in e.expressions.iter().enumerate() {
34854            if i > 0 {
34855                self.write(", ");
34856            }
34857            self.generate_expression(prop)?;
34858        }
34859        Ok(())
34860    }
34861
34862    fn generate_property(&mut self, e: &Property) -> Result<()> {
34863        // name=value
34864        self.generate_expression(&e.this)?;
34865        if let Some(value) = &e.value {
34866            self.write("=");
34867            self.generate_expression(value)?;
34868        }
34869        Ok(())
34870    }
34871
34872    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
34873        self.write_keyword("OPTIONS");
34874        if e.entries.is_empty() {
34875            self.write(" ()");
34876            return Ok(());
34877        }
34878
34879        if self.config.pretty {
34880            self.write(" (");
34881            self.write_newline();
34882            self.indent_level += 1;
34883            for (i, entry) in e.entries.iter().enumerate() {
34884                if i > 0 {
34885                    self.write(",");
34886                    self.write_newline();
34887                }
34888                self.write_indent();
34889                self.generate_identifier(&entry.key)?;
34890                self.write("=");
34891                self.generate_expression(&entry.value)?;
34892            }
34893            self.indent_level -= 1;
34894            self.write_newline();
34895            self.write(")");
34896        } else {
34897            self.write(" (");
34898            for (i, entry) in e.entries.iter().enumerate() {
34899                if i > 0 {
34900                    self.write(", ");
34901                }
34902                self.generate_identifier(&entry.key)?;
34903                self.write("=");
34904                self.generate_expression(&entry.value)?;
34905            }
34906            self.write(")");
34907        }
34908        Ok(())
34909    }
34910
34911    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
34912    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
34913        self.write_keyword("OPTIONS");
34914        self.write(" (");
34915        for (i, opt) in options.iter().enumerate() {
34916            if i > 0 {
34917                self.write(", ");
34918            }
34919            self.generate_option_expression(opt)?;
34920        }
34921        self.write(")");
34922        Ok(())
34923    }
34924
34925    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
34926    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
34927        self.write_keyword("PROPERTIES");
34928        self.write(" (");
34929        for (i, prop) in properties.iter().enumerate() {
34930            if i > 0 {
34931                self.write(", ");
34932            }
34933            self.generate_option_expression(prop)?;
34934        }
34935        self.write(")");
34936        Ok(())
34937    }
34938
34939    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
34940    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
34941        self.write_keyword("ENVIRONMENT");
34942        self.write(" (");
34943        for (i, env_item) in environment.iter().enumerate() {
34944            if i > 0 {
34945                self.write(", ");
34946            }
34947            self.generate_environment_expression(env_item)?;
34948        }
34949        self.write(")");
34950        Ok(())
34951    }
34952
34953    /// Generate an environment expression with spaces around =
34954    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
34955        match expr {
34956            Expression::Eq(eq) => {
34957                // Generate key = value with spaces (Databricks ENVIRONMENT style)
34958                self.generate_expression(&eq.left)?;
34959                self.write(" = ");
34960                self.generate_expression(&eq.right)?;
34961                Ok(())
34962            }
34963            _ => self.generate_expression(expr),
34964        }
34965    }
34966
34967    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
34968    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
34969        self.write_keyword("TBLPROPERTIES");
34970        if self.config.pretty {
34971            self.write(" (");
34972            self.write_newline();
34973            self.indent_level += 1;
34974            for (i, opt) in options.iter().enumerate() {
34975                if i > 0 {
34976                    self.write(",");
34977                    self.write_newline();
34978                }
34979                self.write_indent();
34980                self.generate_option_expression(opt)?;
34981            }
34982            self.indent_level -= 1;
34983            self.write_newline();
34984            self.write(")");
34985        } else {
34986            self.write(" (");
34987            for (i, opt) in options.iter().enumerate() {
34988                if i > 0 {
34989                    self.write(", ");
34990                }
34991                self.generate_option_expression(opt)?;
34992            }
34993            self.write(")");
34994        }
34995        Ok(())
34996    }
34997
34998    /// Generate an option expression without spaces around =
34999    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
35000        match expr {
35001            Expression::Eq(eq) => {
35002                // Generate key=value without spaces
35003                self.generate_expression(&eq.left)?;
35004                self.write("=");
35005                self.generate_expression(&eq.right)?;
35006                Ok(())
35007            }
35008            _ => self.generate_expression(expr),
35009        }
35010    }
35011
35012    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
35013        // Just output the name
35014        self.generate_expression(&e.this)?;
35015        Ok(())
35016    }
35017
35018    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
35019        // PUT source_file @stage [options]
35020        self.write_keyword("PUT");
35021        self.write_space();
35022
35023        // Source file path - preserve original quoting
35024        if e.source_quoted {
35025            self.write("'");
35026            self.write(&e.source);
35027            self.write("'");
35028        } else {
35029            self.write(&e.source);
35030        }
35031
35032        self.write_space();
35033
35034        // Target stage reference - output the string directly (includes @)
35035        if let Expression::Literal(lit) = &e.target {
35036            if let Literal::String(s) = lit.as_ref() {
35037                self.write(s);
35038            }
35039        } else {
35040            self.generate_expression(&e.target)?;
35041        }
35042
35043        // Optional parameters: KEY=VALUE
35044        for param in &e.params {
35045            self.write_space();
35046            self.write(&param.name);
35047            if let Some(ref value) = param.value {
35048                self.write("=");
35049                self.generate_expression(value)?;
35050            }
35051        }
35052
35053        Ok(())
35054    }
35055
35056    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
35057        // QUANTILE(this, quantile)
35058        self.write_keyword("QUANTILE");
35059        self.write("(");
35060        self.generate_expression(&e.this)?;
35061        if let Some(quantile) = &e.quantile {
35062            self.write(", ");
35063            self.generate_expression(quantile)?;
35064        }
35065        self.write(")");
35066        Ok(())
35067    }
35068
35069    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
35070        // QUERY_BAND = this [UPDATE] [FOR scope]
35071        if matches!(
35072            self.config.dialect,
35073            Some(crate::dialects::DialectType::Teradata)
35074        ) {
35075            self.write_keyword("SET");
35076            self.write_space();
35077        }
35078        self.write_keyword("QUERY_BAND");
35079        self.write(" = ");
35080        self.generate_expression(&e.this)?;
35081        if e.update.is_some() {
35082            self.write_space();
35083            self.write_keyword("UPDATE");
35084        }
35085        if let Some(scope) = &e.scope {
35086            self.write_space();
35087            self.write_keyword("FOR");
35088            self.write_space();
35089            self.generate_expression(scope)?;
35090        }
35091        Ok(())
35092    }
35093
35094    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
35095        // this = expression
35096        self.generate_expression(&e.this)?;
35097        if let Some(expression) = &e.expression {
35098            self.write(" = ");
35099            self.generate_expression(expression)?;
35100        }
35101        Ok(())
35102    }
35103
35104    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
35105        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
35106        self.write_keyword("TRANSFORM");
35107        self.write("(");
35108        for (i, expr) in e.expressions.iter().enumerate() {
35109            if i > 0 {
35110                self.write(", ");
35111            }
35112            self.generate_expression(expr)?;
35113        }
35114        self.write(")");
35115        if let Some(row_format_before) = &e.row_format_before {
35116            self.write_space();
35117            self.generate_expression(row_format_before)?;
35118        }
35119        if let Some(record_writer) = &e.record_writer {
35120            self.write_space();
35121            self.write_keyword("RECORDWRITER");
35122            self.write_space();
35123            self.generate_expression(record_writer)?;
35124        }
35125        if let Some(command_script) = &e.command_script {
35126            self.write_space();
35127            self.write_keyword("USING");
35128            self.write_space();
35129            self.generate_expression(command_script)?;
35130        }
35131        if let Some(schema) = &e.schema {
35132            self.write_space();
35133            self.write_keyword("AS");
35134            self.write_space();
35135            self.generate_expression(schema)?;
35136        }
35137        if let Some(row_format_after) = &e.row_format_after {
35138            self.write_space();
35139            self.generate_expression(row_format_after)?;
35140        }
35141        if let Some(record_reader) = &e.record_reader {
35142            self.write_space();
35143            self.write_keyword("RECORDREADER");
35144            self.write_space();
35145            self.generate_expression(record_reader)?;
35146        }
35147        Ok(())
35148    }
35149
35150    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
35151        // RANDN([seed])
35152        self.write_keyword("RANDN");
35153        self.write("(");
35154        if let Some(this) = &e.this {
35155            self.generate_expression(this)?;
35156        }
35157        self.write(")");
35158        Ok(())
35159    }
35160
35161    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
35162        // RANDSTR(this, [generator])
35163        self.write_keyword("RANDSTR");
35164        self.write("(");
35165        self.generate_expression(&e.this)?;
35166        if let Some(generator) = &e.generator {
35167            self.write(", ");
35168            self.generate_expression(generator)?;
35169        }
35170        self.write(")");
35171        Ok(())
35172    }
35173
35174    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
35175        // RANGE_BUCKET(this, expression)
35176        self.write_keyword("RANGE_BUCKET");
35177        self.write("(");
35178        self.generate_expression(&e.this)?;
35179        self.write(", ");
35180        self.generate_expression(&e.expression)?;
35181        self.write(")");
35182        Ok(())
35183    }
35184
35185    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
35186        // RANGE_N(this BETWEEN expressions [EACH each])
35187        self.write_keyword("RANGE_N");
35188        self.write("(");
35189        self.generate_expression(&e.this)?;
35190        self.write_space();
35191        self.write_keyword("BETWEEN");
35192        self.write_space();
35193        for (i, expr) in e.expressions.iter().enumerate() {
35194            if i > 0 {
35195                self.write(", ");
35196            }
35197            self.generate_expression(expr)?;
35198        }
35199        if let Some(each) = &e.each {
35200            self.write_space();
35201            self.write_keyword("EACH");
35202            self.write_space();
35203            self.generate_expression(each)?;
35204        }
35205        self.write(")");
35206        Ok(())
35207    }
35208
35209    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
35210        // READ_CSV(this, expressions...)
35211        self.write_keyword("READ_CSV");
35212        self.write("(");
35213        self.generate_expression(&e.this)?;
35214        for expr in &e.expressions {
35215            self.write(", ");
35216            self.generate_expression(expr)?;
35217        }
35218        self.write(")");
35219        Ok(())
35220    }
35221
35222    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
35223        // READ_PARQUET(expressions...)
35224        self.write_keyword("READ_PARQUET");
35225        self.write("(");
35226        for (i, expr) in e.expressions.iter().enumerate() {
35227            if i > 0 {
35228                self.write(", ");
35229            }
35230            self.generate_expression(expr)?;
35231        }
35232        self.write(")");
35233        Ok(())
35234    }
35235
35236    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
35237        // SEARCH kind FIRST BY this SET expression [USING using]
35238        // or CYCLE this SET expression [USING using]
35239        if e.kind == "CYCLE" {
35240            self.write_keyword("CYCLE");
35241        } else {
35242            self.write_keyword("SEARCH");
35243            self.write_space();
35244            self.write(&e.kind);
35245            self.write_space();
35246            self.write_keyword("FIRST BY");
35247        }
35248        self.write_space();
35249        self.generate_expression(&e.this)?;
35250        self.write_space();
35251        self.write_keyword("SET");
35252        self.write_space();
35253        self.generate_expression(&e.expression)?;
35254        if let Some(using) = &e.using {
35255            self.write_space();
35256            self.write_keyword("USING");
35257            self.write_space();
35258            self.generate_expression(using)?;
35259        }
35260        Ok(())
35261    }
35262
35263    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
35264        // REDUCE(this, initial, merge, [finish])
35265        self.write_keyword("REDUCE");
35266        self.write("(");
35267        self.generate_expression(&e.this)?;
35268        if let Some(initial) = &e.initial {
35269            self.write(", ");
35270            self.generate_expression(initial)?;
35271        }
35272        if let Some(merge) = &e.merge {
35273            self.write(", ");
35274            self.generate_expression(merge)?;
35275        }
35276        if let Some(finish) = &e.finish {
35277            self.write(", ");
35278            self.generate_expression(finish)?;
35279        }
35280        self.write(")");
35281        Ok(())
35282    }
35283
35284    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
35285        // REFERENCES this (expressions) [options]
35286        self.write_keyword("REFERENCES");
35287        self.write_space();
35288        self.generate_expression(&e.this)?;
35289        if !e.expressions.is_empty() {
35290            self.write(" (");
35291            for (i, expr) in e.expressions.iter().enumerate() {
35292                if i > 0 {
35293                    self.write(", ");
35294                }
35295                self.generate_expression(expr)?;
35296            }
35297            self.write(")");
35298        }
35299        for opt in &e.options {
35300            self.write_space();
35301            self.generate_expression(opt)?;
35302        }
35303        Ok(())
35304    }
35305
35306    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
35307        // REFRESH [kind] this
35308        self.write_keyword("REFRESH");
35309        if !e.kind.is_empty() {
35310            self.write_space();
35311            self.write_keyword(&e.kind);
35312        }
35313        self.write_space();
35314        self.generate_expression(&e.this)?;
35315        Ok(())
35316    }
35317
35318    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
35319        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
35320        self.write_keyword("REFRESH");
35321        self.write_space();
35322        self.write_keyword(&e.method);
35323
35324        if let Some(ref kind) = e.kind {
35325            self.write_space();
35326            self.write_keyword("ON");
35327            self.write_space();
35328            self.write_keyword(kind);
35329
35330            // EVERY n UNIT
35331            if let Some(ref every) = e.every {
35332                self.write_space();
35333                self.write_keyword("EVERY");
35334                self.write_space();
35335                self.generate_expression(every)?;
35336                if let Some(ref unit) = e.unit {
35337                    self.write_space();
35338                    self.write_keyword(unit);
35339                }
35340            }
35341
35342            // STARTS 'datetime'
35343            if let Some(ref starts) = e.starts {
35344                self.write_space();
35345                self.write_keyword("STARTS");
35346                self.write_space();
35347                self.generate_expression(starts)?;
35348            }
35349        }
35350        Ok(())
35351    }
35352
35353    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
35354        // REGEXP_COUNT(this, expression, position, parameters)
35355        self.write_keyword("REGEXP_COUNT");
35356        self.write("(");
35357        self.generate_expression(&e.this)?;
35358        self.write(", ");
35359        self.generate_expression(&e.expression)?;
35360        if let Some(position) = &e.position {
35361            self.write(", ");
35362            self.generate_expression(position)?;
35363        }
35364        if let Some(parameters) = &e.parameters {
35365            self.write(", ");
35366            self.generate_expression(parameters)?;
35367        }
35368        self.write(")");
35369        Ok(())
35370    }
35371
35372    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
35373        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
35374        self.write_keyword("REGEXP_EXTRACT_ALL");
35375        self.write("(");
35376        self.generate_expression(&e.this)?;
35377        self.write(", ");
35378        self.generate_expression(&e.expression)?;
35379        if let Some(group) = &e.group {
35380            self.write(", ");
35381            self.generate_expression(group)?;
35382        }
35383        self.write(")");
35384        Ok(())
35385    }
35386
35387    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
35388        // REGEXP_FULL_MATCH(this, expression)
35389        self.write_keyword("REGEXP_FULL_MATCH");
35390        self.write("(");
35391        self.generate_expression(&e.this)?;
35392        self.write(", ");
35393        self.generate_expression(&e.expression)?;
35394        self.write(")");
35395        Ok(())
35396    }
35397
35398    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
35399        use crate::dialects::DialectType;
35400        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
35401        if matches!(
35402            self.config.dialect,
35403            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
35404        ) && e.flag.is_none()
35405        {
35406            self.generate_expression(&e.this)?;
35407            self.write(" ~* ");
35408            self.generate_expression(&e.expression)?;
35409        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && e.flag.is_none() {
35410            // ClickHouse has no case-insensitive regex operator; use match() with an inline
35411            // (?i) flag. Inline it into a string-literal pattern for readable output, and fall
35412            // back to concat() so the flag also applies to a non-literal pattern expression.
35413            self.write("match(");
35414            self.generate_expression(&e.this)?;
35415            self.write(", ");
35416            if let Expression::Literal(lit) = e.expression.as_ref() {
35417                if let Literal::String(s) = lit.as_ref() {
35418                    let insensitive =
35419                        Expression::Literal(Box::new(Literal::String(format!("(?i){s}"))));
35420                    self.generate_expression(&insensitive)?;
35421                    self.write(")");
35422                    return Ok(());
35423                }
35424            }
35425            self.write("concat('(?i)', ");
35426            self.generate_expression(&e.expression)?;
35427            self.write("))");
35428            return Ok(());
35429        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35430            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
35431            self.write_keyword("REGEXP_LIKE");
35432            self.write("(");
35433            self.generate_expression(&e.this)?;
35434            self.write(", ");
35435            self.generate_expression(&e.expression)?;
35436            self.write(", ");
35437            if let Some(flag) = &e.flag {
35438                self.generate_expression(flag)?;
35439            } else {
35440                self.write("'i'");
35441            }
35442            self.write(")");
35443        } else {
35444            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
35445            self.generate_expression(&e.this)?;
35446            self.write_space();
35447            self.write_keyword("REGEXP_ILIKE");
35448            self.write_space();
35449            self.generate_expression(&e.expression)?;
35450            if let Some(flag) = &e.flag {
35451                self.write(", ");
35452                self.generate_expression(flag)?;
35453            }
35454        }
35455        Ok(())
35456    }
35457
35458    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
35459        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
35460        self.write_keyword("REGEXP_INSTR");
35461        self.write("(");
35462        self.generate_expression(&e.this)?;
35463        self.write(", ");
35464        self.generate_expression(&e.expression)?;
35465        if let Some(position) = &e.position {
35466            self.write(", ");
35467            self.generate_expression(position)?;
35468        }
35469        if let Some(occurrence) = &e.occurrence {
35470            self.write(", ");
35471            self.generate_expression(occurrence)?;
35472        }
35473        if let Some(option) = &e.option {
35474            self.write(", ");
35475            self.generate_expression(option)?;
35476        }
35477        if let Some(parameters) = &e.parameters {
35478            self.write(", ");
35479            self.generate_expression(parameters)?;
35480        }
35481        if let Some(group) = &e.group {
35482            self.write(", ");
35483            self.generate_expression(group)?;
35484        }
35485        self.write(")");
35486        Ok(())
35487    }
35488
35489    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
35490        // REGEXP_SPLIT(this, expression, limit)
35491        self.write_keyword("REGEXP_SPLIT");
35492        self.write("(");
35493        self.generate_expression(&e.this)?;
35494        self.write(", ");
35495        self.generate_expression(&e.expression)?;
35496        if let Some(limit) = &e.limit {
35497            self.write(", ");
35498            self.generate_expression(limit)?;
35499        }
35500        self.write(")");
35501        Ok(())
35502    }
35503
35504    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
35505        // REGR_AVGX(this, expression)
35506        self.write_keyword("REGR_AVGX");
35507        self.write("(");
35508        self.generate_expression(&e.this)?;
35509        self.write(", ");
35510        self.generate_expression(&e.expression)?;
35511        self.write(")");
35512        Ok(())
35513    }
35514
35515    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
35516        // REGR_AVGY(this, expression)
35517        self.write_keyword("REGR_AVGY");
35518        self.write("(");
35519        self.generate_expression(&e.this)?;
35520        self.write(", ");
35521        self.generate_expression(&e.expression)?;
35522        self.write(")");
35523        Ok(())
35524    }
35525
35526    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
35527        // REGR_COUNT(this, expression)
35528        self.write_keyword("REGR_COUNT");
35529        self.write("(");
35530        self.generate_expression(&e.this)?;
35531        self.write(", ");
35532        self.generate_expression(&e.expression)?;
35533        self.write(")");
35534        Ok(())
35535    }
35536
35537    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
35538        // REGR_INTERCEPT(this, expression)
35539        self.write_keyword("REGR_INTERCEPT");
35540        self.write("(");
35541        self.generate_expression(&e.this)?;
35542        self.write(", ");
35543        self.generate_expression(&e.expression)?;
35544        self.write(")");
35545        Ok(())
35546    }
35547
35548    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
35549        // REGR_R2(this, expression)
35550        self.write_keyword("REGR_R2");
35551        self.write("(");
35552        self.generate_expression(&e.this)?;
35553        self.write(", ");
35554        self.generate_expression(&e.expression)?;
35555        self.write(")");
35556        Ok(())
35557    }
35558
35559    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
35560        // REGR_SLOPE(this, expression)
35561        self.write_keyword("REGR_SLOPE");
35562        self.write("(");
35563        self.generate_expression(&e.this)?;
35564        self.write(", ");
35565        self.generate_expression(&e.expression)?;
35566        self.write(")");
35567        Ok(())
35568    }
35569
35570    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
35571        // REGR_SXX(this, expression)
35572        self.write_keyword("REGR_SXX");
35573        self.write("(");
35574        self.generate_expression(&e.this)?;
35575        self.write(", ");
35576        self.generate_expression(&e.expression)?;
35577        self.write(")");
35578        Ok(())
35579    }
35580
35581    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
35582        // REGR_SXY(this, expression)
35583        self.write_keyword("REGR_SXY");
35584        self.write("(");
35585        self.generate_expression(&e.this)?;
35586        self.write(", ");
35587        self.generate_expression(&e.expression)?;
35588        self.write(")");
35589        Ok(())
35590    }
35591
35592    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
35593        // REGR_SYY(this, expression)
35594        self.write_keyword("REGR_SYY");
35595        self.write("(");
35596        self.generate_expression(&e.this)?;
35597        self.write(", ");
35598        self.generate_expression(&e.expression)?;
35599        self.write(")");
35600        Ok(())
35601    }
35602
35603    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
35604        // REGR_VALX(this, expression)
35605        self.write_keyword("REGR_VALX");
35606        self.write("(");
35607        self.generate_expression(&e.this)?;
35608        self.write(", ");
35609        self.generate_expression(&e.expression)?;
35610        self.write(")");
35611        Ok(())
35612    }
35613
35614    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
35615        // REGR_VALY(this, expression)
35616        self.write_keyword("REGR_VALY");
35617        self.write("(");
35618        self.generate_expression(&e.this)?;
35619        self.write(", ");
35620        self.generate_expression(&e.expression)?;
35621        self.write(")");
35622        Ok(())
35623    }
35624
35625    fn generate_remote_with_connection_model_property(
35626        &mut self,
35627        e: &RemoteWithConnectionModelProperty,
35628    ) -> Result<()> {
35629        // REMOTE WITH CONNECTION this
35630        self.write_keyword("REMOTE WITH CONNECTION");
35631        self.write_space();
35632        self.generate_expression(&e.this)?;
35633        Ok(())
35634    }
35635
35636    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
35637        // RENAME COLUMN [IF EXISTS] this TO new_name
35638        self.write_keyword("RENAME COLUMN");
35639        if e.exists {
35640            self.write_space();
35641            self.write_keyword("IF EXISTS");
35642        }
35643        self.write_space();
35644        self.generate_expression(&e.this)?;
35645        if let Some(to) = &e.to {
35646            self.write_space();
35647            self.write_keyword("TO");
35648            self.write_space();
35649            self.generate_expression(to)?;
35650        }
35651        Ok(())
35652    }
35653
35654    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
35655        // REPLACE PARTITION expression [FROM source]
35656        self.write_keyword("REPLACE PARTITION");
35657        self.write_space();
35658        self.generate_expression(&e.expression)?;
35659        if let Some(source) = &e.source {
35660            self.write_space();
35661            self.write_keyword("FROM");
35662            self.write_space();
35663            self.generate_expression(source)?;
35664        }
35665        Ok(())
35666    }
35667
35668    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
35669        // RETURNING expressions [INTO into]
35670        // TSQL and Fabric use OUTPUT instead of RETURNING
35671        let keyword = match self.config.dialect {
35672            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
35673            _ => "RETURNING",
35674        };
35675        self.write_keyword(keyword);
35676        self.write_space();
35677        for (i, expr) in e.expressions.iter().enumerate() {
35678            if i > 0 {
35679                self.write(", ");
35680            }
35681            self.generate_expression(expr)?;
35682        }
35683        if let Some(into) = &e.into {
35684            self.write_space();
35685            self.write_keyword("INTO");
35686            self.write_space();
35687            self.generate_expression(into)?;
35688        }
35689        Ok(())
35690    }
35691
35692    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
35693        // OUTPUT expressions [INTO into_table]
35694        self.write_space();
35695        self.write_keyword("OUTPUT");
35696        self.write_space();
35697        for (i, expr) in output.columns.iter().enumerate() {
35698            if i > 0 {
35699                self.write(", ");
35700            }
35701            self.generate_expression(expr)?;
35702        }
35703        if let Some(into_table) = &output.into_table {
35704            self.write_space();
35705            self.write_keyword("INTO");
35706            self.write_space();
35707            self.generate_expression(into_table)?;
35708        }
35709        Ok(())
35710    }
35711
35712    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
35713        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
35714        self.write_keyword("RETURNS");
35715        if e.is_table.is_some() {
35716            self.write_space();
35717            self.write_keyword("TABLE");
35718        }
35719        if let Some(table) = &e.table {
35720            self.write_space();
35721            self.generate_expression(table)?;
35722        } else if let Some(this) = &e.this {
35723            self.write_space();
35724            self.generate_expression(this)?;
35725        }
35726        if e.null.is_some() {
35727            self.write_space();
35728            self.write_keyword("NULL ON NULL INPUT");
35729        }
35730        Ok(())
35731    }
35732
35733    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
35734        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
35735        self.write_keyword("ROLLBACK");
35736
35737        // TSQL always uses ROLLBACK TRANSACTION
35738        if e.this.is_none()
35739            && matches!(
35740                self.config.dialect,
35741                Some(DialectType::TSQL) | Some(DialectType::Fabric)
35742            )
35743        {
35744            self.write_space();
35745            self.write_keyword("TRANSACTION");
35746        }
35747
35748        // Check if this has TRANSACTION keyword or transaction name
35749        if let Some(this) = &e.this {
35750            // Check if it's just the "TRANSACTION" marker or an actual transaction name
35751            let is_transaction_marker = matches!(
35752                this.as_ref(),
35753                Expression::Identifier(id) if id.name == "TRANSACTION"
35754            );
35755
35756            self.write_space();
35757            self.write_keyword("TRANSACTION");
35758
35759            // If it's a real transaction name, output it
35760            if !is_transaction_marker {
35761                self.write_space();
35762                self.generate_expression(this)?;
35763            }
35764        }
35765
35766        // Output TO savepoint
35767        if let Some(savepoint) = &e.savepoint {
35768            self.write_space();
35769            self.write_keyword("TO");
35770            self.write_space();
35771            self.generate_expression(savepoint)?;
35772        }
35773        Ok(())
35774    }
35775
35776    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
35777        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
35778        if e.expressions.is_empty() {
35779            self.write_keyword("WITH ROLLUP");
35780        } else {
35781            self.write_keyword("ROLLUP");
35782            self.write("(");
35783            for (i, expr) in e.expressions.iter().enumerate() {
35784                if i > 0 {
35785                    self.write(", ");
35786                }
35787                self.generate_expression(expr)?;
35788            }
35789            self.write(")");
35790        }
35791        Ok(())
35792    }
35793
35794    fn generate_row_format_delimited_property(
35795        &mut self,
35796        e: &RowFormatDelimitedProperty,
35797    ) -> Result<()> {
35798        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
35799        self.write_keyword("ROW FORMAT DELIMITED");
35800        if let Some(fields) = &e.fields {
35801            self.write_space();
35802            self.write_keyword("FIELDS TERMINATED BY");
35803            self.write_space();
35804            self.generate_expression(fields)?;
35805        }
35806        if let Some(escaped) = &e.escaped {
35807            self.write_space();
35808            self.write_keyword("ESCAPED BY");
35809            self.write_space();
35810            self.generate_expression(escaped)?;
35811        }
35812        if let Some(items) = &e.collection_items {
35813            self.write_space();
35814            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
35815            self.write_space();
35816            self.generate_expression(items)?;
35817        }
35818        if let Some(keys) = &e.map_keys {
35819            self.write_space();
35820            self.write_keyword("MAP KEYS TERMINATED BY");
35821            self.write_space();
35822            self.generate_expression(keys)?;
35823        }
35824        if let Some(lines) = &e.lines {
35825            self.write_space();
35826            self.write_keyword("LINES TERMINATED BY");
35827            self.write_space();
35828            self.generate_expression(lines)?;
35829        }
35830        if let Some(null) = &e.null {
35831            self.write_space();
35832            self.write_keyword("NULL DEFINED AS");
35833            self.write_space();
35834            self.generate_expression(null)?;
35835        }
35836        if let Some(serde) = &e.serde {
35837            self.write_space();
35838            self.generate_expression(serde)?;
35839        }
35840        Ok(())
35841    }
35842
35843    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
35844        // ROW FORMAT this
35845        self.write_keyword("ROW FORMAT");
35846        self.write_space();
35847        self.generate_expression(&e.this)?;
35848        Ok(())
35849    }
35850
35851    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
35852        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
35853        self.write_keyword("ROW FORMAT SERDE");
35854        self.write_space();
35855        self.generate_expression(&e.this)?;
35856        if let Some(props) = &e.serde_properties {
35857            self.write_space();
35858            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
35859            self.generate_expression(props)?;
35860        }
35861        Ok(())
35862    }
35863
35864    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
35865        // SHA2(this, length)
35866        self.write_keyword("SHA2");
35867        self.write("(");
35868        self.generate_expression(&e.this)?;
35869        if let Some(length) = e.length {
35870            self.write(", ");
35871            self.write(&length.to_string());
35872        }
35873        self.write(")");
35874        Ok(())
35875    }
35876
35877    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
35878        // SHA2_DIGEST(this, length)
35879        self.write_keyword("SHA2_DIGEST");
35880        self.write("(");
35881        self.generate_expression(&e.this)?;
35882        if let Some(length) = e.length {
35883            self.write(", ");
35884            self.write(&length.to_string());
35885        }
35886        self.write(")");
35887        Ok(())
35888    }
35889
35890    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
35891        let name = if matches!(
35892            self.config.dialect,
35893            Some(crate::dialects::DialectType::Spark)
35894                | Some(crate::dialects::DialectType::Databricks)
35895        ) {
35896            "TRY_ADD"
35897        } else {
35898            "SAFE_ADD"
35899        };
35900        self.write_keyword(name);
35901        self.write("(");
35902        self.generate_expression(&e.this)?;
35903        self.write(", ");
35904        self.generate_expression(&e.expression)?;
35905        self.write(")");
35906        Ok(())
35907    }
35908
35909    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
35910        // SAFE_DIVIDE(this, expression)
35911        self.write_keyword("SAFE_DIVIDE");
35912        self.write("(");
35913        self.generate_expression(&e.this)?;
35914        self.write(", ");
35915        self.generate_expression(&e.expression)?;
35916        self.write(")");
35917        Ok(())
35918    }
35919
35920    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
35921        let name = if matches!(
35922            self.config.dialect,
35923            Some(crate::dialects::DialectType::Spark)
35924                | Some(crate::dialects::DialectType::Databricks)
35925        ) {
35926            "TRY_MULTIPLY"
35927        } else {
35928            "SAFE_MULTIPLY"
35929        };
35930        self.write_keyword(name);
35931        self.write("(");
35932        self.generate_expression(&e.this)?;
35933        self.write(", ");
35934        self.generate_expression(&e.expression)?;
35935        self.write(")");
35936        Ok(())
35937    }
35938
35939    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
35940        let name = if matches!(
35941            self.config.dialect,
35942            Some(crate::dialects::DialectType::Spark)
35943                | Some(crate::dialects::DialectType::Databricks)
35944        ) {
35945            "TRY_SUBTRACT"
35946        } else {
35947            "SAFE_SUBTRACT"
35948        };
35949        self.write_keyword(name);
35950        self.write("(");
35951        self.generate_expression(&e.this)?;
35952        self.write(", ");
35953        self.generate_expression(&e.expression)?;
35954        self.write(")");
35955        Ok(())
35956    }
35957
35958    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
35959    /// METHOD (size UNIT) [REPEATABLE (seed)]
35960    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
35961        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
35962        if matches!(sample.method, SampleMethod::Bucket) {
35963            self.write(" (");
35964            self.write_keyword("BUCKET");
35965            self.write_space();
35966            if let Some(ref num) = sample.bucket_numerator {
35967                self.generate_expression(num)?;
35968            }
35969            self.write_space();
35970            self.write_keyword("OUT OF");
35971            self.write_space();
35972            if let Some(ref denom) = sample.bucket_denominator {
35973                self.generate_expression(denom)?;
35974            }
35975            if let Some(ref field) = sample.bucket_field {
35976                self.write_space();
35977                self.write_keyword("ON");
35978                self.write_space();
35979                self.generate_expression(field)?;
35980            }
35981            self.write(")");
35982            return Ok(());
35983        }
35984
35985        // Output method name if explicitly specified, or for dialects that always require it
35986        let is_snowflake = matches!(
35987            self.config.dialect,
35988            Some(crate::dialects::DialectType::Snowflake)
35989        );
35990        let is_postgres = matches!(
35991            self.config.dialect,
35992            Some(crate::dialects::DialectType::PostgreSQL)
35993                | Some(crate::dialects::DialectType::Redshift)
35994        );
35995        // Databricks and Spark don't output method names
35996        let is_databricks = matches!(
35997            self.config.dialect,
35998            Some(crate::dialects::DialectType::Databricks)
35999        );
36000        let is_spark = matches!(
36001            self.config.dialect,
36002            Some(crate::dialects::DialectType::Spark)
36003        );
36004        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
36005        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
36006        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
36007        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
36008            self.write_space();
36009            if !sample.explicit_method && (is_snowflake || force_method) {
36010                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
36011                self.write_keyword("BERNOULLI");
36012            } else {
36013                match sample.method {
36014                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
36015                    SampleMethod::System => self.write_keyword("SYSTEM"),
36016                    SampleMethod::Block => self.write_keyword("BLOCK"),
36017                    SampleMethod::Row => self.write_keyword("ROW"),
36018                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
36019                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
36020                    SampleMethod::Bucket => {} // handled above
36021                }
36022            }
36023        }
36024
36025        // Output size, with or without parentheses depending on dialect
36026        let emit_size_no_parens = !self.config.tablesample_requires_parens;
36027        if emit_size_no_parens {
36028            self.write_space();
36029            match &sample.size {
36030                Expression::Tuple(tuple) => {
36031                    for (i, expr) in tuple.expressions.iter().enumerate() {
36032                        if i > 0 {
36033                            self.write(", ");
36034                        }
36035                        self.generate_expression(expr)?;
36036                    }
36037                }
36038                expr => self.generate_expression(expr)?,
36039            }
36040        } else {
36041            self.write(" (");
36042            self.generate_expression(&sample.size)?;
36043        }
36044
36045        // Determine unit
36046        let is_rows_method = matches!(
36047            sample.method,
36048            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
36049        );
36050        let is_percent = matches!(
36051            sample.method,
36052            SampleMethod::Percent
36053                | SampleMethod::System
36054                | SampleMethod::Bernoulli
36055                | SampleMethod::Block
36056        );
36057
36058        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
36059        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
36060        // For Databricks and Spark, always output PERCENT for percentage samples.
36061        let is_presto = matches!(
36062            self.config.dialect,
36063            Some(crate::dialects::DialectType::Presto)
36064                | Some(crate::dialects::DialectType::Trino)
36065                | Some(crate::dialects::DialectType::Athena)
36066        );
36067        let should_output_unit = if is_databricks || is_spark {
36068            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
36069            is_percent || is_rows_method || sample.unit_after_size
36070        } else if is_snowflake || is_postgres || is_presto {
36071            sample.unit_after_size
36072        } else {
36073            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
36074        };
36075
36076        if should_output_unit {
36077            self.write_space();
36078            if sample.is_percent {
36079                self.write_keyword("PERCENT");
36080            } else if is_rows_method && !sample.unit_after_size {
36081                self.write_keyword("ROWS");
36082            } else if sample.unit_after_size {
36083                match sample.method {
36084                    SampleMethod::Percent
36085                    | SampleMethod::System
36086                    | SampleMethod::Bernoulli
36087                    | SampleMethod::Block => {
36088                        self.write_keyword("PERCENT");
36089                    }
36090                    SampleMethod::Row | SampleMethod::Reservoir => {
36091                        self.write_keyword("ROWS");
36092                    }
36093                    _ => self.write_keyword("ROWS"),
36094                }
36095            } else {
36096                self.write_keyword("PERCENT");
36097            }
36098        }
36099
36100        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36101            if let Some(ref offset) = sample.offset {
36102                self.write_space();
36103                self.write_keyword("OFFSET");
36104                self.write_space();
36105                self.generate_expression(offset)?;
36106            }
36107        }
36108        if !emit_size_no_parens {
36109            self.write(")");
36110        }
36111
36112        Ok(())
36113    }
36114
36115    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
36116        // SAMPLE this (ClickHouse uses SAMPLE BY)
36117        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36118            self.write_keyword("SAMPLE BY");
36119        } else {
36120            self.write_keyword("SAMPLE");
36121        }
36122        self.write_space();
36123        self.generate_expression(&e.this)?;
36124        Ok(())
36125    }
36126
36127    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
36128        // this (expressions...)
36129        if let Some(this) = &e.this {
36130            self.generate_expression(this)?;
36131        }
36132        if !e.expressions.is_empty() {
36133            // Add space before column list if there's a preceding expression
36134            if e.this.is_some() {
36135                self.write_space();
36136            }
36137            self.write("(");
36138            for (i, expr) in e.expressions.iter().enumerate() {
36139                if i > 0 {
36140                    self.write(", ");
36141                }
36142                self.generate_expression(expr)?;
36143            }
36144            self.write(")");
36145        }
36146        Ok(())
36147    }
36148
36149    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
36150        // COMMENT this
36151        self.write_keyword("COMMENT");
36152        self.write_space();
36153        self.generate_expression(&e.this)?;
36154        Ok(())
36155    }
36156
36157    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
36158        // [this::]expression
36159        if let Some(this) = &e.this {
36160            self.generate_expression(this)?;
36161            self.write("::");
36162        }
36163        self.generate_expression(&e.expression)?;
36164        Ok(())
36165    }
36166
36167    fn generate_search(&mut self, e: &Search) -> Result<()> {
36168        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
36169        self.write_keyword("SEARCH");
36170        self.write("(");
36171        self.generate_expression(&e.this)?;
36172        self.write(", ");
36173        self.generate_expression(&e.expression)?;
36174        if let Some(json_scope) = &e.json_scope {
36175            self.write(", ");
36176            self.generate_expression(json_scope)?;
36177        }
36178        if let Some(analyzer) = &e.analyzer {
36179            self.write(", ");
36180            self.generate_expression(analyzer)?;
36181        }
36182        if let Some(analyzer_options) = &e.analyzer_options {
36183            self.write(", ");
36184            self.generate_expression(analyzer_options)?;
36185        }
36186        if let Some(search_mode) = &e.search_mode {
36187            self.write(", ");
36188            self.generate_expression(search_mode)?;
36189        }
36190        self.write(")");
36191        Ok(())
36192    }
36193
36194    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
36195        // SEARCH_IP(this, expression)
36196        self.write_keyword("SEARCH_IP");
36197        self.write("(");
36198        self.generate_expression(&e.this)?;
36199        self.write(", ");
36200        self.generate_expression(&e.expression)?;
36201        self.write(")");
36202        Ok(())
36203    }
36204
36205    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
36206        // SECURITY this
36207        self.write_keyword("SECURITY");
36208        self.write_space();
36209        self.generate_expression(&e.this)?;
36210        Ok(())
36211    }
36212
36213    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
36214        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
36215        self.write("SEMANTIC_VIEW(");
36216
36217        if self.config.pretty {
36218            // Pretty print: each clause on its own line
36219            self.write_newline();
36220            self.indent_level += 1;
36221            self.write_indent();
36222            self.generate_expression(&e.this)?;
36223
36224            if let Some(metrics) = &e.metrics {
36225                self.write_newline();
36226                self.write_indent();
36227                self.write_keyword("METRICS");
36228                self.write_space();
36229                self.generate_semantic_view_tuple(metrics)?;
36230            }
36231            if let Some(dimensions) = &e.dimensions {
36232                self.write_newline();
36233                self.write_indent();
36234                self.write_keyword("DIMENSIONS");
36235                self.write_space();
36236                self.generate_semantic_view_tuple(dimensions)?;
36237            }
36238            if let Some(facts) = &e.facts {
36239                self.write_newline();
36240                self.write_indent();
36241                self.write_keyword("FACTS");
36242                self.write_space();
36243                self.generate_semantic_view_tuple(facts)?;
36244            }
36245            if let Some(where_) = &e.where_ {
36246                self.write_newline();
36247                self.write_indent();
36248                self.write_keyword("WHERE");
36249                self.write_space();
36250                self.generate_expression(where_)?;
36251            }
36252            self.write_newline();
36253            self.indent_level -= 1;
36254            self.write_indent();
36255        } else {
36256            // Compact: all on one line
36257            self.generate_expression(&e.this)?;
36258            if let Some(metrics) = &e.metrics {
36259                self.write_space();
36260                self.write_keyword("METRICS");
36261                self.write_space();
36262                self.generate_semantic_view_tuple(metrics)?;
36263            }
36264            if let Some(dimensions) = &e.dimensions {
36265                self.write_space();
36266                self.write_keyword("DIMENSIONS");
36267                self.write_space();
36268                self.generate_semantic_view_tuple(dimensions)?;
36269            }
36270            if let Some(facts) = &e.facts {
36271                self.write_space();
36272                self.write_keyword("FACTS");
36273                self.write_space();
36274                self.generate_semantic_view_tuple(facts)?;
36275            }
36276            if let Some(where_) = &e.where_ {
36277                self.write_space();
36278                self.write_keyword("WHERE");
36279                self.write_space();
36280                self.generate_expression(where_)?;
36281            }
36282        }
36283        self.write(")");
36284        Ok(())
36285    }
36286
36287    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
36288    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
36289        if let Expression::Tuple(t) = expr {
36290            for (i, e) in t.expressions.iter().enumerate() {
36291                if i > 0 {
36292                    self.write(", ");
36293                }
36294                self.generate_expression(e)?;
36295            }
36296        } else {
36297            self.generate_expression(expr)?;
36298        }
36299        Ok(())
36300    }
36301
36302    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
36303        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
36304        if let Some(start) = &e.start {
36305            self.write_keyword("START WITH");
36306            self.write_space();
36307            self.generate_expression(start)?;
36308        }
36309        if let Some(increment) = &e.increment {
36310            self.write_space();
36311            self.write_keyword("INCREMENT BY");
36312            self.write_space();
36313            self.generate_expression(increment)?;
36314        }
36315        if let Some(minvalue) = &e.minvalue {
36316            self.write_space();
36317            self.write_keyword("MINVALUE");
36318            self.write_space();
36319            self.generate_expression(minvalue)?;
36320        }
36321        if let Some(maxvalue) = &e.maxvalue {
36322            self.write_space();
36323            self.write_keyword("MAXVALUE");
36324            self.write_space();
36325            self.generate_expression(maxvalue)?;
36326        }
36327        if let Some(cache) = &e.cache {
36328            self.write_space();
36329            self.write_keyword("CACHE");
36330            self.write_space();
36331            self.generate_expression(cache)?;
36332        }
36333        if let Some(owned) = &e.owned {
36334            self.write_space();
36335            self.write_keyword("OWNED BY");
36336            self.write_space();
36337            self.generate_expression(owned)?;
36338        }
36339        for opt in &e.options {
36340            self.write_space();
36341            self.generate_expression(opt)?;
36342        }
36343        Ok(())
36344    }
36345
36346    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
36347        // [WITH] SERDEPROPERTIES (expressions)
36348        if e.with_.is_some() {
36349            self.write_keyword("WITH");
36350            self.write_space();
36351        }
36352        self.write_keyword("SERDEPROPERTIES");
36353        self.write(" (");
36354        for (i, expr) in e.expressions.iter().enumerate() {
36355            if i > 0 {
36356                self.write(", ");
36357            }
36358            // Generate key=value without spaces around =
36359            match expr {
36360                Expression::Eq(eq) => {
36361                    self.generate_expression(&eq.left)?;
36362                    self.write("=");
36363                    self.generate_expression(&eq.right)?;
36364                }
36365                _ => self.generate_expression(expr)?,
36366            }
36367        }
36368        self.write(")");
36369        Ok(())
36370    }
36371
36372    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
36373        // @@[kind.]this
36374        self.write("@@");
36375        if let Some(kind) = &e.kind {
36376            self.write(kind);
36377            self.write(".");
36378        }
36379        self.generate_expression(&e.this)?;
36380        Ok(())
36381    }
36382
36383    fn generate_set(&mut self, e: &Set) -> Result<()> {
36384        // SET/UNSET [TAG] expressions
36385        if e.unset.is_some() {
36386            self.write_keyword("UNSET");
36387        } else {
36388            self.write_keyword("SET");
36389        }
36390        if e.tag.is_some() {
36391            self.write_space();
36392            self.write_keyword("TAG");
36393        }
36394        if !e.expressions.is_empty() {
36395            self.write_space();
36396            for (i, expr) in e.expressions.iter().enumerate() {
36397                if i > 0 {
36398                    self.write(", ");
36399                }
36400                self.generate_expression(expr)?;
36401            }
36402        }
36403        Ok(())
36404    }
36405
36406    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
36407        // SET this or SETCONFIG this
36408        self.write_keyword("SET");
36409        self.write_space();
36410        self.generate_expression(&e.this)?;
36411        Ok(())
36412    }
36413
36414    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
36415        // [kind] name = value
36416        if let Some(kind) = &e.kind {
36417            self.write_keyword(kind);
36418            self.write_space();
36419        }
36420        self.generate_expression(&e.name)?;
36421        self.write(" = ");
36422        self.generate_expression(&e.value)?;
36423        Ok(())
36424    }
36425
36426    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
36427        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
36428        if let Some(with_) = &e.with_ {
36429            self.generate_expression(with_)?;
36430            self.write_space();
36431        }
36432        self.generate_expression(&e.this)?;
36433        self.write_space();
36434        // kind should be UNION, INTERSECT, EXCEPT, etc.
36435        if let Some(kind) = &e.kind {
36436            self.write_keyword(kind);
36437        }
36438        if e.distinct {
36439            self.write_space();
36440            self.write_keyword("DISTINCT");
36441        } else {
36442            self.write_space();
36443            self.write_keyword("ALL");
36444        }
36445        if e.by_name.is_some() {
36446            self.write_space();
36447            self.write_keyword("BY NAME");
36448        }
36449        self.write_space();
36450        self.generate_expression(&e.expression)?;
36451        Ok(())
36452    }
36453
36454    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
36455        // SET or MULTISET
36456        if e.multi.is_some() {
36457            self.write_keyword("MULTISET");
36458        } else {
36459            self.write_keyword("SET");
36460        }
36461        Ok(())
36462    }
36463
36464    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
36465        // SETTINGS expressions
36466        self.write_keyword("SETTINGS");
36467        if self.config.pretty && e.expressions.len() > 1 {
36468            // Pretty print: each setting on its own line, indented
36469            self.indent_level += 1;
36470            for (i, expr) in e.expressions.iter().enumerate() {
36471                if i > 0 {
36472                    self.write(",");
36473                }
36474                self.write_newline();
36475                self.write_indent();
36476                self.generate_expression(expr)?;
36477            }
36478            self.indent_level -= 1;
36479        } else {
36480            self.write_space();
36481            for (i, expr) in e.expressions.iter().enumerate() {
36482                if i > 0 {
36483                    self.write(", ");
36484                }
36485                self.generate_expression(expr)?;
36486            }
36487        }
36488        Ok(())
36489    }
36490
36491    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
36492        // SHARING = this
36493        self.write_keyword("SHARING");
36494        if let Some(this) = &e.this {
36495            self.write(" = ");
36496            self.generate_expression(this)?;
36497        }
36498        Ok(())
36499    }
36500
36501    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
36502        // Python array slicing: begin:end:step
36503        if let Some(begin) = &e.this {
36504            self.generate_expression(begin)?;
36505        }
36506        self.write(":");
36507        if let Some(end) = &e.expression {
36508            self.generate_expression(end)?;
36509        }
36510        if let Some(step) = &e.step {
36511            self.write(":");
36512            self.generate_expression(step)?;
36513        }
36514        Ok(())
36515    }
36516
36517    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
36518        // SORT_ARRAY(this, asc)
36519        self.write_keyword("SORT_ARRAY");
36520        self.write("(");
36521        self.generate_expression(&e.this)?;
36522        if let Some(asc) = &e.asc {
36523            self.write(", ");
36524            self.generate_expression(asc)?;
36525        }
36526        self.write(")");
36527        Ok(())
36528    }
36529
36530    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
36531        // SORT BY expressions
36532        self.write_keyword("SORT BY");
36533        self.write_space();
36534        for (i, expr) in e.expressions.iter().enumerate() {
36535            if i > 0 {
36536                self.write(", ");
36537            }
36538            self.generate_ordered(expr)?;
36539        }
36540        Ok(())
36541    }
36542
36543    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
36544        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
36545        if e.compound.is_some() {
36546            self.write_keyword("COMPOUND");
36547            self.write_space();
36548        }
36549        self.write_keyword("SORTKEY");
36550        self.write("(");
36551        // If this is a Tuple, unwrap its contents to avoid double parentheses
36552        if let Expression::Tuple(t) = e.this.as_ref() {
36553            for (i, expr) in t.expressions.iter().enumerate() {
36554                if i > 0 {
36555                    self.write(", ");
36556                }
36557                self.generate_expression(expr)?;
36558            }
36559        } else {
36560            self.generate_expression(&e.this)?;
36561        }
36562        self.write(")");
36563        Ok(())
36564    }
36565
36566    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
36567        // SPLIT_PART(this, delimiter, part_index)
36568        self.write_keyword("SPLIT_PART");
36569        self.write("(");
36570        self.generate_expression(&e.this)?;
36571        if let Some(delimiter) = &e.delimiter {
36572            self.write(", ");
36573            self.generate_expression(delimiter)?;
36574        }
36575        if let Some(part_index) = &e.part_index {
36576            self.write(", ");
36577            self.generate_expression(part_index)?;
36578        }
36579        self.write(")");
36580        Ok(())
36581    }
36582
36583    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
36584        // READS SQL DATA or MODIFIES SQL DATA, etc.
36585        self.generate_expression(&e.this)?;
36586        Ok(())
36587    }
36588
36589    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
36590        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
36591        self.write_keyword("SQL SECURITY");
36592        self.write_space();
36593        self.generate_expression(&e.this)?;
36594        Ok(())
36595    }
36596
36597    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
36598        // ST_DISTANCE(this, expression, [use_spheroid])
36599        self.write_keyword("ST_DISTANCE");
36600        self.write("(");
36601        self.generate_expression(&e.this)?;
36602        self.write(", ");
36603        self.generate_expression(&e.expression)?;
36604        if let Some(use_spheroid) = &e.use_spheroid {
36605            self.write(", ");
36606            self.generate_expression(use_spheroid)?;
36607        }
36608        self.write(")");
36609        Ok(())
36610    }
36611
36612    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
36613        // ST_POINT(this, expression)
36614        self.write_keyword("ST_POINT");
36615        self.write("(");
36616        self.generate_expression(&e.this)?;
36617        self.write(", ");
36618        self.generate_expression(&e.expression)?;
36619        self.write(")");
36620        Ok(())
36621    }
36622
36623    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
36624        // IMMUTABLE, STABLE, VOLATILE
36625        self.generate_expression(&e.this)?;
36626        Ok(())
36627    }
36628
36629    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
36630        // STANDARD_HASH(this, [expression])
36631        self.write_keyword("STANDARD_HASH");
36632        self.write("(");
36633        self.generate_expression(&e.this)?;
36634        if let Some(expression) = &e.expression {
36635            self.write(", ");
36636            self.generate_expression(expression)?;
36637        }
36638        self.write(")");
36639        Ok(())
36640    }
36641
36642    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
36643        // STORED BY this
36644        self.write_keyword("STORED BY");
36645        self.write_space();
36646        self.generate_expression(&e.this)?;
36647        Ok(())
36648    }
36649
36650    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
36651        // STRPOS(this, substr) or STRPOS(this, substr, position)
36652        // Different dialects have different function names
36653        use crate::dialects::DialectType;
36654        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36655            // Snowflake: CHARINDEX(substr, str[, position])
36656            self.write_keyword("CHARINDEX");
36657            self.write("(");
36658            if let Some(substr) = &e.substr {
36659                self.generate_expression(substr)?;
36660                self.write(", ");
36661            }
36662            self.generate_expression(&e.this)?;
36663            if let Some(position) = &e.position {
36664                self.write(", ");
36665                self.generate_expression(position)?;
36666            }
36667            self.write(")");
36668        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36669            self.write_keyword("POSITION");
36670            self.write("(");
36671            self.generate_expression(&e.this)?;
36672            if let Some(substr) = &e.substr {
36673                self.write(", ");
36674                self.generate_expression(substr)?;
36675            }
36676            if let Some(position) = &e.position {
36677                self.write(", ");
36678                self.generate_expression(position)?;
36679            }
36680            if let Some(occurrence) = &e.occurrence {
36681                self.write(", ");
36682                self.generate_expression(occurrence)?;
36683            }
36684            self.write(")");
36685        } else if matches!(
36686            self.config.dialect,
36687            Some(DialectType::SQLite)
36688                | Some(DialectType::Oracle)
36689                | Some(DialectType::BigQuery)
36690                | Some(DialectType::Teradata)
36691        ) {
36692            self.write_keyword("INSTR");
36693            self.write("(");
36694            self.generate_expression(&e.this)?;
36695            if let Some(substr) = &e.substr {
36696                self.write(", ");
36697                self.generate_expression(substr)?;
36698            }
36699            if let Some(position) = &e.position {
36700                self.write(", ");
36701                self.generate_expression(position)?;
36702            } else if e.occurrence.is_some() {
36703                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
36704                // Default start position is 1
36705                self.write(", 1");
36706            }
36707            if let Some(occurrence) = &e.occurrence {
36708                self.write(", ");
36709                self.generate_expression(occurrence)?;
36710            }
36711            self.write(")");
36712        } else if matches!(
36713            self.config.dialect,
36714            Some(DialectType::MySQL)
36715                | Some(DialectType::SingleStore)
36716                | Some(DialectType::Doris)
36717                | Some(DialectType::StarRocks)
36718                | Some(DialectType::Hive)
36719                | Some(DialectType::Spark)
36720                | Some(DialectType::Databricks)
36721        ) {
36722            // LOCATE(substr, str[, position]) - substr first
36723            self.write_keyword("LOCATE");
36724            self.write("(");
36725            if let Some(substr) = &e.substr {
36726                self.generate_expression(substr)?;
36727                self.write(", ");
36728            }
36729            self.generate_expression(&e.this)?;
36730            if let Some(position) = &e.position {
36731                self.write(", ");
36732                self.generate_expression(position)?;
36733            }
36734            self.write(")");
36735        } else if matches!(
36736            self.config.dialect,
36737            Some(DialectType::TSQL) | Some(DialectType::Fabric)
36738        ) {
36739            // CHARINDEX(substr, str[, position])
36740            self.write_keyword("CHARINDEX");
36741            self.write("(");
36742            if let Some(substr) = &e.substr {
36743                self.generate_expression(substr)?;
36744                self.write(", ");
36745            }
36746            self.generate_expression(&e.this)?;
36747            if let Some(position) = &e.position {
36748                self.write(", ");
36749                self.generate_expression(position)?;
36750            }
36751            self.write(")");
36752        } else if matches!(
36753            self.config.dialect,
36754            Some(DialectType::PostgreSQL)
36755                | Some(DialectType::Materialize)
36756                | Some(DialectType::RisingWave)
36757                | Some(DialectType::Redshift)
36758        ) {
36759            // POSITION(substr IN str) syntax
36760            self.write_keyword("POSITION");
36761            self.write("(");
36762            if let Some(substr) = &e.substr {
36763                self.generate_expression(substr)?;
36764                self.write(" IN ");
36765            }
36766            self.generate_expression(&e.this)?;
36767            self.write(")");
36768        } else {
36769            self.write_keyword("STRPOS");
36770            self.write("(");
36771            self.generate_expression(&e.this)?;
36772            if let Some(substr) = &e.substr {
36773                self.write(", ");
36774                self.generate_expression(substr)?;
36775            }
36776            if let Some(position) = &e.position {
36777                self.write(", ");
36778                self.generate_expression(position)?;
36779            }
36780            if let Some(occurrence) = &e.occurrence {
36781                self.write(", ");
36782                self.generate_expression(occurrence)?;
36783            }
36784            self.write(")");
36785        }
36786        Ok(())
36787    }
36788
36789    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
36790        match self.config.dialect {
36791            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36792                self.generate_tsql_str_to_temporal(&e.this, e.format.as_deref(), "DATE")?;
36793            }
36794            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
36795                // TO_DATE(this, java_format)
36796                self.write_keyword("TO_DATE");
36797                self.write("(");
36798                self.generate_expression(&e.this)?;
36799                if let Some(format) = &e.format {
36800                    self.write(", '");
36801                    self.write(&Self::strftime_to_java_format(format));
36802                    self.write("'");
36803                }
36804                self.write(")");
36805            }
36806            Some(DialectType::DuckDB) => {
36807                // CAST(STRPTIME(this, format) AS DATE)
36808                self.write_keyword("CAST");
36809                self.write("(");
36810                self.write_keyword("STRPTIME");
36811                self.write("(");
36812                self.generate_expression(&e.this)?;
36813                if let Some(format) = &e.format {
36814                    self.write(", '");
36815                    self.write(format);
36816                    self.write("'");
36817                }
36818                self.write(")");
36819                self.write_keyword(" AS ");
36820                self.write_keyword("DATE");
36821                self.write(")");
36822            }
36823            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
36824                // TO_DATE(this, pg_format)
36825                self.write_keyword("TO_DATE");
36826                self.write("(");
36827                self.generate_expression(&e.this)?;
36828                if let Some(format) = &e.format {
36829                    self.write(", '");
36830                    self.write(&Self::strftime_to_postgres_format(format));
36831                    self.write("'");
36832                }
36833                self.write(")");
36834            }
36835            Some(DialectType::BigQuery) => {
36836                // PARSE_DATE(format, this) - note: format comes first for BigQuery
36837                self.write_keyword("PARSE_DATE");
36838                self.write("(");
36839                if let Some(format) = &e.format {
36840                    self.write("'");
36841                    self.write(format);
36842                    self.write("'");
36843                    self.write(", ");
36844                }
36845                self.generate_expression(&e.this)?;
36846                self.write(")");
36847            }
36848            Some(DialectType::Teradata) => {
36849                // CAST(this AS DATE FORMAT 'teradata_fmt')
36850                self.write_keyword("CAST");
36851                self.write("(");
36852                self.generate_expression(&e.this)?;
36853                self.write_keyword(" AS ");
36854                self.write_keyword("DATE");
36855                if let Some(format) = &e.format {
36856                    self.write_keyword(" FORMAT ");
36857                    self.write("'");
36858                    self.write(&Self::strftime_to_teradata_format(format));
36859                    self.write("'");
36860                }
36861                self.write(")");
36862            }
36863            _ => {
36864                // STR_TO_DATE(this, format) - MySQL default
36865                self.write_keyword("STR_TO_DATE");
36866                self.write("(");
36867                self.generate_expression(&e.this)?;
36868                if let Some(format) = &e.format {
36869                    self.write(", '");
36870                    self.write(format);
36871                    self.write("'");
36872                }
36873                self.write(")");
36874            }
36875        }
36876        Ok(())
36877    }
36878
36879    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
36880    fn strftime_to_teradata_format(fmt: &str) -> String {
36881        let mut result = String::with_capacity(fmt.len() * 2);
36882        let bytes = fmt.as_bytes();
36883        let len = bytes.len();
36884        let mut i = 0;
36885        while i < len {
36886            if bytes[i] == b'%' && i + 1 < len {
36887                let replacement = match bytes[i + 1] {
36888                    b'Y' => "YYYY",
36889                    b'y' => "YY",
36890                    b'm' => "MM",
36891                    b'B' => "MMMM",
36892                    b'b' => "MMM",
36893                    b'd' => "DD",
36894                    b'j' => "DDD",
36895                    b'H' => "HH",
36896                    b'M' => "MI",
36897                    b'S' => "SS",
36898                    b'f' => "SSSSSS",
36899                    b'A' => "EEEE",
36900                    b'a' => "EEE",
36901                    _ => {
36902                        result.push('%');
36903                        i += 1;
36904                        continue;
36905                    }
36906                };
36907                result.push_str(replacement);
36908                i += 2;
36909            } else {
36910                result.push(bytes[i] as char);
36911                i += 1;
36912            }
36913        }
36914        result
36915    }
36916
36917    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
36918    /// Public static version for use by other modules
36919    pub fn strftime_to_java_format_static(fmt: &str) -> String {
36920        Self::strftime_to_java_format(fmt)
36921    }
36922
36923    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
36924    fn strftime_to_java_format(fmt: &str) -> String {
36925        let mut result = String::with_capacity(fmt.len() * 2);
36926        let bytes = fmt.as_bytes();
36927        let len = bytes.len();
36928        let mut i = 0;
36929        while i < len {
36930            if bytes[i] == b'%' && i + 1 < len {
36931                // Check for non-padded variants (%-X)
36932                if bytes[i + 1] == b'-' && i + 2 < len {
36933                    let replacement = match bytes[i + 2] {
36934                        b'd' => "d",
36935                        b'm' => "M",
36936                        b'H' => "H",
36937                        b'M' => "m",
36938                        b'S' => "s",
36939                        _ => {
36940                            result.push('%');
36941                            i += 1;
36942                            continue;
36943                        }
36944                    };
36945                    result.push_str(replacement);
36946                    i += 3;
36947                } else {
36948                    let replacement = match bytes[i + 1] {
36949                        b'Y' => "yyyy",
36950                        b'y' => "yy",
36951                        b'm' => "MM",
36952                        b'B' => "MMMM",
36953                        b'b' => "MMM",
36954                        b'd' => "dd",
36955                        b'j' => "DDD",
36956                        b'H' => "HH",
36957                        b'M' => "mm",
36958                        b'S' => "ss",
36959                        b'f' => "SSSSSS",
36960                        b'A' => "EEEE",
36961                        b'a' => "EEE",
36962                        _ => {
36963                            result.push('%');
36964                            i += 1;
36965                            continue;
36966                        }
36967                    };
36968                    result.push_str(replacement);
36969                    i += 2;
36970                }
36971            } else {
36972                result.push(bytes[i] as char);
36973                i += 1;
36974            }
36975        }
36976        result
36977    }
36978
36979    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
36980    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
36981    fn strftime_to_tsql_format(fmt: &str) -> String {
36982        let mut result = String::with_capacity(fmt.len() * 2);
36983        let bytes = fmt.as_bytes();
36984        let len = bytes.len();
36985        let mut i = 0;
36986        while i < len {
36987            if bytes[i] == b'%' && i + 1 < len {
36988                // Check for non-padded variants (%-X)
36989                if bytes[i + 1] == b'-' && i + 2 < len {
36990                    let replacement = match bytes[i + 2] {
36991                        b'd' => "d",
36992                        b'm' => "M",
36993                        b'H' => "H",
36994                        b'M' => "m",
36995                        b'S' => "s",
36996                        _ => {
36997                            result.push('%');
36998                            i += 1;
36999                            continue;
37000                        }
37001                    };
37002                    result.push_str(replacement);
37003                    i += 3;
37004                } else {
37005                    let replacement = match bytes[i + 1] {
37006                        b'Y' => "yyyy",
37007                        b'y' => "yy",
37008                        b'm' => "MM",
37009                        b'B' => "MMMM",
37010                        b'b' => "MMM",
37011                        b'd' => "dd",
37012                        b'j' => "DDD",
37013                        b'H' => "HH",
37014                        b'M' => "mm",
37015                        b'S' => "ss",
37016                        b'f' => "ffffff",
37017                        b'A' => "dddd",
37018                        b'a' => "ddd",
37019                        _ => {
37020                            result.push('%');
37021                            i += 1;
37022                            continue;
37023                        }
37024                    };
37025                    result.push_str(replacement);
37026                    i += 2;
37027                }
37028            } else {
37029                result.push(bytes[i] as char);
37030                i += 1;
37031            }
37032        }
37033        result
37034    }
37035
37036    fn tsql_convert_style_for_strftime(fmt: &str) -> Option<u16> {
37037        match fmt.trim() {
37038            "%b %d %Y %-I:%M%p" => Some(100),
37039            "%m/%d/%y" => Some(1),
37040            "%y.%m.%d" => Some(2),
37041            "%d/%m/%y" => Some(3),
37042            "%d.%m.%y" => Some(4),
37043            "%d-%m-%y" => Some(5),
37044            "%d %b %y" => Some(6),
37045            "%b %d, %y" => Some(7),
37046            "%H:%M:%S" => Some(108),
37047            "%b %d %Y %-I:%M:%S:%f%p" => Some(109),
37048            "%m-%d-%y" => Some(10),
37049            "%y/%m/%d" => Some(11),
37050            "%y%m%d" => Some(12),
37051            "%d %b %Y %H:%M:%S:%f" => Some(113),
37052            "%H:%M:%S:%f" => Some(114),
37053            "%m/%d/%Y" => Some(101),
37054            "%Y.%m.%d" => Some(102),
37055            "%d/%m/%Y" => Some(103),
37056            "%d.%m.%Y" => Some(104),
37057            "%d-%m-%Y" => Some(105),
37058            "%d %b %Y" => Some(106),
37059            "%b %d, %Y" => Some(107),
37060            "%m-%d-%Y" => Some(110),
37061            "%Y/%m/%d" => Some(111),
37062            "%Y%m%d" => Some(112),
37063            "%Y-%m-%d %H:%M:%S" => Some(120),
37064            "%Y-%m-%d %H:%M:%S.%f" => Some(121),
37065            "%Y-%m-%dT%H:%M:%S" | "%Y-%m-%dT%H:%M:%S.%f" => Some(126),
37066            "%Y-%m-%d" => Some(23),
37067            _ => None,
37068        }
37069    }
37070
37071    fn generate_tsql_str_to_temporal(
37072        &mut self,
37073        this: &Expression,
37074        format: Option<&str>,
37075        target_type: &str,
37076    ) -> Result<()> {
37077        if let Some(format) = format {
37078            if let Some(style) = Self::tsql_convert_style_for_strftime(format) {
37079                self.write_keyword("CONVERT");
37080                self.write("(");
37081                self.write_keyword(target_type);
37082                self.write(", ");
37083                self.generate_expression(this)?;
37084                self.write(", ");
37085                self.write(&style.to_string());
37086                self.write(")");
37087                return Ok(());
37088            }
37089
37090            self.unsupported(format!(
37091                "T-SQL/Fabric {target_type} parsing format '{format}' has no CONVERT style mapping"
37092            ))?;
37093        }
37094
37095        self.write_keyword("CAST");
37096        self.write("(");
37097        self.generate_expression(this)?;
37098        self.write(" AS ");
37099        self.write_keyword(target_type);
37100        self.write(")");
37101        Ok(())
37102    }
37103
37104    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
37105    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
37106    fn decompose_json_path(path: &str) -> Vec<String> {
37107        let mut parts = Vec::new();
37108        // Strip leading $ and optional .
37109        let path = if path.starts_with("$.") {
37110            &path[2..]
37111        } else if path.starts_with('$') {
37112            &path[1..]
37113        } else {
37114            path
37115        };
37116        if path.is_empty() {
37117            return parts;
37118        }
37119        let mut current = String::new();
37120        let chars: Vec<char> = path.chars().collect();
37121        let mut i = 0;
37122        while i < chars.len() {
37123            match chars[i] {
37124                '.' => {
37125                    if !current.is_empty() {
37126                        parts.push(current.clone());
37127                        current.clear();
37128                    }
37129                    i += 1;
37130                }
37131                '[' => {
37132                    if !current.is_empty() {
37133                        parts.push(current.clone());
37134                        current.clear();
37135                    }
37136                    i += 1;
37137                    // Read the content inside brackets
37138                    let mut bracket_content = String::new();
37139                    while i < chars.len() && chars[i] != ']' {
37140                        // Skip quotes inside brackets
37141                        if chars[i] == '"' || chars[i] == '\'' {
37142                            let quote = chars[i];
37143                            i += 1;
37144                            while i < chars.len() && chars[i] != quote {
37145                                bracket_content.push(chars[i]);
37146                                i += 1;
37147                            }
37148                            if i < chars.len() {
37149                                i += 1;
37150                            } // skip closing quote
37151                        } else {
37152                            bracket_content.push(chars[i]);
37153                            i += 1;
37154                        }
37155                    }
37156                    if i < chars.len() {
37157                        i += 1;
37158                    } // skip ]
37159                      // Skip wildcard [*] - don't add as a part
37160                    if bracket_content != "*" {
37161                        parts.push(bracket_content);
37162                    }
37163                }
37164                _ => {
37165                    current.push(chars[i]);
37166                    i += 1;
37167                }
37168            }
37169        }
37170        if !current.is_empty() {
37171            parts.push(current);
37172        }
37173        parts
37174    }
37175
37176    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
37177    fn strftime_to_postgres_format(fmt: &str) -> String {
37178        let mut result = String::with_capacity(fmt.len() * 2);
37179        let bytes = fmt.as_bytes();
37180        let len = bytes.len();
37181        let mut i = 0;
37182        while i < len {
37183            if bytes[i] == b'%' && i + 1 < len {
37184                // Check for non-padded variants (%-X)
37185                if bytes[i + 1] == b'-' && i + 2 < len {
37186                    let replacement = match bytes[i + 2] {
37187                        b'd' => "FMDD",
37188                        b'm' => "FMMM",
37189                        b'H' => "FMHH24",
37190                        b'M' => "FMMI",
37191                        b'S' => "FMSS",
37192                        _ => {
37193                            result.push('%');
37194                            i += 1;
37195                            continue;
37196                        }
37197                    };
37198                    result.push_str(replacement);
37199                    i += 3;
37200                } else {
37201                    let replacement = match bytes[i + 1] {
37202                        b'Y' => "YYYY",
37203                        b'y' => "YY",
37204                        b'm' => "MM",
37205                        b'B' => "Month",
37206                        b'b' => "Mon",
37207                        b'd' => "DD",
37208                        b'j' => "DDD",
37209                        b'H' => "HH24",
37210                        b'M' => "MI",
37211                        b'S' => "SS",
37212                        b'f' => "US",
37213                        b'A' => "Day",
37214                        b'a' => "Dy",
37215                        _ => {
37216                            result.push('%');
37217                            i += 1;
37218                            continue;
37219                        }
37220                    };
37221                    result.push_str(replacement);
37222                    i += 2;
37223                }
37224            } else {
37225                result.push(bytes[i] as char);
37226                i += 1;
37227            }
37228        }
37229        result
37230    }
37231
37232    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
37233    fn strftime_to_snowflake_format(fmt: &str) -> String {
37234        let mut result = String::with_capacity(fmt.len() * 2);
37235        let bytes = fmt.as_bytes();
37236        let len = bytes.len();
37237        let mut i = 0;
37238        while i < len {
37239            if bytes[i] == b'%' && i + 1 < len {
37240                // Check for non-padded variants (%-X)
37241                if bytes[i + 1] == b'-' && i + 2 < len {
37242                    let replacement = match bytes[i + 2] {
37243                        b'd' => "dd",
37244                        b'm' => "mm",
37245                        _ => {
37246                            result.push('%');
37247                            i += 1;
37248                            continue;
37249                        }
37250                    };
37251                    result.push_str(replacement);
37252                    i += 3;
37253                } else {
37254                    let replacement = match bytes[i + 1] {
37255                        b'Y' => "yyyy",
37256                        b'y' => "yy",
37257                        b'm' => "mm",
37258                        b'd' => "DD",
37259                        b'H' => "hh24",
37260                        b'M' => "mi",
37261                        b'S' => "ss",
37262                        b'f' => "ff",
37263                        _ => {
37264                            result.push('%');
37265                            i += 1;
37266                            continue;
37267                        }
37268                    };
37269                    result.push_str(replacement);
37270                    i += 2;
37271                }
37272            } else {
37273                result.push(bytes[i] as char);
37274                i += 1;
37275            }
37276        }
37277        result
37278    }
37279
37280    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
37281        // STR_TO_MAP(this, pair_delim, key_value_delim)
37282        self.write_keyword("STR_TO_MAP");
37283        self.write("(");
37284        self.generate_expression(&e.this)?;
37285        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
37286        let needs_defaults = matches!(
37287            self.config.dialect,
37288            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
37289        );
37290        if let Some(pair_delim) = &e.pair_delim {
37291            self.write(", ");
37292            self.generate_expression(pair_delim)?;
37293        } else if needs_defaults {
37294            self.write(", ','");
37295        }
37296        if let Some(key_value_delim) = &e.key_value_delim {
37297            self.write(", ");
37298            self.generate_expression(key_value_delim)?;
37299        } else if needs_defaults {
37300            self.write(", ':'");
37301        }
37302        self.write(")");
37303        Ok(())
37304    }
37305
37306    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
37307        // Detect format style: strftime (starts with %) vs Snowflake/Java
37308        let is_strftime = e.format.contains('%');
37309        // Helper: get strftime format from whatever style is stored
37310        let to_strftime = |f: &str| -> String {
37311            if is_strftime {
37312                f.to_string()
37313            } else {
37314                Self::snowflake_format_to_strftime(f)
37315            }
37316        };
37317        // Helper: get Java format
37318        let to_java = |f: &str| -> String {
37319            if is_strftime {
37320                Self::strftime_to_java_format(f)
37321            } else {
37322                Self::snowflake_format_to_spark(f)
37323            }
37324        };
37325        // Helper: get PG format
37326        let to_pg = |f: &str| -> String {
37327            if is_strftime {
37328                Self::strftime_to_postgres_format(f)
37329            } else {
37330                Self::convert_strptime_to_postgres_format(f)
37331            }
37332        };
37333
37334        match self.config.dialect {
37335            Some(DialectType::Exasol) => {
37336                self.write_keyword("TO_DATE");
37337                self.write("(");
37338                self.generate_expression(&e.this)?;
37339                self.write(", '");
37340                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
37341                self.write("'");
37342                self.write(")");
37343            }
37344            Some(DialectType::BigQuery) => {
37345                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
37346                let fmt = to_strftime(&e.format);
37347                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
37348                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
37349                self.write_keyword("PARSE_TIMESTAMP");
37350                self.write("('");
37351                self.write(&fmt);
37352                self.write("', ");
37353                self.generate_expression(&e.this)?;
37354                self.write(")");
37355            }
37356            Some(DialectType::Hive) => {
37357                // Hive: CAST(x AS TIMESTAMP) for simple date formats
37358                // Check both the raw format and the converted format (in case it's already Java)
37359                let java_fmt = to_java(&e.format);
37360                if java_fmt == "yyyy-MM-dd HH:mm:ss"
37361                    || java_fmt == "yyyy-MM-dd"
37362                    || e.format == "yyyy-MM-dd HH:mm:ss"
37363                    || e.format == "yyyy-MM-dd"
37364                {
37365                    self.write_keyword("CAST");
37366                    self.write("(");
37367                    self.generate_expression(&e.this)?;
37368                    self.write(" ");
37369                    self.write_keyword("AS TIMESTAMP");
37370                    self.write(")");
37371                } else {
37372                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
37373                    self.write_keyword("CAST");
37374                    self.write("(");
37375                    self.write_keyword("FROM_UNIXTIME");
37376                    self.write("(");
37377                    self.write_keyword("UNIX_TIMESTAMP");
37378                    self.write("(");
37379                    self.generate_expression(&e.this)?;
37380                    self.write(", '");
37381                    self.write(&java_fmt);
37382                    self.write("')");
37383                    self.write(") ");
37384                    self.write_keyword("AS TIMESTAMP");
37385                    self.write(")");
37386                }
37387            }
37388            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37389                // Spark: TO_TIMESTAMP(value, java_format)
37390                let java_fmt = to_java(&e.format);
37391                self.write_keyword("TO_TIMESTAMP");
37392                self.write("(");
37393                self.generate_expression(&e.this)?;
37394                self.write(", '");
37395                self.write(&java_fmt);
37396                self.write("')");
37397            }
37398            Some(DialectType::MySQL) => {
37399                // MySQL: STR_TO_DATE(value, format)
37400                let mut fmt = to_strftime(&e.format);
37401                // MySQL uses %e for non-padded day, %T for %H:%M:%S
37402                fmt = fmt.replace("%-d", "%e");
37403                fmt = fmt.replace("%-m", "%c");
37404                fmt = fmt.replace("%H:%M:%S", "%T");
37405                self.write_keyword("STR_TO_DATE");
37406                self.write("(");
37407                self.generate_expression(&e.this)?;
37408                self.write(", '");
37409                self.write(&fmt);
37410                self.write("')");
37411            }
37412            Some(DialectType::Drill) => {
37413                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
37414                let java_fmt = to_java(&e.format);
37415                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
37416                let java_fmt = java_fmt.replace('T', "''T''");
37417                self.write_keyword("TO_TIMESTAMP");
37418                self.write("(");
37419                self.generate_expression(&e.this)?;
37420                self.write(", '");
37421                self.write(&java_fmt);
37422                self.write("')");
37423            }
37424            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
37425                // Presto: DATE_PARSE(value, strftime_format)
37426                let mut fmt = to_strftime(&e.format);
37427                // Presto uses %e for non-padded day, %T for %H:%M:%S
37428                fmt = fmt.replace("%-d", "%e");
37429                fmt = fmt.replace("%-m", "%c");
37430                fmt = fmt.replace("%H:%M:%S", "%T");
37431                self.write_keyword("DATE_PARSE");
37432                self.write("(");
37433                self.generate_expression(&e.this)?;
37434                self.write(", '");
37435                self.write(&fmt);
37436                self.write("')");
37437            }
37438            Some(DialectType::DuckDB) => {
37439                // DuckDB: STRPTIME(value, strftime_format)
37440                let fmt = to_strftime(&e.format);
37441                self.write_keyword("STRPTIME");
37442                self.write("(");
37443                self.generate_expression(&e.this)?;
37444                self.write(", '");
37445                self.write(&fmt);
37446                self.write("')");
37447            }
37448            Some(DialectType::PostgreSQL)
37449            | Some(DialectType::Redshift)
37450            | Some(DialectType::Materialize) => {
37451                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
37452                let pg_fmt = to_pg(&e.format);
37453                self.write_keyword("TO_TIMESTAMP");
37454                self.write("(");
37455                self.generate_expression(&e.this)?;
37456                self.write(", '");
37457                self.write(&pg_fmt);
37458                self.write("')");
37459            }
37460            Some(DialectType::Oracle) => {
37461                // Oracle: TO_TIMESTAMP(value, pg_format)
37462                let pg_fmt = to_pg(&e.format);
37463                self.write_keyword("TO_TIMESTAMP");
37464                self.write("(");
37465                self.generate_expression(&e.this)?;
37466                self.write(", '");
37467                self.write(&pg_fmt);
37468                self.write("')");
37469            }
37470            Some(DialectType::Snowflake) => {
37471                // Snowflake: TO_TIMESTAMP(value, format) - native format
37472                self.write_keyword("TO_TIMESTAMP");
37473                self.write("(");
37474                self.generate_expression(&e.this)?;
37475                self.write(", '");
37476                self.write(&e.format);
37477                self.write("')");
37478            }
37479            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
37480                self.generate_tsql_str_to_temporal(&e.this, Some(&e.format), "DATETIME2")?;
37481            }
37482            _ => {
37483                // Default: STR_TO_TIME(this, format)
37484                self.write_keyword("STR_TO_TIME");
37485                self.write("(");
37486                self.generate_expression(&e.this)?;
37487                self.write(", '");
37488                self.write(&e.format);
37489                self.write("'");
37490                self.write(")");
37491            }
37492        }
37493        Ok(())
37494    }
37495
37496    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
37497    fn snowflake_format_to_strftime(format: &str) -> String {
37498        let mut result = String::new();
37499        let chars: Vec<char> = format.chars().collect();
37500        let mut i = 0;
37501        while i < chars.len() {
37502            let remaining = &format[i..];
37503            if remaining.starts_with("yyyy") {
37504                result.push_str("%Y");
37505                i += 4;
37506            } else if remaining.starts_with("yy") {
37507                result.push_str("%y");
37508                i += 2;
37509            } else if remaining.starts_with("mmmm") {
37510                result.push_str("%B"); // full month name
37511                i += 4;
37512            } else if remaining.starts_with("mon") {
37513                result.push_str("%b"); // abbreviated month
37514                i += 3;
37515            } else if remaining.starts_with("mm") {
37516                result.push_str("%m");
37517                i += 2;
37518            } else if remaining.starts_with("DD") {
37519                result.push_str("%d");
37520                i += 2;
37521            } else if remaining.starts_with("dy") {
37522                result.push_str("%a"); // abbreviated day name
37523                i += 2;
37524            } else if remaining.starts_with("hh24") {
37525                result.push_str("%H");
37526                i += 4;
37527            } else if remaining.starts_with("hh12") {
37528                result.push_str("%I");
37529                i += 4;
37530            } else if remaining.starts_with("hh") {
37531                result.push_str("%H");
37532                i += 2;
37533            } else if remaining.starts_with("mi") {
37534                result.push_str("%M");
37535                i += 2;
37536            } else if remaining.starts_with("ss") {
37537                result.push_str("%S");
37538                i += 2;
37539            } else if remaining.starts_with("ff") {
37540                // Fractional seconds
37541                result.push_str("%f");
37542                i += 2;
37543                // Skip digits after ff (ff3, ff6, ff9)
37544                while i < chars.len() && chars[i].is_ascii_digit() {
37545                    i += 1;
37546                }
37547            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
37548                result.push_str("%p");
37549                i += 2;
37550            } else if remaining.starts_with("tz") {
37551                result.push_str("%Z");
37552                i += 2;
37553            } else {
37554                result.push(chars[i]);
37555                i += 1;
37556            }
37557        }
37558        result
37559    }
37560
37561    /// Convert Snowflake normalized format to Spark format (Java-style)
37562    fn snowflake_format_to_spark(format: &str) -> String {
37563        let mut result = String::new();
37564        let chars: Vec<char> = format.chars().collect();
37565        let mut i = 0;
37566        while i < chars.len() {
37567            let remaining = &format[i..];
37568            if remaining.starts_with("yyyy") {
37569                result.push_str("yyyy");
37570                i += 4;
37571            } else if remaining.starts_with("yy") {
37572                result.push_str("yy");
37573                i += 2;
37574            } else if remaining.starts_with("mmmm") {
37575                result.push_str("MMMM"); // full month name
37576                i += 4;
37577            } else if remaining.starts_with("mon") {
37578                result.push_str("MMM"); // abbreviated month
37579                i += 3;
37580            } else if remaining.starts_with("mm") {
37581                result.push_str("MM");
37582                i += 2;
37583            } else if remaining.starts_with("DD") {
37584                result.push_str("dd");
37585                i += 2;
37586            } else if remaining.starts_with("dy") {
37587                result.push_str("EEE"); // abbreviated day name
37588                i += 2;
37589            } else if remaining.starts_with("hh24") {
37590                result.push_str("HH");
37591                i += 4;
37592            } else if remaining.starts_with("hh12") {
37593                result.push_str("hh");
37594                i += 4;
37595            } else if remaining.starts_with("hh") {
37596                result.push_str("HH");
37597                i += 2;
37598            } else if remaining.starts_with("mi") {
37599                result.push_str("mm");
37600                i += 2;
37601            } else if remaining.starts_with("ss") {
37602                result.push_str("ss");
37603                i += 2;
37604            } else if remaining.starts_with("ff") {
37605                result.push_str("SSS"); // milliseconds
37606                i += 2;
37607                // Skip digits after ff
37608                while i < chars.len() && chars[i].is_ascii_digit() {
37609                    i += 1;
37610                }
37611            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
37612                result.push_str("a");
37613                i += 2;
37614            } else if remaining.starts_with("tz") {
37615                result.push_str("z");
37616                i += 2;
37617            } else {
37618                result.push(chars[i]);
37619                i += 1;
37620            }
37621        }
37622        result
37623    }
37624
37625    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
37626        match self.config.dialect {
37627            Some(DialectType::DuckDB) => {
37628                // DuckDB: EPOCH(STRPTIME(value, format))
37629                self.write_keyword("EPOCH");
37630                self.write("(");
37631                self.write_keyword("STRPTIME");
37632                self.write("(");
37633                if let Some(this) = &e.this {
37634                    self.generate_expression(this)?;
37635                }
37636                if let Some(format) = &e.format {
37637                    self.write(", '");
37638                    self.write(format);
37639                    self.write("'");
37640                }
37641                self.write("))");
37642            }
37643            Some(DialectType::Hive) => {
37644                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
37645                self.write_keyword("UNIX_TIMESTAMP");
37646                self.write("(");
37647                if let Some(this) = &e.this {
37648                    self.generate_expression(this)?;
37649                }
37650                if let Some(format) = &e.format {
37651                    let java_fmt = Self::strftime_to_java_format(format);
37652                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
37653                        self.write(", '");
37654                        self.write(&java_fmt);
37655                        self.write("'");
37656                    }
37657                }
37658                self.write(")");
37659            }
37660            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37661                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
37662                self.write_keyword("UNIX_TIMESTAMP");
37663                self.write("(");
37664                if let Some(this) = &e.this {
37665                    self.generate_expression(this)?;
37666                }
37667                if let Some(format) = &e.format {
37668                    self.write(", '");
37669                    self.write(format);
37670                    self.write("'");
37671                }
37672                self.write(")");
37673            }
37674            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37675                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
37676                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
37677                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
37678                let java_fmt = Self::strftime_to_java_format(c_fmt);
37679                self.write_keyword("TO_UNIXTIME");
37680                self.write("(");
37681                self.write_keyword("COALESCE");
37682                self.write("(");
37683                self.write_keyword("TRY");
37684                self.write("(");
37685                self.write_keyword("DATE_PARSE");
37686                self.write("(");
37687                self.write_keyword("CAST");
37688                self.write("(");
37689                if let Some(this) = &e.this {
37690                    self.generate_expression(this)?;
37691                }
37692                self.write(" ");
37693                self.write_keyword("AS VARCHAR");
37694                self.write("), '");
37695                self.write(c_fmt);
37696                self.write("')), ");
37697                self.write_keyword("PARSE_DATETIME");
37698                self.write("(");
37699                self.write_keyword("DATE_FORMAT");
37700                self.write("(");
37701                self.write_keyword("CAST");
37702                self.write("(");
37703                if let Some(this) = &e.this {
37704                    self.generate_expression(this)?;
37705                }
37706                self.write(" ");
37707                self.write_keyword("AS TIMESTAMP");
37708                self.write("), '");
37709                self.write(c_fmt);
37710                self.write("'), '");
37711                self.write(&java_fmt);
37712                self.write("')))");
37713            }
37714            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37715                // Spark: UNIX_TIMESTAMP(value, java_format)
37716                self.write_keyword("UNIX_TIMESTAMP");
37717                self.write("(");
37718                if let Some(this) = &e.this {
37719                    self.generate_expression(this)?;
37720                }
37721                if let Some(format) = &e.format {
37722                    let java_fmt = Self::strftime_to_java_format(format);
37723                    self.write(", '");
37724                    self.write(&java_fmt);
37725                    self.write("'");
37726                }
37727                self.write(")");
37728            }
37729            _ => {
37730                // Default: STR_TO_UNIX(this, format)
37731                self.write_keyword("STR_TO_UNIX");
37732                self.write("(");
37733                if let Some(this) = &e.this {
37734                    self.generate_expression(this)?;
37735                }
37736                if let Some(format) = &e.format {
37737                    self.write(", '");
37738                    self.write(format);
37739                    self.write("'");
37740                }
37741                self.write(")");
37742            }
37743        }
37744        Ok(())
37745    }
37746
37747    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
37748        // STRING_TO_ARRAY(this, delimiter, null_string)
37749        self.write_keyword("STRING_TO_ARRAY");
37750        self.write("(");
37751        self.generate_expression(&e.this)?;
37752        if let Some(expression) = &e.expression {
37753            self.write(", ");
37754            self.generate_expression(expression)?;
37755        }
37756        if let Some(null_val) = &e.null {
37757            self.write(", ");
37758            self.generate_expression(null_val)?;
37759        }
37760        self.write(")");
37761        Ok(())
37762    }
37763
37764    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
37765        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
37766            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
37767            self.write_keyword("OBJECT_CONSTRUCT");
37768            self.write("(");
37769            for (i, (name, expr)) in e.fields.iter().enumerate() {
37770                if i > 0 {
37771                    self.write(", ");
37772                }
37773                if let Some(name) = name {
37774                    self.write("'");
37775                    self.write(name);
37776                    self.write("'");
37777                    self.write(", ");
37778                } else {
37779                    self.write("'_");
37780                    self.write(&i.to_string());
37781                    self.write("'");
37782                    self.write(", ");
37783                }
37784                self.generate_expression(expr)?;
37785            }
37786            self.write(")");
37787        } else if self.config.struct_curly_brace_notation {
37788            // DuckDB-style: {'key': value, ...}
37789            self.write("{");
37790            for (i, (name, expr)) in e.fields.iter().enumerate() {
37791                if i > 0 {
37792                    self.write(", ");
37793                }
37794                if let Some(name) = name {
37795                    // Quote the key as a string literal
37796                    self.write("'");
37797                    self.write(name);
37798                    self.write("'");
37799                    self.write(": ");
37800                } else {
37801                    // Unnamed field: use positional key
37802                    self.write("'_");
37803                    self.write(&i.to_string());
37804                    self.write("'");
37805                    self.write(": ");
37806                }
37807                self.generate_expression(expr)?;
37808            }
37809            self.write("}");
37810        } else {
37811            // Standard SQL struct notation
37812            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
37813            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
37814            let value_as_name = matches!(
37815                self.config.dialect,
37816                Some(DialectType::BigQuery)
37817                    | Some(DialectType::Spark)
37818                    | Some(DialectType::Databricks)
37819                    | Some(DialectType::Hive)
37820            );
37821            self.write_keyword("STRUCT");
37822            self.write("(");
37823            for (i, (name, expr)) in e.fields.iter().enumerate() {
37824                if i > 0 {
37825                    self.write(", ");
37826                }
37827                if let Some(name) = name {
37828                    if value_as_name {
37829                        // STRUCT(value AS name)
37830                        self.generate_expression(expr)?;
37831                        self.write_space();
37832                        self.write_keyword("AS");
37833                        self.write_space();
37834                        // Quote name if it contains spaces or special chars
37835                        let needs_quoting = name.contains(' ') || name.contains('-');
37836                        if needs_quoting {
37837                            if matches!(
37838                                self.config.dialect,
37839                                Some(DialectType::Spark)
37840                                    | Some(DialectType::Databricks)
37841                                    | Some(DialectType::Hive)
37842                            ) {
37843                                self.write("`");
37844                                self.write(name);
37845                                self.write("`");
37846                            } else {
37847                                self.write(name);
37848                            }
37849                        } else {
37850                            self.write(name);
37851                        }
37852                    } else {
37853                        // STRUCT(name AS value)
37854                        self.write(name);
37855                        self.write_space();
37856                        self.write_keyword("AS");
37857                        self.write_space();
37858                        self.generate_expression(expr)?;
37859                    }
37860                } else {
37861                    self.generate_expression(expr)?;
37862                }
37863            }
37864            self.write(")");
37865        }
37866        Ok(())
37867    }
37868
37869    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
37870        // STUFF(this, start, length, expression)
37871        self.write_keyword("STUFF");
37872        self.write("(");
37873        self.generate_expression(&e.this)?;
37874        if let Some(start) = &e.start {
37875            self.write(", ");
37876            self.generate_expression(start)?;
37877        }
37878        if let Some(length) = e.length {
37879            self.write(", ");
37880            self.write(&length.to_string());
37881        }
37882        self.write(", ");
37883        self.generate_expression(&e.expression)?;
37884        self.write(")");
37885        Ok(())
37886    }
37887
37888    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
37889        // SUBSTRING_INDEX(this, delimiter, count)
37890        self.write_keyword("SUBSTRING_INDEX");
37891        self.write("(");
37892        self.generate_expression(&e.this)?;
37893        if let Some(delimiter) = &e.delimiter {
37894            self.write(", ");
37895            self.generate_expression(delimiter)?;
37896        }
37897        if let Some(count) = &e.count {
37898            self.write(", ");
37899            self.generate_expression(count)?;
37900        }
37901        self.write(")");
37902        Ok(())
37903    }
37904
37905    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
37906        // SUMMARIZE [TABLE] this
37907        self.write_keyword("SUMMARIZE");
37908        if e.table.is_some() {
37909            self.write_space();
37910            self.write_keyword("TABLE");
37911        }
37912        self.write_space();
37913        self.generate_expression(&e.this)?;
37914        Ok(())
37915    }
37916
37917    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
37918        // SYSTIMESTAMP
37919        self.write_keyword("SYSTIMESTAMP");
37920        Ok(())
37921    }
37922
37923    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
37924        // alias (columns...)
37925        if let Some(this) = &e.this {
37926            self.generate_expression(this)?;
37927        }
37928        if !e.columns.is_empty() {
37929            self.write("(");
37930            for (i, col) in e.columns.iter().enumerate() {
37931                if i > 0 {
37932                    self.write(", ");
37933                }
37934                self.generate_expression(col)?;
37935            }
37936            self.write(")");
37937        }
37938        Ok(())
37939    }
37940
37941    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
37942        // TABLE(this) [AS alias]
37943        self.write_keyword("TABLE");
37944        self.write("(");
37945        self.generate_expression(&e.this)?;
37946        self.write(")");
37947        if let Some(alias) = &e.alias {
37948            self.write_space();
37949            self.write_keyword("AS");
37950            self.write_space();
37951            self.write(alias);
37952        }
37953        Ok(())
37954    }
37955
37956    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
37957        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
37958        self.write_keyword("ROWS FROM");
37959        self.write(" (");
37960        for (i, expr) in e.expressions.iter().enumerate() {
37961            if i > 0 {
37962                self.write(", ");
37963            }
37964            // Each expression is either:
37965            // - A plain function (no alias)
37966            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
37967            match expr {
37968                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
37969                    // First element is the function, second is the TableAlias
37970                    self.generate_expression(&tuple.expressions[0])?;
37971                    self.write_space();
37972                    self.write_keyword("AS");
37973                    self.write_space();
37974                    self.generate_expression(&tuple.expressions[1])?;
37975                }
37976                _ => {
37977                    self.generate_expression(expr)?;
37978                }
37979            }
37980        }
37981        self.write(")");
37982        if e.ordinality {
37983            self.write_space();
37984            self.write_keyword("WITH ORDINALITY");
37985        }
37986        if let Some(alias) = &e.alias {
37987            self.write_space();
37988            self.write_keyword("AS");
37989            self.write_space();
37990            self.generate_expression(alias)?;
37991        }
37992        Ok(())
37993    }
37994
37995    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
37996        use crate::dialects::DialectType;
37997
37998        // New wrapper pattern: expression + Sample struct
37999        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
38000            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
38001            if self.config.alias_post_tablesample {
38002                // Handle Subquery with alias and Alias wrapper
38003                if let Expression::Subquery(ref s) = **this {
38004                    if let Some(ref alias) = s.alias {
38005                        // Create a clone without alias for output
38006                        let mut subquery_no_alias = (**s).clone();
38007                        subquery_no_alias.alias = None;
38008                        subquery_no_alias.column_aliases = Vec::new();
38009                        self.generate_expression(&Expression::Subquery(Box::new(
38010                            subquery_no_alias,
38011                        )))?;
38012                        self.write_space();
38013                        self.write_keyword(self.config.tablesample_keywords);
38014                        self.generate_sample_body(sample)?;
38015                        if let Some(ref seed) = sample.seed {
38016                            self.write_space();
38017                            let use_seed = sample.use_seed_keyword
38018                                && !matches!(
38019                                    self.config.dialect,
38020                                    Some(crate::dialects::DialectType::Databricks)
38021                                        | Some(crate::dialects::DialectType::Spark)
38022                                );
38023                            if use_seed {
38024                                self.write_keyword("SEED");
38025                            } else {
38026                                self.write_keyword("REPEATABLE");
38027                            }
38028                            self.write(" (");
38029                            self.generate_expression(seed)?;
38030                            self.write(")");
38031                        }
38032                        self.write_space();
38033                        self.write_keyword("AS");
38034                        self.write_space();
38035                        self.generate_identifier(alias)?;
38036                        return Ok(());
38037                    }
38038                } else if let Expression::Alias(ref a) = **this {
38039                    // Output the base expression without alias
38040                    self.generate_expression(&a.this)?;
38041                    self.write_space();
38042                    self.write_keyword(self.config.tablesample_keywords);
38043                    self.generate_sample_body(sample)?;
38044                    if let Some(ref seed) = sample.seed {
38045                        self.write_space();
38046                        let use_seed = sample.use_seed_keyword
38047                            && !matches!(
38048                                self.config.dialect,
38049                                Some(crate::dialects::DialectType::Databricks)
38050                                    | Some(crate::dialects::DialectType::Spark)
38051                            );
38052                        if use_seed {
38053                            self.write_keyword("SEED");
38054                        } else {
38055                            self.write_keyword("REPEATABLE");
38056                        }
38057                        self.write(" (");
38058                        self.generate_expression(seed)?;
38059                        self.write(")");
38060                    }
38061                    // Output alias after TABLESAMPLE
38062                    self.write_space();
38063                    self.write_keyword("AS");
38064                    self.write_space();
38065                    self.generate_identifier(&a.alias)?;
38066                    return Ok(());
38067                }
38068            }
38069            // Default: generate wrapped expression first, then TABLESAMPLE
38070            self.generate_expression(this)?;
38071            self.write_space();
38072            self.write_keyword(self.config.tablesample_keywords);
38073            self.generate_sample_body(sample)?;
38074            // Seed for table-level sample
38075            if let Some(ref seed) = sample.seed {
38076                self.write_space();
38077                // Databricks uses REPEATABLE, not SEED
38078                let use_seed = sample.use_seed_keyword
38079                    && !matches!(
38080                        self.config.dialect,
38081                        Some(crate::dialects::DialectType::Databricks)
38082                            | Some(crate::dialects::DialectType::Spark)
38083                    );
38084                if use_seed {
38085                    self.write_keyword("SEED");
38086                } else {
38087                    self.write_keyword("REPEATABLE");
38088                }
38089                self.write(" (");
38090                self.generate_expression(seed)?;
38091                self.write(")");
38092            }
38093            return Ok(());
38094        }
38095
38096        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
38097        self.write_keyword(self.config.tablesample_keywords);
38098        if let Some(method) = &e.method {
38099            self.write_space();
38100            self.write_keyword(method);
38101        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
38102            // Snowflake defaults to BERNOULLI when no method is specified
38103            self.write_space();
38104            self.write_keyword("BERNOULLI");
38105        }
38106        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
38107            self.write_space();
38108            self.write_keyword("BUCKET");
38109            self.write_space();
38110            self.generate_expression(numerator)?;
38111            self.write_space();
38112            self.write_keyword("OUT OF");
38113            self.write_space();
38114            self.generate_expression(denominator)?;
38115            if let Some(field) = &e.bucket_field {
38116                self.write_space();
38117                self.write_keyword("ON");
38118                self.write_space();
38119                self.generate_expression(field)?;
38120            }
38121        } else if !e.expressions.is_empty() {
38122            self.write(" (");
38123            for (i, expr) in e.expressions.iter().enumerate() {
38124                if i > 0 {
38125                    self.write(", ");
38126                }
38127                self.generate_expression(expr)?;
38128            }
38129            self.write(")");
38130        } else if let Some(percent) = &e.percent {
38131            self.write(" (");
38132            self.generate_expression(percent)?;
38133            self.write_space();
38134            self.write_keyword("PERCENT");
38135            self.write(")");
38136        }
38137        Ok(())
38138    }
38139
38140    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
38141        // [prefix]this[postfix]
38142        if let Some(prefix) = &e.prefix {
38143            self.generate_expression(prefix)?;
38144        }
38145        if let Some(this) = &e.this {
38146            self.generate_expression(this)?;
38147        }
38148        if let Some(postfix) = &e.postfix {
38149            self.generate_expression(postfix)?;
38150        }
38151        Ok(())
38152    }
38153
38154    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
38155        // TAG (expressions)
38156        self.write_keyword("TAG");
38157        self.write(" (");
38158        for (i, expr) in e.expressions.iter().enumerate() {
38159            if i > 0 {
38160                self.write(", ");
38161            }
38162            self.generate_expression(expr)?;
38163        }
38164        self.write(")");
38165        Ok(())
38166    }
38167
38168    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
38169        // TEMPORARY or TEMP or [this] TEMPORARY
38170        if let Some(this) = &e.this {
38171            self.generate_expression(this)?;
38172            self.write_space();
38173        }
38174        self.write_keyword("TEMPORARY");
38175        Ok(())
38176    }
38177
38178    /// Generate a Time function expression
38179    /// For most dialects: TIME('value')
38180    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
38181        // Standard: TIME(value)
38182        self.write_keyword("TIME");
38183        self.write("(");
38184        self.generate_expression(&e.this)?;
38185        self.write(")");
38186        Ok(())
38187    }
38188
38189    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
38190        // TIME_ADD(this, expression, unit)
38191        self.write_keyword("TIME_ADD");
38192        self.write("(");
38193        self.generate_expression(&e.this)?;
38194        self.write(", ");
38195        self.generate_expression(&e.expression)?;
38196        if let Some(unit) = &e.unit {
38197            self.write(", ");
38198            self.write_keyword(unit);
38199        }
38200        self.write(")");
38201        Ok(())
38202    }
38203
38204    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
38205        // TIME_DIFF(this, expression, unit)
38206        self.write_keyword("TIME_DIFF");
38207        self.write("(");
38208        self.generate_expression(&e.this)?;
38209        self.write(", ");
38210        self.generate_expression(&e.expression)?;
38211        if let Some(unit) = &e.unit {
38212            self.write(", ");
38213            self.write_keyword(unit);
38214        }
38215        self.write(")");
38216        Ok(())
38217    }
38218
38219    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
38220        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
38221        self.write_keyword("TIME_FROM_PARTS");
38222        self.write("(");
38223        let mut first = true;
38224        if let Some(hour) = &e.hour {
38225            self.generate_expression(hour)?;
38226            first = false;
38227        }
38228        if let Some(minute) = &e.min {
38229            if !first {
38230                self.write(", ");
38231            }
38232            self.generate_expression(minute)?;
38233            first = false;
38234        }
38235        if let Some(second) = &e.sec {
38236            if !first {
38237                self.write(", ");
38238            }
38239            self.generate_expression(second)?;
38240            first = false;
38241        }
38242        if let Some(ns) = &e.nano {
38243            if !first {
38244                self.write(", ");
38245            }
38246            self.generate_expression(ns)?;
38247        }
38248        self.write(")");
38249        Ok(())
38250    }
38251
38252    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
38253        // TIME_SLICE(this, expression, unit)
38254        self.write_keyword("TIME_SLICE");
38255        self.write("(");
38256        self.generate_expression(&e.this)?;
38257        self.write(", ");
38258        self.generate_expression(&e.expression)?;
38259        self.write(", ");
38260        self.write_keyword(&e.unit);
38261        self.write(")");
38262        Ok(())
38263    }
38264
38265    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
38266        // TIME_STR_TO_TIME(this)
38267        self.write_keyword("TIME_STR_TO_TIME");
38268        self.write("(");
38269        self.generate_expression(&e.this)?;
38270        self.write(")");
38271        Ok(())
38272    }
38273
38274    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
38275        // TIME_SUB(this, expression, unit)
38276        self.write_keyword("TIME_SUB");
38277        self.write("(");
38278        self.generate_expression(&e.this)?;
38279        self.write(", ");
38280        self.generate_expression(&e.expression)?;
38281        if let Some(unit) = &e.unit {
38282            self.write(", ");
38283            self.write_keyword(unit);
38284        }
38285        self.write(")");
38286        Ok(())
38287    }
38288
38289    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
38290        match self.config.dialect {
38291            Some(DialectType::Exasol) => {
38292                // Exasol uses TO_CHAR with Exasol-specific format
38293                self.write_keyword("TO_CHAR");
38294                self.write("(");
38295                self.generate_expression(&e.this)?;
38296                self.write(", '");
38297                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
38298                self.write("'");
38299                self.write(")");
38300            }
38301            Some(DialectType::PostgreSQL)
38302            | Some(DialectType::Redshift)
38303            | Some(DialectType::Materialize) => {
38304                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
38305                self.write_keyword("TO_CHAR");
38306                self.write("(");
38307                self.generate_expression(&e.this)?;
38308                self.write(", '");
38309                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
38310                self.write("'");
38311                self.write(")");
38312            }
38313            Some(DialectType::Oracle) => {
38314                // Oracle uses TO_CHAR with PG-like format
38315                self.write_keyword("TO_CHAR");
38316                self.write("(");
38317                self.generate_expression(&e.this)?;
38318                self.write(", '");
38319                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
38320                self.write("'");
38321                self.write(")");
38322            }
38323            Some(DialectType::Drill) => {
38324                // Drill: TO_CHAR with Java format
38325                self.write_keyword("TO_CHAR");
38326                self.write("(");
38327                self.generate_expression(&e.this)?;
38328                self.write(", '");
38329                self.write(&Self::strftime_to_java_format(&e.format));
38330                self.write("'");
38331                self.write(")");
38332            }
38333            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
38334                // TSQL: FORMAT(value, format) with .NET-style format
38335                self.write_keyword("FORMAT");
38336                self.write("(");
38337                self.generate_expression(&e.this)?;
38338                self.write(", '");
38339                self.write(&Self::strftime_to_tsql_format(&e.format));
38340                self.write("'");
38341                self.write(")");
38342            }
38343            Some(DialectType::DuckDB) => {
38344                // DuckDB: STRFTIME(value, format) - keeps C format
38345                self.write_keyword("STRFTIME");
38346                self.write("(");
38347                self.generate_expression(&e.this)?;
38348                self.write(", '");
38349                self.write(&e.format);
38350                self.write("'");
38351                self.write(")");
38352            }
38353            Some(DialectType::BigQuery) => {
38354                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
38355                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
38356                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
38357                self.write_keyword("FORMAT_DATE");
38358                self.write("('");
38359                self.write(&fmt);
38360                self.write("', ");
38361                self.generate_expression(&e.this)?;
38362                self.write(")");
38363            }
38364            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38365                // Hive/Spark: DATE_FORMAT(value, java_format)
38366                self.write_keyword("DATE_FORMAT");
38367                self.write("(");
38368                self.generate_expression(&e.this)?;
38369                self.write(", '");
38370                self.write(&Self::strftime_to_java_format(&e.format));
38371                self.write("'");
38372                self.write(")");
38373            }
38374            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
38375                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
38376                self.write_keyword("DATE_FORMAT");
38377                self.write("(");
38378                self.generate_expression(&e.this)?;
38379                self.write(", '");
38380                self.write(&e.format);
38381                self.write("'");
38382                self.write(")");
38383            }
38384            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38385                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
38386                self.write_keyword("DATE_FORMAT");
38387                self.write("(");
38388                self.generate_expression(&e.this)?;
38389                self.write(", '");
38390                self.write(&e.format);
38391                self.write("'");
38392                self.write(")");
38393            }
38394            _ => {
38395                // Default: TIME_TO_STR(this, format)
38396                self.write_keyword("TIME_TO_STR");
38397                self.write("(");
38398                self.generate_expression(&e.this)?;
38399                self.write(", '");
38400                self.write(&e.format);
38401                self.write("'");
38402                self.write(")");
38403            }
38404        }
38405        Ok(())
38406    }
38407
38408    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
38409        match self.config.dialect {
38410            Some(DialectType::DuckDB) => {
38411                // DuckDB: EPOCH(x)
38412                self.write_keyword("EPOCH");
38413                self.write("(");
38414                self.generate_expression(&e.this)?;
38415                self.write(")");
38416            }
38417            Some(DialectType::Hive)
38418            | Some(DialectType::Spark)
38419            | Some(DialectType::Databricks)
38420            | Some(DialectType::Doris)
38421            | Some(DialectType::StarRocks)
38422            | Some(DialectType::Drill) => {
38423                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
38424                self.write_keyword("UNIX_TIMESTAMP");
38425                self.write("(");
38426                self.generate_expression(&e.this)?;
38427                self.write(")");
38428            }
38429            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38430                // Presto: TO_UNIXTIME(x)
38431                self.write_keyword("TO_UNIXTIME");
38432                self.write("(");
38433                self.generate_expression(&e.this)?;
38434                self.write(")");
38435            }
38436            _ => {
38437                // Default: TIME_TO_UNIX(x)
38438                self.write_keyword("TIME_TO_UNIX");
38439                self.write("(");
38440                self.generate_expression(&e.this)?;
38441                self.write(")");
38442            }
38443        }
38444        Ok(())
38445    }
38446
38447    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
38448        match self.config.dialect {
38449            Some(DialectType::Hive) => {
38450                // Hive: TO_DATE(x)
38451                self.write_keyword("TO_DATE");
38452                self.write("(");
38453                self.generate_expression(&e.this)?;
38454                self.write(")");
38455            }
38456            _ => {
38457                // Default: TIME_STR_TO_DATE(x)
38458                self.write_keyword("TIME_STR_TO_DATE");
38459                self.write("(");
38460                self.generate_expression(&e.this)?;
38461                self.write(")");
38462            }
38463        }
38464        Ok(())
38465    }
38466
38467    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
38468        // TIME_TRUNC(this, unit)
38469        self.write_keyword("TIME_TRUNC");
38470        self.write("(");
38471        self.generate_expression(&e.this)?;
38472        self.write(", ");
38473        self.write_keyword(&e.unit);
38474        self.write(")");
38475        Ok(())
38476    }
38477
38478    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
38479        // Just output the unit name
38480        if let Some(unit) = &e.unit {
38481            self.write_keyword(unit);
38482        }
38483        Ok(())
38484    }
38485
38486    /// Generate a Timestamp function expression
38487    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
38488    /// For other dialects: TIMESTAMP('value')
38489    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
38490        use crate::dialects::DialectType;
38491        use crate::expressions::Literal;
38492
38493        match self.config.dialect {
38494            // Exasol uses TO_TIMESTAMP for Timestamp expressions
38495            Some(DialectType::Exasol) => {
38496                self.write_keyword("TO_TIMESTAMP");
38497                self.write("(");
38498                // Extract the string value from the expression if it's a string literal
38499                if let Some(this) = &e.this {
38500                    match this.as_ref() {
38501                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38502                            let Literal::String(s) = lit.as_ref() else {
38503                                unreachable!()
38504                            };
38505                            self.write("'");
38506                            self.write(s);
38507                            self.write("'");
38508                        }
38509                        _ => {
38510                            self.generate_expression(this)?;
38511                        }
38512                    }
38513                }
38514                self.write(")");
38515            }
38516            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
38517            _ => {
38518                self.write_keyword("TIMESTAMP");
38519                self.write("(");
38520                if let Some(this) = &e.this {
38521                    self.generate_expression(this)?;
38522                }
38523                if let Some(zone) = &e.zone {
38524                    self.write(", ");
38525                    self.generate_expression(zone)?;
38526                }
38527                self.write(")");
38528            }
38529        }
38530        Ok(())
38531    }
38532
38533    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
38534        // TIMESTAMP_ADD(this, expression, unit)
38535        self.write_keyword("TIMESTAMP_ADD");
38536        self.write("(");
38537        self.generate_expression(&e.this)?;
38538        self.write(", ");
38539        self.generate_expression(&e.expression)?;
38540        if let Some(unit) = &e.unit {
38541            self.write(", ");
38542            self.write_keyword(unit);
38543        }
38544        self.write(")");
38545        Ok(())
38546    }
38547
38548    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
38549        // TIMESTAMP_DIFF(this, expression, unit)
38550        self.write_keyword("TIMESTAMP_DIFF");
38551        self.write("(");
38552        self.generate_expression(&e.this)?;
38553        self.write(", ");
38554        self.generate_expression(&e.expression)?;
38555        if let Some(unit) = &e.unit {
38556            self.write(", ");
38557            self.write_keyword(unit);
38558        }
38559        self.write(")");
38560        Ok(())
38561    }
38562
38563    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
38564        // TIMESTAMP_FROM_PARTS(this, expression)
38565        self.write_keyword("TIMESTAMP_FROM_PARTS");
38566        self.write("(");
38567        if let Some(this) = &e.this {
38568            self.generate_expression(this)?;
38569        }
38570        if let Some(expression) = &e.expression {
38571            self.write(", ");
38572            self.generate_expression(expression)?;
38573        }
38574        if let Some(zone) = &e.zone {
38575            self.write(", ");
38576            self.generate_expression(zone)?;
38577        }
38578        if let Some(milli) = &e.milli {
38579            self.write(", ");
38580            self.generate_expression(milli)?;
38581        }
38582        self.write(")");
38583        Ok(())
38584    }
38585
38586    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
38587        // TIMESTAMP_SUB(this, INTERVAL expression unit)
38588        self.write_keyword("TIMESTAMP_SUB");
38589        self.write("(");
38590        self.generate_expression(&e.this)?;
38591        self.write(", ");
38592        self.write_keyword("INTERVAL");
38593        self.write_space();
38594        self.generate_expression(&e.expression)?;
38595        if let Some(unit) = &e.unit {
38596            self.write_space();
38597            self.write_keyword(unit);
38598        }
38599        self.write(")");
38600        Ok(())
38601    }
38602
38603    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
38604        // TIMESTAMP_TZ_FROM_PARTS(...)
38605        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
38606        self.write("(");
38607        if let Some(zone) = &e.zone {
38608            self.generate_expression(zone)?;
38609        }
38610        self.write(")");
38611        Ok(())
38612    }
38613
38614    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
38615        // TO_BINARY(this, [format])
38616        self.write_keyword("TO_BINARY");
38617        self.write("(");
38618        self.generate_expression(&e.this)?;
38619        if let Some(format) = &e.format {
38620            self.write(", '");
38621            self.write(format);
38622            self.write("'");
38623        }
38624        self.write(")");
38625        Ok(())
38626    }
38627
38628    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
38629        // TO_BOOLEAN(this)
38630        self.write_keyword("TO_BOOLEAN");
38631        self.write("(");
38632        self.generate_expression(&e.this)?;
38633        self.write(")");
38634        Ok(())
38635    }
38636
38637    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
38638        // TO_CHAR(this, [format], [nlsparam])
38639        self.write_keyword("TO_CHAR");
38640        self.write("(");
38641        self.generate_expression(&e.this)?;
38642        if let Some(format) = &e.format {
38643            self.write(", '");
38644            self.write(format);
38645            self.write("'");
38646        }
38647        if let Some(nlsparam) = &e.nlsparam {
38648            self.write(", ");
38649            self.generate_expression(nlsparam)?;
38650        }
38651        self.write(")");
38652        Ok(())
38653    }
38654
38655    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
38656        // TO_DECFLOAT(this, [format])
38657        self.write_keyword("TO_DECFLOAT");
38658        self.write("(");
38659        self.generate_expression(&e.this)?;
38660        if let Some(format) = &e.format {
38661            self.write(", '");
38662            self.write(format);
38663            self.write("'");
38664        }
38665        self.write(")");
38666        Ok(())
38667    }
38668
38669    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
38670        // TO_DOUBLE(this, [format])
38671        self.write_keyword("TO_DOUBLE");
38672        self.write("(");
38673        self.generate_expression(&e.this)?;
38674        if let Some(format) = &e.format {
38675            self.write(", '");
38676            self.write(format);
38677            self.write("'");
38678        }
38679        self.write(")");
38680        Ok(())
38681    }
38682
38683    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
38684        // TO_FILE(this, path)
38685        self.write_keyword("TO_FILE");
38686        self.write("(");
38687        self.generate_expression(&e.this)?;
38688        if let Some(path) = &e.path {
38689            self.write(", ");
38690            self.generate_expression(path)?;
38691        }
38692        self.write(")");
38693        Ok(())
38694    }
38695
38696    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
38697        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
38698        // If safe flag is set, output TRY_TO_NUMBER
38699        let is_safe = e.safe.is_some();
38700        if is_safe {
38701            self.write_keyword("TRY_TO_NUMBER");
38702        } else {
38703            self.write_keyword("TO_NUMBER");
38704        }
38705        self.write("(");
38706        self.generate_expression(&e.this)?;
38707        let precision_is_snowflake_default = e.precision.is_none()
38708            || matches!(
38709                e.precision.as_deref(),
38710                Some(Expression::Literal(lit))
38711                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
38712            );
38713        let is_snowflake_default_precision =
38714            matches!(self.config.dialect, Some(DialectType::Snowflake))
38715                && e.nlsparam.is_none()
38716                && e.scale.is_none()
38717                && matches!(
38718                    e.format.as_deref(),
38719                    Some(Expression::Literal(lit))
38720                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
38721                )
38722                && precision_is_snowflake_default;
38723
38724        if !is_snowflake_default_precision {
38725            if let Some(format) = &e.format {
38726                self.write(", ");
38727                self.generate_expression(format)?;
38728            }
38729            if let Some(nlsparam) = &e.nlsparam {
38730                self.write(", ");
38731                self.generate_expression(nlsparam)?;
38732            }
38733            if let Some(precision) = &e.precision {
38734                self.write(", ");
38735                self.generate_expression(precision)?;
38736            }
38737            if let Some(scale) = &e.scale {
38738                self.write(", ");
38739                self.generate_expression(scale)?;
38740            }
38741        }
38742        self.write(")");
38743        Ok(())
38744    }
38745
38746    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
38747        // TO_TABLE this
38748        self.write_keyword("TO_TABLE");
38749        self.write_space();
38750        self.generate_expression(&e.this)?;
38751        Ok(())
38752    }
38753
38754    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
38755        // Check mark to determine the format
38756        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
38757            Expression::Identifier(id) => id.name.clone(),
38758            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38759                let Literal::String(s) = lit.as_ref() else {
38760                    unreachable!()
38761                };
38762                s.clone()
38763            }
38764            _ => String::new(),
38765        });
38766
38767        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
38768        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
38769        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
38770            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
38771        });
38772
38773        // For Presto/Trino: always use START TRANSACTION
38774        let use_start_transaction = matches!(
38775            self.config.dialect,
38776            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
38777        );
38778        // For most dialects: strip TRANSACTION keyword
38779        let strip_transaction = matches!(
38780            self.config.dialect,
38781            Some(DialectType::Snowflake)
38782                | Some(DialectType::PostgreSQL)
38783                | Some(DialectType::Redshift)
38784                | Some(DialectType::MySQL)
38785                | Some(DialectType::Hive)
38786                | Some(DialectType::Spark)
38787                | Some(DialectType::Databricks)
38788                | Some(DialectType::DuckDB)
38789                | Some(DialectType::Oracle)
38790                | Some(DialectType::Doris)
38791                | Some(DialectType::StarRocks)
38792                | Some(DialectType::Materialize)
38793                | Some(DialectType::ClickHouse)
38794        );
38795
38796        if is_start || use_start_transaction {
38797            // START TRANSACTION [modes]
38798            self.write_keyword("START TRANSACTION");
38799            if let Some(modes) = &e.modes {
38800                self.write_space();
38801                self.generate_expression(modes)?;
38802            }
38803        } else {
38804            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
38805            self.write_keyword("BEGIN");
38806
38807            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
38808            let is_kind = e.this.as_ref().map_or(false, |t| {
38809                if let Expression::Identifier(id) = t.as_ref() {
38810                    id.name.eq_ignore_ascii_case("DEFERRED")
38811                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
38812                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
38813                } else {
38814                    false
38815                }
38816            });
38817
38818            // Output kind before TRANSACTION keyword
38819            if is_kind {
38820                if let Some(this) = &e.this {
38821                    self.write_space();
38822                    if let Expression::Identifier(id) = this.as_ref() {
38823                        self.write_keyword(&id.name);
38824                    }
38825                }
38826            }
38827
38828            // Output TRANSACTION keyword if it was present and target supports it
38829            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
38830                self.write_space();
38831                self.write_keyword("TRANSACTION");
38832            }
38833
38834            // Output transaction name (not kind)
38835            if !is_kind {
38836                if let Some(this) = &e.this {
38837                    self.write_space();
38838                    self.generate_expression(this)?;
38839                }
38840            }
38841
38842            // Output WITH MARK 'description' for TSQL
38843            if has_with_mark {
38844                self.write_space();
38845                self.write_keyword("WITH MARK");
38846                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
38847                    if let Literal::String(desc) = lit.as_ref() {
38848                        if !desc.is_empty() {
38849                            self.write_space();
38850                            self.write(&format!("'{}'", desc));
38851                        }
38852                    }
38853                }
38854            }
38855
38856            // Output modes (isolation levels, etc.)
38857            if let Some(modes) = &e.modes {
38858                self.write_space();
38859                self.generate_expression(modes)?;
38860            }
38861        }
38862        Ok(())
38863    }
38864
38865    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
38866        // TRANSFORM(this, expression)
38867        self.write_keyword("TRANSFORM");
38868        self.write("(");
38869        self.generate_expression(&e.this)?;
38870        self.write(", ");
38871        self.generate_expression(&e.expression)?;
38872        self.write(")");
38873        Ok(())
38874    }
38875
38876    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
38877        // TRANSFORM(expressions)
38878        self.write_keyword("TRANSFORM");
38879        self.write("(");
38880        if self.config.pretty && !e.expressions.is_empty() {
38881            self.indent_level += 1;
38882            for (i, expr) in e.expressions.iter().enumerate() {
38883                if i > 0 {
38884                    self.write(",");
38885                }
38886                self.write_newline();
38887                self.write_indent();
38888                self.generate_expression(expr)?;
38889            }
38890            self.indent_level -= 1;
38891            self.write_newline();
38892            self.write(")");
38893        } else {
38894            for (i, expr) in e.expressions.iter().enumerate() {
38895                if i > 0 {
38896                    self.write(", ");
38897                }
38898                self.generate_expression(expr)?;
38899            }
38900            self.write(")");
38901        }
38902        Ok(())
38903    }
38904
38905    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
38906        use crate::dialects::DialectType;
38907        // TRANSIENT is Snowflake-specific; skip for other dialects
38908        if let Some(this) = &e.this {
38909            self.generate_expression(this)?;
38910            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
38911                self.write_space();
38912            }
38913        }
38914        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
38915            self.write_keyword("TRANSIENT");
38916        }
38917        Ok(())
38918    }
38919
38920    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
38921        // TRANSLATE(this, from_, to)
38922        self.write_keyword("TRANSLATE");
38923        self.write("(");
38924        self.generate_expression(&e.this)?;
38925        if let Some(from) = &e.from_ {
38926            self.write(", ");
38927            self.generate_expression(from)?;
38928        }
38929        if let Some(to) = &e.to {
38930            self.write(", ");
38931            self.generate_expression(to)?;
38932        }
38933        self.write(")");
38934        Ok(())
38935    }
38936
38937    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
38938        // TRANSLATE(this USING expression)
38939        self.write_keyword("TRANSLATE");
38940        self.write("(");
38941        self.generate_expression(&e.this)?;
38942        self.write_space();
38943        self.write_keyword("USING");
38944        self.write_space();
38945        self.generate_expression(&e.expression)?;
38946        if e.with_error.is_some() {
38947            self.write_space();
38948            self.write_keyword("WITH ERROR");
38949        }
38950        self.write(")");
38951        Ok(())
38952    }
38953
38954    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
38955        // TRUNCATE TABLE table1, table2, ...
38956        self.write_keyword("TRUNCATE TABLE");
38957        self.write_space();
38958        for (i, expr) in e.expressions.iter().enumerate() {
38959            if i > 0 {
38960                self.write(", ");
38961            }
38962            self.generate_expression(expr)?;
38963        }
38964        Ok(())
38965    }
38966
38967    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
38968        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
38969        self.write_keyword("TRY_BASE64_DECODE_BINARY");
38970        self.write("(");
38971        self.generate_expression(&e.this)?;
38972        if let Some(alphabet) = &e.alphabet {
38973            self.write(", ");
38974            self.generate_expression(alphabet)?;
38975        }
38976        self.write(")");
38977        Ok(())
38978    }
38979
38980    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
38981        // TRY_BASE64_DECODE_STRING(this, [alphabet])
38982        self.write_keyword("TRY_BASE64_DECODE_STRING");
38983        self.write("(");
38984        self.generate_expression(&e.this)?;
38985        if let Some(alphabet) = &e.alphabet {
38986            self.write(", ");
38987            self.generate_expression(alphabet)?;
38988        }
38989        self.write(")");
38990        Ok(())
38991    }
38992
38993    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
38994        // TRY_TO_DECFLOAT(this, [format])
38995        self.write_keyword("TRY_TO_DECFLOAT");
38996        self.write("(");
38997        self.generate_expression(&e.this)?;
38998        if let Some(format) = &e.format {
38999            self.write(", '");
39000            self.write(format);
39001            self.write("'");
39002        }
39003        self.write(")");
39004        Ok(())
39005    }
39006
39007    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
39008        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
39009        self.write_keyword("TS_OR_DS_ADD");
39010        self.write("(");
39011        self.generate_expression(&e.this)?;
39012        self.write(", ");
39013        self.generate_expression(&e.expression)?;
39014        if let Some(unit) = &e.unit {
39015            self.write(", ");
39016            self.write_keyword(unit);
39017        }
39018        if let Some(return_type) = &e.return_type {
39019            self.write(", ");
39020            self.generate_expression(return_type)?;
39021        }
39022        self.write(")");
39023        Ok(())
39024    }
39025
39026    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
39027        // TS_OR_DS_DIFF(this, expression, [unit])
39028        self.write_keyword("TS_OR_DS_DIFF");
39029        self.write("(");
39030        self.generate_expression(&e.this)?;
39031        self.write(", ");
39032        self.generate_expression(&e.expression)?;
39033        if let Some(unit) = &e.unit {
39034            self.write(", ");
39035            self.write_keyword(unit);
39036        }
39037        self.write(")");
39038        Ok(())
39039    }
39040
39041    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
39042        let default_time_format = "%Y-%m-%d %H:%M:%S";
39043        let default_date_format = "%Y-%m-%d";
39044        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
39045            f != default_time_format && f != default_date_format
39046        });
39047
39048        if has_non_default_format {
39049            // With non-default format: dialect-specific handling
39050            let fmt = e.format.as_ref().unwrap();
39051            match self.config.dialect {
39052                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
39053                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
39054                    // STR_TO_DATE is the MySQL-native form of StrToTime
39055                    let str_to_time = crate::expressions::StrToTime {
39056                        this: Box::new((*e.this).clone()),
39057                        format: fmt.clone(),
39058                        zone: None,
39059                        safe: None,
39060                        target_type: None,
39061                    };
39062                    self.generate_str_to_time(&str_to_time)?;
39063                }
39064                Some(DialectType::Hive)
39065                | Some(DialectType::Spark)
39066                | Some(DialectType::Databricks) => {
39067                    // Hive/Spark: TO_DATE(x, java_fmt)
39068                    self.write_keyword("TO_DATE");
39069                    self.write("(");
39070                    self.generate_expression(&e.this)?;
39071                    self.write(", '");
39072                    self.write(&Self::strftime_to_java_format(fmt));
39073                    self.write("')");
39074                }
39075                Some(DialectType::Snowflake) => {
39076                    // Snowflake: TO_DATE(x, snowflake_fmt)
39077                    self.write_keyword("TO_DATE");
39078                    self.write("(");
39079                    self.generate_expression(&e.this)?;
39080                    self.write(", '");
39081                    self.write(&Self::strftime_to_snowflake_format(fmt));
39082                    self.write("')");
39083                }
39084                Some(DialectType::Doris) => {
39085                    // Doris: TO_DATE(x) - ignores format
39086                    self.write_keyword("TO_DATE");
39087                    self.write("(");
39088                    self.generate_expression(&e.this)?;
39089                    self.write(")");
39090                }
39091                _ => {
39092                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
39093                    self.write_keyword("CAST");
39094                    self.write("(");
39095                    let str_to_time = crate::expressions::StrToTime {
39096                        this: Box::new((*e.this).clone()),
39097                        format: fmt.clone(),
39098                        zone: None,
39099                        safe: None,
39100                        target_type: None,
39101                    };
39102                    self.generate_str_to_time(&str_to_time)?;
39103                    self.write_keyword(" AS ");
39104                    self.write_keyword("DATE");
39105                    self.write(")");
39106                }
39107            }
39108        } else {
39109            // Without format (or default format): simple date conversion
39110            match self.config.dialect {
39111                Some(DialectType::MySQL)
39112                | Some(DialectType::SQLite)
39113                | Some(DialectType::StarRocks) => {
39114                    // MySQL/SQLite/StarRocks: DATE(x)
39115                    self.write_keyword("DATE");
39116                    self.write("(");
39117                    self.generate_expression(&e.this)?;
39118                    self.write(")");
39119                }
39120                Some(DialectType::Hive)
39121                | Some(DialectType::Spark)
39122                | Some(DialectType::Databricks)
39123                | Some(DialectType::Snowflake)
39124                | Some(DialectType::Doris) => {
39125                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
39126                    self.write_keyword("TO_DATE");
39127                    self.write("(");
39128                    self.generate_expression(&e.this)?;
39129                    self.write(")");
39130                }
39131                Some(DialectType::Presto)
39132                | Some(DialectType::Trino)
39133                | Some(DialectType::Athena) => {
39134                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
39135                    self.write_keyword("CAST");
39136                    self.write("(");
39137                    self.write_keyword("CAST");
39138                    self.write("(");
39139                    self.generate_expression(&e.this)?;
39140                    self.write_keyword(" AS ");
39141                    self.write_keyword("TIMESTAMP");
39142                    self.write(")");
39143                    self.write_keyword(" AS ");
39144                    self.write_keyword("DATE");
39145                    self.write(")");
39146                }
39147                Some(DialectType::ClickHouse) => {
39148                    // ClickHouse: CAST(x AS Nullable(DATE))
39149                    self.write_keyword("CAST");
39150                    self.write("(");
39151                    self.generate_expression(&e.this)?;
39152                    self.write_keyword(" AS ");
39153                    self.write("Nullable(DATE)");
39154                    self.write(")");
39155                }
39156                _ => {
39157                    // Default: CAST(x AS DATE)
39158                    self.write_keyword("CAST");
39159                    self.write("(");
39160                    self.generate_expression(&e.this)?;
39161                    self.write_keyword(" AS ");
39162                    self.write_keyword("DATE");
39163                    self.write(")");
39164                }
39165            }
39166        }
39167        Ok(())
39168    }
39169
39170    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
39171        // TS_OR_DS_TO_TIME(this, [format])
39172        self.write_keyword("TS_OR_DS_TO_TIME");
39173        self.write("(");
39174        self.generate_expression(&e.this)?;
39175        if let Some(format) = &e.format {
39176            self.write(", '");
39177            self.write(format);
39178            self.write("'");
39179        }
39180        self.write(")");
39181        Ok(())
39182    }
39183
39184    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
39185        // UNHEX(this, [expression])
39186        self.write_keyword("UNHEX");
39187        self.write("(");
39188        self.generate_expression(&e.this)?;
39189        if let Some(expression) = &e.expression {
39190            self.write(", ");
39191            self.generate_expression(expression)?;
39192        }
39193        self.write(")");
39194        Ok(())
39195    }
39196
39197    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
39198        // U&this [UESCAPE escape]
39199        self.write("U&");
39200        self.generate_expression(&e.this)?;
39201        if let Some(escape) = &e.escape {
39202            self.write_space();
39203            self.write_keyword("UESCAPE");
39204            self.write_space();
39205            self.generate_expression(escape)?;
39206        }
39207        Ok(())
39208    }
39209
39210    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
39211        // UNIFORM(this, expression, [gen], [seed])
39212        self.write_keyword("UNIFORM");
39213        self.write("(");
39214        self.generate_expression(&e.this)?;
39215        self.write(", ");
39216        self.generate_expression(&e.expression)?;
39217        if let Some(gen) = &e.gen {
39218            self.write(", ");
39219            self.generate_expression(gen)?;
39220        }
39221        if let Some(seed) = &e.seed {
39222            self.write(", ");
39223            self.generate_expression(seed)?;
39224        }
39225        self.write(")");
39226        Ok(())
39227    }
39228
39229    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
39230        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
39231        self.write_keyword("UNIQUE");
39232        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
39233        if e.nulls.is_some() {
39234            self.write(" NULLS NOT DISTINCT");
39235        }
39236        if let Some(this) = &e.this {
39237            self.write_space();
39238            self.generate_expression(this)?;
39239        }
39240        if let Some(index_type) = &e.index_type {
39241            self.write(" USING ");
39242            self.generate_expression(index_type)?;
39243        }
39244        if let Some(on_conflict) = &e.on_conflict {
39245            self.write_space();
39246            self.generate_expression(on_conflict)?;
39247        }
39248        for opt in &e.options {
39249            self.write_space();
39250            self.generate_expression(opt)?;
39251        }
39252        Ok(())
39253    }
39254
39255    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
39256        // UNIQUE KEY (expressions)
39257        self.write_keyword("UNIQUE KEY");
39258        self.write(" (");
39259        for (i, expr) in e.expressions.iter().enumerate() {
39260            if i > 0 {
39261                self.write(", ");
39262            }
39263            self.generate_expression(expr)?;
39264        }
39265        self.write(")");
39266        Ok(())
39267    }
39268
39269    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
39270        // ROLLUP (r1(col1, col2), r2(col1))
39271        self.write_keyword("ROLLUP");
39272        self.write(" (");
39273        for (i, index) in e.expressions.iter().enumerate() {
39274            if i > 0 {
39275                self.write(", ");
39276            }
39277            self.generate_identifier(&index.name)?;
39278            self.write("(");
39279            for (j, col) in index.expressions.iter().enumerate() {
39280                if j > 0 {
39281                    self.write(", ");
39282                }
39283                self.generate_identifier(col)?;
39284            }
39285            self.write(")");
39286        }
39287        self.write(")");
39288        Ok(())
39289    }
39290
39291    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
39292        match self.config.dialect {
39293            Some(DialectType::DuckDB) => {
39294                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
39295                self.write_keyword("STRFTIME");
39296                self.write("(");
39297                self.write_keyword("TO_TIMESTAMP");
39298                self.write("(");
39299                self.generate_expression(&e.this)?;
39300                self.write("), '");
39301                if let Some(format) = &e.format {
39302                    self.write(format);
39303                }
39304                self.write("')");
39305            }
39306            Some(DialectType::Hive) => {
39307                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
39308                self.write_keyword("FROM_UNIXTIME");
39309                self.write("(");
39310                self.generate_expression(&e.this)?;
39311                if let Some(format) = &e.format {
39312                    if format != "yyyy-MM-dd HH:mm:ss" {
39313                        self.write(", '");
39314                        self.write(format);
39315                        self.write("'");
39316                    }
39317                }
39318                self.write(")");
39319            }
39320            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39321                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
39322                self.write_keyword("DATE_FORMAT");
39323                self.write("(");
39324                self.write_keyword("FROM_UNIXTIME");
39325                self.write("(");
39326                self.generate_expression(&e.this)?;
39327                self.write("), '");
39328                if let Some(format) = &e.format {
39329                    self.write(format);
39330                }
39331                self.write("')");
39332            }
39333            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
39334                // Spark: FROM_UNIXTIME(value, format)
39335                self.write_keyword("FROM_UNIXTIME");
39336                self.write("(");
39337                self.generate_expression(&e.this)?;
39338                if let Some(format) = &e.format {
39339                    self.write(", '");
39340                    self.write(format);
39341                    self.write("'");
39342                }
39343                self.write(")");
39344            }
39345            _ => {
39346                // Default: UNIX_TO_STR(this, [format])
39347                self.write_keyword("UNIX_TO_STR");
39348                self.write("(");
39349                self.generate_expression(&e.this)?;
39350                if let Some(format) = &e.format {
39351                    self.write(", '");
39352                    self.write(format);
39353                    self.write("'");
39354                }
39355                self.write(")");
39356            }
39357        }
39358        Ok(())
39359    }
39360
39361    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
39362        use crate::dialects::DialectType;
39363        let scale = e.scale.unwrap_or(0); // 0 = seconds
39364
39365        match self.config.dialect {
39366            Some(DialectType::Snowflake) => {
39367                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
39368                self.write_keyword("TO_TIMESTAMP");
39369                self.write("(");
39370                self.generate_expression(&e.this)?;
39371                if let Some(s) = e.scale {
39372                    if s > 0 {
39373                        self.write(", ");
39374                        self.write(&s.to_string());
39375                    }
39376                }
39377                self.write(")");
39378            }
39379            Some(DialectType::BigQuery) => {
39380                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
39381                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
39382                match scale {
39383                    0 => {
39384                        self.write_keyword("TIMESTAMP_SECONDS");
39385                        self.write("(");
39386                        self.generate_expression(&e.this)?;
39387                        self.write(")");
39388                    }
39389                    3 => {
39390                        self.write_keyword("TIMESTAMP_MILLIS");
39391                        self.write("(");
39392                        self.generate_expression(&e.this)?;
39393                        self.write(")");
39394                    }
39395                    6 => {
39396                        self.write_keyword("TIMESTAMP_MICROS");
39397                        self.write("(");
39398                        self.generate_expression(&e.this)?;
39399                        self.write(")");
39400                    }
39401                    _ => {
39402                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
39403                        self.write_keyword("TIMESTAMP_SECONDS");
39404                        self.write("(CAST(");
39405                        self.generate_expression(&e.this)?;
39406                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
39407                    }
39408                }
39409            }
39410            Some(DialectType::Spark) => {
39411                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
39412                // TIMESTAMP_MILLIS(value) for scale=3
39413                // TIMESTAMP_MICROS(value) for scale=6
39414                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
39415                match scale {
39416                    0 => {
39417                        self.write_keyword("CAST");
39418                        self.write("(");
39419                        self.write_keyword("FROM_UNIXTIME");
39420                        self.write("(");
39421                        self.generate_expression(&e.this)?;
39422                        self.write(") ");
39423                        self.write_keyword("AS TIMESTAMP");
39424                        self.write(")");
39425                    }
39426                    3 => {
39427                        self.write_keyword("TIMESTAMP_MILLIS");
39428                        self.write("(");
39429                        self.generate_expression(&e.this)?;
39430                        self.write(")");
39431                    }
39432                    6 => {
39433                        self.write_keyword("TIMESTAMP_MICROS");
39434                        self.write("(");
39435                        self.generate_expression(&e.this)?;
39436                        self.write(")");
39437                    }
39438                    _ => {
39439                        self.write_keyword("TIMESTAMP_SECONDS");
39440                        self.write("(");
39441                        self.generate_expression(&e.this)?;
39442                        self.write(&format!(" / POWER(10, {}))", scale));
39443                    }
39444                }
39445            }
39446            Some(DialectType::Databricks) => {
39447                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
39448                // TIMESTAMP_MILLIS(value) for scale=3
39449                // TIMESTAMP_MICROS(value) for scale=6
39450                match scale {
39451                    0 => {
39452                        self.write_keyword("CAST");
39453                        self.write("(");
39454                        self.write_keyword("FROM_UNIXTIME");
39455                        self.write("(");
39456                        self.generate_expression(&e.this)?;
39457                        self.write(") ");
39458                        self.write_keyword("AS TIMESTAMP");
39459                        self.write(")");
39460                    }
39461                    3 => {
39462                        self.write_keyword("TIMESTAMP_MILLIS");
39463                        self.write("(");
39464                        self.generate_expression(&e.this)?;
39465                        self.write(")");
39466                    }
39467                    6 => {
39468                        self.write_keyword("TIMESTAMP_MICROS");
39469                        self.write("(");
39470                        self.generate_expression(&e.this)?;
39471                        self.write(")");
39472                    }
39473                    _ => {
39474                        self.write_keyword("TIMESTAMP_SECONDS");
39475                        self.write("(");
39476                        self.generate_expression(&e.this)?;
39477                        self.write(&format!(" / POWER(10, {}))", scale));
39478                    }
39479                }
39480            }
39481            Some(DialectType::Hive) => {
39482                // Hive: FROM_UNIXTIME(value)
39483                if scale == 0 {
39484                    self.write_keyword("FROM_UNIXTIME");
39485                    self.write("(");
39486                    self.generate_expression(&e.this)?;
39487                    self.write(")");
39488                } else {
39489                    self.write_keyword("FROM_UNIXTIME");
39490                    self.write("(");
39491                    self.generate_expression(&e.this)?;
39492                    self.write(&format!(" / POWER(10, {})", scale));
39493                    self.write(")");
39494                }
39495            }
39496            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39497                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
39498                // FROM_UNIXTIME(value) for scale=0
39499                if scale == 0 {
39500                    self.write_keyword("FROM_UNIXTIME");
39501                    self.write("(");
39502                    self.generate_expression(&e.this)?;
39503                    self.write(")");
39504                } else {
39505                    self.write_keyword("FROM_UNIXTIME");
39506                    self.write("(CAST(");
39507                    self.generate_expression(&e.this)?;
39508                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
39509                }
39510            }
39511            Some(DialectType::DuckDB) => {
39512                // DuckDB: TO_TIMESTAMP(value) for scale=0
39513                // EPOCH_MS(value) for scale=3
39514                // MAKE_TIMESTAMP(value) for scale=6
39515                match scale {
39516                    0 => {
39517                        self.write_keyword("TO_TIMESTAMP");
39518                        self.write("(");
39519                        self.generate_expression(&e.this)?;
39520                        self.write(")");
39521                    }
39522                    3 => {
39523                        self.write_keyword("EPOCH_MS");
39524                        self.write("(");
39525                        self.generate_expression(&e.this)?;
39526                        self.write(")");
39527                    }
39528                    6 => {
39529                        self.write_keyword("MAKE_TIMESTAMP");
39530                        self.write("(");
39531                        self.generate_expression(&e.this)?;
39532                        self.write(")");
39533                    }
39534                    _ => {
39535                        self.write_keyword("TO_TIMESTAMP");
39536                        self.write("(");
39537                        self.generate_expression(&e.this)?;
39538                        self.write(&format!(" / POWER(10, {}))", scale));
39539                        self.write_keyword(" AT TIME ZONE");
39540                        self.write(" 'UTC'");
39541                    }
39542                }
39543            }
39544            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
39545                // Doris/StarRocks: FROM_UNIXTIME(value)
39546                self.write_keyword("FROM_UNIXTIME");
39547                self.write("(");
39548                self.generate_expression(&e.this)?;
39549                self.write(")");
39550            }
39551            Some(DialectType::Oracle) => {
39552                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
39553                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
39554                self.generate_expression(&e.this)?;
39555                self.write(" / 86400)");
39556            }
39557            Some(DialectType::Redshift) => {
39558                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
39559                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
39560                self.write("(TIMESTAMP 'epoch' + ");
39561                if scale == 0 {
39562                    self.generate_expression(&e.this)?;
39563                } else {
39564                    self.write("(");
39565                    self.generate_expression(&e.this)?;
39566                    self.write(&format!(" / POWER(10, {}))", scale));
39567                }
39568                self.write(" * INTERVAL '1 SECOND')");
39569            }
39570            Some(DialectType::Exasol) => {
39571                // Exasol: FROM_POSIX_TIME(value)
39572                self.write_keyword("FROM_POSIX_TIME");
39573                self.write("(");
39574                self.generate_expression(&e.this)?;
39575                self.write(")");
39576            }
39577            _ => {
39578                // Default: TO_TIMESTAMP(value[, scale])
39579                self.write_keyword("TO_TIMESTAMP");
39580                self.write("(");
39581                self.generate_expression(&e.this)?;
39582                if let Some(s) = e.scale {
39583                    self.write(", ");
39584                    self.write(&s.to_string());
39585                }
39586                self.write(")");
39587            }
39588        }
39589        Ok(())
39590    }
39591
39592    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
39593        // NAME col VALUE col1, col2, ...
39594        if !matches!(&*e.this, Expression::Null(_)) {
39595            self.write_keyword("NAME");
39596            self.write_space();
39597            self.generate_expression(&e.this)?;
39598        }
39599        if !e.expressions.is_empty() {
39600            self.write_space();
39601            self.write_keyword("VALUE");
39602            self.write_space();
39603            for (i, expr) in e.expressions.iter().enumerate() {
39604                if i > 0 {
39605                    self.write(", ");
39606                }
39607                self.generate_expression(expr)?;
39608            }
39609        }
39610        Ok(())
39611    }
39612
39613    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
39614        // this(expressions) or (this)(expressions)
39615        if e.wrapped.is_some() {
39616            self.write("(");
39617        }
39618        self.generate_expression(&e.this)?;
39619        if e.wrapped.is_some() {
39620            self.write(")");
39621        }
39622        self.write("(");
39623        for (i, expr) in e.expressions.iter().enumerate() {
39624            if i > 0 {
39625                self.write(", ");
39626            }
39627            self.generate_expression(expr)?;
39628        }
39629        self.write(")");
39630        Ok(())
39631    }
39632
39633    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
39634        // USING TEMPLATE this
39635        self.write_keyword("USING TEMPLATE");
39636        self.write_space();
39637        self.generate_expression(&e.this)?;
39638        Ok(())
39639    }
39640
39641    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
39642        // UTC_TIME
39643        self.write_keyword("UTC_TIME");
39644        Ok(())
39645    }
39646
39647    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
39648        if matches!(
39649            self.config.dialect,
39650            Some(crate::dialects::DialectType::ClickHouse)
39651        ) {
39652            self.write_keyword("CURRENT_TIMESTAMP");
39653            self.write("('UTC')");
39654        } else {
39655            self.write_keyword("UTC_TIMESTAMP");
39656        }
39657        Ok(())
39658    }
39659
39660    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
39661        use crate::dialects::DialectType;
39662        // Choose UUID function name based on target dialect
39663        let func_name = match self.config.dialect {
39664            Some(DialectType::Snowflake) => "UUID_STRING",
39665            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
39666            Some(DialectType::BigQuery) => "GENERATE_UUID",
39667            _ => {
39668                if let Some(name) = &e.name {
39669                    name.as_str()
39670                } else {
39671                    "UUID"
39672                }
39673            }
39674        };
39675        self.write_keyword(func_name);
39676        self.write("(");
39677        if let Some(this) = &e.this {
39678            self.generate_expression(this)?;
39679        }
39680        self.write(")");
39681        Ok(())
39682    }
39683
39684    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
39685        // MAP(key1, value1, key2, value2, ...)
39686        self.write_keyword("MAP");
39687        self.write("(");
39688        let mut first = true;
39689        for (k, v) in e.keys.iter().zip(e.values.iter()) {
39690            if !first {
39691                self.write(", ");
39692            }
39693            self.generate_expression(k)?;
39694            self.write(", ");
39695            self.generate_expression(v)?;
39696            first = false;
39697        }
39698        self.write(")");
39699        Ok(())
39700    }
39701
39702    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
39703        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
39704        self.write_keyword("VECTOR_SEARCH");
39705        self.write("(");
39706        self.generate_expression(&e.this)?;
39707        if let Some(col) = &e.column_to_search {
39708            self.write(", ");
39709            self.generate_expression(col)?;
39710        }
39711        if let Some(query_table) = &e.query_table {
39712            self.write(", ");
39713            self.generate_expression(query_table)?;
39714        }
39715        if let Some(query_col) = &e.query_column_to_search {
39716            self.write(", ");
39717            self.generate_expression(query_col)?;
39718        }
39719        if let Some(top_k) = &e.top_k {
39720            self.write(", ");
39721            self.generate_expression(top_k)?;
39722        }
39723        if let Some(dist_type) = &e.distance_type {
39724            self.write(", ");
39725            self.generate_expression(dist_type)?;
39726        }
39727        self.write(")");
39728        Ok(())
39729    }
39730
39731    fn generate_version(&mut self, e: &Version) -> Result<()> {
39732        // Python: f"FOR {expression.name} {kind} {expr}"
39733        // e.this = Identifier("TIMESTAMP" or "VERSION")
39734        // e.kind = "AS OF" (or "BETWEEN", etc.)
39735        // e.expression = the value expression
39736        // Hive does NOT use the FOR prefix for time travel
39737        use crate::dialects::DialectType;
39738        let skip_for = matches!(
39739            self.config.dialect,
39740            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
39741        );
39742        if !skip_for {
39743            self.write_keyword("FOR");
39744            self.write_space();
39745        }
39746        // Extract the name from this (which is an Identifier expression)
39747        match e.this.as_ref() {
39748            Expression::Identifier(ident) => {
39749                self.write_keyword(&ident.name);
39750            }
39751            _ => {
39752                self.generate_expression(&e.this)?;
39753            }
39754        }
39755        self.write_space();
39756        self.write_keyword(&e.kind);
39757        if let Some(expression) = &e.expression {
39758            self.write_space();
39759            self.generate_expression(expression)?;
39760        }
39761        Ok(())
39762    }
39763
39764    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
39765        // Python: return self.sql(expression, "this")
39766        self.generate_expression(&e.this)?;
39767        Ok(())
39768    }
39769
39770    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
39771        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
39772        if e.this.is_some() {
39773            self.write_keyword("NOT VOLATILE");
39774        } else {
39775            self.write_keyword("VOLATILE");
39776        }
39777        Ok(())
39778    }
39779
39780    fn generate_watermark_column_constraint(
39781        &mut self,
39782        e: &WatermarkColumnConstraint,
39783    ) -> Result<()> {
39784        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
39785        self.write_keyword("WATERMARK FOR");
39786        self.write_space();
39787        self.generate_expression(&e.this)?;
39788        self.write_space();
39789        self.write_keyword("AS");
39790        self.write_space();
39791        self.generate_expression(&e.expression)?;
39792        Ok(())
39793    }
39794
39795    fn generate_week(&mut self, e: &Week) -> Result<()> {
39796        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
39797        self.write_keyword("WEEK");
39798        self.write("(");
39799        self.generate_expression(&e.this)?;
39800        if let Some(mode) = &e.mode {
39801            self.write(", ");
39802            self.generate_expression(mode)?;
39803        }
39804        self.write(")");
39805        Ok(())
39806    }
39807
39808    fn generate_when(&mut self, e: &When) -> Result<()> {
39809        // Python: WHEN {matched}{source}{condition} THEN {then}
39810        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
39811        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
39812        self.write_keyword("WHEN");
39813        self.write_space();
39814
39815        // Check if matched
39816        if let Some(matched) = &e.matched {
39817            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
39818            match matched.as_ref() {
39819                Expression::Boolean(b) if b.value => {
39820                    self.write_keyword("MATCHED");
39821                }
39822                _ => {
39823                    self.write_keyword("NOT MATCHED");
39824                }
39825            }
39826        } else {
39827            self.write_keyword("NOT MATCHED");
39828        }
39829
39830        // BY SOURCE / BY TARGET
39831        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
39832        // BY TARGET is the default and typically omitted in output
39833        // Only emit if the dialect supports BY SOURCE syntax
39834        if self.config.matched_by_source {
39835            if let Some(source) = &e.source {
39836                if let Expression::Boolean(b) = source.as_ref() {
39837                    if b.value {
39838                        // BY SOURCE
39839                        self.write_space();
39840                        self.write_keyword("BY SOURCE");
39841                    }
39842                    // BY TARGET (b.value == false) is omitted as it's the default
39843                } else {
39844                    // For non-boolean source, output as BY SOURCE (legacy behavior)
39845                    self.write_space();
39846                    self.write_keyword("BY SOURCE");
39847                }
39848            }
39849        }
39850
39851        // Condition
39852        if let Some(condition) = &e.condition {
39853            self.write_space();
39854            self.write_keyword("AND");
39855            self.write_space();
39856            self.generate_expression(condition)?;
39857        }
39858
39859        self.write_space();
39860        self.write_keyword("THEN");
39861        self.write_space();
39862
39863        // Generate the then expression (could be INSERT, UPDATE, DELETE)
39864        // MERGE actions are stored as Tuples with the action keyword as first element
39865        self.generate_merge_action(&e.then)?;
39866
39867        Ok(())
39868    }
39869
39870    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
39871        match action {
39872            Expression::Tuple(tuple) => {
39873                let elements = &tuple.expressions;
39874                if elements.is_empty() {
39875                    return self.generate_expression(action);
39876                }
39877                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
39878                match &elements[0] {
39879                    Expression::Var(v) if v.this == "INSERT" => {
39880                        self.write_keyword("INSERT");
39881                        // Spark: INSERT * (insert all columns)
39882                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
39883                            self.write(" *");
39884                            if let Some(Expression::Where(w)) = elements.get(2) {
39885                                self.write_space();
39886                                self.generate_where(w)?;
39887                            }
39888                        } else {
39889                            let mut values_idx = 1;
39890                            // Check if second element is column list (Tuple)
39891                            if elements.len() > 1 {
39892                                if let Expression::Tuple(cols) = &elements[1] {
39893                                    // Could be columns or values - if there's a third element, second is columns
39894                                    if elements.len() > 2 {
39895                                        // Second is columns, third is values
39896                                        self.write(" (");
39897                                        for (i, col) in cols.expressions.iter().enumerate() {
39898                                            if i > 0 {
39899                                                self.write(", ");
39900                                            }
39901                                            // Strip MERGE target qualifiers from INSERT column list
39902                                            if !self.merge_strip_qualifiers.is_empty() {
39903                                                let stripped = self.strip_merge_qualifier(col);
39904                                                self.generate_expression(&stripped)?;
39905                                            } else {
39906                                                self.generate_expression(col)?;
39907                                            }
39908                                        }
39909                                        self.write(")");
39910                                        values_idx = 2;
39911                                    } else {
39912                                        // Only two elements: INSERT + values (no explicit columns)
39913                                        values_idx = 1;
39914                                    }
39915                                }
39916                            }
39917                            let mut next_idx = values_idx;
39918                            // Generate VALUES clause
39919                            if values_idx < elements.len()
39920                                && !matches!(&elements[values_idx], Expression::Where(_))
39921                            {
39922                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
39923                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
39924                                if !is_row {
39925                                    self.write_space();
39926                                    self.write_keyword("VALUES");
39927                                }
39928                                self.write(" ");
39929                                if let Expression::Tuple(vals) = &elements[values_idx] {
39930                                    self.write("(");
39931                                    for (i, val) in vals.expressions.iter().enumerate() {
39932                                        if i > 0 {
39933                                            self.write(", ");
39934                                        }
39935                                        self.generate_expression(val)?;
39936                                    }
39937                                    self.write(")");
39938                                } else {
39939                                    self.generate_expression(&elements[values_idx])?;
39940                                }
39941                                next_idx += 1;
39942                            }
39943                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
39944                                self.write_space();
39945                                self.generate_where(w)?;
39946                            }
39947                        } // close else for INSERT * check
39948                    }
39949                    Expression::Var(v) if v.this == "UPDATE" => {
39950                        self.write_keyword("UPDATE");
39951                        // Spark: UPDATE * (update all columns)
39952                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
39953                            self.write(" *");
39954                            if let Some(Expression::Where(w)) = elements.get(2) {
39955                                self.write_space();
39956                                self.generate_where(w)?;
39957                            }
39958                        } else if elements.len() > 1 {
39959                            self.write_space();
39960                            self.write_keyword("SET");
39961                            // In pretty mode, put assignments on next line with extra indent
39962                            if self.config.pretty {
39963                                self.write_newline();
39964                                self.indent_level += 1;
39965                                self.write_indent();
39966                            } else {
39967                                self.write_space();
39968                            }
39969                            if let Expression::Tuple(assignments) = &elements[1] {
39970                                for (i, assignment) in assignments.expressions.iter().enumerate() {
39971                                    if i > 0 {
39972                                        if self.config.pretty {
39973                                            self.write(",");
39974                                            self.write_newline();
39975                                            self.write_indent();
39976                                        } else {
39977                                            self.write(", ");
39978                                        }
39979                                    }
39980                                    // Strip MERGE target qualifiers from left side of UPDATE SET
39981                                    if !self.merge_strip_qualifiers.is_empty() {
39982                                        self.generate_merge_set_assignment(assignment)?;
39983                                    } else {
39984                                        self.generate_expression(assignment)?;
39985                                    }
39986                                }
39987                            } else {
39988                                self.generate_expression(&elements[1])?;
39989                            }
39990                            if self.config.pretty {
39991                                self.indent_level -= 1;
39992                            }
39993                            if let Some(Expression::Where(w)) = elements.get(2) {
39994                                self.write_space();
39995                                self.generate_where(w)?;
39996                            }
39997                        }
39998                    }
39999                    Expression::Var(v) if v.this == "DELETE" => {
40000                        self.write_keyword("DELETE");
40001                        if let Some(Expression::Where(w)) = elements.get(1) {
40002                            self.write_space();
40003                            self.generate_where(w)?;
40004                        }
40005                    }
40006                    _ => {
40007                        // Fallback: generic tuple generation
40008                        self.generate_expression(action)?;
40009                    }
40010                }
40011            }
40012            Expression::Var(v)
40013                if v.this == "INSERT"
40014                    || v.this == "UPDATE"
40015                    || v.this == "DELETE"
40016                    || v.this == "DO NOTHING" =>
40017            {
40018                self.write_keyword(&v.this);
40019            }
40020            _ => {
40021                self.generate_expression(action)?;
40022            }
40023        }
40024        Ok(())
40025    }
40026
40027    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
40028    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
40029        match assignment {
40030            Expression::Eq(eq) => {
40031                // Strip qualifier from the left side if it matches a MERGE target name
40032                let stripped_left = self.strip_merge_qualifier(&eq.left);
40033                self.generate_expression(&stripped_left)?;
40034                self.write(" = ");
40035                self.generate_expression(&eq.right)?;
40036                Ok(())
40037            }
40038            other => self.generate_expression(other),
40039        }
40040    }
40041
40042    /// Strip table qualifier from a column reference if it matches a MERGE target name
40043    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
40044        match expr {
40045            Expression::Column(col) => {
40046                if let Some(ref table_ident) = col.table {
40047                    if self
40048                        .merge_strip_qualifiers
40049                        .iter()
40050                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
40051                    {
40052                        // Strip the table qualifier
40053                        let mut col = col.clone();
40054                        col.table = None;
40055                        return Expression::Column(col);
40056                    }
40057                }
40058                expr.clone()
40059            }
40060            Expression::Dot(dot) => {
40061                // table.column -> column (strip qualifier)
40062                if let Expression::Identifier(id) = &dot.this {
40063                    if self
40064                        .merge_strip_qualifiers
40065                        .iter()
40066                        .any(|n| n.eq_ignore_ascii_case(&id.name))
40067                    {
40068                        return Expression::Identifier(dot.field.clone());
40069                    }
40070                }
40071                expr.clone()
40072            }
40073            _ => expr.clone(),
40074        }
40075    }
40076
40077    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
40078        // Python: return self.expressions(expression, sep=" ", indent=False)
40079        for (i, expr) in e.expressions.iter().enumerate() {
40080            if i > 0 {
40081                // In pretty mode, each WHEN clause on its own line
40082                if self.config.pretty {
40083                    self.write_newline();
40084                    self.write_indent();
40085                } else {
40086                    self.write_space();
40087                }
40088            }
40089            self.generate_expression(expr)?;
40090        }
40091        Ok(())
40092    }
40093
40094    fn generate_where(&mut self, e: &Where) -> Result<()> {
40095        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
40096        self.write_keyword("WHERE");
40097        self.write_space();
40098        self.generate_expression(&e.this)?;
40099        Ok(())
40100    }
40101
40102    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
40103        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
40104        self.write_keyword("WIDTH_BUCKET");
40105        self.write("(");
40106        self.generate_expression(&e.this)?;
40107        if let Some(min_value) = &e.min_value {
40108            self.write(", ");
40109            self.generate_expression(min_value)?;
40110        }
40111        if let Some(max_value) = &e.max_value {
40112            self.write(", ");
40113            self.generate_expression(max_value)?;
40114        }
40115        if let Some(num_buckets) = &e.num_buckets {
40116            self.write(", ");
40117            self.generate_expression(num_buckets)?;
40118        }
40119        self.write(")");
40120        Ok(())
40121    }
40122
40123    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
40124        // Window specification: PARTITION BY ... ORDER BY ... frame
40125        self.generate_window_spec(e)
40126    }
40127
40128    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
40129        // Window specification: PARTITION BY ... ORDER BY ... frame
40130        let mut has_content = false;
40131
40132        // PARTITION BY
40133        if !e.partition_by.is_empty() {
40134            self.write_keyword("PARTITION BY");
40135            self.write_space();
40136            for (i, expr) in e.partition_by.iter().enumerate() {
40137                if i > 0 {
40138                    self.write(", ");
40139                }
40140                self.generate_expression(expr)?;
40141            }
40142            has_content = true;
40143        }
40144
40145        // ORDER BY
40146        if !e.order_by.is_empty() {
40147            if has_content {
40148                self.write_space();
40149            }
40150            self.write_keyword("ORDER BY");
40151            self.write_space();
40152            for (i, ordered) in e.order_by.iter().enumerate() {
40153                if i > 0 {
40154                    self.write(", ");
40155                }
40156                self.generate_expression(&ordered.this)?;
40157                if ordered.desc {
40158                    self.write_space();
40159                    self.write_keyword("DESC");
40160                } else if ordered.explicit_asc {
40161                    self.write_space();
40162                    self.write_keyword("ASC");
40163                }
40164                if let Some(nulls_first) = ordered.nulls_first {
40165                    self.write_space();
40166                    self.write_keyword("NULLS");
40167                    self.write_space();
40168                    if nulls_first {
40169                        self.write_keyword("FIRST");
40170                    } else {
40171                        self.write_keyword("LAST");
40172                    }
40173                }
40174            }
40175            has_content = true;
40176        }
40177
40178        // Frame specification
40179        if let Some(frame) = &e.frame {
40180            if has_content {
40181                self.write_space();
40182            }
40183            self.generate_window_frame(frame)?;
40184        }
40185
40186        Ok(())
40187    }
40188
40189    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
40190        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
40191        self.write_keyword("WITH");
40192        self.write_space();
40193        if e.no.is_some() {
40194            self.write_keyword("NO");
40195            self.write_space();
40196        }
40197        self.write_keyword("DATA");
40198
40199        // statistics
40200        if let Some(statistics) = &e.statistics {
40201            self.write_space();
40202            self.write_keyword("AND");
40203            self.write_space();
40204            // Check if statistics is true or false
40205            match statistics.as_ref() {
40206                Expression::Boolean(b) if !b.value => {
40207                    self.write_keyword("NO");
40208                    self.write_space();
40209                }
40210                _ => {}
40211            }
40212            self.write_keyword("STATISTICS");
40213        }
40214        Ok(())
40215    }
40216
40217    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
40218        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
40219        self.write_keyword("WITH FILL");
40220
40221        if let Some(from_) = &e.from_ {
40222            self.write_space();
40223            self.write_keyword("FROM");
40224            self.write_space();
40225            self.generate_expression(from_)?;
40226        }
40227
40228        if let Some(to) = &e.to {
40229            self.write_space();
40230            self.write_keyword("TO");
40231            self.write_space();
40232            self.generate_expression(to)?;
40233        }
40234
40235        if let Some(step) = &e.step {
40236            self.write_space();
40237            self.write_keyword("STEP");
40238            self.write_space();
40239            self.generate_expression(step)?;
40240        }
40241
40242        if let Some(staleness) = &e.staleness {
40243            self.write_space();
40244            self.write_keyword("STALENESS");
40245            self.write_space();
40246            self.generate_expression(staleness)?;
40247        }
40248
40249        if let Some(interpolate) = &e.interpolate {
40250            self.write_space();
40251            self.write_keyword("INTERPOLATE");
40252            self.write(" (");
40253            // INTERPOLATE items use reversed alias format: name AS expression
40254            self.generate_interpolate_item(interpolate)?;
40255            self.write(")");
40256        }
40257
40258        Ok(())
40259    }
40260
40261    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
40262    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
40263        match expr {
40264            Expression::Alias(alias) => {
40265                // Output as: alias_name AS expression
40266                self.generate_identifier(&alias.alias)?;
40267                self.write_space();
40268                self.write_keyword("AS");
40269                self.write_space();
40270                self.generate_expression(&alias.this)?;
40271            }
40272            Expression::Tuple(tuple) => {
40273                for (i, item) in tuple.expressions.iter().enumerate() {
40274                    if i > 0 {
40275                        self.write(", ");
40276                    }
40277                    self.generate_interpolate_item(item)?;
40278                }
40279            }
40280            other => {
40281                self.generate_expression(other)?;
40282            }
40283        }
40284        Ok(())
40285    }
40286
40287    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
40288        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
40289        self.write_keyword("WITH JOURNAL TABLE");
40290        self.write("=");
40291        self.generate_expression(&e.this)?;
40292        Ok(())
40293    }
40294
40295    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
40296        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
40297        self.generate_expression(&e.this)?;
40298        self.write_space();
40299        self.write_keyword("WITH");
40300        self.write_space();
40301        self.write_keyword(&e.op);
40302        Ok(())
40303    }
40304
40305    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
40306        // Python: return f"WITH {self.expressions(expression, flat=True)}"
40307        self.write_keyword("WITH");
40308        self.write_space();
40309        for (i, expr) in e.expressions.iter().enumerate() {
40310            if i > 0 {
40311                self.write(", ");
40312            }
40313            self.generate_expression(expr)?;
40314        }
40315        Ok(())
40316    }
40317
40318    fn generate_with_schema_binding_property(
40319        &mut self,
40320        e: &WithSchemaBindingProperty,
40321    ) -> Result<()> {
40322        // Python: return f"WITH {self.sql(expression, 'this')}"
40323        self.write_keyword("WITH");
40324        self.write_space();
40325        self.generate_expression(&e.this)?;
40326        Ok(())
40327    }
40328
40329    fn generate_with_system_versioning_property(
40330        &mut self,
40331        e: &WithSystemVersioningProperty,
40332    ) -> Result<()> {
40333        // Python: complex logic for SYSTEM_VERSIONING with options
40334        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
40335        // or SYSTEM_VERSIONING=ON/OFF
40336        // with WITH(...) wrapper if with_ is set
40337
40338        let mut parts = Vec::new();
40339
40340        if let Some(this) = &e.this {
40341            // HISTORY_TABLE=...
40342            let mut s = String::from("HISTORY_TABLE=");
40343            let mut gen = Generator::new();
40344            gen.generate_expression(this)?;
40345            s.push_str(&gen.output);
40346            parts.push(s);
40347        }
40348
40349        if let Some(data_consistency) = &e.data_consistency {
40350            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
40351            let mut gen = Generator::new();
40352            gen.generate_expression(data_consistency)?;
40353            s.push_str(&gen.output);
40354            parts.push(s);
40355        }
40356
40357        if let Some(retention_period) = &e.retention_period {
40358            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
40359            let mut gen = Generator::new();
40360            gen.generate_expression(retention_period)?;
40361            s.push_str(&gen.output);
40362            parts.push(s);
40363        }
40364
40365        self.write_keyword("SYSTEM_VERSIONING");
40366        self.write("=");
40367
40368        if !parts.is_empty() {
40369            self.write_keyword("ON");
40370            self.write("(");
40371            self.write(&parts.join(", "));
40372            self.write(")");
40373        } else if e.on.is_some() {
40374            self.write_keyword("ON");
40375        } else {
40376            self.write_keyword("OFF");
40377        }
40378
40379        // Wrap in WITH(...) if with_ is set
40380        if e.with_.is_some() {
40381            let inner = self.output.clone();
40382            self.output.clear();
40383            self.write("WITH(");
40384            self.write(&inner);
40385            self.write(")");
40386        }
40387
40388        Ok(())
40389    }
40390
40391    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
40392        // Python: f"WITH ({self.expressions(expression, flat=True)})"
40393        self.write_keyword("WITH");
40394        self.write(" (");
40395        for (i, expr) in e.expressions.iter().enumerate() {
40396            if i > 0 {
40397                self.write(", ");
40398            }
40399            self.generate_expression(expr)?;
40400        }
40401        self.write(")");
40402        Ok(())
40403    }
40404
40405    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
40406        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
40407        // return self.func("XMLELEMENT", name, *expression.expressions)
40408        self.write_keyword("XMLELEMENT");
40409        self.write("(");
40410
40411        if e.evalname.is_some() {
40412            self.write_keyword("EVALNAME");
40413        } else {
40414            self.write_keyword("NAME");
40415        }
40416        self.write_space();
40417        self.generate_expression(&e.this)?;
40418
40419        for expr in &e.expressions {
40420            self.write(", ");
40421            self.generate_expression(expr)?;
40422        }
40423        self.write(")");
40424        Ok(())
40425    }
40426
40427    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
40428        // XMLGET(this, expression [, instance])
40429        self.write_keyword("XMLGET");
40430        self.write("(");
40431        self.generate_expression(&e.this)?;
40432        self.write(", ");
40433        self.generate_expression(&e.expression)?;
40434        if let Some(instance) = &e.instance {
40435            self.write(", ");
40436            self.generate_expression(instance)?;
40437        }
40438        self.write(")");
40439        Ok(())
40440    }
40441
40442    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
40443        // Python: this + optional (expr)
40444        self.generate_expression(&e.this)?;
40445        if let Some(expression) = &e.expression {
40446            self.write("(");
40447            self.generate_expression(expression)?;
40448            self.write(")");
40449        }
40450        Ok(())
40451    }
40452
40453    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
40454        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
40455        self.write_keyword("XMLTABLE");
40456        self.write("(");
40457
40458        if self.config.pretty {
40459            self.indent_level += 1;
40460            self.write_newline();
40461            self.write_indent();
40462            self.generate_expression(&e.this)?;
40463
40464            if let Some(passing) = &e.passing {
40465                self.write_newline();
40466                self.write_indent();
40467                self.write_keyword("PASSING");
40468                if let Expression::Tuple(tuple) = passing.as_ref() {
40469                    for expr in &tuple.expressions {
40470                        self.write_newline();
40471                        self.indent_level += 1;
40472                        self.write_indent();
40473                        self.generate_expression(expr)?;
40474                        self.indent_level -= 1;
40475                    }
40476                } else {
40477                    self.write_newline();
40478                    self.indent_level += 1;
40479                    self.write_indent();
40480                    self.generate_expression(passing)?;
40481                    self.indent_level -= 1;
40482                }
40483            }
40484
40485            if e.by_ref.is_some() {
40486                self.write_newline();
40487                self.write_indent();
40488                self.write_keyword("RETURNING SEQUENCE BY REF");
40489            }
40490
40491            if !e.columns.is_empty() {
40492                self.write_newline();
40493                self.write_indent();
40494                self.write_keyword("COLUMNS");
40495                for (i, col) in e.columns.iter().enumerate() {
40496                    self.write_newline();
40497                    self.indent_level += 1;
40498                    self.write_indent();
40499                    self.generate_expression(col)?;
40500                    self.indent_level -= 1;
40501                    if i < e.columns.len() - 1 {
40502                        self.write(",");
40503                    }
40504                }
40505            }
40506
40507            self.indent_level -= 1;
40508            self.write_newline();
40509            self.write_indent();
40510            self.write(")");
40511            return Ok(());
40512        }
40513
40514        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
40515        if let Some(namespaces) = &e.namespaces {
40516            self.write_keyword("XMLNAMESPACES");
40517            self.write("(");
40518            // Unwrap Tuple if present to avoid extra parentheses
40519            if let Expression::Tuple(tuple) = namespaces.as_ref() {
40520                for (i, expr) in tuple.expressions.iter().enumerate() {
40521                    if i > 0 {
40522                        self.write(", ");
40523                    }
40524                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
40525                    // See xmlnamespace_sql in generator.py
40526                    if !matches!(expr, Expression::Alias(_)) {
40527                        self.write_keyword("DEFAULT");
40528                        self.write_space();
40529                    }
40530                    self.generate_expression(expr)?;
40531                }
40532            } else {
40533                // Single namespace - check if DEFAULT
40534                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
40535                    self.write_keyword("DEFAULT");
40536                    self.write_space();
40537                }
40538                self.generate_expression(namespaces)?;
40539            }
40540            self.write("), ");
40541        }
40542
40543        // XPath expression
40544        self.generate_expression(&e.this)?;
40545
40546        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
40547        if let Some(passing) = &e.passing {
40548            self.write_space();
40549            self.write_keyword("PASSING");
40550            self.write_space();
40551            // Unwrap Tuple if present to avoid extra parentheses
40552            if let Expression::Tuple(tuple) = passing.as_ref() {
40553                for (i, expr) in tuple.expressions.iter().enumerate() {
40554                    if i > 0 {
40555                        self.write(", ");
40556                    }
40557                    self.generate_expression(expr)?;
40558                }
40559            } else {
40560                self.generate_expression(passing)?;
40561            }
40562        }
40563
40564        // RETURNING SEQUENCE BY REF
40565        if e.by_ref.is_some() {
40566            self.write_space();
40567            self.write_keyword("RETURNING SEQUENCE BY REF");
40568        }
40569
40570        // COLUMNS clause
40571        if !e.columns.is_empty() {
40572            self.write_space();
40573            self.write_keyword("COLUMNS");
40574            self.write_space();
40575            for (i, col) in e.columns.iter().enumerate() {
40576                if i > 0 {
40577                    self.write(", ");
40578                }
40579                self.generate_expression(col)?;
40580            }
40581        }
40582
40583        self.write(")");
40584        Ok(())
40585    }
40586
40587    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
40588        // Python: return self.connector_sql(expression, "XOR", stack)
40589        // Handles: this XOR expression or expressions joined by XOR
40590        if let Some(this) = &e.this {
40591            self.generate_expression(this)?;
40592            if let Some(expression) = &e.expression {
40593                self.write_space();
40594                self.write_keyword("XOR");
40595                self.write_space();
40596                self.generate_expression(expression)?;
40597            }
40598        }
40599
40600        // Handle multiple expressions
40601        for (i, expr) in e.expressions.iter().enumerate() {
40602            if i > 0 || e.this.is_some() {
40603                self.write_space();
40604                self.write_keyword("XOR");
40605                self.write_space();
40606            }
40607            self.generate_expression(expr)?;
40608        }
40609        Ok(())
40610    }
40611
40612    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
40613        // ZIPF(this, elementcount [, gen])
40614        self.write_keyword("ZIPF");
40615        self.write("(");
40616        self.generate_expression(&e.this)?;
40617        if let Some(elementcount) = &e.elementcount {
40618            self.write(", ");
40619            self.generate_expression(elementcount)?;
40620        }
40621        if let Some(gen) = &e.gen {
40622            self.write(", ");
40623            self.generate_expression(gen)?;
40624        }
40625        self.write(")");
40626        Ok(())
40627    }
40628}
40629
40630impl Default for Generator {
40631    fn default() -> Self {
40632        Self::new()
40633    }
40634}
40635
40636#[cfg(test)]
40637mod tests {
40638    use super::*;
40639    use crate::parser::Parser;
40640
40641    fn roundtrip(sql: &str) -> String {
40642        let ast = Parser::parse_sql(sql).unwrap();
40643        Generator::sql(&ast[0]).unwrap()
40644    }
40645
40646    #[test]
40647    fn test_simple_select() {
40648        let result = roundtrip("SELECT 1");
40649        assert_eq!(result, "SELECT 1");
40650    }
40651
40652    #[test]
40653    fn test_select_from() {
40654        let result = roundtrip("SELECT a, b FROM t");
40655        assert_eq!(result, "SELECT a, b FROM t");
40656    }
40657
40658    #[test]
40659    fn test_select_where() {
40660        let result = roundtrip("SELECT * FROM t WHERE x = 1");
40661        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
40662    }
40663
40664    #[test]
40665    fn test_select_join() {
40666        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
40667        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
40668    }
40669
40670    #[test]
40671    fn test_insert() {
40672        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
40673        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
40674    }
40675
40676    #[test]
40677    fn test_pretty_print() {
40678        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
40679        let result = Generator::pretty_sql(&ast[0]).unwrap();
40680        assert!(result.contains('\n'));
40681    }
40682
40683    #[test]
40684    fn test_window_function() {
40685        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
40686        assert_eq!(
40687            result,
40688            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
40689        );
40690    }
40691
40692    #[test]
40693    fn test_window_function_with_frame() {
40694        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
40695        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
40696    }
40697
40698    #[test]
40699    fn test_aggregate_with_filter() {
40700        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
40701        assert_eq!(
40702            result,
40703            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
40704        );
40705    }
40706
40707    #[test]
40708    fn test_subscript() {
40709        let result = roundtrip("SELECT arr[0]");
40710        assert_eq!(result, "SELECT arr[0]");
40711    }
40712
40713    // DDL tests
40714    #[test]
40715    fn test_create_table() {
40716        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
40717        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
40718    }
40719
40720    #[test]
40721    fn test_create_table_with_constraints() {
40722        let result = roundtrip(
40723            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
40724        );
40725        assert_eq!(
40726            result,
40727            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
40728        );
40729    }
40730
40731    #[test]
40732    fn test_create_table_if_not_exists() {
40733        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
40734        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
40735    }
40736
40737    #[test]
40738    fn test_drop_table() {
40739        let result = roundtrip("DROP TABLE users");
40740        assert_eq!(result, "DROP TABLE users");
40741    }
40742
40743    #[test]
40744    fn test_drop_table_if_exists_cascade() {
40745        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
40746        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
40747    }
40748
40749    #[test]
40750    fn test_alter_table_add_column() {
40751        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
40752        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
40753    }
40754
40755    #[test]
40756    fn test_alter_table_drop_column() {
40757        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
40758        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
40759    }
40760
40761    #[test]
40762    fn test_create_index() {
40763        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
40764        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
40765    }
40766
40767    #[test]
40768    fn test_create_unique_index() {
40769        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
40770        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
40771    }
40772
40773    #[test]
40774    fn test_drop_index() {
40775        let result = roundtrip("DROP INDEX idx_name");
40776        assert_eq!(result, "DROP INDEX idx_name");
40777
40778        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
40779        assert_eq!(
40780            result,
40781            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
40782        );
40783
40784        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
40785        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
40786    }
40787
40788    #[test]
40789    fn test_create_view() {
40790        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
40791        assert_eq!(
40792            result,
40793            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
40794        );
40795    }
40796
40797    #[test]
40798    fn test_drop_view() {
40799        let result = roundtrip("DROP VIEW active_users");
40800        assert_eq!(result, "DROP VIEW active_users");
40801    }
40802
40803    #[test]
40804    fn test_truncate() {
40805        let result = roundtrip("TRUNCATE TABLE users");
40806        assert_eq!(result, "TRUNCATE TABLE users");
40807    }
40808
40809    #[test]
40810    fn test_string_literal_escaping_default() {
40811        // Default: double single quotes
40812        let result = roundtrip("SELECT 'hello'");
40813        assert_eq!(result, "SELECT 'hello'");
40814
40815        // Single quotes are doubled
40816        let result = roundtrip("SELECT 'it''s a test'");
40817        assert_eq!(result, "SELECT 'it''s a test'");
40818    }
40819
40820    #[test]
40821    fn test_not_in_style_prefix_default_generic() {
40822        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
40823        assert_eq!(
40824            result,
40825            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
40826        );
40827    }
40828
40829    #[test]
40830    fn test_not_in_style_infix_generic_override() {
40831        let ast =
40832            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
40833                .unwrap();
40834        let config = GeneratorConfig {
40835            not_in_style: NotInStyle::Infix,
40836            ..Default::default()
40837        };
40838        let mut gen = Generator::with_config(config);
40839        let result = gen.generate(&ast[0]).unwrap();
40840        assert_eq!(
40841            result,
40842            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
40843        );
40844    }
40845
40846    #[test]
40847    fn test_string_literal_escaping_mysql() {
40848        use crate::dialects::DialectType;
40849
40850        let config = GeneratorConfig {
40851            dialect: Some(DialectType::MySQL),
40852            ..Default::default()
40853        };
40854
40855        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
40856        let mut gen = Generator::with_config(config.clone());
40857        let result = gen.generate(&ast[0]).unwrap();
40858        assert_eq!(result, "SELECT 'hello'");
40859
40860        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
40861        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
40862        let mut gen = Generator::with_config(config.clone());
40863        let result = gen.generate(&ast[0]).unwrap();
40864        assert_eq!(result, "SELECT 'it''s'");
40865    }
40866
40867    #[test]
40868    fn test_string_literal_escaping_postgres() {
40869        use crate::dialects::DialectType;
40870
40871        let config = GeneratorConfig {
40872            dialect: Some(DialectType::PostgreSQL),
40873            ..Default::default()
40874        };
40875
40876        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
40877        let mut gen = Generator::with_config(config.clone());
40878        let result = gen.generate(&ast[0]).unwrap();
40879        assert_eq!(result, "SELECT 'hello'");
40880
40881        // PostgreSQL uses doubled quotes for regular strings
40882        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
40883        let mut gen = Generator::with_config(config.clone());
40884        let result = gen.generate(&ast[0]).unwrap();
40885        assert_eq!(result, "SELECT 'it''s'");
40886    }
40887
40888    #[test]
40889    fn test_string_literal_escaping_bigquery() {
40890        use crate::dialects::DialectType;
40891
40892        let config = GeneratorConfig {
40893            dialect: Some(DialectType::BigQuery),
40894            ..Default::default()
40895        };
40896
40897        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
40898        let mut gen = Generator::with_config(config.clone());
40899        let result = gen.generate(&ast[0]).unwrap();
40900        assert_eq!(result, "SELECT 'hello'");
40901
40902        // BigQuery escapes single quotes with backslash
40903        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
40904        let mut gen = Generator::with_config(config.clone());
40905        let result = gen.generate(&ast[0]).unwrap();
40906        assert_eq!(result, "SELECT 'it\\'s'");
40907    }
40908
40909    #[test]
40910    fn test_generate_deep_and_chain_without_stack_growth() {
40911        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
40912            Expression::column("c0"),
40913            Expression::number(0),
40914        )));
40915
40916        for i in 1..2500 {
40917            let predicate = Expression::Eq(Box::new(BinaryOp::new(
40918                Expression::column(format!("c{i}")),
40919                Expression::number(i as i64),
40920            )));
40921            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
40922        }
40923
40924        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
40925        assert!(sql.contains("c2499 = 2499"), "{}", sql);
40926    }
40927
40928    #[test]
40929    fn test_generate_deep_or_chain_without_stack_growth() {
40930        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
40931            Expression::column("c0"),
40932            Expression::number(0),
40933        )));
40934
40935        for i in 1..2500 {
40936            let predicate = Expression::Eq(Box::new(BinaryOp::new(
40937                Expression::column(format!("c{i}")),
40938                Expression::number(i as i64),
40939            )));
40940            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
40941        }
40942
40943        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
40944        assert!(sql.contains("c2499 = 2499"), "{}", sql);
40945    }
40946}