//! SQL Generator -- converts an AST back into SQL strings.
//!
//! The central type is [`Generator`], which walks an [`Expression`] tree and
//! emits a SQL string. Generation is controlled by a [`GeneratorConfig`] that
//! specifies the target dialect, formatting preferences, identifier quoting
//! style, function name casing, and many other dialect-specific flags.
//!
//! For one-off generation the static helpers [`Generator::sql`] and
//! [`Generator::pretty_sql`] are the simplest entry points. For repeated
//! generation, construct a `Generator` once with [`Generator::with_config`]
//! and call [`Generator::generate`] for each expression.

use crate::error::Result;
use crate::expressions::*;
use crate::DialectType;

/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
///
/// The generator walks the expression tree and emits dialect-specific SQL text.
/// It supports pretty-printing with configurable indentation, identifier quoting,
/// keyword casing, function name normalization, and 30+ SQL dialect variants.
///
/// # Usage
///
/// ```rust,ignore
/// use polyglot_sql::generator::Generator;
/// use polyglot_sql::parser::Parser;
///
/// let ast = Parser::parse_sql("SELECT 1")?;
/// // Quick one-shot generation (default config):
/// let sql = Generator::sql(&ast[0])?;
///
/// // Pretty-printed output:
/// let pretty = Generator::pretty_sql(&ast[0])?;
///
/// // Custom config (e.g. for a specific dialect):
/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
/// let mut gen = Generator::with_config(config);
/// let sql = gen.generate(&ast[0])?;
/// ```
pub struct Generator {
    config: GeneratorConfig,
    output: String,
    indent_level: usize,
    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
    athena_hive_context: bool,
    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
    sqlite_inline_pk_columns: std::collections::HashSet<String>,
    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
    merge_strip_qualifiers: Vec<String>,
}

/// Controls how SQL function names are cased in generated output.
///
/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
/// - `Lower` -- `count`, `sum`, `coalesce`
/// - `None` -- preserve the original casing from the parsed input
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum NormalizeFunctions {
    /// Emit function names in UPPER CASE (default).
    #[default]
    Upper,
    /// Emit function names in lower case.
    Lower,
    /// Preserve the original casing from the parsed input.
    None,
}

/// Strategy for generating row-limiting clauses across SQL dialects.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum LimitFetchStyle {
    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
    #[default]
    Limit,
    /// `TOP n` -- TSQL (SQL Server).
    Top,
    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
    FetchFirst,
}

/// Identifier quote style (start/end characters)
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IdentifierQuoteStyle {
    /// Start character for quoting identifiers (e.g., '"', '`', '[')
    pub start: char,
    /// End character for quoting identifiers (e.g., '"', '`', ']')
    pub end: char,
}

impl Default for IdentifierQuoteStyle {
    fn default() -> Self {
        Self {
            start: '"',
            end: '"',
        }
    }
}

impl IdentifierQuoteStyle {
    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
    pub const DOUBLE_QUOTE: Self = Self {
        start: '"',
        end: '"',
    };
    /// Backtick style (MySQL, BigQuery, Spark, Hive)
    pub const BACKTICK: Self = Self {
        start: '`',
        end: '`',
    };
    /// Square bracket style (TSQL, SQLite)
    pub const BRACKET: Self = Self {
        start: '[',
        end: ']',
    };
}

/// Configuration for the SQL [`Generator`].
///
/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
/// syntax, feature support flags, and more.
///
/// Most users should start from `GeneratorConfig::default()` and override only the
/// fields they need. Dialect-specific presets are applied automatically when
/// `dialect` is set via the higher-level transpilation API.
///
/// # Key fields
///
/// | Field | Default | Purpose |
/// |-------|---------|---------|
/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
/// | `pretty` | `false` | Enable multi-line, indented output |
/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
#[derive(Debug, Clone)]
pub struct GeneratorConfig {
    // ===== Basic formatting =====
    /// Pretty print with indentation
    pub pretty: bool,
    /// Indentation string (default 2 spaces)
    pub indent: String,
    /// Maximum text width before wrapping (default 80)
    pub max_text_width: usize,
    /// Quote identifier style (deprecated, use identifier_quote_style instead)
    pub identifier_quote: char,
    /// Identifier quote style with separate start/end characters
    pub identifier_quote_style: IdentifierQuoteStyle,
    /// Uppercase keywords
    pub uppercase_keywords: bool,
    /// Normalize identifiers to lowercase when generating
    pub normalize_identifiers: bool,
    /// Dialect type for dialect-specific generation
    pub dialect: Option<crate::dialects::DialectType>,
    /// How to output function names (UPPER, lower, or as-is)
    pub normalize_functions: NormalizeFunctions,
    /// String escape character
    pub string_escape: char,
    /// Whether identifiers are case-sensitive
    pub case_sensitive_identifiers: bool,
    /// Whether unquoted identifiers can start with a digit
    pub identifiers_can_start_with_digit: bool,
    /// Whether to always quote identifiers regardless of reserved keyword status
    /// Used by dialects like Athena/Presto that prefer quoted identifiers
    pub always_quote_identifiers: bool,

    // ===== Null handling =====
    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
    /// True: Full Support, false: No support
    pub null_ordering_supported: bool,
    /// Whether ignore nulls is inside the agg or outside
    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
    pub ignore_nulls_in_func: bool,
    /// Whether the NVL2 function is supported
    pub nvl2_supported: bool,

    // ===== Limit/Fetch =====
    /// How to output LIMIT clauses
    pub limit_fetch_style: LimitFetchStyle,
    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
    pub limit_is_top: bool,
    /// Whether limit and fetch allows expressions or just literals
    pub limit_only_literals: bool,

    // ===== Interval =====
    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
    pub single_string_interval: bool,
    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
    pub interval_allows_plural_form: bool,

    // ===== CTE =====
    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
    pub cte_recursive_keyword_required: bool,

    // ===== VALUES =====
    /// Whether VALUES can be used as a table source
    pub values_as_table: bool,
    /// Wrap derived values in parens (standard but Spark doesn't support)
    pub wrap_derived_values: bool,

    // ===== TABLESAMPLE =====
    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
    pub tablesample_seed_keyword: &'static str,
    /// Whether parentheses are required around the table sample's expression
    pub tablesample_requires_parens: bool,
    /// Whether a table sample clause's size needs to be followed by ROWS keyword
    pub tablesample_size_is_rows: bool,
    /// The keyword(s) to use when generating a sample clause
    pub tablesample_keywords: &'static str,
    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
    pub tablesample_with_method: bool,
    /// Whether the table alias comes after tablesample (Oracle, Hive)
    pub alias_post_tablesample: bool,

    // ===== Aggregate =====
    /// Whether aggregate FILTER (WHERE ...) is supported
    pub aggregate_filter_supported: bool,
    /// Whether DISTINCT can be followed by multiple args in an AggFunc
    pub multi_arg_distinct: bool,
    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
    pub quantified_no_paren_space: bool,
    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
    pub supports_median: bool,

    // ===== SELECT =====
    /// Whether SELECT ... INTO is supported
    pub supports_select_into: bool,
    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
    pub locking_reads_supported: bool,

    // ===== Table/Join =====
    /// Whether a table is allowed to be renamed with a db
    pub rename_table_with_db: bool,
    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
    pub semi_anti_join_with_side: bool,
    /// Whether named columns are allowed in table aliases
    pub supports_table_alias_columns: bool,
    /// Whether join hints should be generated
    pub join_hints: bool,
    /// Whether table hints should be generated
    pub table_hints: bool,
    /// Whether query hints should be generated
    pub query_hints: bool,
    /// What kind of separator to use for query hints
    pub query_hint_sep: &'static str,
    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
    pub supports_column_join_marks: bool,

    // ===== DDL =====
    /// Whether CREATE INDEX USING method should have no space before column parens
    /// true: `USING btree(col)`, false: `USING btree (col)`
    pub index_using_no_space: bool,
    /// Whether UNLOGGED tables can be created
    pub supports_unlogged_tables: bool,
    /// Whether CREATE TABLE LIKE statement is supported
    pub supports_create_table_like: bool,
    /// Whether the LikeProperty needs to be inside the schema clause
    pub like_property_inside_schema: bool,
    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
    pub alter_table_include_column_keyword: bool,
    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
    pub supports_table_copy: bool,
    /// The syntax to use when altering the type of a column
    pub alter_set_type: &'static str,
    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
    pub alter_set_wrapped: bool,

    // ===== Timestamp/Timezone =====
    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
    pub tz_to_with_time_zone: bool,
    /// Whether CONVERT_TIMEZONE() is supported
    pub supports_convert_timezone: bool,

    // ===== JSON =====
    /// Whether the JSON extraction operators expect a value of type JSON
    pub json_type_required_for_extraction: bool,
    /// Whether bracketed keys like ["foo"] are supported in JSON paths
    pub json_path_bracketed_key_supported: bool,
    /// Whether to escape keys using single quotes in JSON paths
    pub json_path_single_quote_escape: bool,
    /// Whether to quote the generated expression of JsonPath
    pub quote_json_path: bool,
    /// What delimiter to use for separating JSON key/value pairs
    pub json_key_value_pair_sep: &'static str,

    // ===== COPY =====
    /// Whether parameters from COPY statement are wrapped in parentheses
    pub copy_params_are_wrapped: bool,
    /// Whether values of params are set with "=" token or empty space
    pub copy_params_eq_required: bool,
    /// Whether COPY statement has INTO keyword
    pub copy_has_into_keyword: bool,

    // ===== Window functions =====
    /// Whether EXCLUDE in window specification is supported
    pub supports_window_exclude: bool,
    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
    pub unnest_with_ordinality: bool,
    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
    /// Exasol uses lowercase for these specific keywords
    pub lowercase_window_frame_keywords: bool,
    /// Whether to normalize single-bound window frames to BETWEEN form
    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
    pub normalize_window_frame_between: bool,

    // ===== Array =====
    /// Whether ARRAY_CONCAT can be generated with varlen args
    pub array_concat_is_var_len: bool,
    /// Whether exp.ArraySize should generate the dimension arg too
    /// None -> Doesn't support, false -> optional, true -> required
    pub array_size_dim_required: Option<bool>,
    /// Whether any(f(x) for x in array) can be implemented
    pub can_implement_array_any: bool,
    /// Function used for array size
    pub array_size_name: &'static str,

    // ===== BETWEEN =====
    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
    pub supports_between_flags: bool,

    // ===== Boolean =====
    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
    pub is_bool_allowed: bool,
    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
    pub ensure_bools: bool,

    // ===== EXTRACT =====
    /// Whether to generate an unquoted value for EXTRACT's date part argument
    pub extract_allows_quotes: bool,
    /// Whether to normalize date parts in EXTRACT
    pub normalize_extract_date_parts: bool,

    // ===== Other features =====
    /// Whether the conditional TRY(expression) function is supported
    pub try_supported: bool,
    /// Whether the UESCAPE syntax in unicode strings is supported
    pub supports_uescape: bool,
    /// Whether the function TO_NUMBER is supported
    pub supports_to_number: bool,
    /// Whether CONCAT requires >1 arguments
    pub supports_single_arg_concat: bool,
    /// Whether LAST_DAY function supports a date part argument
    pub last_day_supports_date_part: bool,
    /// Whether a projection can explode into multiple rows
    pub supports_exploding_projections: bool,
    /// Whether UNIX_SECONDS(timestamp) is supported
    pub supports_unix_seconds: bool,
    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
    pub supports_like_quantifiers: bool,
    /// Whether multi-argument DECODE(...) function is supported
    pub supports_decode_case: bool,
    /// Whether set op modifiers apply to the outer set op or select
    pub set_op_modifiers: bool,
    /// Whether FROM is supported in UPDATE statements
    pub update_statement_supports_from: bool,

    // ===== COLLATE =====
    /// Whether COLLATE is a function instead of a binary operator
    pub collate_is_func: bool,

    // ===== INSERT =====
    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
    pub duplicate_key_update_with_set: bool,
    /// INSERT OVERWRITE TABLE x override
    pub insert_overwrite: &'static str,

    // ===== RETURNING =====
    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
    pub returning_end: bool,

    // ===== MERGE =====
    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
    pub matched_by_source: bool,

    // ===== CREATE FUNCTION =====
    /// Whether create function uses an AS before the RETURN
    pub create_function_return_as: bool,
    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
    pub parameter_default_equals: bool,

    // ===== COMPUTED COLUMN =====
    /// Whether to include the type of a computed column in the CREATE DDL
    pub computed_column_with_type: bool,

    // ===== UNPIVOT =====
    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
    pub unpivot_aliases_are_identifiers: bool,

    // ===== STAR =====
    /// The keyword to use when generating a star projection with excluded columns
    pub star_except: &'static str,

    // ===== HEX =====
    /// The HEX function name
    pub hex_func: &'static str,

    // ===== WITH =====
    /// The keywords to use when prefixing WITH based properties
    pub with_properties_prefix: &'static str,

    // ===== PAD =====
    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
    pub pad_fill_pattern_is_required: bool,

    // ===== INDEX =====
    /// The string used for creating an index on a table
    pub index_on: &'static str,

    // ===== GROUPING =====
    /// The separator for grouping sets and rollups
    pub groupings_sep: &'static str,

    // ===== STRUCT =====
    /// Delimiters for STRUCT type
    pub struct_delimiter: (&'static str, &'static str),
    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
    pub struct_curly_brace_notation: bool,
    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
    pub array_bracket_only: bool,
    /// Separator between struct field name and type (": " for Hive, " " for others)
    pub struct_field_sep: &'static str,

    // ===== EXCEPT/INTERSECT =====
    /// Whether EXCEPT and INTERSECT operations can return duplicates
    pub except_intersect_support_all_clause: bool,

    // ===== PARAMETERS/PLACEHOLDERS =====
    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
    pub parameter_token: &'static str,
    /// Named placeholder token (: for most, % for PostgreSQL)
    pub named_placeholder_token: &'static str,

    // ===== DATA TYPES =====
    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
    pub data_type_specifiers_allowed: bool,

    // ===== COMMENT =====
    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
    /// StarRocks and Doris use naked COMMENT syntax without `=`
    pub schema_comment_with_eq: bool,
}

impl Default for GeneratorConfig {
    fn default() -> Self {
        Self {
            // ===== Basic formatting =====
            pretty: false,
            indent: "  ".to_string(),
            max_text_width: 80,
            identifier_quote: '"',
            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
            uppercase_keywords: true,
            normalize_identifiers: false,
            dialect: None,
            normalize_functions: NormalizeFunctions::Upper,
            string_escape: '\'',
            case_sensitive_identifiers: false,
            identifiers_can_start_with_digit: false,
            always_quote_identifiers: false,

            // ===== Null handling =====
            null_ordering_supported: true,
            ignore_nulls_in_func: false,
            nvl2_supported: true,

            // ===== Limit/Fetch =====
            limit_fetch_style: LimitFetchStyle::Limit,
            limit_is_top: false,
            limit_only_literals: false,

            // ===== Interval =====
            single_string_interval: false,
            interval_allows_plural_form: true,

            // ===== CTE =====
            cte_recursive_keyword_required: true,

            // ===== VALUES =====
            values_as_table: true,
            wrap_derived_values: true,

            // ===== TABLESAMPLE =====
            tablesample_seed_keyword: "SEED",
            tablesample_requires_parens: true,
            tablesample_size_is_rows: true,
            tablesample_keywords: "TABLESAMPLE",
            tablesample_with_method: true,
            alias_post_tablesample: false,

            // ===== Aggregate =====
            aggregate_filter_supported: true,
            multi_arg_distinct: true,
            quantified_no_paren_space: false,
            supports_median: true,

            // ===== SELECT =====
            supports_select_into: false,
            locking_reads_supported: true,

            // ===== Table/Join =====
            rename_table_with_db: true,
            semi_anti_join_with_side: true,
            supports_table_alias_columns: true,
            join_hints: true,
            table_hints: true,
            query_hints: true,
            query_hint_sep: ", ",
            supports_column_join_marks: false,

            // ===== DDL =====
            index_using_no_space: false,
            supports_unlogged_tables: false,
            supports_create_table_like: true,
            like_property_inside_schema: false,
            alter_table_include_column_keyword: true,
            supports_table_copy: true,
            alter_set_type: "SET DATA TYPE",
            alter_set_wrapped: false,

            // ===== Timestamp/Timezone =====
            tz_to_with_time_zone: false,
            supports_convert_timezone: false,

            // ===== JSON =====
            json_type_required_for_extraction: false,
            json_path_bracketed_key_supported: true,
            json_path_single_quote_escape: false,
            quote_json_path: true,
            json_key_value_pair_sep: ":",

            // ===== COPY =====
            copy_params_are_wrapped: true,
            copy_params_eq_required: false,
            copy_has_into_keyword: true,

            // ===== Window functions =====
            supports_window_exclude: false,
            unnest_with_ordinality: true,
            lowercase_window_frame_keywords: false,
            normalize_window_frame_between: false,

            // ===== Array =====
            array_concat_is_var_len: true,
            array_size_dim_required: None,
            can_implement_array_any: false,
            array_size_name: "ARRAY_LENGTH",

            // ===== BETWEEN =====
            supports_between_flags: false,

            // ===== Boolean =====
            is_bool_allowed: true,
            ensure_bools: false,

            // ===== EXTRACT =====
            extract_allows_quotes: true,
            normalize_extract_date_parts: false,

            // ===== Other features =====
            try_supported: true,
            supports_uescape: true,
            supports_to_number: true,
            supports_single_arg_concat: true,
            last_day_supports_date_part: true,
            supports_exploding_projections: true,
            supports_unix_seconds: false,
            supports_like_quantifiers: true,
            supports_decode_case: true,
            set_op_modifiers: true,
            update_statement_supports_from: true,

            // ===== COLLATE =====
            collate_is_func: false,

            // ===== INSERT =====
            duplicate_key_update_with_set: true,
            insert_overwrite: " OVERWRITE TABLE",

            // ===== RETURNING =====
            returning_end: true,

            // ===== MERGE =====
            matched_by_source: true,

            // ===== CREATE FUNCTION =====
            create_function_return_as: true,
            parameter_default_equals: false,

            // ===== COMPUTED COLUMN =====
            computed_column_with_type: true,

            // ===== UNPIVOT =====
            unpivot_aliases_are_identifiers: true,

            // ===== STAR =====
            star_except: "EXCEPT",

            // ===== HEX =====
            hex_func: "HEX",

            // ===== WITH =====
            with_properties_prefix: "WITH",

            // ===== PAD =====
            pad_fill_pattern_is_required: false,

            // ===== INDEX =====
            index_on: "ON",

            // ===== GROUPING =====
            groupings_sep: ",",

            // ===== STRUCT =====
            struct_delimiter: ("<", ">"),
            struct_curly_brace_notation: false,
            array_bracket_only: false,
            struct_field_sep: " ",

            // ===== EXCEPT/INTERSECT =====
            except_intersect_support_all_clause: true,

            // ===== PARAMETERS/PLACEHOLDERS =====
            parameter_token: "@",
            named_placeholder_token: ":",

            // ===== DATA TYPES =====
            data_type_specifiers_allowed: false,

            // ===== COMMENT =====
            schema_comment_with_eq: true,
        }
    }
}

/// SQL reserved keywords that require quoting when used as identifiers
/// Based on ANSI SQL standards and common dialect-specific reserved words
mod reserved_keywords {
    use std::collections::HashSet;
    use std::sync::LazyLock;

    /// Standard SQL reserved keywords (ANSI SQL:2016)
    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        [
            "all",
            "alter",
            "and",
            "any",
            "array",
            "as",
            "asc",
            "at",
            "authorization",
            "begin",
            "between",
            "both",
            "by",
            "case",
            "cast",
            "check",
            "collate",
            "column",
            "commit",
            "constraint",
            "create",
            "cross",
            "cube",
            "current",
            "current_date",
            "current_time",
            "current_timestamp",
            "current_user",
            "default",
            "delete",
            "desc",
            "distinct",
            "drop",
            "else",
            "end",
            "escape",
            "except",
            "execute",
            "exists",
            "external",
            "false",
            "fetch",
            "filter",
            "for",
            "foreign",
            "from",
            "full",
            "function",
            "grant",
            "group",
            "grouping",
            "having",
            "if",
            "in",
            "index",
            "inner",
            "insert",
            "intersect",
            "interval",
            "into",
            "is",
            "join",
            "key",
            "leading",
            "left",
            "like",
            "limit",
            "local",
            "localtime",
            "localtimestamp",
            "match",
            "merge",
            "natural",
            "no",
            "not",
            "null",
            "of",
            "offset",
            "on",
            "only",
            "or",
            "order",
            "outer",
            "over",
            "partition",
            "primary",
            "procedure",
            "range",
            "references",
            "right",
            "rollback",
            "rollup",
            "row",
            "rows",
            "select",
            "session_user",
            "set",
            "some",
            "table",
            "tablesample",
            "then",
            "to",
            "trailing",
            "true",
            "truncate",
            "union",
            "unique",
            "unknown",
            "update",
            "user",
            "using",
            "values",
            "view",
            "when",
            "where",
            "window",
            "with",
        ]
        .into_iter()
        .collect()
    });

    /// BigQuery-specific reserved keywords
    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = SQL_RESERVED.clone();
        set.extend([
            "assert_rows_modified",
            "at",
            "contains",
            "cube",
            "current",
            "define",
            "enum",
            "escape",
            "exclude",
            "following",
            "for",
            "groups",
            "hash",
            "ignore",
            "lateral",
            "lookup",
            "new",
            "no",
            "nulls",
            "of",
            "over",
            "preceding",
            "proto",
            "qualify",
            "recursive",
            "respect",
            "struct",
            "tablesample",
            "treat",
            "unbounded",
            "unnest",
            "window",
            "within",
        ]);
        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
        set.remove("grant");
        set.remove("key");
        set.remove("index");
        set.remove("values");
        set.remove("table");
        set
    });

    /// MySQL-specific reserved keywords
    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = SQL_RESERVED.clone();
        set.extend([
            "accessible",
            "add",
            "analyze",
            "asensitive",
            "before",
            "bigint",
            "binary",
            "blob",
            "call",
            "cascade",
            "change",
            "char",
            "character",
            "condition",
            "continue",
            "convert",
            "current_date",
            "current_time",
            "current_timestamp",
            "current_user",
            "cursor",
            "database",
            "databases",
            "day_hour",
            "day_microsecond",
            "day_minute",
            "day_second",
            "dec",
            "decimal",
            "declare",
            "delayed",
            "describe",
            "deterministic",
            "distinctrow",
            "div",
            "double",
            "dual",
            "each",
            "elseif",
            "enclosed",
            "escaped",
            "exit",
            "explain",
            "float",
            "float4",
            "float8",
            "force",
            "get",
            "high_priority",
            "hour_microsecond",
            "hour_minute",
            "hour_second",
            "ignore",
            "infile",
            "inout",
            "insensitive",
            "int",
            "int1",
            "int2",
            "int3",
            "int4",
            "int8",
            "integer",
            "iterate",
            "keys",
            "kill",
            "leave",
            "linear",
            "lines",
            "load",
            "lock",
            "long",
            "longblob",
            "longtext",
            "loop",
            "low_priority",
            "master_ssl_verify_server_cert",
            "maxvalue",
            "mediumblob",
            "mediumint",
            "mediumtext",
            "middleint",
            "minute_microsecond",
            "minute_second",
            "mod",
            "modifies",
            "no_write_to_binlog",
            "numeric",
            "optimize",
            "option",
            "optionally",
            "out",
            "outfile",
            "precision",
            "purge",
            "read",
            "reads",
            "real",
            "regexp",
            "release",
            "rename",
            "repeat",
            "replace",
            "require",
            "resignal",
            "restrict",
            "return",
            "revoke",
            "rlike",
            "schema",
            "schemas",
            "second_microsecond",
            "sensitive",
            "separator",
            "show",
            "signal",
            "smallint",
            "spatial",
            "specific",
            "sql",
            "sql_big_result",
            "sql_calc_found_rows",
            "sql_small_result",
            "sqlexception",
            "sqlstate",
            "sqlwarning",
            "ssl",
            "starting",
            "straight_join",
            "terminated",
            "text",
            "tinyblob",
            "tinyint",
            "tinytext",
            "trigger",
            "undo",
            "unlock",
            "unsigned",
            "usage",
            "utc_date",
            "utc_time",
            "utc_timestamp",
            "varbinary",
            "varchar",
            "varcharacter",
            "varying",
            "while",
            "write",
            "xor",
            "year_month",
            "zerofill",
        ]);
        set.remove("table");
        set
    });

    /// Doris-specific reserved keywords
    /// Extends MySQL reserved with additional Doris-specific words
    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = MYSQL_RESERVED.clone();
        set.extend([
            "aggregate",
            "anti",
            "array",
            "backend",
            "backup",
            "begin",
            "bitmap",
            "boolean",
            "broker",
            "buckets",
            "cached",
            "cancel",
            "cast",
            "catalog",
            "charset",
            "cluster",
            "collation",
            "columns",
            "comment",
            "commit",
            "config",
            "connection",
            "count",
            "current",
            "data",
            "date",
            "datetime",
            "day",
            "deferred",
            "distributed",
            "dynamic",
            "enable",
            "end",
            "events",
            "export",
            "external",
            "fields",
            "first",
            "follower",
            "format",
            "free",
            "frontend",
            "full",
            "functions",
            "global",
            "grants",
            "hash",
            "help",
            "hour",
            "install",
            "intermediate",
            "json",
            "label",
            "last",
            "less",
            "level",
            "link",
            "local",
            "location",
            "max",
            "merge",
            "min",
            "minute",
            "modify",
            "month",
            "name",
            "names",
            "negative",
            "nulls",
            "observer",
            "offset",
            "only",
            "open",
            "overwrite",
            "password",
            "path",
            "plan",
            "plugin",
            "plugins",
            "policy",
            "process",
            "properties",
            "property",
            "query",
            "quota",
            "recover",
            "refresh",
            "repair",
            "replica",
            "repository",
            "resource",
            "restore",
            "resume",
            "role",
            "roles",
            "rollback",
            "rollup",
            "routine",
            "sample",
            "second",
            "semi",
            "session",
            "signed",
            "snapshot",
            "start",
            "stats",
            "status",
            "stop",
            "stream",
            "string",
            "sum",
            "tables",
            "tablet",
            "temporary",
            "text",
            "timestamp",
            "transaction",
            "trash",
            "trim",
            "truncate",
            "type",
            "user",
            "value",
            "variables",
            "verbose",
            "version",
            "view",
            "warnings",
            "week",
            "work",
            "year",
        ]);
        set
    });

    /// PostgreSQL-specific reserved keywords
    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = SQL_RESERVED.clone();
        set.extend([
            "analyse",
            "analyze",
            "asymmetric",
            "binary",
            "collation",
            "concurrently",
            "current_catalog",
            "current_role",
            "current_schema",
            "deferrable",
            "do",
            "freeze",
            "ilike",
            "initially",
            "isnull",
            "lateral",
            "notnull",
            "placing",
            "returning",
            "similar",
            "symmetric",
            "variadic",
            "verbose",
        ]);
        // PostgreSQL doesn't require quoting for these keywords
        set.remove("default");
        set.remove("interval");
        set.remove("match");
        set.remove("offset");
        set.remove("table");
        set
    });

    /// Redshift-specific reserved keywords
    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        [
            "aes128",
            "aes256",
            "all",
            "allowoverwrite",
            "analyse",
            "analyze",
            "and",
            "any",
            "array",
            "as",
            "asc",
            "authorization",
            "az64",
            "backup",
            "between",
            "binary",
            "blanksasnull",
            "both",
            "bytedict",
            "bzip2",
            "case",
            "cast",
            "check",
            "collate",
            "column",
            "constraint",
            "create",
            "credentials",
            "cross",
            "current_date",
            "current_time",
            "current_timestamp",
            "current_user",
            "current_user_id",
            "default",
            "deferrable",
            "deflate",
            "defrag",
            "delta",
            "delta32k",
            "desc",
            "disable",
            "distinct",
            "do",
            "else",
            "emptyasnull",
            "enable",
            "encode",
            "encrypt",
            "encryption",
            "end",
            "except",
            "explicit",
            "false",
            "for",
            "foreign",
            "freeze",
            "from",
            "full",
            "globaldict256",
            "globaldict64k",
            "grant",
            "group",
            "gzip",
            "having",
            "identity",
            "ignore",
            "ilike",
            "in",
            "initially",
            "inner",
            "intersect",
            "interval",
            "into",
            "is",
            "isnull",
            "join",
            "leading",
            "left",
            "like",
            "limit",
            "localtime",
            "localtimestamp",
            "lun",
            "luns",
            "lzo",
            "lzop",
            "minus",
            "mostly16",
            "mostly32",
            "mostly8",
            "natural",
            "new",
            "not",
            "notnull",
            "null",
            "nulls",
            "off",
            "offline",
            "offset",
            "oid",
            "old",
            "on",
            "only",
            "open",
            "or",
            "order",
            "outer",
            "overlaps",
            "parallel",
            "partition",
            "percent",
            "permissions",
            "pivot",
            "placing",
            "primary",
            "raw",
            "readratio",
            "recover",
            "references",
            "rejectlog",
            "resort",
            "respect",
            "restore",
            "right",
            "select",
            "session_user",
            "similar",
            "snapshot",
            "some",
            "sysdate",
            "system",
            "table",
            "tag",
            "tdes",
            "text255",
            "text32k",
            "then",
            "timestamp",
            "to",
            "top",
            "trailing",
            "true",
            "truncatecolumns",
            "type",
            "union",
            "unique",
            "unnest",
            "unpivot",
            "user",
            "using",
            "verbose",
            "wallet",
            "when",
            "where",
            "with",
            "without",
        ]
        .into_iter()
        .collect()
    });

    /// DuckDB-specific reserved keywords
    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = POSTGRES_RESERVED.clone();
        set.extend([
            "anti",
            "asof",
            "columns",
            "describe",
            "groups",
            "macro",
            "pivot",
            "pivot_longer",
            "pivot_wider",
            "qualify",
            "replace",
            "respect",
            "semi",
            "show",
            "table",
            "unpivot",
        ]);
        set.remove("at");
        set.remove("row");
        set
    });

    /// Teradata-specific reserved keywords
    pub static TERADATA_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = SQL_RESERVED.clone();
        set.extend([
            "abort",
            "abortsession",
            "account",
            "activity",
            "add",
            "after",
            "algorithm",
            "all",
            "allocate",
            "alter",
            "amp",
            "analyse",
            "analyze",
            "and",
            "ansidate",
            "any",
            "are",
            "array",
            "as",
            "asc",
            "at",
            "authorization",
            "avg",
            "before",
            "begin",
            "between",
            "bigint",
            "binary",
            "blob",
            "both",
            "bt",
            "but",
            "by",
            "byte",
            "byteint",
            "bytes",
            "call",
            "cascade",
            "case",
            "casespecific",
            "cast",
            "cd",
            "char",
            "character",
            "characters",
            "character_length",
            "chars",
            "check",
            "checkpoint",
            "class",
            "clob",
            "close",
            "cluster",
            "cm",
            "coalesce",
            "collation",
            "collect",
            "column",
            "comment",
            "commit",
            "compress",
            "connect",
            "constraint",
            "constructor",
            "consume",
            "contains",
            "continue",
            "convert",
            "copy",
            "correlation",
            "cos",
            "count",
            "create",
            "cross",
            "cs",
            "csum",
            "current",
            "current_date",
            "current_time",
            "current_timestamp",
            "cursor",
            "cv",
            "cycle",
            "data",
            "database",
            "date",
            "dateform",
            "day",
            "deallocate",
            "dec",
            "decimal",
            "declare",
            "default",
            "deferred",
            "degrees",
            "del",
            "delete",
            "desc",
            "describe",
            "descriptor",
            "deterministic",
            "diagnostic",
            "disabled",
            "distinct",
            "do",
            "domain",
            "double",
            "drop",
            "dual",
            "dump",
            "dynamic",
            "each",
            "else",
            "elseif",
            "enabled",
            "end",
            "eq",
            "error",
            "errorfiles",
            "errortables",
            "escape",
            "et",
            "except",
            "exception",
            "exclusive",
            "exec",
            "execute",
            "exists",
            "exit",
            "exp",
            "explain",
            "external",
            "extract",
            "fallback",
            "false",
            "fastexport",
            "fetch",
            "first",
            "float",
            "for",
            "force",
            "foreign",
            "format",
            "found",
            "freespace",
            "from",
            "full",
            "function",
            "ge",
            "get",
            "give",
            "global",
            "go",
            "goto",
            "grant",
            "graphic",
            "group",
            "gt",
            "handler",
            "hash",
            "hashamp",
            "hashbakamp",
            "hashbucket",
            "hashrow",
            "having",
            "help",
            "hour",
            "identity",
            "if",
            "immediate",
            "in",
            "index",
            "indicator",
            "initiate",
            "inner",
            "inout",
            "input",
            "ins",
            "insert",
            "instead",
            "int",
            "integer",
            "integerdate",
            "intersect",
            "interval",
            "into",
            "is",
            "iterate",
            "join",
            "journal",
            "key",
            "kurtosis",
            "language",
            "large",
            "le",
            "leading",
            "leave",
            "left",
            "level",
            "like",
            "limit",
            "ln",
            "loading",
            "local",
            "locator",
            "lock",
            "locking",
            "log",
            "logging",
            "logon",
            "long",
            "loop",
            "lower",
            "lt",
            "macro",
            "map",
            "match",
            "mavg",
            "max",
            "maximum",
            "mcharacters",
            "mdiff",
            "merge",
            "method",
            "min",
            "minimum",
            "minus",
            "minute",
            "mlinreg",
            "mload",
            "mod",
            "mode",
            "modifies",
            "modify",
            "monitor",
            "monresource",
            "monsession",
            "month",
            "msubstr",
            "msum",
            "multiset",
            "named",
            "names",
            "national",
            "natural",
            "nchar",
            "nclob",
            "ne",
            "new",
            "next",
            "no",
            "none",
            "not",
            "nowait",
            "null",
            "nullif",
            "nullifzero",
            "number",
            "numeric",
            "object",
            "objects",
            "octet_length",
            "of",
            "off",
            "old",
            "on",
            "only",
            "open",
            "option",
            "or",
            "order",
            "ordinality",
            "out",
            "outer",
            "output",
            "over",
            "overlaps",
            "override",
            "parameter",
            "password",
            "percent",
            "perm",
            "permanent",
            "position",
            "precision",
            "prepare",
            "preserve",
            "primary",
            "privileges",
            "procedure",
            "profile",
            "proportional",
            "protection",
            "public",
            "qualified",
            "qualify",
            "quantile",
            "queue",
            "radians",
            "random",
            "range_n",
            "rank",
            "reads",
            "real",
            "recursive",
            "references",
            "referencing",
            "regr_avgx",
            "regr_avgy",
            "regr_count",
            "regr_intercept",
            "regr_r2",
            "regr_slope",
            "regr_sxx",
            "regr_sxy",
            "regr_syy",
            "relative",
            "release",
            "rename",
            "repeat",
            "replace",
            "replication",
            "request",
            "resignal",
            "restart",
            "restore",
            "restrict",
            "result",
            "resume",
            "retrieve",
            "return",
            "returns",
            "revert",
            "revoke",
            "right",
            "rights",
            "role",
            "rollback",
            "rollforward",
            "rollup",
            "row",
            "row_number",
            "rowid",
            "rows",
            "sample",
            "sampleid",
            "scroll",
            "second",
            "seconds",
            "sel",
            "select",
            "session",
            "set",
            "setresrate",
            "sets",
            "setsessrate",
            "share",
            "show",
            "signal",
            "sin",
            "skew",
            "smallint",
            "some",
            "source",
            "specific",
            "spool",
            "sql",
            "sqlexception",
            "sqlstate",
            "sqltext",
            "sqlwarning",
            "sqrt",
            "ss",
            "start",
            "startup",
            "statement",
            "statistics",
            "stddev_pop",
            "stddev_samp",
            "string",
            "subscriber",
            "subset",
            "substr",
            "substring",
            "sum",
            "summary",
            "suspend",
            "system_user",
            "table",
            "tan",
            "then",
            "threshold",
            "time",
            "timezone_hour",
            "timezone_minute",
            "title",
            "to",
            "top",
            "trace",
            "trailing",
            "transaction",
            "translate",
            "translate_chk",
            "trigger",
            "trim",
            "true",
            "type",
            "uc",
            "ud",
            "uescape",
            "undefined",
            "under",
            "undo",
            "union",
            "unique",
            "until",
            "upd",
            "update",
            "upper",
            "upsert",
            "usage",
            "user",
            "using",
            "value",
            "values",
            "var_pop",
            "var_samp",
            "varbyte",
            "varchar",
            "vargraphic",
            "varying",
            "view",
            "volatile",
            "when",
            "where",
            "while",
            "width_bucket",
            "with",
            "without",
            "work",
            "write",
            "year",
            "zeroifnull",
            "zone",
        ]);
        set
    });

    /// Presto/Trino/Athena-specific reserved keywords
    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = SQL_RESERVED.clone();
        set.extend([
            "alter",
            "and",
            "as",
            "between",
            "by",
            "case",
            "cast",
            "constraint",
            "create",
            "cross",
            "cube",
            "current_catalog",
            "current_date",
            "current_path",
            "current_role",
            "current_schema",
            "current_time",
            "current_timestamp",
            "current_user",
            "deallocate",
            "delete",
            "describe",
            "distinct",
            "drop",
            "else",
            "end",
            "escape",
            "except",
            "execute",
            "exists",
            "extract",
            "false",
            "for",
            "from",
            "full",
            "group",
            "grouping",
            "having",
            "in",
            "inner",
            "insert",
            "intersect",
            "into",
            "is",
            "join",
            "json_array",
            "json_exists",
            "json_object",
            "json_query",
            "json_table",
            "json_value",
            "left",
            "like",
            "listagg",
            "localtime",
            "localtimestamp",
            "natural",
            "normalize",
            "not",
            "null",
            "on",
            "or",
            "order",
            "outer",
            "prepare",
            "recursive",
            "right",
            "rollup",
            "select",
            "skip",
            "table",
            "then",
            "trim",
            "true",
            "uescape",
            "union",
            "unnest",
            "using",
            "values",
            "when",
            "where",
            "with",
        ]);
        set
    });

    /// StarRocks-specific reserved keywords
    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        [
            "add",
            "all",
            "alter",
            "analyze",
            "and",
            "array",
            "as",
            "asc",
            "between",
            "bigint",
            "bitmap",
            "both",
            "by",
            "case",
            "char",
            "character",
            "check",
            "collate",
            "column",
            "compaction",
            "convert",
            "create",
            "cross",
            "cube",
            "current_date",
            "current_role",
            "current_time",
            "current_timestamp",
            "current_user",
            "database",
            "databases",
            "decimal",
            "decimalv2",
            "decimal32",
            "decimal64",
            "decimal128",
            "default",
            "deferred",
            "delete",
            "dense_rank",
            "desc",
            "describe",
            "distinct",
            "double",
            "drop",
            "dual",
            "else",
            "except",
            "exists",
            "explain",
            "false",
            "first_value",
            "float",
            "for",
            "force",
            "from",
            "full",
            "function",
            "grant",
            "group",
            "grouping",
            "grouping_id",
            "groups",
            "having",
            "hll",
            "host",
            "if",
            "ignore",
            "immediate",
            "in",
            "index",
            "infile",
            "inner",
            "insert",
            "int",
            "integer",
            "intersect",
            "into",
            "is",
            "join",
            "json",
            "key",
            "keys",
            "kill",
            "lag",
            "largeint",
            "last_value",
            "lateral",
            "lead",
            "left",
            "like",
            "limit",
            "load",
            "localtime",
            "localtimestamp",
            "maxvalue",
            "minus",
            "mod",
            "not",
            "ntile",
            "null",
            "on",
            "or",
            "order",
            "outer",
            "outfile",
            "over",
            "partition",
            "percentile",
            "primary",
            "procedure",
            "qualify",
            "range",
            "rank",
            "read",
            "regexp",
            "release",
            "rename",
            "replace",
            "revoke",
            "right",
            "rlike",
            "row",
            "row_number",
            "rows",
            "schema",
            "schemas",
            "select",
            "set",
            "set_var",
            "show",
            "smallint",
            "system",
            "table",
            "terminated",
            "text",
            "then",
            "tinyint",
            "to",
            "true",
            "union",
            "unique",
            "unsigned",
            "update",
            "use",
            "using",
            "values",
            "varchar",
            "when",
            "where",
            "with",
        ]
        .into_iter()
        .collect()
    });

    /// SingleStore-specific reserved keywords
    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        let mut set = MYSQL_RESERVED.clone();
        set.extend([
            // Additional SingleStore reserved keywords from Python sqlglot
            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
            "abs",
            "account",
            "acos",
            "adddate",
            "addtime",
            "admin",
            "aes_decrypt",
            "aes_encrypt",
            "aggregate",
            "aggregates",
            "aggregator",
            "anti_join",
            "any_value",
            "approx_count_distinct",
            "approx_percentile",
            "arrange",
            "arrangement",
            "asin",
            "atan",
            "atan2",
            "attach",
            "autostats",
            "avro",
            "background",
            "backup",
            "batch",
            "batches",
            "boot_strapping",
            "ceil",
            "ceiling",
            "coercibility",
            "columnar",
            "columnstore",
            "compile",
            "concurrent",
            "connection_id",
            "cos",
            "cot",
            "current_security_groups",
            "current_security_roles",
            "dayname",
            "dayofmonth",
            "dayofweek",
            "dayofyear",
            "degrees",
            "dot_product",
            "dump",
            "durability",
            "earliest",
            "echo",
            "election",
            "euclidean_distance",
            "exp",
            "extractor",
            "extractors",
            "floor",
            "foreground",
            "found_rows",
            "from_base64",
            "from_days",
            "from_unixtime",
            "fs",
            "fulltext",
            "gc",
            "gcs",
            "geography",
            "geography_area",
            "geography_contains",
            "geography_distance",
            "geography_intersects",
            "geography_latitude",
            "geography_length",
            "geography_longitude",
            "geographypoint",
            "geography_point",
            "geography_within_distance",
            "geometry",
            "geometry_area",
            "geometry_contains",
            "geometry_distance",
            "geometry_filter",
            "geometry_intersects",
            "geometry_length",
            "geometrypoint",
            "geometry_point",
            "geometry_within_distance",
            "geometry_x",
            "geometry_y",
            "greatest",
            "groups",
            "group_concat",
            "gzip",
            "hdfs",
            "hex",
            "highlight",
            "ifnull",
            "ilike",
            "inet_aton",
            "inet_ntoa",
            "inet6_aton",
            "inet6_ntoa",
            "initcap",
            "instr",
            "interpreter_mode",
            "isnull",
            "json",
            "json_agg",
            "json_array_contains_double",
            "json_array_contains_json",
            "json_array_contains_string",
            "json_delete_key",
            "json_extract_double",
            "json_extract_json",
            "json_extract_string",
            "json_extract_bigint",
            "json_get_type",
            "json_length",
            "json_set_double",
            "json_set_json",
            "json_set_string",
            "kafka",
            "lag",
            "last_day",
            "last_insert_id",
            "latest",
            "lcase",
            "lead",
            "leaf",
            "least",
            "leaves",
            "length",
            "license",
            "links",
            "llvm",
            "ln",
            "load",
            "locate",
            "log",
            "log10",
            "log2",
            "lpad",
            "lz4",
            "management",
            "match",
            "mbc",
            "md5",
            "median",
            "memsql",
            "memsql_deserialize",
            "memsql_serialize",
            "metadata",
            "microsecond",
            "minute",
            "model",
            "monthname",
            "months_between",
            "mpl",
            "namespace",
            "node",
            "noparam",
            "now",
            "nth_value",
            "ntile",
            "nullcols",
            "nullif",
            "object",
            "octet_length",
            "offsets",
            "online",
            "optimizer",
            "orphan",
            "parquet",
            "partitions",
            "pause",
            "percentile_cont",
            "percentile_disc",
            "periodic",
            "persisted",
            "pi",
            "pipeline",
            "pipelines",
            "plancache",
            "plugins",
            "pool",
            "pools",
            "pow",
            "power",
            "process",
            "processlist",
            "profile",
            "profiles",
            "quarter",
            "queries",
            "query",
            "radians",
            "rand",
            "record",
            "reduce",
            "redundancy",
            "regexp_match",
            "regexp_substr",
            "remote",
            "replication",
            "resource",
            "resource_pool",
            "restore",
            "retry",
            "role",
            "roles",
            "round",
            "rpad",
            "rtrim",
            "running",
            "s3",
            "scalar",
            "sec_to_time",
            "second",
            "security_lists_intersect",
            "semi_join",
            "sha",
            "sha1",
            "sha2",
            "shard",
            "sharded",
            "sharded_id",
            "sigmoid",
            "sign",
            "sin",
            "skip",
            "sleep",
            "snapshot",
            "soname",
            "sparse",
            "spatial_check_index",
            "split",
            "sqrt",
            "standalone",
            "std",
            "stddev",
            "stddev_pop",
            "stddev_samp",
            "stop",
            "str_to_date",
            "subdate",
            "substr",
            "substring_index",
            "success",
            "synchronize",
            "table_checksum",
            "tan",
            "task",
            "timediff",
            "time_bucket",
            "time_format",
            "time_to_sec",
            "timestampadd",
            "timestampdiff",
            "to_base64",
            "to_char",
            "to_date",
            "to_days",
            "to_json",
            "to_number",
            "to_seconds",
            "to_timestamp",
            "tracelogs",
            "transform",
            "trim",
            "trunc",
            "truncate",
            "ucase",
            "unhex",
            "unix_timestamp",
            "utc_date",
            "utc_time",
            "utc_timestamp",
            "vacuum",
            "variance",
            "var_pop",
            "var_samp",
            "vector_sub",
            "voting",
            "week",
            "weekday",
            "weekofyear",
            "workload",
            "year",
        ]);
        // Remove "all" because ORDER BY ALL needs ALL unquoted
        set.remove("all");
        set
    });

    /// SQLite-specific reserved keywords
    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
    /// Reference: https://www.sqlite.org/lang_keywords.html
    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
        // SQLite only truly reserves these - everything else can be used as identifier unquoted
        [
            "abort",
            "action",
            "add",
            "after",
            "all",
            "alter",
            "always",
            "analyze",
            "and",
            "as",
            "asc",
            "attach",
            "autoincrement",
            "before",
            "begin",
            "between",
            "by",
            "cascade",
            "case",
            "cast",
            "check",
            "collate",
            "column",
            "commit",
            "conflict",
            "constraint",
            "create",
            "cross",
            "current",
            "current_date",
            "current_time",
            "current_timestamp",
            "database",
            "default",
            "deferrable",
            "deferred",
            "delete",
            "desc",
            "detach",
            "distinct",
            "do",
            "drop",
            "each",
            "else",
            "end",
            "escape",
            "except",
            "exclude",
            "exclusive",
            "exists",
            "explain",
            "fail",
            "filter",
            "first",
            "following",
            "for",
            "foreign",
            "from",
            "full",
            "generated",
            "glob",
            "group",
            "groups",
            "having",
            "if",
            "ignore",
            "immediate",
            "in",
            "index",
            "indexed",
            "initially",
            "inner",
            "insert",
            "instead",
            "intersect",
            "into",
            "is",
            "isnull",
            "join",
            "key",
            "last",
            "left",
            "like",
            "limit",
            "natural",
            "no",
            "not",
            "nothing",
            "notnull",
            "null",
            "nulls",
            "of",
            "offset",
            "on",
            "or",
            "order",
            "others",
            "outer",
            "partition",
            "plan",
            "pragma",
            "preceding",
            "primary",
            "query",
            "raise",
            "range",
            "recursive",
            "references",
            "regexp",
            "reindex",
            "release",
            "rename",
            "replace",
            "restrict",
            "returning",
            "right",
            "rollback",
            "row",
            "rows",
            "savepoint",
            "select",
            "set",
            "table",
            "temp",
            "temporary",
            "then",
            "ties",
            "to",
            "transaction",
            "trigger",
            "unbounded",
            "union",
            "unique",
            "update",
            "using",
            "vacuum",
            "values",
            "view",
            "virtual",
            "when",
            "where",
            "window",
            "with",
            "without",
        ]
        .into_iter()
        .collect()
    });
}

impl Generator {
    /// Create a new generator with the default configuration.
    ///
    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
    /// and no dialect-specific transformations.
    pub fn new() -> Self {
        Self::with_config(GeneratorConfig::default())
    }

    /// Create a generator with a custom [`GeneratorConfig`].
    ///
    /// Use this when you need dialect-specific output, pretty-printing, or other
    /// non-default settings.
    pub fn with_config(config: GeneratorConfig) -> Self {
        Self {
            config,
            output: String::new(),
            indent_level: 0,
            athena_hive_context: false,
            sqlite_inline_pk_columns: std::collections::HashSet::new(),
            merge_strip_qualifiers: Vec::new(),
        }
    }

    /// Add column aliases to a query expression for TSQL SELECT INTO.
    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
    /// Recursively processes all SELECT expressions in the query tree.
    fn add_column_aliases_to_query(expr: Expression) -> Expression {
        match expr {
            Expression::Select(mut select) => {
                // Add aliases to all select expressions that don't already have them
                select.expressions = select
                    .expressions
                    .into_iter()
                    .map(Self::add_alias_to_expression)
                    .collect();

                // Recursively process subqueries in FROM clause
                if let Some(ref mut from) = select.from {
                    from.expressions = from
                        .expressions
                        .iter()
                        .cloned()
                        .map(Self::add_column_aliases_to_query)
                        .collect();
                }

                Expression::Select(select)
            }
            Expression::Subquery(mut sq) => {
                sq.this = Self::add_column_aliases_to_query(sq.this);
                Expression::Subquery(sq)
            }
            Expression::Paren(mut p) => {
                p.this = Self::add_column_aliases_to_query(p.this);
                Expression::Paren(p)
            }
            // For other expressions (Union, Intersect, etc.), pass through
            other => other,
        }
    }

    /// Add an alias to a single select expression if it doesn't already have one.
    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
    fn add_alias_to_expression(expr: Expression) -> Expression {
        use crate::expressions::Alias;

        match &expr {
            // Already aliased - just return it
            Expression::Alias(_) => expr,

            // Column reference: add alias from column name
            Expression::Column(col) => Expression::Alias(Box::new(Alias {
                this: expr.clone(),
                alias: col.name.clone(),
                column_aliases: Vec::new(),
                pre_alias_comments: Vec::new(),
                trailing_comments: Vec::new(),
            })),

            // Identifier: add alias from identifier name
            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
                this: expr.clone(),
                alias: ident.clone(),
                column_aliases: Vec::new(),
                pre_alias_comments: Vec::new(),
                trailing_comments: Vec::new(),
            })),

            // Subquery: recursively process and add alias if inner returns a named column
            Expression::Subquery(sq) => {
                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
                // Subqueries that are already aliased keep their alias
                if sq.alias.is_some() {
                    processed
                } else {
                    // If there's no alias, keep it as-is (let TSQL handle it)
                    processed
                }
            }

            // Star expressions (*) - don't alias
            Expression::Star(_) => expr,

            // For other expressions, don't add an alias
            // (function calls, literals, etc. would need explicit aliases anyway)
            _ => expr,
        }
    }

    /// Try to evaluate a constant arithmetic expression to a number literal.
    /// Returns the evaluated result if the expression is a constant arithmetic expression,
    /// otherwise returns the original expression.
    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
        match expr {
            Expression::Literal(Literal::Number(n)) => n.parse::<i64>().ok(),
            Expression::Add(op) => {
                let left = Self::try_evaluate_constant(&op.left)?;
                let right = Self::try_evaluate_constant(&op.right)?;
                Some(left + right)
            }
            Expression::Sub(op) => {
                let left = Self::try_evaluate_constant(&op.left)?;
                let right = Self::try_evaluate_constant(&op.right)?;
                Some(left - right)
            }
            Expression::Mul(op) => {
                let left = Self::try_evaluate_constant(&op.left)?;
                let right = Self::try_evaluate_constant(&op.right)?;
                Some(left * right)
            }
            Expression::Div(op) => {
                let left = Self::try_evaluate_constant(&op.left)?;
                let right = Self::try_evaluate_constant(&op.right)?;
                if right != 0 {
                    Some(left / right)
                } else {
                    None
                }
            }
            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
            _ => None,
        }
    }

    /// Check if an identifier is a reserved keyword for the current dialect
    fn is_reserved_keyword(&self, name: &str) -> bool {
        use crate::dialects::DialectType;
        let lower = name.to_lowercase();
        let lower_ref = lower.as_str();

        match self.config.dialect {
            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
            }
            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
            Some(DialectType::SingleStore) => {
                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
            }
            Some(DialectType::StarRocks) => {
                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
            }
            Some(DialectType::PostgreSQL)
            | Some(DialectType::CockroachDB)
            | Some(DialectType::Materialize)
            | Some(DialectType::RisingWave) => {
                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
            }
            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
            // meaning it never quotes identifiers based on reserved word status.
            Some(DialectType::Snowflake) => false,
            // ClickHouse: don't quote reserved keywords to preserve identity output
            Some(DialectType::ClickHouse) => false,
            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
            Some(DialectType::Teradata) => reserved_keywords::TERADATA_RESERVED.contains(lower_ref),
            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
            Some(DialectType::TSQL)
            | Some(DialectType::Fabric)
            | Some(DialectType::Oracle)
            | Some(DialectType::Spark)
            | Some(DialectType::Databricks)
            | Some(DialectType::Hive)
            | Some(DialectType::Solr) => false,
            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
            }
            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
            // For Generic dialect or None, don't add extra quoting to preserve identity
            Some(DialectType::Generic) | None => false,
            // For other dialects, use standard SQL reserved keywords
            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
        }
    }

    /// Normalize function name based on dialect settings
    fn normalize_func_name(&self, name: &str) -> String {
        match self.config.normalize_functions {
            NormalizeFunctions::Upper => name.to_uppercase(),
            NormalizeFunctions::Lower => name.to_lowercase(),
            NormalizeFunctions::None => name.to_string(),
        }
    }

    /// Generate a SQL string from an AST expression.
    ///
    /// This is the primary generation method. It clears any previous internal state,
    /// walks the expression tree, and returns the resulting SQL text. The output
    /// respects the [`GeneratorConfig`] that was supplied at construction time.
    ///
    /// The generator can be reused across multiple calls; each call to `generate`
    /// resets the internal buffer.
    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
        self.output.clear();
        self.generate_expression(expr)?;
        Ok(std::mem::take(&mut self.output))
    }

    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
    ///
    /// This is a static helper that creates a throwaway `Generator` internally.
    /// For repeated generation, prefer constructing a `Generator` once and calling
    /// [`generate`](Self::generate) on it.
    pub fn sql(expr: &Expression) -> Result<String> {
        let mut gen = Generator::new();
        gen.generate(expr)
    }

    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
    ///
    /// Produces human-readable output with newlines and indentation. A trailing
    /// semicolon is appended automatically if not already present.
    pub fn pretty_sql(expr: &Expression) -> Result<String> {
        let config = GeneratorConfig {
            pretty: true,
            ..Default::default()
        };
        let mut gen = Generator::with_config(config);
        let mut sql = gen.generate(expr)?;
        // Add semicolon for pretty output
        if !sql.ends_with(';') {
            sql.push(';');
        }
        Ok(sql)
    }

    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Select(select) => self.generate_select(select),
            Expression::Union(union) => self.generate_union(union),
            Expression::Intersect(intersect) => self.generate_intersect(intersect),
            Expression::Except(except) => self.generate_except(except),
            Expression::Insert(insert) => self.generate_insert(insert),
            Expression::Update(update) => self.generate_update(update),
            Expression::Delete(delete) => self.generate_delete(delete),
            Expression::Literal(lit) => self.generate_literal(lit),
            Expression::Boolean(b) => self.generate_boolean(b),
            Expression::Null(_) => {
                self.write_keyword("NULL");
                Ok(())
            }
            Expression::Identifier(id) => self.generate_identifier(id),
            Expression::Column(col) => self.generate_column(col),
            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
            Expression::Connect(c) => self.generate_connect_expr(c),
            Expression::Prior(p) => self.generate_prior(p),
            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
            Expression::Table(table) => self.generate_table(table),
            Expression::StageReference(sr) => self.generate_stage_reference(sr),
            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
            Expression::Star(star) => self.generate_star(star),
            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
            Expression::Alias(alias) => self.generate_alias(alias),
            Expression::Cast(cast) => self.generate_cast(cast),
            Expression::Collation(coll) => self.generate_collation(coll),
            Expression::Case(case) => self.generate_case(case),
            Expression::Function(func) => self.generate_function(func),
            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
            Expression::WindowFunction(wf) => self.generate_window_function(wf),
            Expression::WithinGroup(wg) => self.generate_within_group(wg),
            Expression::Interval(interval) => self.generate_interval(interval),

            // String functions
            Expression::ConcatWs(f) => self.generate_concat_ws(f),
            Expression::Substring(f) => self.generate_substring(f),
            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
            Expression::Trim(f) => self.generate_trim(f),
            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
            Expression::Replace(f) => self.generate_replace(f),
            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
            Expression::Left(f) => self.generate_left_right("LEFT", f),
            Expression::Right(f) => self.generate_left_right("RIGHT", f),
            Expression::Repeat(f) => self.generate_repeat(f),
            Expression::Lpad(f) => self.generate_pad("LPAD", f),
            Expression::Rpad(f) => self.generate_pad("RPAD", f),
            Expression::Split(f) => self.generate_split(f),
            Expression::RegexpLike(f) => self.generate_regexp_like(f),
            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
            Expression::Overlay(f) => self.generate_overlay(f),

            // Math functions
            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
            Expression::Round(f) => self.generate_round(f),
            Expression::Floor(f) => self.generate_floor(f),
            Expression::Ceil(f) => self.generate_ceil(f),
            Expression::Power(f) => self.generate_power(f),
            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
            Expression::Log(f) => self.generate_log(f),
            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),

            // Date/time functions
            Expression::CurrentDate(_) => {
                self.write_keyword("CURRENT_DATE");
                Ok(())
            }
            Expression::CurrentTime(f) => self.generate_current_time(f),
            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
            Expression::DateDiff(f) => self.generate_datediff(f),
            Expression::DateTrunc(f) => self.generate_date_trunc(f),
            Expression::Extract(f) => self.generate_extract(f),
            Expression::ToDate(f) => self.generate_to_date(f),
            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),

            // Control flow functions
            Expression::Coalesce(f) => {
                // Use original function name if preserved (COALESCE, IFNULL)
                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
                self.generate_vararg_func(func_name, &f.expressions)
            }
            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
            Expression::IfFunc(f) => self.generate_if_func(f),
            Expression::IfNull(f) => self.generate_ifnull(f),
            Expression::Nvl(f) => self.generate_nvl(f),
            Expression::Nvl2(f) => self.generate_nvl2(f),

            // Type conversion
            Expression::TryCast(cast) => self.generate_try_cast(cast),
            Expression::SafeCast(cast) => self.generate_safe_cast(cast),

            // Typed aggregate functions
            Expression::Count(f) => self.generate_count(f),
            Expression::Sum(f) => self.generate_agg_func("SUM", f),
            Expression::Avg(f) => self.generate_agg_func("AVG", f),
            Expression::Min(f) => self.generate_agg_func("MIN", f),
            Expression::Max(f) => self.generate_agg_func("MAX", f),
            Expression::GroupConcat(f) => self.generate_group_concat(f),
            Expression::StringAgg(f) => self.generate_string_agg(f),
            Expression::ListAgg(f) => self.generate_listagg(f),
            Expression::ArrayAgg(f) => {
                // Allow cross-dialect transforms to override the function name
                // (e.g., COLLECT_LIST for Spark)
                let override_name = f
                    .name
                    .as_ref()
                    .filter(|n| n.to_uppercase() != "ARRAY_AGG")
                    .map(|n| n.to_uppercase());
                match override_name {
                    Some(name) => self.generate_agg_func(&name, f),
                    None => self.generate_agg_func("ARRAY_AGG", f),
                }
            }
            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
            Expression::SumIf(f) => self.generate_sum_if(f),
            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
            Expression::VarPop(f) => {
                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
                    "VARIANCE_POP"
                } else {
                    "VAR_POP"
                };
                self.generate_agg_func(name, f)
            }
            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
            Expression::Skewness(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Snowflake) => "SKEW",
                    _ => "SKEWNESS",
                };
                self.generate_agg_func(name, f)
            }
            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
            Expression::Mode(f) => self.generate_agg_func("MODE", f),
            Expression::First(f) => self.generate_agg_func("FIRST", f),
            Expression::Last(f) => self.generate_agg_func("LAST", f),
            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
            Expression::ApproxDistinct(f) => {
                match self.config.dialect {
                    Some(DialectType::Hive)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::BigQuery) => {
                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
                    }
                    Some(DialectType::Redshift) => {
                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
                        self.write_keyword("APPROXIMATE COUNT");
                        self.write("(");
                        self.write_keyword("DISTINCT");
                        self.write(" ");
                        self.generate_expression(&f.this)?;
                        self.write(")");
                        Ok(())
                    }
                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
                }
            }
            Expression::ApproxCountDistinct(f) => {
                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
            }
            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
            Expression::LogicalAnd(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
                    Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::PostgreSQL)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::Redshift) => "BOOL_AND",
                    Some(DialectType::Oracle)
                    | Some(DialectType::SQLite)
                    | Some(DialectType::MySQL) => "MIN",
                    _ => "BOOL_AND",
                };
                self.generate_agg_func(name, f)
            }
            Expression::LogicalOr(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
                    Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::PostgreSQL)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::Redshift) => "BOOL_OR",
                    Some(DialectType::Oracle)
                    | Some(DialectType::SQLite)
                    | Some(DialectType::MySQL) => "MAX",
                    _ => "BOOL_OR",
                };
                self.generate_agg_func(name, f)
            }

            // Typed window functions
            Expression::RowNumber(_) => {
                if self.config.dialect == Some(DialectType::ClickHouse) {
                    self.write("row_number");
                } else {
                    self.write_keyword("ROW_NUMBER");
                }
                self.write("()");
                Ok(())
            }
            Expression::Rank(r) => {
                self.write_keyword("RANK");
                self.write("(");
                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
                if !r.args.is_empty() {
                    for (i, arg) in r.args.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(arg)?;
                    }
                } else if let Some(order_by) = &r.order_by {
                    // DuckDB: RANK(ORDER BY col)
                    self.write_keyword(" ORDER BY ");
                    for (i, ob) in order_by.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_ordered(ob)?;
                    }
                }
                self.write(")");
                Ok(())
            }
            Expression::DenseRank(dr) => {
                self.write_keyword("DENSE_RANK");
                self.write("(");
                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
                for (i, arg) in dr.args.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(arg)?;
                }
                self.write(")");
                Ok(())
            }
            Expression::NTile(f) => self.generate_ntile(f),
            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
            Expression::FirstValue(f) => self.generate_value_func("FIRST_VALUE", f),
            Expression::LastValue(f) => self.generate_value_func("LAST_VALUE", f),
            Expression::NthValue(f) => self.generate_nth_value(f),
            Expression::PercentRank(pr) => {
                self.write_keyword("PERCENT_RANK");
                self.write("(");
                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
                if !pr.args.is_empty() {
                    for (i, arg) in pr.args.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(arg)?;
                    }
                } else if let Some(order_by) = &pr.order_by {
                    // DuckDB: PERCENT_RANK(ORDER BY col)
                    self.write_keyword(" ORDER BY ");
                    for (i, ob) in order_by.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_ordered(ob)?;
                    }
                }
                self.write(")");
                Ok(())
            }
            Expression::CumeDist(cd) => {
                self.write_keyword("CUME_DIST");
                self.write("(");
                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
                if !cd.args.is_empty() {
                    for (i, arg) in cd.args.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(arg)?;
                    }
                } else if let Some(order_by) = &cd.order_by {
                    // DuckDB: CUME_DIST(ORDER BY col)
                    self.write_keyword(" ORDER BY ");
                    for (i, ob) in order_by.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_ordered(ob)?;
                    }
                }
                self.write(")");
                Ok(())
            }
            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),

            // Additional string functions
            Expression::Contains(f) => {
                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
            }
            Expression::StartsWith(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
                    _ => "STARTS_WITH",
                };
                self.generate_binary_func(name, &f.this, &f.expression)
            }
            Expression::EndsWith(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Snowflake) => "ENDSWITH",
                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
                    Some(DialectType::ClickHouse) => "endsWith",
                    _ => "ENDS_WITH",
                };
                self.generate_binary_func(name, &f.this, &f.expression)
            }
            Expression::Position(f) => self.generate_position(f),
            Expression::Initcap(f) => match self.config.dialect {
                Some(DialectType::Presto)
                | Some(DialectType::Trino)
                | Some(DialectType::Athena) => {
                    self.write_keyword("REGEXP_REPLACE");
                    self.write("(");
                    self.generate_expression(&f.this)?;
                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
                    Ok(())
                }
                _ => self.generate_simple_func("INITCAP", &f.this),
            },
            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
            Expression::CharFunc(f) => self.generate_char_func(f),
            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
            Expression::Levenshtein(f) => {
                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
            }

            // Additional math functions
            Expression::ModFunc(f) => self.generate_mod_func(f),
            Expression::Random(_) => {
                self.write_keyword("RANDOM");
                self.write("()");
                Ok(())
            }
            Expression::Rand(f) => self.generate_rand(f),
            Expression::TruncFunc(f) => self.generate_truncate_func(f),
            Expression::Pi(_) => {
                self.write_keyword("PI");
                self.write("()");
                Ok(())
            }
            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
            Expression::Atan2(f) => {
                let name = f.original_name.as_deref().unwrap_or("ATAN2");
                self.generate_binary_func(name, &f.this, &f.expression)
            }

            // Control flow
            Expression::Decode(f) => self.generate_decode(f),

            // Additional date/time functions
            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
            Expression::DayOfWeek(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
                    Some(DialectType::DuckDB) => "ISODOW",
                    _ => "DAYOFWEEK",
                };
                self.generate_simple_func(name, &f.this)
            }
            Expression::DayOfMonth(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
                    _ => "DAYOFMONTH",
                };
                self.generate_simple_func(name, &f.this)
            }
            Expression::DayOfYear(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
                    _ => "DAYOFYEAR",
                };
                self.generate_simple_func(name, &f.this)
            }
            Expression::WeekOfYear(f) => {
                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
                let name = match self.config.dialect {
                    Some(DialectType::Hive)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
                    _ => "WEEK_OF_YEAR",
                };
                self.generate_simple_func(name, &f.this)
            }
            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
            Expression::AddMonths(f) => {
                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
            }
            Expression::MonthsBetween(f) => {
                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
            }
            Expression::LastDay(f) => self.generate_last_day(f),
            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
            Expression::MakeDate(f) => self.generate_make_date(f),
            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),

            // Array functions
            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
            Expression::ArrayContains(f) => {
                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
            }
            Expression::ArrayPosition(f) => {
                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
            }
            Expression::ArrayAppend(f) => {
                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
            }
            Expression::ArrayPrepend(f) => {
                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
            }
            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
            Expression::ArraySort(f) => self.generate_array_sort(f),
            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
            Expression::Unnest(f) => self.generate_unnest(f),
            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
            Expression::ArrayFilter(f) => self.generate_array_filter(f),
            Expression::ArrayTransform(f) => self.generate_array_transform(f),
            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
            Expression::ArrayCompact(f) => self.generate_simple_func("ARRAY_COMPACT", &f.this),
            Expression::ArrayIntersect(f) => {
                self.generate_binary_func("ARRAY_INTERSECT", &f.this, &f.expression)
            }
            Expression::ArrayUnion(f) => {
                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
            }
            Expression::ArrayExcept(f) => {
                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
            }
            Expression::ArrayRemove(f) => {
                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
            }
            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),

            // Struct functions
            Expression::StructFunc(f) => self.generate_struct_constructor(f),
            Expression::StructExtract(f) => self.generate_struct_extract(f),
            Expression::NamedStruct(f) => self.generate_named_struct(f),

            // Map functions
            Expression::MapFunc(f) => self.generate_map_constructor(f),
            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
            Expression::MapFromArrays(f) => {
                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
            }
            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
            Expression::MapContainsKey(f) => {
                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
            }
            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
            Expression::ElementAt(f) => {
                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
            }
            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),

            // JSON functions
            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
            Expression::JsonExtractScalar(f) => {
                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
            }
            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
            Expression::JsonObject(f) => self.generate_json_object(f),
            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
            Expression::JsonArrayLength(f) => {
                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
            }
            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
            Expression::ParseJson(f) => {
                let name = match self.config.dialect {
                    Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => "JSON_PARSE",
                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
                        // PostgreSQL: CAST(x AS JSON)
                        self.write_keyword("CAST");
                        self.write("(");
                        self.generate_expression(&f.this)?;
                        self.write_keyword(" AS ");
                        self.write_keyword("JSON");
                        self.write(")");
                        return Ok(());
                    }
                    Some(DialectType::Hive)
                    | Some(DialectType::Spark)
                    | Some(DialectType::MySQL)
                    | Some(DialectType::SingleStore)
                    | Some(DialectType::TiDB)
                    | Some(DialectType::TSQL) => {
                        // Hive/Spark/MySQL/TSQL: just emit the string literal
                        self.generate_expression(&f.this)?;
                        return Ok(());
                    }
                    Some(DialectType::DuckDB) => "JSON",
                    _ => "PARSE_JSON",
                };
                self.generate_simple_func(name, &f.this)
            }
            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
            Expression::JsonMergePatch(f) => {
                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
            }
            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),

            // Type casting/conversion
            Expression::Convert(f) => self.generate_convert(f),
            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),

            // Additional expressions
            Expression::Lambda(f) => self.generate_lambda(f),
            Expression::Parameter(f) => self.generate_parameter(f),
            Expression::Placeholder(f) => self.generate_placeholder(f),
            Expression::NamedArgument(f) => self.generate_named_argument(f),
            Expression::TableArgument(f) => self.generate_table_argument(f),
            Expression::SqlComment(f) => self.generate_sql_comment(f),

            // Additional predicates
            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
            Expression::SimilarTo(f) => self.generate_similar_to(f),
            Expression::Any(f) => self.generate_quantified("ANY", f),
            Expression::All(f) => self.generate_quantified("ALL", f),
            Expression::Overlaps(f) => self.generate_overlaps(f),

            // Bitwise operations
            Expression::BitwiseLeftShift(op) => {
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto) | Some(DialectType::Trino)
                ) {
                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else if matches!(
                    self.config.dialect,
                    Some(DialectType::Spark) | Some(DialectType::Databricks)
                ) {
                    self.write_keyword("SHIFTLEFT");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else {
                    self.generate_binary_op(op, "<<")
                }
            }
            Expression::BitwiseRightShift(op) => {
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto) | Some(DialectType::Trino)
                ) {
                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else if matches!(
                    self.config.dialect,
                    Some(DialectType::Spark) | Some(DialectType::Databricks)
                ) {
                    self.write_keyword("SHIFTRIGHT");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else {
                    self.generate_binary_op(op, ">>")
                }
            }
            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),

            // Array/struct/map access
            Expression::Subscript(s) => self.generate_subscript(s),
            Expression::Dot(d) => self.generate_dot_access(d),
            Expression::MethodCall(m) => self.generate_method_call(m),
            Expression::ArraySlice(s) => self.generate_array_slice(s),

            Expression::And(op) => self.generate_binary_op(op, "AND"),
            Expression::Or(op) => self.generate_binary_op(op, "OR"),
            Expression::Add(op) => self.generate_binary_op(op, "+"),
            Expression::Sub(op) => self.generate_binary_op(op, "-"),
            Expression::Mul(op) => self.generate_binary_op(op, "*"),
            Expression::Div(op) => self.generate_binary_op(op, "/"),
            Expression::IntDiv(f) => {
                use crate::dialects::DialectType;
                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
                    // DuckDB uses // operator for integer division
                    self.generate_expression(&f.this)?;
                    self.write(" // ");
                    self.generate_expression(&f.expression)?;
                    Ok(())
                } else if matches!(
                    self.config.dialect,
                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
                ) {
                    // Hive/Spark use DIV as an infix operator
                    self.generate_expression(&f.this)?;
                    self.write(" ");
                    self.write_keyword("DIV");
                    self.write(" ");
                    self.generate_expression(&f.expression)?;
                    Ok(())
                } else {
                    // Other dialects use DIV function
                    self.write_keyword("DIV");
                    self.write("(");
                    self.generate_expression(&f.this)?;
                    self.write(", ");
                    self.generate_expression(&f.expression)?;
                    self.write(")");
                    Ok(())
                }
            }
            Expression::Mod(op) => {
                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
                    self.generate_binary_op(op, "MOD")
                } else {
                    self.generate_binary_op(op, "%")
                }
            }
            Expression::Eq(op) => self.generate_binary_op(op, "="),
            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
            Expression::Lt(op) => self.generate_binary_op(op, "<"),
            Expression::Lte(op) => self.generate_binary_op(op, "<="),
            Expression::Gt(op) => self.generate_binary_op(op, ">"),
            Expression::Gte(op) => self.generate_binary_op(op, ">="),
            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
            Expression::Concat(op) => {
                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
                if self.config.dialect == Some(DialectType::Solr) {
                    self.generate_binary_op(op, "OR")
                } else {
                    self.generate_binary_op(op, "||")
                }
            }
            Expression::BitwiseAnd(op) => {
                // Presto/Trino use BITWISE_AND function
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto) | Some(DialectType::Trino)
                ) {
                    self.write_keyword("BITWISE_AND");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else {
                    self.generate_binary_op(op, "&")
                }
            }
            Expression::BitwiseOr(op) => {
                // Presto/Trino use BITWISE_OR function
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto) | Some(DialectType::Trino)
                ) {
                    self.write_keyword("BITWISE_OR");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else {
                    self.generate_binary_op(op, "|")
                }
            }
            Expression::BitwiseXor(op) => {
                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto) | Some(DialectType::Trino)
                ) {
                    self.write_keyword("BITWISE_XOR");
                    self.write("(");
                    self.generate_expression(&op.left)?;
                    self.write(", ");
                    self.generate_expression(&op.right)?;
                    self.write(")");
                    Ok(())
                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
                    self.generate_binary_op(op, "#")
                } else {
                    self.generate_binary_op(op, "^")
                }
            }
            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
            Expression::JSONBContains(f) => {
                // PostgreSQL JSONB contains key operator: a ? b
                self.generate_expression(&f.this)?;
                self.write_space();
                self.write("?");
                self.write_space();
                self.generate_expression(&f.expression)
            }
            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
            Expression::Neg(op) => self.generate_unary_op(op, "-"),
            Expression::BitwiseNot(op) => {
                // Presto/Trino use BITWISE_NOT function
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto) | Some(DialectType::Trino)
                ) {
                    self.write_keyword("BITWISE_NOT");
                    self.write("(");
                    self.generate_expression(&op.this)?;
                    self.write(")");
                    Ok(())
                } else {
                    self.generate_unary_op(op, "~")
                }
            }
            Expression::In(in_expr) => self.generate_in(in_expr),
            Expression::Between(between) => self.generate_between(between),
            Expression::IsNull(is_null) => self.generate_is_null(is_null),
            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
            Expression::IsJson(is_json) => self.generate_is_json(is_json),
            Expression::Is(is_expr) => self.generate_is(is_expr),
            Expression::Exists(exists) => self.generate_exists(exists),
            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
            Expression::Subquery(subquery) => self.generate_subquery(subquery),
            Expression::Paren(paren) => {
                // JoinedTable already outputs its own parentheses, so don't double-wrap
                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));

                // Check if inner expression is a statement (SELECT, UNION, etc.) for pretty formatting
                let is_statement = matches!(
                    &paren.this,
                    Expression::Select(_)
                        | Expression::Union(_)
                        | Expression::Intersect(_)
                        | Expression::Except(_)
                        | Expression::Subquery(_)
                );

                if !skip_parens {
                    self.write("(");
                    if self.config.pretty && is_statement {
                        self.write_newline();
                        self.indent_level += 1;
                        self.write_indent();
                    }
                }
                self.generate_expression(&paren.this)?;
                if !skip_parens {
                    if self.config.pretty && is_statement {
                        self.write_newline();
                        self.indent_level -= 1;
                    }
                    self.write(")");
                }
                // Output trailing comments after closing paren
                for comment in &paren.trailing_comments {
                    self.write(" ");
                    self.write(comment);
                }
                Ok(())
            }
            Expression::Array(arr) => self.generate_array(arr),
            Expression::Tuple(tuple) => self.generate_tuple(tuple),
            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
            Expression::Ordered(ordered) => self.generate_ordered(ordered),
            Expression::DataType(dt) => self.generate_data_type(dt),
            Expression::Raw(raw) => {
                self.write(&raw.sql);
                Ok(())
            }
            Expression::Command(cmd) => {
                self.write(&cmd.this);
                Ok(())
            }
            Expression::Kill(kill) => {
                self.write_keyword("KILL");
                if let Some(kind) = &kill.kind {
                    self.write_space();
                    self.write_keyword(kind);
                }
                self.write_space();
                self.generate_expression(&kill.this)?;
                Ok(())
            }
            Expression::Execute(exec) => {
                self.write_keyword("EXEC");
                self.write_space();
                self.generate_expression(&exec.this)?;
                for (i, param) in exec.parameters.iter().enumerate() {
                    if i == 0 {
                        self.write_space();
                    } else {
                        self.write(", ");
                    }
                    self.write(&param.name);
                    self.write("=");
                    self.generate_expression(&param.value)?;
                }
                Ok(())
            }
            Expression::Annotated(annotated) => {
                self.generate_expression(&annotated.this)?;
                for comment in &annotated.trailing_comments {
                    self.write(" ");
                    self.write_formatted_comment(comment);
                }
                Ok(())
            }

            // DDL statements
            Expression::CreateTable(ct) => self.generate_create_table(ct),
            Expression::DropTable(dt) => self.generate_drop_table(dt),
            Expression::AlterTable(at) => self.generate_alter_table(at),
            Expression::CreateIndex(ci) => self.generate_create_index(ci),
            Expression::DropIndex(di) => self.generate_drop_index(di),
            Expression::CreateView(cv) => self.generate_create_view(cv),
            Expression::DropView(dv) => self.generate_drop_view(dv),
            Expression::AlterView(av) => self.generate_alter_view(av),
            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
            Expression::Truncate(tr) => self.generate_truncate(tr),
            Expression::Use(u) => self.generate_use(u),
            // Phase 4: Additional DDL statements
            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
            Expression::CreateFunction(cf) => self.generate_create_function(cf),
            Expression::DropFunction(df) => self.generate_drop_function(df),
            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
            Expression::CreateType(ct) => self.generate_create_type(ct),
            Expression::DropType(dt) => self.generate_drop_type(dt),
            Expression::Describe(d) => self.generate_describe(d),
            Expression::Show(s) => self.generate_show(s),

            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
            Expression::Cache(c) => self.generate_cache(c),
            Expression::Uncache(u) => self.generate_uncache(u),
            Expression::LoadData(l) => self.generate_load_data(l),
            Expression::Pragma(p) => self.generate_pragma(p),
            Expression::Grant(g) => self.generate_grant(g),
            Expression::Revoke(r) => self.generate_revoke(r),
            Expression::Comment(c) => self.generate_comment(c),
            Expression::SetStatement(s) => self.generate_set_statement(s),

            // PIVOT/UNPIVOT
            Expression::Pivot(pivot) => self.generate_pivot(pivot),
            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),

            // VALUES table constructor
            Expression::Values(values) => self.generate_values(values),

            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
            Expression::AIAgg(e) => self.generate_ai_agg(e),
            Expression::AIClassify(e) => self.generate_ai_classify(e),
            Expression::AddPartition(e) => self.generate_add_partition(e),
            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
            Expression::Aliases(e) => self.generate_aliases(e),
            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
            Expression::AlterColumn(e) => self.generate_alter_column(e),
            Expression::AlterSession(e) => self.generate_alter_session(e),
            Expression::AlterSet(e) => self.generate_alter_set(e),
            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
            Expression::Analyze(e) => self.generate_analyze(e),
            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
            Expression::Anonymous(e) => self.generate_anonymous(e),
            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
            Expression::Apply(e) => self.generate_apply(e),
            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
            Expression::ArgMax(e) => self.generate_arg_max(e),
            Expression::ArgMin(e) => self.generate_arg_min(e),
            Expression::ArrayAll(e) => self.generate_array_all(e),
            Expression::ArrayAny(e) => self.generate_array_any(e),
            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
            Expression::ArraySum(e) => self.generate_array_sum(e),
            Expression::AtIndex(e) => self.generate_at_index(e),
            Expression::Attach(e) => self.generate_attach(e),
            Expression::AttachOption(e) => self.generate_attach_option(e),
            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
            Expression::BackupProperty(e) => self.generate_backup_property(e),
            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
            Expression::Base64Encode(e) => self.generate_base64_encode(e),
            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
            Expression::Booland(e) => self.generate_booland(e),
            Expression::Boolor(e) => self.generate_boolor(e),
            Expression::BuildProperty(e) => self.generate_build_property(e),
            Expression::ByteString(e) => self.generate_byte_string(e),
            Expression::CaseSpecificColumnConstraint(e) => {
                self.generate_case_specific_column_constraint(e)
            }
            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
            Expression::Changes(e) => self.generate_changes(e),
            Expression::CharacterSetColumnConstraint(e) => {
                self.generate_character_set_column_constraint(e)
            }
            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
            Expression::CheckJson(e) => self.generate_check_json(e),
            Expression::CheckXml(e) => self.generate_check_xml(e),
            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
            Expression::Clone(e) => self.generate_clone(e),
            Expression::ClusterBy(e) => self.generate_cluster_by(e),
            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
            Expression::CollateProperty(e) => self.generate_collate_property(e),
            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
            Expression::ColumnPosition(e) => self.generate_column_position(e),
            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
            Expression::Columns(e) => self.generate_columns(e),
            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
            Expression::Commit(e) => self.generate_commit(e),
            Expression::Comprehension(e) => self.generate_comprehension(e),
            Expression::Compress(e) => self.generate_compress(e),
            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
            Expression::Constraint(e) => self.generate_constraint(e),
            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
            Expression::Copy(e) => self.generate_copy(e),
            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
            Expression::Corr(e) => self.generate_corr(e),
            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
            Expression::CovarPop(e) => self.generate_covar_pop(e),
            Expression::CovarSamp(e) => self.generate_covar_samp(e),
            Expression::Credentials(e) => self.generate_credentials(e),
            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
            Expression::Cte(e) => self.generate_cte(e),
            Expression::Cube(e) => self.generate_cube(e),
            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
            Expression::CurrentSchema(e) => self.generate_current_schema(e),
            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
            Expression::CurrentUser(e) => self.generate_current_user(e),
            Expression::DPipe(e) => self.generate_d_pipe(e),
            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
            Expression::Date(e) => self.generate_date_func(e),
            Expression::DateBin(e) => self.generate_date_bin(e),
            Expression::DateFormatColumnConstraint(e) => {
                self.generate_date_format_column_constraint(e)
            }
            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
            Expression::Datetime(e) => self.generate_datetime(e),
            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
            Expression::Dayname(e) => self.generate_dayname(e),
            Expression::Declare(e) => self.generate_declare(e),
            Expression::DeclareItem(e) => self.generate_declare_item(e),
            Expression::DecodeCase(e) => self.generate_decode_case(e),
            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
            Expression::DecompressString(e) => self.generate_decompress_string(e),
            Expression::Decrypt(e) => self.generate_decrypt(e),
            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
            Expression::DefinerProperty(e) => self.generate_definer_property(e),
            Expression::Detach(e) => self.generate_detach(e),
            Expression::DictProperty(e) => self.generate_dict_property(e),
            Expression::DictRange(e) => self.generate_dict_range(e),
            Expression::Directory(e) => self.generate_directory(e),
            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
            Expression::DistributeBy(e) => self.generate_distribute_by(e),
            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
            Expression::DotProduct(e) => self.generate_dot_product(e),
            Expression::DropPartition(e) => self.generate_drop_partition(e),
            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
            Expression::Elt(e) => self.generate_elt(e),
            Expression::Encode(e) => self.generate_encode(e),
            Expression::EncodeProperty(e) => self.generate_encode_property(e),
            Expression::Encrypt(e) => self.generate_encrypt(e),
            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
            Expression::EngineProperty(e) => self.generate_engine_property(e),
            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
            Expression::EphemeralColumnConstraint(e) => {
                self.generate_ephemeral_column_constraint(e)
            }
            Expression::EqualNull(e) => self.generate_equal_null(e),
            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
            Expression::Export(e) => self.generate_export(e),
            Expression::ExternalProperty(e) => self.generate_external_property(e),
            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
            Expression::Fetch(e) => self.generate_fetch(e),
            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
            Expression::Filter(e) => self.generate_filter(e),
            Expression::Float64(e) => self.generate_float64(e),
            Expression::ForIn(e) => self.generate_for_in(e),
            Expression::ForeignKey(e) => self.generate_foreign_key(e),
            Expression::Format(e) => self.generate_format(e),
            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
            Expression::From(e) => self.generate_from(e),
            Expression::FromBase(e) => self.generate_from_base(e),
            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
            Expression::GapFill(e) => self.generate_gap_fill(e),
            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
            Expression::GenerateSeries(e) => self.generate_generate_series(e),
            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
            Expression::GeneratedAsIdentityColumnConstraint(e) => {
                self.generate_generated_as_identity_column_constraint(e)
            }
            Expression::GeneratedAsRowColumnConstraint(e) => {
                self.generate_generated_as_row_column_constraint(e)
            }
            Expression::Get(e) => self.generate_get(e),
            Expression::GetExtract(e) => self.generate_get_extract(e),
            Expression::Getbit(e) => self.generate_getbit(e),
            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
            Expression::Group(e) => self.generate_group(e),
            Expression::GroupBy(e) => self.generate_group_by(e),
            Expression::Grouping(e) => self.generate_grouping(e),
            Expression::GroupingId(e) => self.generate_grouping_id(e),
            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
            Expression::HashAgg(e) => self.generate_hash_agg(e),
            Expression::Having(e) => self.generate_having(e),
            Expression::HavingMax(e) => self.generate_having_max(e),
            Expression::Heredoc(e) => self.generate_heredoc(e),
            Expression::HexEncode(e) => self.generate_hex_encode(e),
            Expression::Hll(e) => self.generate_hll(e),
            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
            Expression::IncludeProperty(e) => self.generate_include_property(e),
            Expression::Index(e) => self.generate_index(e),
            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
            Expression::IndexParameters(e) => self.generate_index_parameters(e),
            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
            Expression::Install(e) => self.generate_install(e),
            Expression::IntervalOp(e) => self.generate_interval_op(e),
            Expression::IntervalSpan(e) => self.generate_interval_span(e),
            Expression::IntoClause(e) => self.generate_into_clause(e),
            Expression::Introducer(e) => self.generate_introducer(e),
            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
            Expression::JSON(e) => self.generate_json(e),
            Expression::JSONArray(e) => self.generate_json_array(e),
            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
            Expression::JSONExists(e) => self.generate_json_exists(e),
            Expression::JSONCast(e) => self.generate_json_cast(e),
            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
            Expression::JSONFormat(e) => self.generate_json_format(e),
            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
            Expression::JSONKeys(e) => self.generate_json_keys(e),
            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
            Expression::JSONPath(e) => self.generate_json_path_expr(e),
            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
            Expression::JSONRemove(e) => self.generate_json_remove(e),
            Expression::JSONSchema(e) => self.generate_json_schema(e),
            Expression::JSONSet(e) => self.generate_json_set(e),
            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
            Expression::JSONTable(e) => self.generate_json_table(e),
            Expression::JSONType(e) => self.generate_json_type(e),
            Expression::JSONValue(e) => self.generate_json_value(e),
            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
            Expression::JoinHint(e) => self.generate_join_hint(e),
            Expression::JournalProperty(e) => self.generate_journal_property(e),
            Expression::LanguageProperty(e) => self.generate_language_property(e),
            Expression::Lateral(e) => self.generate_lateral(e),
            Expression::LikeProperty(e) => self.generate_like_property(e),
            Expression::Limit(e) => self.generate_limit(e),
            Expression::LimitOptions(e) => self.generate_limit_options(e),
            Expression::List(e) => self.generate_list(e),
            Expression::ToMap(e) => self.generate_tomap(e),
            Expression::Localtime(e) => self.generate_localtime(e),
            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
            Expression::LocationProperty(e) => self.generate_location_property(e),
            Expression::Lock(e) => self.generate_lock(e),
            Expression::LockProperty(e) => self.generate_lock_property(e),
            Expression::LockingProperty(e) => self.generate_locking_property(e),
            Expression::LockingStatement(e) => self.generate_locking_statement(e),
            Expression::LogProperty(e) => self.generate_log_property(e),
            Expression::MD5Digest(e) => self.generate_md5_digest(e),
            Expression::MLForecast(e) => self.generate_ml_forecast(e),
            Expression::MLTranslate(e) => self.generate_ml_translate(e),
            Expression::MakeInterval(e) => self.generate_make_interval(e),
            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
            Expression::Map(e) => self.generate_map(e),
            Expression::MapCat(e) => self.generate_map_cat(e),
            Expression::MapDelete(e) => self.generate_map_delete(e),
            Expression::MapInsert(e) => self.generate_map_insert(e),
            Expression::MapPick(e) => self.generate_map_pick(e),
            Expression::MaskingPolicyColumnConstraint(e) => {
                self.generate_masking_policy_column_constraint(e)
            }
            Expression::MatchAgainst(e) => self.generate_match_against(e),
            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
            Expression::Merge(e) => self.generate_merge(e),
            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
            Expression::Minhash(e) => self.generate_minhash(e),
            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
            Expression::Monthname(e) => self.generate_monthname(e),
            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
            Expression::NextValueFor(e) => self.generate_next_value_for(e),
            Expression::Normal(e) => self.generate_normal(e),
            Expression::Normalize(e) => self.generate_normalize(e),
            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
            Expression::Nullif(e) => self.generate_nullif(e),
            Expression::NumberToStr(e) => self.generate_number_to_str(e),
            Expression::ObjectAgg(e) => self.generate_object_agg(e),
            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
            Expression::ObjectInsert(e) => self.generate_object_insert(e),
            Expression::Offset(e) => self.generate_offset(e),
            Expression::Qualify(e) => self.generate_qualify(e),
            Expression::OnCluster(e) => self.generate_on_cluster(e),
            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
            Expression::OnCondition(e) => self.generate_on_condition(e),
            Expression::OnConflict(e) => self.generate_on_conflict(e),
            Expression::OnProperty(e) => self.generate_on_property(e),
            Expression::Opclass(e) => self.generate_opclass(e),
            Expression::OpenJSON(e) => self.generate_open_json(e),
            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
            Expression::Operator(e) => self.generate_operator(e),
            Expression::OrderBy(e) => self.generate_order_by(e),
            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
            Expression::ParseIp(e) => self.generate_parse_ip(e),
            Expression::ParseJSON(e) => self.generate_parse_json(e),
            Expression::ParseTime(e) => self.generate_parse_time(e),
            Expression::ParseUrl(e) => self.generate_parse_url(e),
            Expression::Partition(e) => self.generate_partition_expr(e),
            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
            Expression::PartitionByRangePropertyDynamic(e) => {
                self.generate_partition_by_range_property_dynamic(e)
            }
            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
            Expression::PartitionList(e) => self.generate_partition_list(e),
            Expression::PartitionRange(e) => self.generate_partition_range(e),
            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
            Expression::PeriodForSystemTimeConstraint(e) => {
                self.generate_period_for_system_time_constraint(e)
            }
            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
            Expression::PivotAny(e) => self.generate_pivot_any(e),
            Expression::Predict(e) => self.generate_predict(e),
            Expression::PreviousDay(e) => self.generate_previous_day(e),
            Expression::PrimaryKey(e) => self.generate_primary_key(e),
            Expression::PrimaryKeyColumnConstraint(e) => {
                self.generate_primary_key_column_constraint(e)
            }
            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
            Expression::ProjectionDef(e) => self.generate_projection_def(e),
            Expression::Properties(e) => self.generate_properties(e),
            Expression::Property(e) => self.generate_property(e),
            Expression::PseudoType(e) => self.generate_pseudo_type(e),
            Expression::Put(e) => self.generate_put(e),
            Expression::Quantile(e) => self.generate_quantile(e),
            Expression::QueryBand(e) => self.generate_query_band(e),
            Expression::QueryOption(e) => self.generate_query_option(e),
            Expression::QueryTransform(e) => self.generate_query_transform(e),
            Expression::Randn(e) => self.generate_randn(e),
            Expression::Randstr(e) => self.generate_randstr(e),
            Expression::RangeBucket(e) => self.generate_range_bucket(e),
            Expression::RangeN(e) => self.generate_range_n(e),
            Expression::ReadCSV(e) => self.generate_read_csv(e),
            Expression::ReadParquet(e) => self.generate_read_parquet(e),
            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
            Expression::Reduce(e) => self.generate_reduce(e),
            Expression::Reference(e) => self.generate_reference(e),
            Expression::Refresh(e) => self.generate_refresh(e),
            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
            Expression::RegexpCount(e) => self.generate_regexp_count(e),
            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
            Expression::RegrCount(e) => self.generate_regr_count(e),
            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
            Expression::RegrR2(e) => self.generate_regr_r2(e),
            Expression::RegrSlope(e) => self.generate_regr_slope(e),
            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
            Expression::RegrSyy(e) => self.generate_regr_syy(e),
            Expression::RegrValx(e) => self.generate_regr_valx(e),
            Expression::RegrValy(e) => self.generate_regr_valy(e),
            Expression::RemoteWithConnectionModelProperty(e) => {
                self.generate_remote_with_connection_model_property(e)
            }
            Expression::RenameColumn(e) => self.generate_rename_column(e),
            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
            Expression::Returning(e) => self.generate_returning(e),
            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
            Expression::Rollback(e) => self.generate_rollback(e),
            Expression::Rollup(e) => self.generate_rollup(e),
            Expression::RowFormatDelimitedProperty(e) => {
                self.generate_row_format_delimited_property(e)
            }
            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
            Expression::SHA2(e) => self.generate_sha2(e),
            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
            Expression::SafeAdd(e) => self.generate_safe_add(e),
            Expression::SafeDivide(e) => self.generate_safe_divide(e),
            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
            Expression::SampleProperty(e) => self.generate_sample_property(e),
            Expression::Schema(e) => self.generate_schema(e),
            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
            Expression::Search(e) => self.generate_search(e),
            Expression::SearchIp(e) => self.generate_search_ip(e),
            Expression::SecurityProperty(e) => self.generate_security_property(e),
            Expression::SemanticView(e) => self.generate_semantic_view(e),
            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
            Expression::SessionParameter(e) => self.generate_session_parameter(e),
            Expression::Set(e) => self.generate_set(e),
            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
            Expression::SetItem(e) => self.generate_set_item(e),
            Expression::SetOperation(e) => self.generate_set_operation(e),
            Expression::SetProperty(e) => self.generate_set_property(e),
            Expression::SettingsProperty(e) => self.generate_settings_property(e),
            Expression::SharingProperty(e) => self.generate_sharing_property(e),
            Expression::Slice(e) => self.generate_slice(e),
            Expression::SortArray(e) => self.generate_sort_array(e),
            Expression::SortBy(e) => self.generate_sort_by(e),
            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
            Expression::SplitPart(e) => self.generate_split_part(e),
            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
            Expression::StDistance(e) => self.generate_st_distance(e),
            Expression::StPoint(e) => self.generate_st_point(e),
            Expression::StabilityProperty(e) => self.generate_stability_property(e),
            Expression::StandardHash(e) => self.generate_standard_hash(e),
            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
            Expression::StrPosition(e) => self.generate_str_position(e),
            Expression::StrToDate(e) => self.generate_str_to_date(e),
            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
            Expression::StrToMap(e) => self.generate_str_to_map(e),
            Expression::StrToTime(e) => self.generate_str_to_time(e),
            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
            Expression::StringToArray(e) => self.generate_string_to_array(e),
            Expression::Struct(e) => self.generate_struct(e),
            Expression::Stuff(e) => self.generate_stuff(e),
            Expression::SubstringIndex(e) => self.generate_substring_index(e),
            Expression::Summarize(e) => self.generate_summarize(e),
            Expression::Systimestamp(e) => self.generate_systimestamp(e),
            Expression::TableAlias(e) => self.generate_table_alias(e),
            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
            Expression::RowsFrom(e) => self.generate_rows_from(e),
            Expression::TableSample(e) => self.generate_table_sample(e),
            Expression::Tag(e) => self.generate_tag(e),
            Expression::Tags(e) => self.generate_tags(e),
            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
            Expression::Time(e) => self.generate_time_func(e),
            Expression::TimeAdd(e) => self.generate_time_add(e),
            Expression::TimeDiff(e) => self.generate_time_diff(e),
            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
            Expression::TimeSlice(e) => self.generate_time_slice(e),
            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
            Expression::TimeSub(e) => self.generate_time_sub(e),
            Expression::TimeToStr(e) => self.generate_time_to_str(e),
            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
            Expression::TimeUnit(e) => self.generate_time_unit(e),
            Expression::Timestamp(e) => self.generate_timestamp_func(e),
            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
            Expression::ToBinary(e) => self.generate_to_binary(e),
            Expression::ToBoolean(e) => self.generate_to_boolean(e),
            Expression::ToChar(e) => self.generate_to_char(e),
            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
            Expression::ToDouble(e) => self.generate_to_double(e),
            Expression::ToFile(e) => self.generate_to_file(e),
            Expression::ToNumber(e) => self.generate_to_number(e),
            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
            Expression::Transaction(e) => self.generate_transaction(e),
            Expression::Transform(e) => self.generate_transform(e),
            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
            Expression::TransientProperty(e) => self.generate_transient_property(e),
            Expression::Translate(e) => self.generate_translate(e),
            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
            Expression::TruncateTable(e) => self.generate_truncate_table(e),
            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
            Expression::Unhex(e) => self.generate_unhex(e),
            Expression::UnicodeString(e) => self.generate_unicode_string(e),
            Expression::Uniform(e) => self.generate_uniform(e),
            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
            Expression::RollupProperty(e) => self.generate_rollup_property(e),
            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
            Expression::UtcTime(e) => self.generate_utc_time(e),
            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
            Expression::Uuid(e) => self.generate_uuid(e),
            Expression::Var(v) => {
                if matches!(self.config.dialect, Some(DialectType::MySQL))
                    && v.this.len() > 2
                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
                {
                    return self.generate_identifier(&Identifier {
                        name: v.this.clone(),
                        quoted: true,
                        trailing_comments: Vec::new(),
                    });
                }
                self.write(&v.this);
                Ok(())
            }
            Expression::VarMap(e) => self.generate_var_map(e),
            Expression::VectorSearch(e) => self.generate_vector_search(e),
            Expression::Version(e) => self.generate_version(e),
            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
            Expression::WatermarkColumnConstraint(e) => {
                self.generate_watermark_column_constraint(e)
            }
            Expression::Week(e) => self.generate_week(e),
            Expression::When(e) => self.generate_when(e),
            Expression::Whens(e) => self.generate_whens(e),
            Expression::Where(e) => self.generate_where(e),
            Expression::WidthBucket(e) => self.generate_width_bucket(e),
            Expression::Window(e) => self.generate_window(e),
            Expression::WindowSpec(e) => self.generate_window_spec(e),
            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
            Expression::WithFill(e) => self.generate_with_fill(e),
            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
            Expression::WithOperator(e) => self.generate_with_operator(e),
            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
            Expression::WithSchemaBindingProperty(e) => {
                self.generate_with_schema_binding_property(e)
            }
            Expression::WithSystemVersioningProperty(e) => {
                self.generate_with_system_versioning_property(e)
            }
            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
            Expression::XMLElement(e) => self.generate_xml_element(e),
            Expression::XMLGet(e) => self.generate_xml_get(e),
            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
            Expression::XMLTable(e) => self.generate_xml_table(e),
            Expression::Xor(e) => self.generate_xor(e),
            Expression::Zipf(e) => self.generate_zipf(e),
            _ => {
                // Fallback for unimplemented expressions
                self.write(&format!("/* unimplemented: {:?} */", expr));
                Ok(())
            }
        }
    }

    fn generate_select(&mut self, select: &Select) -> Result<()> {
        use crate::dialects::DialectType;

        // Output leading comments before SELECT
        for comment in &select.leading_comments {
            self.write_formatted_comment(comment);
            self.write(" ");
        }

        // WITH clause
        if let Some(with) = &select.with {
            self.generate_with(with)?;
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
        }

        // Output post-SELECT comments (comments that appeared after SELECT keyword)
        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
        for comment in &select.post_select_comments {
            self.write_formatted_comment(comment);
            self.write(" ");
        }

        self.write_keyword("SELECT");

        // Generate query hint if present /*+ ... */
        if let Some(hint) = &select.hint {
            self.generate_hint(hint)?;
        }

        // For SQL Server, convert LIMIT to TOP (structural transformation)
        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
        // TOP clause (SQL Server style - before DISTINCT)
        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
            && select.top.is_none()
            && select.limit.is_some()
            && select.offset.is_none(); // Don't use TOP when there's OFFSET

        // For TOP-supporting dialects: DISTINCT before TOP
        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
        let is_top_dialect = matches!(
            self.config.dialect,
            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
        );

        if select.distinct && (is_top_dialect || select.top.is_some()) {
            self.write_space();
            self.write_keyword("DISTINCT");
        }

        if is_top_dialect {
            if let Some(top) = &select.top {
                self.write_space();
                self.write_keyword("TOP");
                if top.parenthesized {
                    self.write(" (");
                    self.generate_expression(&top.this)?;
                    self.write(")");
                } else {
                    self.write_space();
                    self.generate_expression(&top.this)?;
                }
                if top.percent {
                    self.write_space();
                    self.write_keyword("PERCENT");
                }
                if top.with_ties {
                    self.write_space();
                    self.write_keyword("WITH TIES");
                }
            } else if use_top_from_limit {
                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
                if let Some(limit) = &select.limit {
                    self.write_space();
                    self.write_keyword("TOP");
                    // Use parentheses for complex expressions, but not for simple literals
                    let is_simple_literal =
                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
                    if is_simple_literal {
                        self.write_space();
                        self.generate_expression(&limit.this)?;
                    } else {
                        self.write(" (");
                        self.generate_expression(&limit.this)?;
                        self.write(")");
                    }
                }
            }
        }

        if select.distinct && !is_top_dialect && select.top.is_none() {
            self.write_space();
            self.write_keyword("DISTINCT");
        }

        // DISTINCT ON clause (PostgreSQL)
        if let Some(distinct_on) = &select.distinct_on {
            self.write_space();
            self.write_keyword("ON");
            self.write(" (");
            for (i, expr) in distinct_on.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }

        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
        for modifier in &select.operation_modifiers {
            self.write_space();
            self.write_keyword(modifier);
        }

        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
        if let Some(kind) = &select.kind {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.write_keyword(kind);
        }

        // Expressions (only if there are any)
        if !select.expressions.is_empty() {
            if self.config.pretty {
                self.write_newline();
                self.indent_level += 1;
            } else {
                self.write_space();
            }
        }

        for (i, expr) in select.expressions.iter().enumerate() {
            if i > 0 {
                self.write(",");
                if self.config.pretty {
                    self.write_newline();
                } else {
                    self.write_space();
                }
            }
            if self.config.pretty {
                self.write_indent();
            }
            self.generate_expression(expr)?;
        }

        if self.config.pretty && !select.expressions.is_empty() {
            self.indent_level -= 1;
        }

        // INTO clause (SELECT ... INTO table_name)
        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
        if let Some(into) = &select.into {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            if into.bulk_collect {
                self.write_keyword("BULK COLLECT INTO");
            } else {
                self.write_keyword("INTO");
            }
            if into.temporary {
                self.write_space();
                self.write_keyword("TEMPORARY");
            }
            if into.unlogged {
                self.write_space();
                self.write_keyword("UNLOGGED");
            }
            self.write_space();
            // If we have multiple expressions, output them comma-separated
            if !into.expressions.is_empty() {
                for (i, expr) in into.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            } else {
                self.generate_expression(&into.this)?;
            }
        }

        // FROM clause
        if let Some(from) = &select.from {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("FROM");
            self.write_space();

            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
            let has_tablesample = from
                .expressions
                .iter()
                .any(|e| matches!(e, Expression::TableSample(_)));
            let use_cross_join = !has_tablesample
                && matches!(
                    self.config.dialect,
                    Some(DialectType::BigQuery)
                        | Some(DialectType::Hive)
                        | Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::SQLite)
                        | Some(DialectType::ClickHouse)
                );

            // Snowflake wraps standalone VALUES in FROM clause with parentheses
            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));

            for (i, expr) in from.expressions.iter().enumerate() {
                if i > 0 {
                    if use_cross_join {
                        self.write(" CROSS JOIN ");
                    } else {
                        self.write(", ");
                    }
                }
                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
                    self.write("(");
                    self.generate_expression(expr)?;
                    self.write(")");
                } else {
                    self.generate_expression(expr)?;
                }
            }
        }

        // JOINs
        for join in &select.joins {
            self.generate_join(join)?;
        }

        // Output deferred ON/USING conditions (right-to-left, which is reverse order)
        // These are joins where the condition was specified after all JOINs
        for join in select.joins.iter().rev() {
            if join.deferred_condition {
                self.generate_join_condition(join)?;
            }
        }

        // LATERAL VIEW clauses (Hive/Spark)
        for lateral_view in &select.lateral_views {
            self.generate_lateral_view(lateral_view)?;
        }

        // PREWHERE (ClickHouse)
        if let Some(prewhere) = &select.prewhere {
            self.write_clause_condition("PREWHERE", prewhere)?;
        }

        // WHERE
        if let Some(where_clause) = &select.where_clause {
            self.write_clause_condition("WHERE", &where_clause.this)?;
        }

        // CONNECT BY (Oracle hierarchical queries)
        if let Some(connect) = &select.connect {
            self.generate_connect(connect)?;
        }

        // GROUP BY
        if let Some(group_by) = &select.group_by {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("GROUP BY");
            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
            match group_by.all {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("ALL");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("DISTINCT");
                }
                None => {}
            }
            if !group_by.expressions.is_empty() {
                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
                // These are represented as Cube/Rollup expressions with empty expressions at the end
                let mut trailing_cube = false;
                let mut trailing_rollup = false;
                let mut plain_expressions: Vec<&Expression> = Vec::new();
                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
                let mut cube_expressions: Vec<&Expression> = Vec::new();
                let mut rollup_expressions: Vec<&Expression> = Vec::new();

                for expr in &group_by.expressions {
                    match expr {
                        Expression::Cube(c) if c.expressions.is_empty() => {
                            trailing_cube = true;
                        }
                        Expression::Rollup(r) if r.expressions.is_empty() => {
                            trailing_rollup = true;
                        }
                        Expression::Function(f) if f.name == "CUBE" => {
                            cube_expressions.push(expr);
                        }
                        Expression::Function(f) if f.name == "ROLLUP" => {
                            rollup_expressions.push(expr);
                        }
                        Expression::Function(f) if f.name == "GROUPING SETS" => {
                            grouping_sets_expressions.push(expr);
                        }
                        _ => {
                            plain_expressions.push(expr);
                        }
                    }
                }

                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
                let mut regular_expressions: Vec<&Expression> = Vec::new();
                regular_expressions.extend(plain_expressions);
                regular_expressions.extend(grouping_sets_expressions);
                regular_expressions.extend(cube_expressions);
                regular_expressions.extend(rollup_expressions);

                if self.config.pretty {
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                } else {
                    self.write_space();
                }

                for (i, expr) in regular_expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }

                if self.config.pretty {
                    self.indent_level -= 1;
                }

                // Output trailing WITH CUBE or WITH ROLLUP
                if trailing_cube {
                    self.write_space();
                    self.write_keyword("WITH CUBE");
                } else if trailing_rollup {
                    self.write_space();
                    self.write_keyword("WITH ROLLUP");
                }
            }

            // ClickHouse: WITH TOTALS
            if group_by.totals {
                self.write_space();
                self.write_keyword("WITH TOTALS");
            }
        }

        // HAVING
        if let Some(having) = &select.having {
            self.write_clause_condition("HAVING", &having.this)?;
        }

        // QUALIFY and WINDOW clause ordering depends on input SQL
        if select.qualify_after_window {
            // WINDOW before QUALIFY (DuckDB style)
            if let Some(windows) = &select.windows {
                self.write_window_clause(windows)?;
            }
            if let Some(qualify) = &select.qualify {
                self.write_clause_condition("QUALIFY", &qualify.this)?;
            }
        } else {
            // QUALIFY before WINDOW (Snowflake/BigQuery default)
            if let Some(qualify) = &select.qualify {
                self.write_clause_condition("QUALIFY", &qualify.this)?;
            }
            if let Some(windows) = &select.windows {
                self.write_window_clause(windows)?;
            }
        }

        // DISTRIBUTE BY (Hive/Spark)
        if let Some(distribute_by) = &select.distribute_by {
            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
        }

        // CLUSTER BY (Hive/Spark)
        if let Some(cluster_by) = &select.cluster_by {
            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
        }

        // SORT BY (Hive/Spark - comes before ORDER BY)
        if let Some(sort_by) = &select.sort_by {
            self.write_order_clause("SORT BY", &sort_by.expressions)?;
        }

        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
        if let Some(order_by) = &select.order_by {
            let keyword = if order_by.siblings {
                "ORDER SIBLINGS BY"
            } else {
                "ORDER BY"
            };
            self.write_order_clause(keyword, &order_by.expressions)?;
        }

        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
        if select.order_by.is_none()
            && select.fetch.is_some()
            && matches!(
                self.config.dialect,
                Some(DialectType::TSQL) | Some(DialectType::Fabric)
            )
        {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
        }

        // LIMIT and OFFSET
        // PostgreSQL and others use: LIMIT count OFFSET offset
        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
        let is_presto_like = matches!(
            self.config.dialect,
            Some(DialectType::Presto) | Some(DialectType::Trino)
        );

        if is_presto_like && select.offset.is_some() {
            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
            if let Some(offset) = &select.offset {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                self.write_keyword("OFFSET");
                self.write_space();
                self.write_limit_expr(&offset.this)?;
                if offset.rows == Some(true) {
                    self.write_space();
                    self.write_keyword("ROWS");
                }
            }
            if let Some(limit) = &select.limit {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                self.write_keyword("LIMIT");
                self.write_space();
                self.write_limit_expr(&limit.this)?;
                if limit.percent {
                    self.write_space();
                    self.write_keyword("PERCENT");
                }
            }
        } else {
            // Check if FETCH will be converted to LIMIT (used for ordering)
            let fetch_as_limit = select.fetch.as_ref().is_some_and(|fetch| {
                !fetch.percent
                    && !fetch.with_ties
                    && fetch.count.is_some()
                    && matches!(
                        self.config.dialect,
                        Some(DialectType::Spark)
                            | Some(DialectType::Hive)
                            | Some(DialectType::DuckDB)
                            | Some(DialectType::SQLite)
                            | Some(DialectType::MySQL)
                            | Some(DialectType::BigQuery)
                            | Some(DialectType::Databricks)
                            | Some(DialectType::StarRocks)
                            | Some(DialectType::Doris)
                            | Some(DialectType::Athena)
                            | Some(DialectType::ClickHouse)
                            | Some(DialectType::Redshift)
                    )
            });

            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
            if let Some(limit) = &select.limit {
                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
                    if self.config.pretty {
                        self.write_newline();
                        self.write_indent();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("LIMIT");
                    self.write_space();
                    self.write_limit_expr(&limit.this)?;
                    if limit.percent {
                        self.write_space();
                        self.write_keyword("PERCENT");
                    }
                }
            }

            // Convert TOP to LIMIT for non-TOP dialects
            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
                if let Some(top) = &select.top {
                    if !top.percent && !top.with_ties {
                        if self.config.pretty {
                            self.write_newline();
                            self.write_indent();
                        } else {
                            self.write_space();
                        }
                        self.write_keyword("LIMIT");
                        self.write_space();
                        self.generate_expression(&top.this)?;
                    }
                }
            }

            // If FETCH will be converted to LIMIT and there's also OFFSET,
            // emit LIMIT from FETCH BEFORE the OFFSET
            if fetch_as_limit && select.offset.is_some() {
                if let Some(fetch) = &select.fetch {
                    if self.config.pretty {
                        self.write_newline();
                        self.write_indent();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("LIMIT");
                    self.write_space();
                    self.generate_expression(fetch.count.as_ref().unwrap())?;
                }
            }

            // OFFSET
            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
            if let Some(offset) = &select.offset {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
                    // SQL Server 2012+ OFFSET ... FETCH syntax
                    self.write_keyword("OFFSET");
                    self.write_space();
                    self.write_limit_expr(&offset.this)?;
                    self.write_space();
                    self.write_keyword("ROWS");
                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
                    if let Some(limit) = &select.limit {
                        self.write_space();
                        self.write_keyword("FETCH NEXT");
                        self.write_space();
                        self.write_limit_expr(&limit.this)?;
                        self.write_space();
                        self.write_keyword("ROWS ONLY");
                    }
                } else {
                    self.write_keyword("OFFSET");
                    self.write_space();
                    self.write_limit_expr(&offset.this)?;
                    // Output ROWS keyword if it was in the original SQL
                    if offset.rows == Some(true) {
                        self.write_space();
                        self.write_keyword("ROWS");
                    }
                }
            }
        }

        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            if let Some(limit_by) = &select.limit_by {
                if !limit_by.is_empty() {
                    self.write_space();
                    self.write_keyword("BY");
                    self.write_space();
                    for (i, expr) in limit_by.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(expr)?;
                    }
                }
            }
        }

        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            if let Some(settings) = &select.settings {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                self.write_keyword("SETTINGS");
                self.write_space();
                for (i, expr) in settings.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            }

            if let Some(format_expr) = &select.format {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                self.write_keyword("FORMAT");
                self.write_space();
                self.generate_expression(format_expr)?;
            }
        }

        // FETCH FIRST/NEXT
        if let Some(fetch) = &select.fetch {
            // Check if we already emitted LIMIT from FETCH before OFFSET
            let fetch_already_as_limit = select.offset.is_some()
                && !fetch.percent
                && !fetch.with_ties
                && fetch.count.is_some()
                && matches!(
                    self.config.dialect,
                    Some(DialectType::Spark)
                        | Some(DialectType::Hive)
                        | Some(DialectType::DuckDB)
                        | Some(DialectType::SQLite)
                        | Some(DialectType::MySQL)
                        | Some(DialectType::BigQuery)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::StarRocks)
                        | Some(DialectType::Doris)
                        | Some(DialectType::Athena)
                        | Some(DialectType::ClickHouse)
                        | Some(DialectType::Redshift)
                );

            if fetch_already_as_limit {
                // Already emitted as LIMIT before OFFSET, skip
            } else {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }

                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
                let use_limit = !fetch.percent
                    && !fetch.with_ties
                    && fetch.count.is_some()
                    && matches!(
                        self.config.dialect,
                        Some(DialectType::Spark)
                            | Some(DialectType::Hive)
                            | Some(DialectType::DuckDB)
                            | Some(DialectType::SQLite)
                            | Some(DialectType::MySQL)
                            | Some(DialectType::BigQuery)
                            | Some(DialectType::Databricks)
                            | Some(DialectType::StarRocks)
                            | Some(DialectType::Doris)
                            | Some(DialectType::Athena)
                            | Some(DialectType::ClickHouse)
                            | Some(DialectType::Redshift)
                    );

                if use_limit {
                    self.write_keyword("LIMIT");
                    self.write_space();
                    self.generate_expression(fetch.count.as_ref().unwrap())?;
                } else {
                    self.write_keyword("FETCH");
                    self.write_space();
                    self.write_keyword(&fetch.direction);
                    if let Some(ref count) = fetch.count {
                        self.write_space();
                        self.generate_expression(count)?;
                    }
                    if fetch.percent {
                        self.write_space();
                        self.write_keyword("PERCENT");
                    }
                    if fetch.rows {
                        self.write_space();
                        self.write_keyword("ROWS");
                    }
                    if fetch.with_ties {
                        self.write_space();
                        self.write_keyword("WITH TIES");
                    } else {
                        self.write_space();
                        self.write_keyword("ONLY");
                    }
                }
            } // close fetch_already_as_limit else
        }

        // SAMPLE / TABLESAMPLE
        if let Some(sample) = &select.sample {
            use crate::dialects::DialectType;
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }

            if sample.is_using_sample {
                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
                self.write_keyword("USING SAMPLE");
                self.generate_sample_body(sample)?;
            } else {
                self.write_keyword("TABLESAMPLE");

                // Snowflake defaults to BERNOULLI when no explicit method is given
                let snowflake_bernoulli =
                    matches!(self.config.dialect, Some(DialectType::Snowflake))
                        && !sample.explicit_method;
                if snowflake_bernoulli {
                    self.write_space();
                    self.write_keyword("BERNOULLI");
                }

                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
                if matches!(sample.method, SampleMethod::Bucket) {
                    self.write_space();
                    self.write("(");
                    self.write_keyword("BUCKET");
                    self.write_space();
                    if let Some(ref num) = sample.bucket_numerator {
                        self.generate_expression(num)?;
                    }
                    self.write_space();
                    self.write_keyword("OUT OF");
                    self.write_space();
                    if let Some(ref denom) = sample.bucket_denominator {
                        self.generate_expression(denom)?;
                    }
                    if let Some(ref field) = sample.bucket_field {
                        self.write_space();
                        self.write_keyword("ON");
                        self.write_space();
                        self.generate_expression(field)?;
                    }
                    self.write(")");
                } else if sample.unit_after_size {
                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
                    if sample.explicit_method && sample.method_before_size {
                        self.write_space();
                        match sample.method {
                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
                            SampleMethod::System => self.write_keyword("SYSTEM"),
                            SampleMethod::Block => self.write_keyword("BLOCK"),
                            SampleMethod::Row => self.write_keyword("ROW"),
                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
                            _ => {}
                        }
                    }
                    self.write(" (");
                    self.generate_expression(&sample.size)?;
                    self.write_space();
                    match sample.method {
                        SampleMethod::Percent => self.write_keyword("PERCENT"),
                        SampleMethod::Row => self.write_keyword("ROWS"),
                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
                        _ => {
                            self.write_keyword("PERCENT");
                        }
                    }
                    self.write(")");
                } else {
                    // Syntax: TABLESAMPLE METHOD (size)
                    self.write_space();
                    match sample.method {
                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
                        SampleMethod::System => self.write_keyword("SYSTEM"),
                        SampleMethod::Block => self.write_keyword("BLOCK"),
                        SampleMethod::Row => self.write_keyword("ROW"),
                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
                        SampleMethod::Bucket => {}
                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
                    }
                    self.write(" (");
                    self.generate_expression(&sample.size)?;
                    if matches!(sample.method, SampleMethod::Percent) {
                        self.write_space();
                        self.write_keyword("PERCENT");
                    }
                    self.write(")");
                }
            }

            if let Some(seed) = &sample.seed {
                self.write_space();
                // Databricks/Spark use REPEATABLE, not SEED
                let use_seed = sample.use_seed_keyword
                    && !matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::Databricks)
                            | Some(crate::dialects::DialectType::Spark)
                    );
                if use_seed {
                    self.write_keyword("SEED");
                } else {
                    self.write_keyword("REPEATABLE");
                }
                self.write(" (");
                self.generate_expression(seed)?;
                self.write(")");
            }
        }

        // FOR UPDATE/SHARE locks
        // Skip locking clauses for dialects that don't support them
        if self.config.locking_reads_supported {
            for lock in &select.locks {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                self.generate_lock(lock)?;
            }
        }

        // FOR XML clause (T-SQL)
        if !select.for_xml.is_empty() {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("FOR XML");
            for (i, opt) in select.for_xml.iter().enumerate() {
                if self.config.pretty {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_newline();
                    self.write_indent();
                    self.write("  "); // extra indent for options
                } else {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_space();
                }
                self.generate_for_xml_option(opt)?;
            }
        }

        // TSQL: OPTION clause
        if let Some(ref option) = select.option {
            if matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::TSQL)
                    | Some(crate::dialects::DialectType::Fabric)
            ) {
                self.write_space();
                self.write(option);
            }
        }

        Ok(())
    }

    /// Generate a single FOR XML option
    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
        match opt {
            Expression::QueryOption(qo) => {
                // Extract the option name from Var
                if let Expression::Var(var) = &*qo.this {
                    self.write(&var.this);
                } else {
                    self.generate_expression(&qo.this)?;
                }
                // If there's an expression (like PATH('element')), output it in parens
                if let Some(expr) = &qo.expression {
                    self.write("(");
                    self.generate_expression(expr)?;
                    self.write(")");
                }
            }
            _ => {
                self.generate_expression(opt)?;
            }
        }
        Ok(())
    }

    fn generate_with(&mut self, with: &With) -> Result<()> {
        use crate::dialects::DialectType;

        // Output leading comments before WITH
        for comment in &with.leading_comments {
            self.write(comment);
            self.write(" ");
        }
        self.write_keyword("WITH");
        if with.recursive {
            self.write_space();
            self.write_keyword("RECURSIVE");
        }
        self.write_space();

        // BigQuery doesn't support column aliases in CTE definitions
        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));

        for (i, cte) in with.ctes.iter().enumerate() {
            if i > 0 {
                self.write(",");
                if self.config.pretty {
                    self.write_space();
                } else {
                    self.write(" ");
                }
            }
            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
                self.generate_expression(&cte.this)?;
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.generate_identifier(&cte.alias)?;
                continue;
            }
            self.generate_identifier(&cte.alias)?;
            if !cte.columns.is_empty() && !skip_cte_columns {
                self.write("(");
                for (j, col) in cte.columns.iter().enumerate() {
                    if j > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }
            // USING KEY (columns) for DuckDB recursive CTEs
            if !cte.key_expressions.is_empty() {
                self.write_space();
                self.write_keyword("USING KEY");
                self.write(" (");
                for (i, key) in cte.key_expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(key)?;
                }
                self.write(")");
            }
            self.write_space();
            self.write_keyword("AS");
            // MATERIALIZED / NOT MATERIALIZED
            if let Some(materialized) = cte.materialized {
                self.write_space();
                if materialized {
                    self.write_keyword("MATERIALIZED");
                } else {
                    self.write_keyword("NOT MATERIALIZED");
                }
            }
            self.write(" (");
            if self.config.pretty {
                self.write_newline();
                self.indent_level += 1;
                self.write_indent();
            }
            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
            let wrap_values_in_select = matches!(
                self.config.dialect,
                Some(DialectType::Spark) | Some(DialectType::Databricks)
            ) && matches!(&cte.this, Expression::Values(_));

            if wrap_values_in_select {
                self.write_keyword("SELECT");
                self.write(" * ");
                self.write_keyword("FROM");
                self.write_space();
            }
            self.generate_expression(&cte.this)?;
            if self.config.pretty {
                self.write_newline();
                self.indent_level -= 1;
                self.write_indent();
            }
            self.write(")");
        }

        // Generate SEARCH/CYCLE clause if present
        if let Some(search) = &with.search {
            self.write_space();
            self.generate_expression(search)?;
        }

        Ok(())
    }

    fn generate_join(&mut self, join: &Join) -> Result<()> {
        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
        if join.kind == JoinKind::Implicit {
            self.write(",");
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.generate_expression(&join.this)?;
            return Ok(());
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }

        // Helper: format hint suffix (e.g., " LOOP" or "")
        // Only include join hints for dialects that support them
        let hint_str = if self.config.join_hints {
            join.join_hint
                .as_ref()
                .map(|h| format!(" {}", h))
                .unwrap_or_default()
        } else {
            String::new()
        };

        let clickhouse_join_keyword =
            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
                if let Some(hint) = &join.join_hint {
                    let mut global = false;
                    let mut strictness: Option<&'static str> = None;
                    for part in hint.split_whitespace() {
                        match part.to_uppercase().as_str() {
                            "GLOBAL" => global = true,
                            "ANY" => strictness = Some("ANY"),
                            "ASOF" => strictness = Some("ASOF"),
                            "SEMI" => strictness = Some("SEMI"),
                            "ANTI" => strictness = Some("ANTI"),
                            _ => {}
                        }
                    }

                    if global || strictness.is_some() {
                        let join_type = match join.kind {
                            JoinKind::Left => {
                                if join.use_outer_keyword {
                                    "LEFT OUTER"
                                } else if join.use_inner_keyword {
                                    "LEFT INNER"
                                } else {
                                    "LEFT"
                                }
                            }
                            JoinKind::Right => {
                                if join.use_outer_keyword {
                                    "RIGHT OUTER"
                                } else if join.use_inner_keyword {
                                    "RIGHT INNER"
                                } else {
                                    "RIGHT"
                                }
                            }
                            JoinKind::Full => {
                                if join.use_outer_keyword {
                                    "FULL OUTER"
                                } else {
                                    "FULL"
                                }
                            }
                            JoinKind::Inner => {
                                if join.use_inner_keyword {
                                    "INNER"
                                } else {
                                    ""
                                }
                            }
                            _ => "",
                        };

                        let mut parts = Vec::new();
                        if global {
                            parts.push("GLOBAL");
                        }
                        if !join_type.is_empty() {
                            parts.push(join_type);
                        }
                        if let Some(strict) = strictness {
                            parts.push(strict);
                        }
                        parts.push("JOIN");
                        Some(parts.join(" "))
                    } else {
                        None
                    }
                } else {
                    None
                }
            } else {
                None
            };

        if let Some(keyword) = clickhouse_join_keyword {
            self.write_keyword(&keyword);
        } else {
            match join.kind {
                JoinKind::Inner => {
                    if join.use_inner_keyword {
                        self.write_keyword(&format!("INNER{} JOIN", hint_str));
                    } else {
                        self.write_keyword(&format!(
                            "{}JOIN",
                            if hint_str.is_empty() {
                                String::new()
                            } else {
                                format!("{} ", hint_str.trim())
                            }
                        ));
                    }
                }
                JoinKind::Left => {
                    if join.use_outer_keyword {
                        self.write_keyword(&format!("LEFT OUTER{} JOIN", hint_str));
                    } else if join.use_inner_keyword {
                        self.write_keyword(&format!("LEFT INNER{} JOIN", hint_str));
                    } else {
                        self.write_keyword(&format!("LEFT{} JOIN", hint_str));
                    }
                }
                JoinKind::Right => {
                    if join.use_outer_keyword {
                        self.write_keyword(&format!("RIGHT OUTER{} JOIN", hint_str));
                    } else if join.use_inner_keyword {
                        self.write_keyword(&format!("RIGHT INNER{} JOIN", hint_str));
                    } else {
                        self.write_keyword(&format!("RIGHT{} JOIN", hint_str));
                    }
                }
                JoinKind::Full => {
                    if join.use_outer_keyword {
                        self.write_keyword(&format!("FULL OUTER{} JOIN", hint_str));
                    } else {
                        self.write_keyword(&format!("FULL{} JOIN", hint_str));
                    }
                }
                JoinKind::Outer => self.write_keyword("OUTER JOIN"),
                JoinKind::Cross => self.write_keyword("CROSS JOIN"),
                JoinKind::Natural => self.write_keyword("NATURAL JOIN"),
                JoinKind::NaturalLeft => {
                    if join.use_outer_keyword {
                        self.write_keyword("NATURAL LEFT OUTER JOIN");
                    } else {
                        self.write_keyword("NATURAL LEFT JOIN");
                    }
                }
                JoinKind::NaturalRight => {
                    if join.use_outer_keyword {
                        self.write_keyword("NATURAL RIGHT OUTER JOIN");
                    } else {
                        self.write_keyword("NATURAL RIGHT JOIN");
                    }
                }
                JoinKind::NaturalFull => {
                    if join.use_outer_keyword {
                        self.write_keyword("NATURAL FULL OUTER JOIN");
                    } else {
                        self.write_keyword("NATURAL FULL JOIN");
                    }
                }
                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
                JoinKind::CrossApply => {
                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
                        self.write_keyword("CROSS APPLY");
                    } else {
                        self.write_keyword("INNER JOIN LATERAL");
                    }
                }
                JoinKind::OuterApply => {
                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
                        self.write_keyword("OUTER APPLY");
                    } else {
                        self.write_keyword("LEFT JOIN LATERAL");
                    }
                }
                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
                JoinKind::AsOfLeft => {
                    if join.use_outer_keyword {
                        self.write_keyword("ASOF LEFT OUTER JOIN");
                    } else {
                        self.write_keyword("ASOF LEFT JOIN");
                    }
                }
                JoinKind::AsOfRight => {
                    if join.use_outer_keyword {
                        self.write_keyword("ASOF RIGHT OUTER JOIN");
                    } else {
                        self.write_keyword("ASOF RIGHT JOIN");
                    }
                }
                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
                JoinKind::LeftLateral => {
                    if join.use_outer_keyword {
                        self.write_keyword("LEFT OUTER LATERAL JOIN");
                    } else {
                        self.write_keyword("LEFT LATERAL JOIN");
                    }
                }
                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
                JoinKind::Implicit => {
                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
                    use crate::dialects::DialectType;
                    if matches!(
                        self.config.dialect,
                        Some(DialectType::BigQuery)
                            | Some(DialectType::Hive)
                            | Some(DialectType::Spark)
                            | Some(DialectType::Databricks)
                    ) {
                        self.write_keyword("CROSS JOIN");
                    } else {
                        // Implicit join uses comma: FROM a, b
                        // We already wrote a space before the match, so replace with comma
                        // by removing trailing space and writing ", "
                        self.output.truncate(self.output.trim_end().len());
                        self.write(",");
                    }
                }
                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
            }
        }

        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
            self.write_space();
            match &join.this {
                Expression::Tuple(t) => {
                    for (i, item) in t.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(item)?;
                    }
                }
                other => {
                    self.generate_expression(other)?;
                }
            }
        } else {
            self.write_space();
            self.generate_expression(&join.this)?;
        }

        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
        if !join.deferred_condition {
            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
            if let Some(match_cond) = &join.match_condition {
                self.write_space();
                self.write_keyword("MATCH_CONDITION");
                self.write(" (");
                self.generate_expression(match_cond)?;
                self.write(")");
            }

            if let Some(on) = &join.on {
                if self.config.pretty {
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                    self.write_keyword("ON");
                    self.write_space();
                    self.generate_join_on_condition(on)?;
                    self.indent_level -= 1;
                } else {
                    self.write_space();
                    self.write_keyword("ON");
                    self.write_space();
                    self.generate_expression(on)?;
                }
            }

            if !join.using.is_empty() {
                if self.config.pretty {
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                    self.write_keyword("USING");
                    self.write(" (");
                    for (i, col) in join.using.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                    self.indent_level -= 1;
                } else {
                    self.write_space();
                    self.write_keyword("USING");
                    self.write(" (");
                    for (i, col) in join.using.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                }
            }
        }

        // Generate PIVOT/UNPIVOT expressions that follow this join
        for pivot in &join.pivots {
            self.write_space();
            self.generate_expression(pivot)?;
        }

        Ok(())
    }

    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
        if let Some(match_cond) = &join.match_condition {
            self.write_space();
            self.write_keyword("MATCH_CONDITION");
            self.write(" (");
            self.generate_expression(match_cond)?;
            self.write(")");
        }

        if let Some(on) = &join.on {
            if self.config.pretty {
                self.write_newline();
                self.indent_level += 1;
                self.write_indent();
                self.write_keyword("ON");
                self.write_space();
                // In pretty mode, split AND conditions onto separate lines
                self.generate_join_on_condition(on)?;
                self.indent_level -= 1;
            } else {
                self.write_space();
                self.write_keyword("ON");
                self.write_space();
                self.generate_expression(on)?;
            }
        }

        if !join.using.is_empty() {
            if self.config.pretty {
                self.write_newline();
                self.indent_level += 1;
                self.write_indent();
                self.write_keyword("USING");
                self.write(" (");
                for (i, col) in join.using.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
                self.indent_level -= 1;
            } else {
                self.write_space();
                self.write_keyword("USING");
                self.write(" (");
                for (i, col) in join.using.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }
        }

        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
        for pivot in &join.pivots {
            self.write_space();
            self.generate_expression(pivot)?;
        }

        Ok(())
    }

    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
        // If it's an AND expression, split each condition onto a new line
        if let Expression::And(and_op) = expr {
            // Generate left side (might be another AND)
            self.generate_join_on_condition(&and_op.left)?;
            // AND on new line
            self.write_newline();
            self.write_indent();
            self.write_keyword("AND");
            self.write_space();
            // Generate right side (should be a single condition)
            self.generate_expression(&and_op.right)?;
        } else {
            // Base case: single condition
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
        self.write("(");
        self.generate_expression(&jt.left)?;

        // Generate all joins
        for join in &jt.joins {
            self.generate_join(join)?;
        }

        // Generate LATERAL VIEW clauses (Hive/Spark)
        for lv in &jt.lateral_views {
            self.generate_lateral_view(lv)?;
        }

        self.write(")");

        // Alias
        if let Some(alias) = &jt.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(alias)?;
        }

        Ok(())
    }

    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
        use crate::dialects::DialectType;

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }

        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
        let use_lateral_join = matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL)
                | Some(DialectType::DuckDB)
                | Some(DialectType::Snowflake)
                | Some(DialectType::TSQL)
                | Some(DialectType::Presto)
                | Some(DialectType::Trino)
                | Some(DialectType::Athena)
        );

        // Check if target dialect should use UNNEST instead of EXPLODE
        let use_unnest = matches!(
            self.config.dialect,
            Some(DialectType::DuckDB)
                | Some(DialectType::Presto)
                | Some(DialectType::Trino)
                | Some(DialectType::Athena)
        );

        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
        let (is_posexplode, func_args) = match &lv.this {
            Expression::Explode(uf) => {
                // Expression::Explode is the dedicated EXPLODE expression type
                (false, vec![uf.this.clone()])
            }
            Expression::Function(func) => {
                let name = func.name.to_uppercase();
                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
                    (true, func.args.clone())
                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
                    (false, func.args.clone())
                } else {
                    (false, vec![])
                }
            }
            _ => (false, vec![]),
        };

        if use_lateral_join {
            // Convert to CROSS JOIN for PostgreSQL-like dialects
            if lv.outer {
                self.write_keyword("LEFT JOIN LATERAL");
            } else {
                self.write_keyword("CROSS JOIN");
            }
            self.write_space();

            if use_unnest && !func_args.is_empty() {
                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
                // For DuckDB, also convert ARRAY(y) -> [y]
                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
                    // DuckDB: ARRAY(y) -> [y]
                    func_args
                        .iter()
                        .map(|a| {
                            if let Expression::Function(ref f) = a {
                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
                                    return Expression::ArrayFunc(Box::new(
                                        crate::expressions::ArrayConstructor {
                                            expressions: f.args.clone(),
                                            bracket_notation: true,
                                            use_list_keyword: false,
                                        },
                                    ));
                                }
                            }
                            a.clone()
                        })
                        .collect::<Vec<_>>()
                } else if matches!(
                    self.config.dialect,
                    Some(DialectType::Presto)
                        | Some(DialectType::Trino)
                        | Some(DialectType::Athena)
                ) {
                    // Presto: ARRAY(y) -> ARRAY[y]
                    func_args
                        .iter()
                        .map(|a| {
                            if let Expression::Function(ref f) = a {
                                if f.name.to_uppercase() == "ARRAY" && !f.args.is_empty() {
                                    return Expression::ArrayFunc(Box::new(
                                        crate::expressions::ArrayConstructor {
                                            expressions: f.args.clone(),
                                            bracket_notation: true,
                                            use_list_keyword: false,
                                        },
                                    ));
                                }
                            }
                            a.clone()
                        })
                        .collect::<Vec<_>>()
                } else {
                    func_args
                };

                self.write_keyword("UNNEST");
                self.write("(");
                for (i, arg) in unnest_args.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(arg)?;
                }
                self.write(")");

                // POSEXPLODE -> WITH ORDINALITY
                if is_posexplode {
                    self.write_space();
                    self.write_keyword("WITH ORDINALITY");
                }
            } else {
                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
                if !lv.outer {
                    self.write_keyword("LATERAL");
                    self.write_space();
                }
                self.generate_expression(&lv.this)?;
            }

            // Add table and column aliases
            if let Some(alias) = &lv.table_alias {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.generate_identifier(alias)?;
                if !lv.column_aliases.is_empty() {
                    self.write("(");
                    for (i, col) in lv.column_aliases.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                }
            } else if !lv.column_aliases.is_empty() {
                // Column aliases without table alias
                self.write_space();
                self.write_keyword("AS");
                self.write(" t(");
                for (i, col) in lv.column_aliases.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }

            // For LEFT JOIN LATERAL, need ON TRUE
            if lv.outer {
                self.write_space();
                self.write_keyword("ON TRUE");
            }
        } else {
            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
            self.write_keyword("LATERAL VIEW");
            if lv.outer {
                self.write_space();
                self.write_keyword("OUTER");
            }
            self.write_space();
            self.generate_expression(&lv.this)?;

            // Table alias
            if let Some(alias) = &lv.table_alias {
                self.write_space();
                self.generate_identifier(alias)?;
            }

            // Column aliases
            if !lv.column_aliases.is_empty() {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                for (i, col) in lv.column_aliases.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
            }
        }

        Ok(())
    }

    fn generate_union(&mut self, union: &Union) -> Result<()> {
        // WITH clause
        if let Some(with) = &union.with {
            self.generate_with(with)?;
            self.write_space();
        }
        self.generate_expression(&union.left)?;
        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }

        // BigQuery set operation modifiers: [side] [kind] UNION
        if let Some(side) = &union.side {
            self.write_keyword(side);
            self.write_space();
        }
        if let Some(kind) = &union.kind {
            self.write_keyword(kind);
            self.write_space();
        }

        self.write_keyword("UNION");
        if union.all {
            self.write_space();
            self.write_keyword("ALL");
        } else if union.distinct {
            self.write_space();
            self.write_keyword("DISTINCT");
        }

        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
        // DuckDB: BY NAME
        if union.corresponding || union.by_name {
            self.write_space();
            self.write_keyword("BY NAME");
        }
        if !union.on_columns.is_empty() {
            self.write_space();
            self.write_keyword("ON");
            self.write(" (");
            for (i, col) in union.on_columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(col)?;
            }
            self.write(")");
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }
        self.generate_expression(&union.right)?;
        // ORDER BY, LIMIT, OFFSET for the set operation
        if let Some(order_by) = &union.order_by {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ordered) in order_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ordered)?;
            }
        }
        if let Some(limit) = &union.limit {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(limit)?;
        }
        if let Some(offset) = &union.offset {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("OFFSET");
            self.write_space();
            self.generate_expression(offset)?;
        }
        // DISTRIBUTE BY (Hive/Spark)
        if let Some(distribute_by) = &union.distribute_by {
            self.write_space();
            self.write_keyword("DISTRIBUTE BY");
            self.write_space();
            for (i, expr) in distribute_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        // SORT BY (Hive/Spark)
        if let Some(sort_by) = &union.sort_by {
            self.write_space();
            self.write_keyword("SORT BY");
            self.write_space();
            for (i, ord) in sort_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        // CLUSTER BY (Hive/Spark)
        if let Some(cluster_by) = &union.cluster_by {
            self.write_space();
            self.write_keyword("CLUSTER BY");
            self.write_space();
            for (i, ord) in cluster_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        Ok(())
    }

    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
        // WITH clause
        if let Some(with) = &intersect.with {
            self.generate_with(with)?;
            self.write_space();
        }
        self.generate_expression(&intersect.left)?;
        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }

        // BigQuery set operation modifiers: [side] [kind] INTERSECT
        if let Some(side) = &intersect.side {
            self.write_keyword(side);
            self.write_space();
        }
        if let Some(kind) = &intersect.kind {
            self.write_keyword(kind);
            self.write_space();
        }

        self.write_keyword("INTERSECT");
        if intersect.all {
            self.write_space();
            self.write_keyword("ALL");
        } else if intersect.distinct {
            self.write_space();
            self.write_keyword("DISTINCT");
        }

        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
        // DuckDB: BY NAME
        if intersect.corresponding || intersect.by_name {
            self.write_space();
            self.write_keyword("BY NAME");
        }
        if !intersect.on_columns.is_empty() {
            self.write_space();
            self.write_keyword("ON");
            self.write(" (");
            for (i, col) in intersect.on_columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(col)?;
            }
            self.write(")");
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }
        self.generate_expression(&intersect.right)?;
        // ORDER BY, LIMIT, OFFSET for the set operation
        if let Some(order_by) = &intersect.order_by {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ordered) in order_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ordered)?;
            }
        }
        if let Some(limit) = &intersect.limit {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(limit)?;
        }
        if let Some(offset) = &intersect.offset {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("OFFSET");
            self.write_space();
            self.generate_expression(offset)?;
        }
        // DISTRIBUTE BY (Hive/Spark)
        if let Some(distribute_by) = &intersect.distribute_by {
            self.write_space();
            self.write_keyword("DISTRIBUTE BY");
            self.write_space();
            for (i, expr) in distribute_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        // SORT BY (Hive/Spark)
        if let Some(sort_by) = &intersect.sort_by {
            self.write_space();
            self.write_keyword("SORT BY");
            self.write_space();
            for (i, ord) in sort_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        // CLUSTER BY (Hive/Spark)
        if let Some(cluster_by) = &intersect.cluster_by {
            self.write_space();
            self.write_keyword("CLUSTER BY");
            self.write_space();
            for (i, ord) in cluster_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        Ok(())
    }

    fn generate_except(&mut self, except: &Except) -> Result<()> {
        use crate::dialects::DialectType;

        // WITH clause
        if let Some(with) = &except.with {
            self.generate_with(with)?;
            self.write_space();
        }

        self.generate_expression(&except.left)?;
        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }

        // BigQuery set operation modifiers: [side] [kind] EXCEPT
        if let Some(side) = &except.side {
            self.write_keyword(side);
            self.write_space();
        }
        if let Some(kind) = &except.kind {
            self.write_keyword(kind);
            self.write_space();
        }

        // Oracle uses MINUS instead of EXCEPT
        match self.config.dialect {
            Some(DialectType::Oracle) => {
                self.write_keyword("MINUS");
                // Note: Oracle MINUS doesn't support ALL
            }
            _ => {
                self.write_keyword("EXCEPT");
                if except.all {
                    self.write_space();
                    self.write_keyword("ALL");
                } else if except.distinct {
                    self.write_space();
                    self.write_keyword("DISTINCT");
                }
            }
        }

        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
        // DuckDB: BY NAME
        if except.corresponding || except.by_name {
            self.write_space();
            self.write_keyword("BY NAME");
        }
        if !except.on_columns.is_empty() {
            self.write_space();
            self.write_keyword("ON");
            self.write(" (");
            for (i, col) in except.on_columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(col)?;
            }
            self.write(")");
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }
        self.generate_expression(&except.right)?;
        // ORDER BY, LIMIT, OFFSET for the set operation
        if let Some(order_by) = &except.order_by {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ordered) in order_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ordered)?;
            }
        }
        if let Some(limit) = &except.limit {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(limit)?;
        }
        if let Some(offset) = &except.offset {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("OFFSET");
            self.write_space();
            self.generate_expression(offset)?;
        }
        // DISTRIBUTE BY (Hive/Spark)
        if let Some(distribute_by) = &except.distribute_by {
            self.write_space();
            self.write_keyword("DISTRIBUTE BY");
            self.write_space();
            for (i, expr) in distribute_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        // SORT BY (Hive/Spark)
        if let Some(sort_by) = &except.sort_by {
            self.write_space();
            self.write_keyword("SORT BY");
            self.write_space();
            for (i, ord) in sort_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        // CLUSTER BY (Hive/Spark)
        if let Some(cluster_by) = &except.cluster_by {
            self.write_space();
            self.write_keyword("CLUSTER BY");
            self.write_space();
            for (i, ord) in cluster_by.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        Ok(())
    }

    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
        let prepend_query_cte = if insert.with.is_none() {
            use crate::dialects::DialectType;
            let should_prepend = matches!(
                self.config.dialect,
                Some(DialectType::TSQL)
                    | Some(DialectType::Fabric)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive)
            );
            if should_prepend {
                if let Some(Expression::Select(select)) = &insert.query {
                    select.with.clone()
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        };

        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
        if let Some(with) = &insert.with {
            self.generate_with(with)?;
            self.write_space();
        } else if let Some(with) = &prepend_query_cte {
            self.generate_with(with)?;
            self.write_space();
        }

        // Output leading comments before INSERT
        for comment in &insert.leading_comments {
            self.write(comment);
            self.write(" ");
        }

        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
        if let Some(dir) = &insert.directory {
            self.write_keyword("INSERT OVERWRITE");
            if dir.local {
                self.write_space();
                self.write_keyword("LOCAL");
            }
            self.write_space();
            self.write_keyword("DIRECTORY");
            self.write_space();
            self.write("'");
            self.write(&dir.path);
            self.write("'");

            // ROW FORMAT clause
            if let Some(row_format) = &dir.row_format {
                self.write_space();
                self.write_keyword("ROW FORMAT");
                if row_format.delimited {
                    self.write_space();
                    self.write_keyword("DELIMITED");
                }
                if let Some(val) = &row_format.fields_terminated_by {
                    self.write_space();
                    self.write_keyword("FIELDS TERMINATED BY");
                    self.write_space();
                    self.write("'");
                    self.write(val);
                    self.write("'");
                }
                if let Some(val) = &row_format.collection_items_terminated_by {
                    self.write_space();
                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
                    self.write_space();
                    self.write("'");
                    self.write(val);
                    self.write("'");
                }
                if let Some(val) = &row_format.map_keys_terminated_by {
                    self.write_space();
                    self.write_keyword("MAP KEYS TERMINATED BY");
                    self.write_space();
                    self.write("'");
                    self.write(val);
                    self.write("'");
                }
                if let Some(val) = &row_format.lines_terminated_by {
                    self.write_space();
                    self.write_keyword("LINES TERMINATED BY");
                    self.write_space();
                    self.write("'");
                    self.write(val);
                    self.write("'");
                }
                if let Some(val) = &row_format.null_defined_as {
                    self.write_space();
                    self.write_keyword("NULL DEFINED AS");
                    self.write_space();
                    self.write("'");
                    self.write(val);
                    self.write("'");
                }
            }

            // STORED AS clause
            if let Some(format) = &dir.stored_as {
                self.write_space();
                self.write_keyword("STORED AS");
                self.write_space();
                self.write_keyword(format);
            }

            // Query (SELECT statement)
            if let Some(query) = &insert.query {
                self.write_space();
                self.generate_expression(query)?;
            }

            return Ok(());
        }

        if insert.is_replace {
            // MySQL/SQLite REPLACE INTO statement
            self.write_keyword("REPLACE INTO");
        } else if insert.overwrite {
            // Use dialect-specific INSERT OVERWRITE format
            self.write_keyword("INSERT");
            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
            if let Some(ref hint) = insert.hint {
                self.generate_hint(hint)?;
            }
            self.write(&self.config.insert_overwrite.to_uppercase());
        } else if let Some(ref action) = insert.conflict_action {
            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
            self.write_keyword("INSERT OR");
            self.write_space();
            self.write_keyword(action);
            self.write_space();
            self.write_keyword("INTO");
        } else if insert.ignore {
            // MySQL INSERT IGNORE syntax
            self.write_keyword("INSERT IGNORE INTO");
        } else {
            self.write_keyword("INSERT");
            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
            if let Some(ref hint) = insert.hint {
                self.generate_hint(hint)?;
            }
            self.write_space();
            self.write_keyword("INTO");
        }
        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
        if let Some(ref func) = insert.function_target {
            self.write_space();
            self.write_keyword("FUNCTION");
            self.write_space();
            self.generate_expression(func)?;
        } else {
            self.write_space();
            self.generate_table(&insert.table)?;
        }

        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
        if let Some(ref alias) = insert.alias {
            self.write_space();
            if insert.alias_explicit_as {
                self.write_keyword("AS");
                self.write_space();
            }
            self.generate_identifier(alias)?;
        }

        // IF EXISTS clause (Hive)
        if insert.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        // REPLACE WHERE clause (Databricks)
        if let Some(ref replace_where) = insert.replace_where {
            self.write_space();
            self.write_keyword("REPLACE WHERE");
            self.write_space();
            self.generate_expression(replace_where)?;
        }

        // Generate PARTITION clause if present
        if !insert.partition.is_empty() {
            self.write_space();
            self.write_keyword("PARTITION");
            self.write("(");
            for (i, (col, val)) in insert.partition.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(col)?;
                if let Some(v) = val {
                    self.write(" = ");
                    self.generate_expression(v)?;
                }
            }
            self.write(")");
        }

        // ClickHouse: PARTITION BY expr
        if let Some(ref partition_by) = insert.partition_by {
            self.write_space();
            self.write_keyword("PARTITION BY");
            self.write_space();
            self.generate_expression(partition_by)?;
        }

        // ClickHouse: SETTINGS key = val, ...
        if !insert.settings.is_empty() {
            self.write_space();
            self.write_keyword("SETTINGS");
            self.write_space();
            for (i, setting) in insert.settings.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(setting)?;
            }
        }

        if !insert.columns.is_empty() {
            if insert.alias.is_some() && insert.alias_explicit_as {
                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
                self.write("(");
            } else {
                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
                self.write(" (");
            }
            for (i, col) in insert.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(col)?;
            }
            self.write(")");
        }

        // OUTPUT clause (TSQL)
        if let Some(ref output) = insert.output {
            self.generate_output_clause(output)?;
        }

        // BY NAME modifier (DuckDB)
        if insert.by_name {
            self.write_space();
            self.write_keyword("BY NAME");
        }

        if insert.default_values {
            self.write_space();
            self.write_keyword("DEFAULT VALUES");
        } else if let Some(query) = &insert.query {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
            if prepend_query_cte.is_some() {
                if let Expression::Select(select) = query {
                    let mut select_no_with = select.clone();
                    select_no_with.with = None;
                    self.generate_select(&select_no_with)?;
                } else {
                    self.generate_expression(query)?;
                }
            } else {
                self.generate_expression(query)?;
            }
        } else if !insert.values.is_empty() {
            if self.config.pretty {
                // Pretty printing: VALUES on new line, each tuple indented
                self.write_newline();
                self.write_keyword("VALUES");
                self.write_newline();
                self.indent_level += 1;
                for (i, row) in insert.values.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                        self.write_newline();
                    }
                    self.write_indent();
                    self.write("(");
                    for (j, val) in row.iter().enumerate() {
                        if j > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(val)?;
                    }
                    self.write(")");
                }
                self.indent_level -= 1;
            } else {
                // Non-pretty: single line
                self.write_space();
                self.write_keyword("VALUES");
                for (i, row) in insert.values.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write(" (");
                    for (j, val) in row.iter().enumerate() {
                        if j > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(val)?;
                    }
                    self.write(")");
                }
            }
        }

        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
        if let Some(ref source) = insert.source {
            self.write_space();
            self.write_keyword("TABLE");
            self.write_space();
            self.generate_expression(source)?;
        }

        // Source alias (MySQL: VALUES (...) AS new_data)
        if let Some(alias) = &insert.source_alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(alias)?;
        }

        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
        if let Some(on_conflict) = &insert.on_conflict {
            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
                self.write_space();
                self.generate_expression(on_conflict)?;
            }
        }

        // RETURNING clause
        if !insert.returning.is_empty() {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            for (i, expr) in insert.returning.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }

        Ok(())
    }

    fn generate_update(&mut self, update: &Update) -> Result<()> {
        // Output leading comments before UPDATE
        for comment in &update.leading_comments {
            self.write(comment);
            self.write(" ");
        }

        // WITH clause (CTEs)
        if let Some(ref with) = update.with {
            self.generate_with(with)?;
            self.write_space();
        }

        self.write_keyword("UPDATE");
        self.write_space();
        self.generate_table(&update.table)?;

        let mysql_like_update_from = matches!(
            self.config.dialect,
            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
        ) && update.from_clause.is_some();

        let mut set_pairs = update.set.clone();

        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
        let mut pre_set_joins = update.table_joins.clone();
        if mysql_like_update_from {
            let target_name = update
                .table
                .alias
                .as_ref()
                .map(|a| a.name.clone())
                .unwrap_or_else(|| update.table.name.name.clone());

            for (col, _) in &mut set_pairs {
                if !col.name.contains('.') {
                    col.name = format!("{}.{}", target_name, col.name);
                }
            }

            if let Some(from_clause) = &update.from_clause {
                for table_expr in &from_clause.expressions {
                    pre_set_joins.push(crate::expressions::Join {
                        this: table_expr.clone(),
                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
                            value: true,
                        })),
                        using: Vec::new(),
                        kind: crate::expressions::JoinKind::Inner,
                        use_inner_keyword: false,
                        use_outer_keyword: false,
                        deferred_condition: false,
                        join_hint: None,
                        match_condition: None,
                        pivots: Vec::new(),
                    });
                }
            }
            for join in &update.from_joins {
                let mut join = join.clone();
                if join.on.is_none() && join.using.is_empty() {
                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
                        value: true,
                    }));
                }
                pre_set_joins.push(join);
            }
        }

        // Extra tables for multi-table UPDATE (MySQL syntax)
        for extra_table in &update.extra_tables {
            self.write(", ");
            self.generate_table(extra_table)?;
        }

        // JOINs attached to the table list (MySQL multi-table syntax)
        for join in &pre_set_joins {
            // generate_join already adds a leading space
            self.generate_join(join)?;
        }

        // Teradata: FROM clause comes before SET
        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
        if teradata_from_before_set && !mysql_like_update_from {
            if let Some(ref from_clause) = update.from_clause {
                self.write_space();
                self.write_keyword("FROM");
                self.write_space();
                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(table_expr)?;
                }
            }
            for join in &update.from_joins {
                self.generate_join(join)?;
            }
        }

        self.write_space();
        self.write_keyword("SET");
        self.write_space();

        for (i, (col, val)) in set_pairs.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_identifier(col)?;
            self.write(" = ");
            self.generate_expression(val)?;
        }

        // OUTPUT clause (TSQL)
        if let Some(ref output) = update.output {
            self.generate_output_clause(output)?;
        }

        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
        if !mysql_like_update_from && !teradata_from_before_set {
            if let Some(ref from_clause) = update.from_clause {
                self.write_space();
                self.write_keyword("FROM");
                self.write_space();
                // Generate each table in the FROM clause
                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(table_expr)?;
                }
            }
        }

        if !mysql_like_update_from && !teradata_from_before_set {
            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
            for join in &update.from_joins {
                self.generate_join(join)?;
            }
        }

        if let Some(where_clause) = &update.where_clause {
            self.write_space();
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(&where_clause.this)?;
        }

        // RETURNING clause
        if !update.returning.is_empty() {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            for (i, expr) in update.returning.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }

        // ORDER BY clause (MySQL)
        if let Some(ref order_by) = update.order_by {
            self.write_space();
            self.generate_order_by(order_by)?;
        }

        // LIMIT clause (MySQL)
        if let Some(ref limit) = update.limit {
            self.write_space();
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(limit)?;
        }

        Ok(())
    }

    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
        // Output WITH clause if present
        if let Some(with) = &delete.with {
            self.generate_with(with)?;
            self.write_space();
        }

        // Output leading comments before DELETE
        for comment in &delete.leading_comments {
            self.write(comment);
            self.write(" ");
        }

        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
        if !delete.tables.is_empty() && !delete.tables_from_using {
            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
            self.write_keyword("DELETE");
            self.write_space();
            for (i, tbl) in delete.tables.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_table(tbl)?;
            }
            // TSQL: OUTPUT clause between target table and FROM
            if let Some(ref output) = delete.output {
                self.generate_output_clause(output)?;
            }
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            self.generate_table(&delete.table)?;
        } else if !delete.tables.is_empty() && delete.tables_from_using {
            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
            self.write_keyword("DELETE FROM");
            self.write_space();
            for (i, tbl) in delete.tables.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_table(tbl)?;
            }
        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
            // BigQuery-style DELETE without FROM keyword
            self.write_keyword("DELETE");
            self.write_space();
            self.generate_table(&delete.table)?;
        } else {
            self.write_keyword("DELETE FROM");
            self.write_space();
            self.generate_table(&delete.table)?;
        }

        // ClickHouse: ON CLUSTER clause
        if let Some(ref on_cluster) = delete.on_cluster {
            self.write_space();
            self.generate_on_cluster(on_cluster)?;
        }

        // FORCE INDEX hint (MySQL)
        if let Some(ref idx) = delete.force_index {
            self.write_space();
            self.write_keyword("FORCE INDEX");
            self.write(" (");
            self.write(idx);
            self.write(")");
        }

        // Optional alias
        if let Some(ref alias) = delete.alias {
            self.write_space();
            if delete.alias_explicit_as
                || matches!(self.config.dialect, Some(DialectType::BigQuery))
            {
                self.write_keyword("AS");
                self.write_space();
            }
            self.generate_identifier(alias)?;
        }

        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
        if !delete.tables_from_using {
            for join in &delete.joins {
                self.generate_join(join)?;
            }
        }

        // USING clause (PostgreSQL/DuckDB/MySQL)
        if !delete.using.is_empty() {
            self.write_space();
            self.write_keyword("USING");
            for (i, table) in delete.using.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                }
                self.write_space();
                // Check if the table has subquery hints (DuckDB USING with subquery)
                if !table.hints.is_empty() && table.name.is_empty() {
                    // Subquery in USING: (VALUES ...) AS alias(cols)
                    self.generate_expression(&table.hints[0])?;
                    if let Some(ref alias) = table.alias {
                        self.write_space();
                        if table.alias_explicit_as {
                            self.write_keyword("AS");
                            self.write_space();
                        }
                        self.generate_identifier(alias)?;
                        if !table.column_aliases.is_empty() {
                            self.write("(");
                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
                                if j > 0 {
                                    self.write(", ");
                                }
                                self.generate_identifier(col_alias)?;
                            }
                            self.write(")");
                        }
                    }
                } else {
                    self.generate_table(table)?;
                }
            }
        }

        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
        if delete.tables_from_using {
            for join in &delete.joins {
                self.generate_join(join)?;
            }
        }

        // OUTPUT clause (TSQL) - only if not already emitted in the early position
        let output_already_emitted =
            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
        if !output_already_emitted {
            if let Some(ref output) = delete.output {
                self.generate_output_clause(output)?;
            }
        }

        if let Some(where_clause) = &delete.where_clause {
            self.write_space();
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(&where_clause.this)?;
        }

        // ORDER BY clause (MySQL)
        if let Some(ref order_by) = delete.order_by {
            self.write_space();
            self.generate_order_by(order_by)?;
        }

        // LIMIT clause (MySQL)
        if let Some(ref limit) = delete.limit {
            self.write_space();
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(limit)?;
        }

        // RETURNING clause (PostgreSQL)
        if !delete.returning.is_empty() {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            for (i, expr) in delete.returning.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }

        Ok(())
    }

    // ==================== DDL Generation ====================

    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
        // Athena: Determine if this is Hive-style DDL or Trino-style DML
        // CREATE TABLE AS SELECT uses Trino (double quotes)
        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
        let saved_athena_hive_context = self.athena_hive_context;
        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Athena)
        ) {
            // Use Hive context if:
            // 1. It's an EXTERNAL table, OR
            // 2. There's no AS SELECT clause
            let is_external = ct
                .table_modifier
                .as_ref()
                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
                .unwrap_or(false);
            let has_as_select = ct.as_select.is_some();
            self.athena_hive_context = is_external || !has_as_select;
        }

        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::TSQL)
        ) {
            if let Some(ref query) = ct.as_select {
                // Output WITH CTE clause if present
                if let Some(with_cte) = &ct.with_cte {
                    self.generate_with(with_cte)?;
                    self.write_space();
                }

                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
                self.write_keyword("SELECT");
                self.write(" * ");
                self.write_keyword("INTO");
                self.write_space();

                // If temporary, prefix with # for TSQL temp table
                if ct.temporary {
                    self.write("#");
                }
                self.generate_table(&ct.name)?;

                self.write_space();
                self.write_keyword("FROM");
                self.write(" (");
                // For TSQL, add aliases to select columns to preserve column names
                let aliased_query = Self::add_column_aliases_to_query(query.clone());
                self.generate_expression(&aliased_query)?;
                self.write(") ");
                self.write_keyword("AS");
                self.write(" temp");
                return Ok(());
            }
        }

        // Output WITH CTE clause if present
        if let Some(with_cte) = &ct.with_cte {
            self.generate_with(with_cte)?;
            self.write_space();
        }

        // Output leading comments before CREATE
        for comment in &ct.leading_comments {
            self.write(comment);
            self.write(" ");
        }
        self.write_keyword("CREATE");

        if ct.or_replace {
            self.write_space();
            self.write_keyword("OR REPLACE");
        }

        if ct.temporary {
            self.write_space();
            // Oracle uses GLOBAL TEMPORARY TABLE syntax
            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
                self.write_keyword("GLOBAL TEMPORARY");
            } else {
                self.write_keyword("TEMPORARY");
            }
        }

        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
        let is_dictionary = ct
            .table_modifier
            .as_ref()
            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
            .unwrap_or(false);
        if let Some(ref modifier) = ct.table_modifier {
            // TRANSIENT is Snowflake-specific - skip for other dialects
            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
                || modifier.eq_ignore_ascii_case("SET")
                || modifier.eq_ignore_ascii_case("MULTISET")
                || modifier.to_uppercase().contains("VOLATILE")
                || modifier.to_uppercase().starts_with("SET ")
                || modifier.to_uppercase().starts_with("MULTISET ");
            let skip_teradata =
                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
            if !skip_transient && !skip_teradata {
                self.write_space();
                self.write_keyword(modifier);
            }
        }

        if !is_dictionary {
            self.write_space();
            self.write_keyword("TABLE");
        }

        if ct.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_table(&ct.name)?;

        // ClickHouse: ON CLUSTER clause
        if let Some(ref on_cluster) = ct.on_cluster {
            self.write_space();
            self.generate_on_cluster(on_cluster)?;
        }

        // Teradata: options after table name before column list (comma-separated)
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Teradata)
        ) && !ct.teradata_post_name_options.is_empty()
        {
            for opt in &ct.teradata_post_name_options {
                self.write(", ");
                self.write(opt);
            }
        }

        // Snowflake: COPY GRANTS clause
        if ct.copy_grants {
            self.write_space();
            self.write_keyword("COPY GRANTS");
        }

        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
        if let Some(ref using_template) = ct.using_template {
            self.write_space();
            self.write_keyword("USING TEMPLATE");
            self.write_space();
            self.generate_expression(using_template)?;
            return Ok(());
        }

        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
        if let Some(ref clone_source) = ct.clone_source {
            self.write_space();
            if ct.is_copy && self.config.supports_table_copy {
                // BigQuery uses COPY
                self.write_keyword("COPY");
            } else if ct.shallow_clone {
                self.write_keyword("SHALLOW CLONE");
            } else {
                self.write_keyword("CLONE");
            }
            self.write_space();
            self.generate_table(clone_source)?;
            // Generate AT/BEFORE time travel clause (stored as Raw expression)
            if let Some(ref at_clause) = ct.clone_at_clause {
                self.write_space();
                self.generate_expression(at_clause)?;
            }
            return Ok(());
        }

        // Handle PARTITION OF property
        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
        if let Some(ref partition_of) = ct.partition_of {
            self.write_space();

            // Extract the PartitionedOfProperty parts to generate them separately
            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
                // Output: PARTITION OF <table>
                self.write_keyword("PARTITION OF");
                self.write_space();
                self.generate_expression(&pop.this)?;

                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
                    self.write(" (");
                    let mut first = true;
                    for col in &ct.columns {
                        if !first {
                            self.write(", ");
                        }
                        first = false;
                        self.generate_column_def(col)?;
                    }
                    for constraint in &ct.constraints {
                        if !first {
                            self.write(", ");
                        }
                        first = false;
                        self.generate_table_constraint(constraint)?;
                    }
                    self.write(")");
                }

                // Output partition bound spec: FOR VALUES ... or DEFAULT
                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
                    self.write_space();
                    self.write_keyword("FOR VALUES");
                    self.write_space();
                    self.generate_expression(&pop.expression)?;
                } else {
                    self.write_space();
                    self.write_keyword("DEFAULT");
                }
            } else {
                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
                self.generate_expression(partition_of)?;

                // Output columns/constraints if present
                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
                    self.write(" (");
                    let mut first = true;
                    for col in &ct.columns {
                        if !first {
                            self.write(", ");
                        }
                        first = false;
                        self.generate_column_def(col)?;
                    }
                    for constraint in &ct.constraints {
                        if !first {
                            self.write(", ");
                        }
                        first = false;
                        self.generate_table_constraint(constraint)?;
                    }
                    self.write(")");
                }
            }

            // Output table properties (e.g., PARTITION BY RANGE(population))
            for prop in &ct.properties {
                self.write_space();
                self.generate_expression(prop)?;
            }

            return Ok(());
        }

        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
        // This matches Python sqlglot's behavior for SQLite dialect
        self.sqlite_inline_pk_columns.clear();
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::SQLite)
        ) {
            for constraint in &ct.constraints {
                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
                    // Only inline if: single column, no constraint name, and column exists in table
                    if columns.len() == 1 && name.is_none() {
                        let pk_col_name = columns[0].name.to_lowercase();
                        // Check if this column exists in the table
                        if ct
                            .columns
                            .iter()
                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
                        {
                            self.sqlite_inline_pk_columns.insert(pk_col_name);
                        }
                    }
                }
            }
        }

        // Output columns if present (even for CTAS with columns)
        if !ct.columns.is_empty() {
            if self.config.pretty {
                // Pretty print: each column on new line
                self.write(" (");
                self.write_newline();
                self.indent_level += 1;
                for (i, col) in ct.columns.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                        self.write_newline();
                    }
                    self.write_indent();
                    self.generate_column_def(col)?;
                }
                // Table constraints (skip inlined PRIMARY KEY for SQLite)
                for constraint in &ct.constraints {
                    // Skip single-column PRIMARY KEY that was inlined for SQLite
                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
                        if columns.len() == 1
                            && name.is_none()
                            && self
                                .sqlite_inline_pk_columns
                                .contains(&columns[0].name.to_lowercase())
                        {
                            continue;
                        }
                    }
                    self.write(",");
                    self.write_newline();
                    self.write_indent();
                    self.generate_table_constraint(constraint)?;
                }
                self.indent_level -= 1;
                self.write_newline();
                self.write(")");
            } else {
                self.write(" (");
                for (i, col) in ct.columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_column_def(col)?;
                }
                // Table constraints (skip inlined PRIMARY KEY for SQLite)
                let mut first_constraint = true;
                for constraint in &ct.constraints {
                    // Skip single-column PRIMARY KEY that was inlined for SQLite
                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
                        if columns.len() == 1
                            && name.is_none()
                            && self
                                .sqlite_inline_pk_columns
                                .contains(&columns[0].name.to_lowercase())
                        {
                            continue;
                        }
                    }
                    if first_constraint {
                        self.write(", ");
                        first_constraint = false;
                    } else {
                        self.write(", ");
                    }
                    self.generate_table_constraint(constraint)?;
                }
                self.write(")");
            }
        } else if !ct.constraints.is_empty() {
            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
            let has_like_only = ct
                .constraints
                .iter()
                .all(|c| matches!(c, TableConstraint::Like { .. }));
            let has_tags_only = ct
                .constraints
                .iter()
                .all(|c| matches!(c, TableConstraint::Tags(_)));
            // MySQL: CREATE TABLE A LIKE B (no parens)
            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
            // PostgreSQL and others: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
            let is_mysql = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::MySQL)
                    | Some(crate::dialects::DialectType::SingleStore)
                    | Some(crate::dialects::DialectType::TiDB)
            );
            let use_parens = !(has_like_only && is_mysql) && !has_tags_only;
            if self.config.pretty && use_parens {
                self.write(" (");
                self.write_newline();
                self.indent_level += 1;
                for (i, constraint) in ct.constraints.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                        self.write_newline();
                    }
                    self.write_indent();
                    self.generate_table_constraint(constraint)?;
                }
                self.indent_level -= 1;
                self.write_newline();
                self.write(")");
            } else {
                if use_parens {
                    self.write(" (");
                } else {
                    self.write_space();
                }
                for (i, constraint) in ct.constraints.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_table_constraint(constraint)?;
                }
                if use_parens {
                    self.write(")");
                }
            }
        }

        // TSQL ON filegroup or ON filegroup (partition_column) clause
        if let Some(ref on_prop) = ct.on_property {
            self.write(" ");
            self.write_keyword("ON");
            self.write(" ");
            self.generate_expression(&on_prop.this)?;
        }

        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
        if !is_clickhouse {
            for prop in &ct.properties {
                if let Expression::SchemaCommentProperty(_) = prop {
                    if self.config.pretty {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    self.generate_expression(prop)?;
                }
            }
        }

        // WITH properties (output after columns if columns exist, otherwise before AS)
        if !ct.with_properties.is_empty() {
            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
            let is_snowflake_special_table = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::Snowflake)
            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
            if is_snowflake_special_table {
                for (key, value) in &ct.with_properties {
                    self.write_space();
                    self.write(key);
                    self.write("=");
                    self.write(value);
                }
            } else if self.config.pretty {
                self.write_newline();
                self.write_keyword("WITH");
                self.write(" (");
                self.write_newline();
                self.indent_level += 1;
                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                        self.write_newline();
                    }
                    self.write_indent();
                    self.write(key);
                    self.write("=");
                    self.write(value);
                }
                self.indent_level -= 1;
                self.write_newline();
                self.write(")");
            } else {
                self.write_space();
                self.write_keyword("WITH");
                self.write(" (");
                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write(key);
                    self.write("=");
                    self.write(value);
                }
                self.write(")");
            }
        }

        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
            if is_clickhouse && ct.as_select.is_some() {
                let mut pre = Vec::new();
                let mut post = Vec::new();
                for prop in &ct.properties {
                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
                        post.push(prop);
                    } else {
                        pre.push(prop);
                    }
                }
                (pre, post)
            } else {
                (ct.properties.iter().collect(), Vec::new())
            };

        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
        for prop in pre_as_properties {
            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
                continue;
            }
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
            if let Expression::Properties(props) = prop {
                let is_hive_dialect = matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::Hive)
                        | Some(crate::dialects::DialectType::Spark)
                        | Some(crate::dialects::DialectType::Databricks)
                        | Some(crate::dialects::DialectType::Athena)
                );
                let is_doris_starrocks = matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::Doris)
                        | Some(crate::dialects::DialectType::StarRocks)
                );
                if is_hive_dialect {
                    self.generate_tblproperties_clause(&props.expressions)?;
                } else if is_doris_starrocks {
                    self.generate_properties_clause(&props.expressions)?;
                } else {
                    self.generate_options_clause(&props.expressions)?;
                }
            } else {
                self.generate_expression(prop)?;
            }
        }

        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
        for prop in &ct.post_table_properties {
            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
                self.write(" WITH(");
                self.generate_system_versioning_content(svp)?;
                self.write(")");
            } else if let Expression::Properties(props) = prop {
                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
                let is_doris_starrocks = matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::Doris)
                        | Some(crate::dialects::DialectType::StarRocks)
                );
                self.write_space();
                if is_doris_starrocks {
                    self.generate_properties_clause(&props.expressions)?;
                } else {
                    self.generate_options_clause(&props.expressions)?;
                }
            } else {
                self.write_space();
                self.generate_expression(prop)?;
            }
        }

        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
        // Only output for StarRocks target
        if let Some(ref rollup) = ct.rollup {
            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
                self.write_space();
                self.generate_rollup_property(rollup)?;
            }
        }

        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
        // Only output for MySQL-compatible dialects; strip for others during transpilation
        // COMMENT is also used by Hive/Spark so we selectively preserve it
        let is_mysql_compatible = matches!(
            self.config.dialect,
            Some(DialectType::MySQL)
                | Some(DialectType::SingleStore)
                | Some(DialectType::Doris)
                | Some(DialectType::StarRocks)
                | None
        );
        let is_hive_compatible = matches!(
            self.config.dialect,
            Some(DialectType::Hive)
                | Some(DialectType::Spark)
                | Some(DialectType::Databricks)
                | Some(DialectType::Athena)
        );
        let mysql_pretty_options =
            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
        for (key, value) in &ct.mysql_table_options {
            // Skip non-MySQL-specific options for non-MySQL targets
            let should_output = if is_mysql_compatible {
                true
            } else if is_hive_compatible && key == "COMMENT" {
                true // COMMENT is valid in Hive/Spark table definitions
            } else {
                false
            };
            if should_output {
                if mysql_pretty_options {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
                self.write_keyword(key);
                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
                if key == "COMMENT" && !self.config.schema_comment_with_eq {
                    self.write_space();
                } else {
                    self.write("=");
                }
                self.write(value);
            }
        }

        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
        if ct.temporary
            && matches!(
                self.config.dialect,
                Some(DialectType::Spark) | Some(DialectType::Databricks)
            )
            && ct.as_select.is_none()
        {
            self.write_space();
            self.write_keyword("USING PARQUET");
        }

        // PostgreSQL INHERITS clause
        if !ct.inherits.is_empty() {
            self.write_space();
            self.write_keyword("INHERITS");
            self.write(" (");
            for (i, parent) in ct.inherits.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_table(parent)?;
            }
            self.write(")");
        }

        // CREATE TABLE AS SELECT
        if let Some(ref query) = ct.as_select {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            if ct.as_select_parenthesized {
                self.write("(");
            }
            self.generate_expression(query)?;
            if ct.as_select_parenthesized {
                self.write(")");
            }

            // Teradata: WITH DATA / WITH NO DATA
            if let Some(with_data) = ct.with_data {
                self.write_space();
                self.write_keyword("WITH");
                if !with_data {
                    self.write_space();
                    self.write_keyword("NO");
                }
                self.write_space();
                self.write_keyword("DATA");
            }

            // Teradata: AND STATISTICS / AND NO STATISTICS
            if let Some(with_statistics) = ct.with_statistics {
                self.write_space();
                self.write_keyword("AND");
                if !with_statistics {
                    self.write_space();
                    self.write_keyword("NO");
                }
                self.write_space();
                self.write_keyword("STATISTICS");
            }

            // Teradata: Index specifications
            for index in &ct.teradata_indexes {
                self.write_space();
                match index.kind {
                    TeradataIndexKind::NoPrimary => {
                        self.write_keyword("NO PRIMARY INDEX");
                    }
                    TeradataIndexKind::Primary => {
                        self.write_keyword("PRIMARY INDEX");
                    }
                    TeradataIndexKind::PrimaryAmp => {
                        self.write_keyword("PRIMARY AMP INDEX");
                    }
                    TeradataIndexKind::Unique => {
                        self.write_keyword("UNIQUE INDEX");
                    }
                    TeradataIndexKind::UniquePrimary => {
                        self.write_keyword("UNIQUE PRIMARY INDEX");
                    }
                    TeradataIndexKind::Secondary => {
                        self.write_keyword("INDEX");
                    }
                }
                // Output index name if present
                if let Some(ref name) = index.name {
                    self.write_space();
                    self.write(name);
                }
                // Output columns if present
                if !index.columns.is_empty() {
                    self.write(" (");
                    for (i, col) in index.columns.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.write(col);
                    }
                    self.write(")");
                }
            }

            // Teradata: ON COMMIT behavior for volatile tables
            if let Some(ref on_commit) = ct.on_commit {
                self.write_space();
                self.write_keyword("ON COMMIT");
                self.write_space();
                match on_commit {
                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
                }
            }

            if !post_as_properties.is_empty() {
                for prop in post_as_properties {
                    self.write_space();
                    self.generate_expression(prop)?;
                }
            }

            // Restore Athena Hive context before early return
            self.athena_hive_context = saved_athena_hive_context;
            return Ok(());
        }

        // ON COMMIT behavior (for non-CTAS tables)
        if let Some(ref on_commit) = ct.on_commit {
            self.write_space();
            self.write_keyword("ON COMMIT");
            self.write_space();
            match on_commit {
                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
            }
        }

        // Restore Athena Hive context
        self.athena_hive_context = saved_athena_hive_context;

        Ok(())
    }

    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
        // Output column name
        self.generate_identifier(&col.name)?;
        // Output data type if known
        if !matches!(col.data_type, DataType::Unknown) {
            self.write_space();
            self.generate_data_type(&col.data_type)?;
        }
        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
        for constraint in &col.constraints {
            if let ColumnConstraint::Path(path_expr) = constraint {
                self.write_space();
                self.write_keyword("PATH");
                self.write_space();
                self.generate_expression(path_expr)?;
            }
        }
        Ok(())
    }

    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
        // Check if this is a TSQL computed column (no data type)
        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
            && col
                .constraints
                .iter()
                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
        let omit_computed_type = !self.config.computed_column_with_type
            && col
                .constraints
                .iter()
                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));

        // Check if this is a partition column spec (no data type, type is Unknown)
        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);

        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
        // Also check the no_type flag for SQLite columns without types
        let has_no_type = col.no_type
            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
                && col.constraints.is_empty());

        self.generate_identifier(&col.name)?;

        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
        let serial_expansion = if matches!(
            self.config.dialect,
            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
        ) {
            if let DataType::Custom { ref name } = col.data_type {
                match name.to_uppercase().as_str() {
                    "SERIAL" => Some("INT"),
                    "BIGSERIAL" => Some("BIGINT"),
                    "SMALLSERIAL" => Some("SMALLINT"),
                    _ => None,
                }
            } else {
                None
            }
        } else {
            None
        };

        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
        {
            self.write_space();
            if let Some(int_type) = serial_expansion {
                // SERIAL -> INT (+ constraints added below)
                self.write_keyword(int_type);
            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
                // For DuckDB: convert unsigned integer types to their unsigned equivalents
                let unsigned_type = match &col.data_type {
                    DataType::Int { .. } => Some("UINTEGER"),
                    DataType::BigInt { .. } => Some("UBIGINT"),
                    DataType::SmallInt { .. } => Some("USMALLINT"),
                    DataType::TinyInt { .. } => Some("UTINYINT"),
                    _ => None,
                };
                if let Some(utype) = unsigned_type {
                    self.write_keyword(utype);
                } else {
                    self.generate_data_type(&col.data_type)?;
                }
            } else {
                self.generate_data_type(&col.data_type)?;
            }
        }

        // MySQL type modifiers (must come right after data type)
        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
            self.write_space();
            self.write_keyword("UNSIGNED");
        }
        if col.zerofill {
            self.write_space();
            self.write_keyword("ZEROFILL");
        }

        // Teradata column attributes (must come right after data type, in specific order)
        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS

        if let Some(ref charset) = col.character_set {
            self.write_space();
            self.write_keyword("CHARACTER SET");
            self.write_space();
            self.write(charset);
        }

        if col.uppercase {
            self.write_space();
            self.write_keyword("UPPERCASE");
        }

        if let Some(casespecific) = col.casespecific {
            self.write_space();
            if casespecific {
                self.write_keyword("CASESPECIFIC");
            } else {
                self.write_keyword("NOT CASESPECIFIC");
            }
        }

        if let Some(ref format) = col.format {
            self.write_space();
            self.write_keyword("FORMAT");
            self.write(" '");
            self.write(format);
            self.write("'");
        }

        if let Some(ref title) = col.title {
            self.write_space();
            self.write_keyword("TITLE");
            self.write(" '");
            self.write(title);
            self.write("'");
        }

        if let Some(length) = col.inline_length {
            self.write_space();
            self.write_keyword("INLINE LENGTH");
            self.write(" ");
            self.write(&length.to_string());
        }

        if let Some(ref compress) = col.compress {
            self.write_space();
            self.write_keyword("COMPRESS");
            if !compress.is_empty() {
                // Single string literal: output without parentheses (Teradata syntax)
                if compress.len() == 1 {
                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
                        self.write_space();
                        self.generate_expression(&compress[0])?;
                    } else {
                        self.write(" (");
                        self.generate_expression(&compress[0])?;
                        self.write(")");
                    }
                } else {
                    self.write(" (");
                    for (i, val) in compress.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(val)?;
                    }
                    self.write(")");
                }
            }
        }

        // Column constraints - output in original order if constraint_order is populated
        // Otherwise fall back to legacy fixed order for backward compatibility
        if !col.constraint_order.is_empty() {
            // Use constraint_order for original ordering
            // Track indices for constraints stored in the constraints Vec
            let mut references_idx = 0;
            let mut check_idx = 0;
            let mut generated_idx = 0;
            let mut collate_idx = 0;
            let mut comment_idx = 0;
            // The preprocessing in dialects/mod.rs now handles the correct ordering of
            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
            let defer_not_null_after_identity = false;
            let mut pending_not_null_after_identity = false;

            for constraint_type in &col.constraint_order {
                match constraint_type {
                    ConstraintType::PrimaryKey => {
                        // Materialize doesn't support PRIMARY KEY column constraints
                        if col.primary_key
                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
                        {
                            if let Some(ref cname) = col.primary_key_constraint_name {
                                self.write_space();
                                self.write_keyword("CONSTRAINT");
                                self.write_space();
                                self.write(cname);
                            }
                            self.write_space();
                            self.write_keyword("PRIMARY KEY");
                            if let Some(ref order) = col.primary_key_order {
                                self.write_space();
                                match order {
                                    SortOrder::Asc => self.write_keyword("ASC"),
                                    SortOrder::Desc => self.write_keyword("DESC"),
                                }
                            }
                        }
                    }
                    ConstraintType::Unique => {
                        if col.unique {
                            if let Some(ref cname) = col.unique_constraint_name {
                                self.write_space();
                                self.write_keyword("CONSTRAINT");
                                self.write_space();
                                self.write(cname);
                            }
                            self.write_space();
                            self.write_keyword("UNIQUE");
                            // PostgreSQL 15+: NULLS NOT DISTINCT
                            if col.unique_nulls_not_distinct {
                                self.write(" NULLS NOT DISTINCT");
                            }
                        }
                    }
                    ConstraintType::NotNull => {
                        if col.nullable == Some(false) {
                            if defer_not_null_after_identity {
                                pending_not_null_after_identity = true;
                                continue;
                            }
                            if let Some(ref cname) = col.not_null_constraint_name {
                                self.write_space();
                                self.write_keyword("CONSTRAINT");
                                self.write_space();
                                self.write(cname);
                            }
                            self.write_space();
                            self.write_keyword("NOT NULL");
                        }
                    }
                    ConstraintType::Null => {
                        if col.nullable == Some(true) {
                            self.write_space();
                            self.write_keyword("NULL");
                        }
                    }
                    ConstraintType::Default => {
                        if let Some(ref default) = col.default {
                            self.write_space();
                            self.write_keyword("DEFAULT");
                            self.write_space();
                            self.generate_expression(default)?;
                        }
                    }
                    ConstraintType::AutoIncrement => {
                        if col.auto_increment {
                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
                            if matches!(
                                self.config.dialect,
                                Some(crate::dialects::DialectType::DuckDB)
                            ) {
                                // Skip - DuckDB uses sequences or rowid instead
                            } else if matches!(
                                self.config.dialect,
                                Some(crate::dialects::DialectType::Materialize)
                            ) {
                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
                                if !matches!(col.nullable, Some(false)) {
                                    self.write_space();
                                    self.write_keyword("NOT NULL");
                                }
                            } else if matches!(
                                self.config.dialect,
                                Some(crate::dialects::DialectType::PostgreSQL)
                            ) {
                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
                                self.write_space();
                                self.generate_auto_increment_keyword(col)?;
                            } else {
                                self.write_space();
                                self.generate_auto_increment_keyword(col)?;
                                if pending_not_null_after_identity {
                                    self.write_space();
                                    self.write_keyword("NOT NULL");
                                    pending_not_null_after_identity = false;
                                }
                            }
                        } // close else for DuckDB skip
                    }
                    ConstraintType::References => {
                        // Find next References constraint
                        while references_idx < col.constraints.len() {
                            if let ColumnConstraint::References(fk_ref) =
                                &col.constraints[references_idx]
                            {
                                // CONSTRAINT name if present
                                if let Some(ref name) = fk_ref.constraint_name {
                                    self.write_space();
                                    self.write_keyword("CONSTRAINT");
                                    self.write_space();
                                    self.write(name);
                                }
                                self.write_space();
                                if fk_ref.has_foreign_key_keywords {
                                    self.write_keyword("FOREIGN KEY");
                                    self.write_space();
                                }
                                self.write_keyword("REFERENCES");
                                self.write_space();
                                self.generate_table(&fk_ref.table)?;
                                if !fk_ref.columns.is_empty() {
                                    self.write(" (");
                                    for (i, c) in fk_ref.columns.iter().enumerate() {
                                        if i > 0 {
                                            self.write(", ");
                                        }
                                        self.generate_identifier(c)?;
                                    }
                                    self.write(")");
                                }
                                self.generate_referential_actions(fk_ref)?;
                                references_idx += 1;
                                break;
                            }
                            references_idx += 1;
                        }
                    }
                    ConstraintType::Check => {
                        // Find next Check constraint
                        while check_idx < col.constraints.len() {
                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
                                // Output CONSTRAINT name if present (only for first CHECK)
                                if check_idx == 0 {
                                    if let Some(ref cname) = col.check_constraint_name {
                                        self.write_space();
                                        self.write_keyword("CONSTRAINT");
                                        self.write_space();
                                        self.write(cname);
                                    }
                                }
                                self.write_space();
                                self.write_keyword("CHECK");
                                self.write(" (");
                                self.generate_expression(expr)?;
                                self.write(")");
                                check_idx += 1;
                                break;
                            }
                            check_idx += 1;
                        }
                    }
                    ConstraintType::GeneratedAsIdentity => {
                        // Find next GeneratedAsIdentity constraint
                        while generated_idx < col.constraints.len() {
                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
                                &col.constraints[generated_idx]
                            {
                                self.write_space();
                                // Redshift uses IDENTITY(start, increment) syntax
                                if matches!(
                                    self.config.dialect,
                                    Some(crate::dialects::DialectType::Redshift)
                                ) {
                                    self.write_keyword("IDENTITY");
                                    self.write("(");
                                    if let Some(ref start) = gen.start {
                                        self.generate_expression(start)?;
                                    } else {
                                        self.write("0");
                                    }
                                    self.write(", ");
                                    if let Some(ref incr) = gen.increment {
                                        self.generate_expression(incr)?;
                                    } else {
                                        self.write("1");
                                    }
                                    self.write(")");
                                } else {
                                    self.write_keyword("GENERATED");
                                    if gen.always {
                                        self.write_space();
                                        self.write_keyword("ALWAYS");
                                    } else {
                                        self.write_space();
                                        self.write_keyword("BY DEFAULT");
                                        if gen.on_null {
                                            self.write_space();
                                            self.write_keyword("ON NULL");
                                        }
                                    }
                                    self.write_space();
                                    self.write_keyword("AS IDENTITY");

                                    let has_options = gen.start.is_some()
                                        || gen.increment.is_some()
                                        || gen.minvalue.is_some()
                                        || gen.maxvalue.is_some()
                                        || gen.cycle.is_some();
                                    if has_options {
                                        self.write(" (");
                                        let mut first = true;
                                        if let Some(ref start) = gen.start {
                                            if !first {
                                                self.write(" ");
                                            }
                                            first = false;
                                            self.write_keyword("START WITH");
                                            self.write_space();
                                            self.generate_expression(start)?;
                                        }
                                        if let Some(ref incr) = gen.increment {
                                            if !first {
                                                self.write(" ");
                                            }
                                            first = false;
                                            self.write_keyword("INCREMENT BY");
                                            self.write_space();
                                            self.generate_expression(incr)?;
                                        }
                                        if let Some(ref minv) = gen.minvalue {
                                            if !first {
                                                self.write(" ");
                                            }
                                            first = false;
                                            self.write_keyword("MINVALUE");
                                            self.write_space();
                                            self.generate_expression(minv)?;
                                        }
                                        if let Some(ref maxv) = gen.maxvalue {
                                            if !first {
                                                self.write(" ");
                                            }
                                            first = false;
                                            self.write_keyword("MAXVALUE");
                                            self.write_space();
                                            self.generate_expression(maxv)?;
                                        }
                                        if let Some(cycle) = gen.cycle {
                                            if !first {
                                                self.write(" ");
                                            }
                                            if cycle {
                                                self.write_keyword("CYCLE");
                                            } else {
                                                self.write_keyword("NO CYCLE");
                                            }
                                        }
                                        self.write(")");
                                    }
                                }
                                generated_idx += 1;
                                break;
                            }
                            generated_idx += 1;
                        }
                    }
                    ConstraintType::Collate => {
                        // Find next Collate constraint
                        while collate_idx < col.constraints.len() {
                            if let ColumnConstraint::Collate(collation) =
                                &col.constraints[collate_idx]
                            {
                                self.write_space();
                                self.write_keyword("COLLATE");
                                self.write_space();
                                self.generate_identifier(collation)?;
                                collate_idx += 1;
                                break;
                            }
                            collate_idx += 1;
                        }
                    }
                    ConstraintType::Comment => {
                        // Find next Comment constraint
                        while comment_idx < col.constraints.len() {
                            if let ColumnConstraint::Comment(comment) =
                                &col.constraints[comment_idx]
                            {
                                self.write_space();
                                self.write_keyword("COMMENT");
                                self.write_space();
                                self.generate_string_literal(comment)?;
                                comment_idx += 1;
                                break;
                            }
                            comment_idx += 1;
                        }
                    }
                    ConstraintType::Tags => {
                        // Find next Tags constraint (Snowflake)
                        for constraint in &col.constraints {
                            if let ColumnConstraint::Tags(tags) = constraint {
                                self.write_space();
                                self.write_keyword("TAG");
                                self.write(" (");
                                for (i, expr) in tags.expressions.iter().enumerate() {
                                    if i > 0 {
                                        self.write(", ");
                                    }
                                    self.generate_expression(expr)?;
                                }
                                self.write(")");
                                break;
                            }
                        }
                    }
                    ConstraintType::ComputedColumn => {
                        // Find next ComputedColumn constraint
                        for constraint in &col.constraints {
                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
                                self.write_space();
                                self.generate_computed_column_inline(cc)?;
                                break;
                            }
                        }
                    }
                    ConstraintType::GeneratedAsRow => {
                        // Find next GeneratedAsRow constraint
                        for constraint in &col.constraints {
                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
                                self.write_space();
                                self.generate_generated_as_row_inline(gar)?;
                                break;
                            }
                        }
                    }
                    ConstraintType::OnUpdate => {
                        if let Some(ref expr) = col.on_update {
                            self.write_space();
                            self.write_keyword("ON UPDATE");
                            self.write_space();
                            self.generate_expression(expr)?;
                        }
                    }
                    ConstraintType::Encode => {
                        if let Some(ref encoding) = col.encoding {
                            self.write_space();
                            self.write_keyword("ENCODE");
                            self.write_space();
                            self.write(encoding);
                        }
                    }
                    ConstraintType::Path => {
                        // Find next Path constraint
                        for constraint in &col.constraints {
                            if let ColumnConstraint::Path(path_expr) = constraint {
                                self.write_space();
                                self.write_keyword("PATH");
                                self.write_space();
                                self.generate_expression(path_expr)?;
                                break;
                            }
                        }
                    }
                }
            }
            if pending_not_null_after_identity {
                self.write_space();
                self.write_keyword("NOT NULL");
            }
        } else {
            // Legacy fixed order for backward compatibility
            if col.primary_key {
                self.write_space();
                self.write_keyword("PRIMARY KEY");
                if let Some(ref order) = col.primary_key_order {
                    self.write_space();
                    match order {
                        SortOrder::Asc => self.write_keyword("ASC"),
                        SortOrder::Desc => self.write_keyword("DESC"),
                    }
                }
            }

            if col.unique {
                self.write_space();
                self.write_keyword("UNIQUE");
                // PostgreSQL 15+: NULLS NOT DISTINCT
                if col.unique_nulls_not_distinct {
                    self.write(" NULLS NOT DISTINCT");
                }
            }

            match col.nullable {
                Some(false) => {
                    self.write_space();
                    self.write_keyword("NOT NULL");
                }
                Some(true) => {
                    self.write_space();
                    self.write_keyword("NULL");
                }
                None => {}
            }

            if let Some(ref default) = col.default {
                self.write_space();
                self.write_keyword("DEFAULT");
                self.write_space();
                self.generate_expression(default)?;
            }

            if col.auto_increment {
                self.write_space();
                self.generate_auto_increment_keyword(col)?;
            }

            // Column-level constraints from Vec
            for constraint in &col.constraints {
                match constraint {
                    ColumnConstraint::References(fk_ref) => {
                        self.write_space();
                        if fk_ref.has_foreign_key_keywords {
                            self.write_keyword("FOREIGN KEY");
                            self.write_space();
                        }
                        self.write_keyword("REFERENCES");
                        self.write_space();
                        self.generate_table(&fk_ref.table)?;
                        if !fk_ref.columns.is_empty() {
                            self.write(" (");
                            for (i, c) in fk_ref.columns.iter().enumerate() {
                                if i > 0 {
                                    self.write(", ");
                                }
                                self.generate_identifier(c)?;
                            }
                            self.write(")");
                        }
                        self.generate_referential_actions(fk_ref)?;
                    }
                    ColumnConstraint::Check(expr) => {
                        self.write_space();
                        self.write_keyword("CHECK");
                        self.write(" (");
                        self.generate_expression(expr)?;
                        self.write(")");
                    }
                    ColumnConstraint::GeneratedAsIdentity(gen) => {
                        self.write_space();
                        // Redshift uses IDENTITY(start, increment) syntax
                        if matches!(
                            self.config.dialect,
                            Some(crate::dialects::DialectType::Redshift)
                        ) {
                            self.write_keyword("IDENTITY");
                            self.write("(");
                            if let Some(ref start) = gen.start {
                                self.generate_expression(start)?;
                            } else {
                                self.write("0");
                            }
                            self.write(", ");
                            if let Some(ref incr) = gen.increment {
                                self.generate_expression(incr)?;
                            } else {
                                self.write("1");
                            }
                            self.write(")");
                        } else {
                            self.write_keyword("GENERATED");
                            if gen.always {
                                self.write_space();
                                self.write_keyword("ALWAYS");
                            } else {
                                self.write_space();
                                self.write_keyword("BY DEFAULT");
                                if gen.on_null {
                                    self.write_space();
                                    self.write_keyword("ON NULL");
                                }
                            }
                            self.write_space();
                            self.write_keyword("AS IDENTITY");

                            let has_options = gen.start.is_some()
                                || gen.increment.is_some()
                                || gen.minvalue.is_some()
                                || gen.maxvalue.is_some()
                                || gen.cycle.is_some();
                            if has_options {
                                self.write(" (");
                                let mut first = true;
                                if let Some(ref start) = gen.start {
                                    if !first {
                                        self.write(" ");
                                    }
                                    first = false;
                                    self.write_keyword("START WITH");
                                    self.write_space();
                                    self.generate_expression(start)?;
                                }
                                if let Some(ref incr) = gen.increment {
                                    if !first {
                                        self.write(" ");
                                    }
                                    first = false;
                                    self.write_keyword("INCREMENT BY");
                                    self.write_space();
                                    self.generate_expression(incr)?;
                                }
                                if let Some(ref minv) = gen.minvalue {
                                    if !first {
                                        self.write(" ");
                                    }
                                    first = false;
                                    self.write_keyword("MINVALUE");
                                    self.write_space();
                                    self.generate_expression(minv)?;
                                }
                                if let Some(ref maxv) = gen.maxvalue {
                                    if !first {
                                        self.write(" ");
                                    }
                                    first = false;
                                    self.write_keyword("MAXVALUE");
                                    self.write_space();
                                    self.generate_expression(maxv)?;
                                }
                                if let Some(cycle) = gen.cycle {
                                    if !first {
                                        self.write(" ");
                                    }
                                    if cycle {
                                        self.write_keyword("CYCLE");
                                    } else {
                                        self.write_keyword("NO CYCLE");
                                    }
                                }
                                self.write(")");
                            }
                        }
                    }
                    ColumnConstraint::Collate(collation) => {
                        self.write_space();
                        self.write_keyword("COLLATE");
                        self.write_space();
                        self.generate_identifier(collation)?;
                    }
                    ColumnConstraint::Comment(comment) => {
                        self.write_space();
                        self.write_keyword("COMMENT");
                        self.write_space();
                        self.generate_string_literal(comment)?;
                    }
                    ColumnConstraint::Path(path_expr) => {
                        self.write_space();
                        self.write_keyword("PATH");
                        self.write_space();
                        self.generate_expression(path_expr)?;
                    }
                    _ => {} // Other constraints handled above
                }
            }

            // Redshift: ENCODE encoding_type (legacy path)
            if let Some(ref encoding) = col.encoding {
                self.write_space();
                self.write_keyword("ENCODE");
                self.write_space();
                self.write(encoding);
            }
        }

        // ClickHouse: CODEC(...)
        if let Some(ref codec) = col.codec {
            self.write_space();
            self.write_keyword("CODEC");
            self.write("(");
            self.write(codec);
            self.write(")");
        }

        // ClickHouse: EPHEMERAL [expr]
        if let Some(ref ephemeral) = col.ephemeral {
            self.write_space();
            self.write_keyword("EPHEMERAL");
            if let Some(ref expr) = ephemeral {
                self.write_space();
                self.generate_expression(expr)?;
            }
        }

        // ClickHouse: MATERIALIZED expr
        if let Some(ref mat_expr) = col.materialized_expr {
            self.write_space();
            self.write_keyword("MATERIALIZED");
            self.write_space();
            self.generate_expression(mat_expr)?;
        }

        // ClickHouse: ALIAS expr
        if let Some(ref alias_expr) = col.alias_expr {
            self.write_space();
            self.write_keyword("ALIAS");
            self.write_space();
            self.generate_expression(alias_expr)?;
        }

        // ClickHouse: TTL expr
        if let Some(ref ttl_expr) = col.ttl_expr {
            self.write_space();
            self.write_keyword("TTL");
            self.write_space();
            self.generate_expression(ttl_expr)?;
        }

        // TSQL: NOT FOR REPLICATION
        if col.not_for_replication
            && matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::TSQL)
                    | Some(crate::dialects::DialectType::Fabric)
            )
        {
            self.write_space();
            self.write_keyword("NOT FOR REPLICATION");
        }

        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
        if !col.options.is_empty() {
            self.write_space();
            self.generate_options_clause(&col.options)?;
        }

        // SQLite: Inline PRIMARY KEY from table constraint
        // This comes at the end, after all existing column constraints
        if !col.primary_key
            && self
                .sqlite_inline_pk_columns
                .contains(&col.name.name.to_lowercase())
        {
            self.write_space();
            self.write_keyword("PRIMARY KEY");
        }

        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
        if serial_expansion.is_some() {
            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
                self.write_space();
                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
                self.write_space();
                self.write_keyword("NOT NULL");
            }
        }

        Ok(())
    }

    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
        match constraint {
            TableConstraint::PrimaryKey {
                name,
                columns,
                include_columns,
                modifiers,
                has_constraint_keyword,
            } => {
                if let Some(ref n) = name {
                    if *has_constraint_keyword {
                        self.write_keyword("CONSTRAINT");
                        self.write_space();
                        self.generate_identifier(n)?;
                        self.write_space();
                    }
                }
                self.write_keyword("PRIMARY KEY");
                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
                if let Some(ref clustered) = modifiers.clustered {
                    self.write_space();
                    self.write_keyword(clustered);
                }
                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
                if let Some(ref n) = name {
                    if !*has_constraint_keyword {
                        self.write_space();
                        self.generate_identifier(n)?;
                    }
                }
                self.write(" (");
                for (i, col) in columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
                if !include_columns.is_empty() {
                    self.write_space();
                    self.write_keyword("INCLUDE");
                    self.write(" (");
                    for (i, col) in include_columns.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                }
                self.generate_constraint_modifiers(modifiers);
            }
            TableConstraint::Unique {
                name,
                columns,
                columns_parenthesized,
                modifiers,
                has_constraint_keyword,
                nulls_not_distinct,
            } => {
                if let Some(ref n) = name {
                    if *has_constraint_keyword {
                        self.write_keyword("CONSTRAINT");
                        self.write_space();
                        self.generate_identifier(n)?;
                        self.write_space();
                    }
                }
                self.write_keyword("UNIQUE");
                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
                if let Some(ref clustered) = modifiers.clustered {
                    self.write_space();
                    self.write_keyword(clustered);
                }
                // PostgreSQL 15+: NULLS NOT DISTINCT
                if *nulls_not_distinct {
                    self.write(" NULLS NOT DISTINCT");
                }
                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
                if let Some(ref n) = name {
                    if !*has_constraint_keyword {
                        self.write_space();
                        self.generate_identifier(n)?;
                    }
                }
                if *columns_parenthesized {
                    self.write(" (");
                    for (i, col) in columns.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                } else {
                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
                    for col in columns.iter() {
                        self.write_space();
                        self.generate_identifier(col)?;
                    }
                }
                self.generate_constraint_modifiers(modifiers);
            }
            TableConstraint::ForeignKey {
                name,
                columns,
                references,
                on_delete,
                on_update,
                modifiers,
            } => {
                if let Some(ref n) = name {
                    self.write_keyword("CONSTRAINT");
                    self.write_space();
                    self.generate_identifier(n)?;
                    self.write_space();
                }
                self.write_keyword("FOREIGN KEY");
                self.write(" (");
                for (i, col) in columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
                if let Some(ref refs) = references {
                    self.write(" ");
                    self.write_keyword("REFERENCES");
                    self.write_space();
                    self.generate_table(&refs.table)?;
                    if !refs.columns.is_empty() {
                        if self.config.pretty {
                            self.write(" (");
                            self.write_newline();
                            self.indent_level += 1;
                            for (i, col) in refs.columns.iter().enumerate() {
                                if i > 0 {
                                    self.write(",");
                                    self.write_newline();
                                }
                                self.write_indent();
                                self.generate_identifier(col)?;
                            }
                            self.indent_level -= 1;
                            self.write_newline();
                            self.write_indent();
                            self.write(")");
                        } else {
                            self.write(" (");
                            for (i, col) in refs.columns.iter().enumerate() {
                                if i > 0 {
                                    self.write(", ");
                                }
                                self.generate_identifier(col)?;
                            }
                            self.write(")");
                        }
                    }
                    self.generate_referential_actions(refs)?;
                } else {
                    // No REFERENCES - output ON DELETE/ON UPDATE directly
                    if let Some(ref action) = on_delete {
                        self.write_space();
                        self.write_keyword("ON DELETE");
                        self.write_space();
                        self.generate_referential_action(action);
                    }
                    if let Some(ref action) = on_update {
                        self.write_space();
                        self.write_keyword("ON UPDATE");
                        self.write_space();
                        self.generate_referential_action(action);
                    }
                }
                self.generate_constraint_modifiers(modifiers);
            }
            TableConstraint::Check {
                name,
                expression,
                modifiers,
            } => {
                if let Some(ref n) = name {
                    self.write_keyword("CONSTRAINT");
                    self.write_space();
                    self.generate_identifier(n)?;
                    self.write_space();
                }
                self.write_keyword("CHECK");
                self.write(" (");
                self.generate_expression(expression)?;
                self.write(")");
                self.generate_constraint_modifiers(modifiers);
            }
            TableConstraint::Index {
                name,
                columns,
                kind,
                modifiers,
                use_key_keyword,
                expression,
                index_type,
                granularity,
            } => {
                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
                if expression.is_some() {
                    self.write_keyword("INDEX");
                    if let Some(ref n) = name {
                        self.write_space();
                        self.generate_identifier(n)?;
                    }
                    if let Some(ref expr) = expression {
                        self.write_space();
                        self.generate_expression(expr)?;
                    }
                    if let Some(ref idx_type) = index_type {
                        self.write_space();
                        self.write_keyword("TYPE");
                        self.write_space();
                        self.generate_expression(idx_type)?;
                    }
                    if let Some(ref gran) = granularity {
                        self.write_space();
                        self.write_keyword("GRANULARITY");
                        self.write_space();
                        self.generate_expression(gran)?;
                    }
                } else {
                    // Standard INDEX syntax
                    // Determine the index keyword to use
                    // MySQL normalizes KEY to INDEX
                    use crate::dialects::DialectType;
                    let index_keyword = if *use_key_keyword
                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
                    {
                        "KEY"
                    } else {
                        "INDEX"
                    };

                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
                    if let Some(ref k) = kind {
                        self.write_keyword(k);
                        // For UNIQUE, don't add INDEX/KEY keyword
                        if k != "UNIQUE" {
                            self.write_space();
                            self.write_keyword(index_keyword);
                        }
                    } else {
                        self.write_keyword(index_keyword);
                    }

                    // Output USING before name if using_before_columns is true and there's no name
                    if modifiers.using_before_columns && name.is_none() {
                        if let Some(ref using) = modifiers.using {
                            self.write_space();
                            self.write_keyword("USING");
                            self.write_space();
                            self.write_keyword(using);
                        }
                    }

                    // Output index name if present
                    if let Some(ref n) = name {
                        self.write_space();
                        self.generate_identifier(n)?;
                    }

                    // Output USING after name but before columns if using_before_columns and there's a name
                    if modifiers.using_before_columns && name.is_some() {
                        if let Some(ref using) = modifiers.using {
                            self.write_space();
                            self.write_keyword("USING");
                            self.write_space();
                            self.write_keyword(using);
                        }
                    }

                    // Output columns
                    self.write(" (");
                    for (i, col) in columns.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");

                    // Output USING after columns if not using_before_columns
                    if !modifiers.using_before_columns {
                        if let Some(ref using) = modifiers.using {
                            self.write_space();
                            self.write_keyword("USING");
                            self.write_space();
                            self.write_keyword(using);
                        }
                    }

                    // Output other constraint modifiers (but skip USING since we already handled it)
                    self.generate_constraint_modifiers_without_using(modifiers);
                }
            }
            TableConstraint::Projection { name, expression } => {
                // ClickHouse: PROJECTION name (SELECT ...)
                self.write_keyword("PROJECTION");
                self.write_space();
                self.generate_identifier(name)?;
                self.write(" (");
                self.generate_expression(expression)?;
                self.write(")");
            }
            TableConstraint::Like { source, options } => {
                self.write_keyword("LIKE");
                self.write_space();
                self.generate_table(source)?;
                for (action, prop) in options {
                    self.write_space();
                    match action {
                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
                    }
                    self.write_space();
                    self.write_keyword(prop);
                }
            }
            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
                self.write_keyword("PERIOD FOR SYSTEM_TIME");
                self.write(" (");
                self.generate_identifier(start_col)?;
                self.write(", ");
                self.generate_identifier(end_col)?;
                self.write(")");
            }
            TableConstraint::Exclude {
                name,
                using,
                elements,
                include_columns,
                where_clause,
                with_params,
                using_index_tablespace,
                modifiers: _,
            } => {
                if let Some(ref n) = name {
                    self.write_keyword("CONSTRAINT");
                    self.write_space();
                    self.generate_identifier(n)?;
                    self.write_space();
                }
                self.write_keyword("EXCLUDE");
                if let Some(ref method) = using {
                    self.write_space();
                    self.write_keyword("USING");
                    self.write_space();
                    self.write(method);
                    self.write("(");
                } else {
                    self.write(" (");
                }
                for (i, elem) in elements.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write(&elem.expression);
                    self.write_space();
                    self.write_keyword("WITH");
                    self.write_space();
                    self.write(&elem.operator);
                }
                self.write(")");
                if !include_columns.is_empty() {
                    self.write_space();
                    self.write_keyword("INCLUDE");
                    self.write(" (");
                    for (i, col) in include_columns.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                }
                if !with_params.is_empty() {
                    self.write_space();
                    self.write_keyword("WITH");
                    self.write(" (");
                    for (i, (key, val)) in with_params.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.write(key);
                        self.write("=");
                        self.write(val);
                    }
                    self.write(")");
                }
                if let Some(ref tablespace) = using_index_tablespace {
                    self.write_space();
                    self.write_keyword("USING INDEX TABLESPACE");
                    self.write_space();
                    self.write(tablespace);
                }
                if let Some(ref where_expr) = where_clause {
                    self.write_space();
                    self.write_keyword("WHERE");
                    self.write(" (");
                    self.generate_expression(where_expr)?;
                    self.write(")");
                }
            }
            TableConstraint::Tags(tags) => {
                self.write_keyword("TAG");
                self.write(" (");
                for (i, expr) in tags.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
            TableConstraint::InitiallyDeferred { deferred } => {
                self.write_keyword("INITIALLY");
                self.write_space();
                if *deferred {
                    self.write_keyword("DEFERRED");
                } else {
                    self.write_keyword("IMMEDIATE");
                }
            }
        }
        Ok(())
    }

    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
        // Output USING BTREE/HASH (MySQL) - comes first
        if let Some(using) = &modifiers.using {
            self.write_space();
            self.write_keyword("USING");
            self.write_space();
            self.write_keyword(using);
        }
        // Output ENFORCED/NOT ENFORCED
        if let Some(enforced) = modifiers.enforced {
            self.write_space();
            if enforced {
                self.write_keyword("ENFORCED");
            } else {
                self.write_keyword("NOT ENFORCED");
            }
        }
        // Output DEFERRABLE/NOT DEFERRABLE
        if let Some(deferrable) = modifiers.deferrable {
            self.write_space();
            if deferrable {
                self.write_keyword("DEFERRABLE");
            } else {
                self.write_keyword("NOT DEFERRABLE");
            }
        }
        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
        if let Some(initially_deferred) = modifiers.initially_deferred {
            self.write_space();
            if initially_deferred {
                self.write_keyword("INITIALLY DEFERRED");
            } else {
                self.write_keyword("INITIALLY IMMEDIATE");
            }
        }
        // Output NORELY
        if modifiers.norely {
            self.write_space();
            self.write_keyword("NORELY");
        }
        // Output RELY
        if modifiers.rely {
            self.write_space();
            self.write_keyword("RELY");
        }
        // Output NOT VALID (PostgreSQL)
        if modifiers.not_valid {
            self.write_space();
            self.write_keyword("NOT VALID");
        }
        // Output ON CONFLICT (SQLite)
        if let Some(on_conflict) = &modifiers.on_conflict {
            self.write_space();
            self.write_keyword("ON CONFLICT");
            self.write_space();
            self.write_keyword(on_conflict);
        }
        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
        if !modifiers.with_options.is_empty() {
            self.write_space();
            self.write_keyword("WITH");
            self.write(" (");
            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(key);
                self.write("=");
                self.write(value);
            }
            self.write(")");
        }
        // Output TSQL ON filegroup
        if let Some(ref fg) = modifiers.on_filegroup {
            self.write_space();
            self.write_keyword("ON");
            self.write_space();
            let _ = self.generate_identifier(fg);
        }
    }

    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
        // Output ENFORCED/NOT ENFORCED
        if let Some(enforced) = modifiers.enforced {
            self.write_space();
            if enforced {
                self.write_keyword("ENFORCED");
            } else {
                self.write_keyword("NOT ENFORCED");
            }
        }
        // Output DEFERRABLE/NOT DEFERRABLE
        if let Some(deferrable) = modifiers.deferrable {
            self.write_space();
            if deferrable {
                self.write_keyword("DEFERRABLE");
            } else {
                self.write_keyword("NOT DEFERRABLE");
            }
        }
        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
        if let Some(initially_deferred) = modifiers.initially_deferred {
            self.write_space();
            if initially_deferred {
                self.write_keyword("INITIALLY DEFERRED");
            } else {
                self.write_keyword("INITIALLY IMMEDIATE");
            }
        }
        // Output NORELY
        if modifiers.norely {
            self.write_space();
            self.write_keyword("NORELY");
        }
        // Output RELY
        if modifiers.rely {
            self.write_space();
            self.write_keyword("RELY");
        }
        // Output NOT VALID (PostgreSQL)
        if modifiers.not_valid {
            self.write_space();
            self.write_keyword("NOT VALID");
        }
        // Output ON CONFLICT (SQLite)
        if let Some(on_conflict) = &modifiers.on_conflict {
            self.write_space();
            self.write_keyword("ON CONFLICT");
            self.write_space();
            self.write_keyword(on_conflict);
        }
        // Output MySQL index-specific modifiers
        self.generate_index_specific_modifiers(modifiers);
    }

    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
        if let Some(ref comment) = modifiers.comment {
            self.write_space();
            self.write_keyword("COMMENT");
            self.write(" '");
            self.write(comment);
            self.write("'");
        }
        if let Some(visible) = modifiers.visible {
            self.write_space();
            if visible {
                self.write_keyword("VISIBLE");
            } else {
                self.write_keyword("INVISIBLE");
            }
        }
        if let Some(ref attr) = modifiers.engine_attribute {
            self.write_space();
            self.write_keyword("ENGINE_ATTRIBUTE");
            self.write(" = '");
            self.write(attr);
            self.write("'");
        }
        if let Some(ref parser) = modifiers.with_parser {
            self.write_space();
            self.write_keyword("WITH PARSER");
            self.write_space();
            self.write(parser);
        }
    }

    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
        if !fk_ref.match_after_actions {
            if let Some(ref match_type) = fk_ref.match_type {
                self.write_space();
                self.write_keyword("MATCH");
                self.write_space();
                match match_type {
                    MatchType::Full => self.write_keyword("FULL"),
                    MatchType::Partial => self.write_keyword("PARTIAL"),
                    MatchType::Simple => self.write_keyword("SIMPLE"),
                }
            }
        }

        // Output ON UPDATE and ON DELETE in the original order
        if fk_ref.on_update_first {
            if let Some(ref action) = fk_ref.on_update {
                self.write_space();
                self.write_keyword("ON UPDATE");
                self.write_space();
                self.generate_referential_action(action);
            }
            if let Some(ref action) = fk_ref.on_delete {
                self.write_space();
                self.write_keyword("ON DELETE");
                self.write_space();
                self.generate_referential_action(action);
            }
        } else {
            if let Some(ref action) = fk_ref.on_delete {
                self.write_space();
                self.write_keyword("ON DELETE");
                self.write_space();
                self.generate_referential_action(action);
            }
            if let Some(ref action) = fk_ref.on_update {
                self.write_space();
                self.write_keyword("ON UPDATE");
                self.write_space();
                self.generate_referential_action(action);
            }
        }

        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
        if fk_ref.match_after_actions {
            if let Some(ref match_type) = fk_ref.match_type {
                self.write_space();
                self.write_keyword("MATCH");
                self.write_space();
                match match_type {
                    MatchType::Full => self.write_keyword("FULL"),
                    MatchType::Partial => self.write_keyword("PARTIAL"),
                    MatchType::Simple => self.write_keyword("SIMPLE"),
                }
            }
        }

        // DEFERRABLE / NOT DEFERRABLE
        if let Some(deferrable) = fk_ref.deferrable {
            self.write_space();
            if deferrable {
                self.write_keyword("DEFERRABLE");
            } else {
                self.write_keyword("NOT DEFERRABLE");
            }
        }

        Ok(())
    }

    fn generate_referential_action(&mut self, action: &ReferentialAction) {
        match action {
            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
        }
    }

    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
        // Athena: DROP TABLE uses Hive engine (backticks)
        let saved_athena_hive_context = self.athena_hive_context;
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Athena)
        ) {
            self.athena_hive_context = true;
        }

        self.write_keyword("DROP TABLE");

        if dt.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        for (i, table) in dt.names.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_table(table)?;
        }

        if dt.cascade_constraints {
            self.write_space();
            self.write_keyword("CASCADE CONSTRAINTS");
        } else if dt.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        if dt.purge {
            self.write_space();
            self.write_keyword("PURGE");
        }

        // Restore Athena Hive context
        self.athena_hive_context = saved_athena_hive_context;

        Ok(())
    }

    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
        // Athena: ALTER TABLE uses Hive engine (backticks)
        let saved_athena_hive_context = self.athena_hive_context;
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Athena)
        ) {
            self.athena_hive_context = true;
        }

        self.write_keyword("ALTER TABLE");
        if at.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }
        self.write_space();
        self.generate_table(&at.name)?;

        // ClickHouse: ON CLUSTER clause
        if let Some(ref on_cluster) = at.on_cluster {
            self.write_space();
            self.generate_on_cluster(on_cluster)?;
        }

        // Hive: PARTITION(key=value, ...) clause
        if let Some(ref partition) = at.partition {
            self.write_space();
            self.write_keyword("PARTITION");
            self.write("(");
            for (i, (key, value)) in partition.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(key)?;
                self.write(" = ");
                self.generate_expression(value)?;
            }
            self.write(")");
        }

        // TSQL: WITH CHECK / WITH NOCHECK modifier
        if let Some(ref with_check) = at.with_check {
            self.write_space();
            self.write_keyword(with_check);
        }

        if self.config.pretty {
            // In pretty mode, format actions with newlines and indentation
            self.write_newline();
            self.indent_level += 1;
            for (i, action) in at.actions.iter().enumerate() {
                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
                let is_continuation = i > 0
                    && matches!(
                        (&at.actions[i - 1], action),
                        (
                            AlterTableAction::AddColumn { .. },
                            AlterTableAction::AddColumn { .. }
                        ) | (
                            AlterTableAction::AddConstraint(_),
                            AlterTableAction::AddConstraint(_)
                        )
                    );
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_alter_action_with_continuation(action, is_continuation)?;
            }
            self.indent_level -= 1;
        } else {
            for (i, action) in at.actions.iter().enumerate() {
                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
                let is_continuation = i > 0
                    && matches!(
                        (&at.actions[i - 1], action),
                        (
                            AlterTableAction::AddColumn { .. },
                            AlterTableAction::AddColumn { .. }
                        ) | (
                            AlterTableAction::AddConstraint(_),
                            AlterTableAction::AddConstraint(_)
                        )
                    );
                if i > 0 {
                    self.write(",");
                }
                self.write_space();
                self.generate_alter_action_with_continuation(action, is_continuation)?;
            }
        }

        // MySQL ALTER TABLE trailing options
        if let Some(ref algorithm) = at.algorithm {
            self.write(", ");
            self.write_keyword("ALGORITHM");
            self.write("=");
            self.write_keyword(algorithm);
        }
        if let Some(ref lock) = at.lock {
            self.write(", ");
            self.write_keyword("LOCK");
            self.write("=");
            self.write_keyword(lock);
        }

        // Restore Athena Hive context
        self.athena_hive_context = saved_athena_hive_context;

        Ok(())
    }

    fn generate_alter_action_with_continuation(
        &mut self,
        action: &AlterTableAction,
        is_continuation: bool,
    ) -> Result<()> {
        match action {
            AlterTableAction::AddColumn {
                column,
                if_not_exists,
                position,
            } => {
                use crate::dialects::DialectType;
                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
                // For other dialects, repeat ADD COLUMN for each
                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
                let is_tsql_like = matches!(
                    self.config.dialect,
                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                );
                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));

                if is_continuation && (is_snowflake || is_tsql_like) {
                    // Don't write ADD keyword for continuation in Snowflake/TSQL
                } else if is_snowflake {
                    self.write_keyword("ADD");
                    self.write_space();
                } else if is_athena {
                    // Athena uses ADD COLUMNS (col_def) syntax
                    self.write_keyword("ADD COLUMNS");
                    self.write(" (");
                } else if self.config.alter_table_include_column_keyword {
                    self.write_keyword("ADD COLUMN");
                    self.write_space();
                } else {
                    // Dialects like Oracle and TSQL don't use COLUMN keyword
                    self.write_keyword("ADD");
                    self.write_space();
                }

                if *if_not_exists {
                    self.write_keyword("IF NOT EXISTS");
                    self.write_space();
                }
                self.generate_column_def(column)?;

                // Close parenthesis for Athena
                if is_athena {
                    self.write(")");
                }

                // Column position (FIRST or AFTER)
                if let Some(pos) = position {
                    self.write_space();
                    match pos {
                        ColumnPosition::First => self.write_keyword("FIRST"),
                        ColumnPosition::After(col_name) => {
                            self.write_keyword("AFTER");
                            self.write_space();
                            self.generate_identifier(col_name)?;
                        }
                    }
                }
            }
            AlterTableAction::DropColumn {
                name,
                if_exists,
                cascade,
            } => {
                self.write_keyword("DROP COLUMN");
                if *if_exists {
                    self.write_space();
                    self.write_keyword("IF EXISTS");
                }
                self.write_space();
                self.generate_identifier(name)?;
                if *cascade {
                    self.write_space();
                    self.write_keyword("CASCADE");
                }
            }
            AlterTableAction::DropColumns { names } => {
                self.write_keyword("DROP COLUMNS");
                self.write(" (");
                for (i, name) in names.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(name)?;
                }
                self.write(")");
            }
            AlterTableAction::RenameColumn {
                old_name,
                new_name,
                if_exists,
            } => {
                self.write_keyword("RENAME COLUMN");
                if *if_exists {
                    self.write_space();
                    self.write_keyword("IF EXISTS");
                }
                self.write_space();
                self.generate_identifier(old_name)?;
                self.write_space();
                self.write_keyword("TO");
                self.write_space();
                self.generate_identifier(new_name)?;
            }
            AlterTableAction::AlterColumn {
                name,
                action,
                use_modify_keyword,
            } => {
                use crate::dialects::DialectType;
                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
                let use_modify = *use_modify_keyword
                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
                        && matches!(action, AlterColumnAction::SetDataType { .. }));
                if use_modify {
                    self.write_keyword("MODIFY COLUMN");
                    self.write_space();
                    self.generate_identifier(name)?;
                    // For MODIFY COLUMN, output the type directly
                    if let AlterColumnAction::SetDataType {
                        data_type,
                        using: _,
                        collate,
                    } = action
                    {
                        self.write_space();
                        self.generate_data_type(data_type)?;
                        // Output COLLATE clause if present
                        if let Some(collate_name) = collate {
                            self.write_space();
                            self.write_keyword("COLLATE");
                            self.write_space();
                            // Output as single-quoted string
                            self.write(&format!("'{}'", collate_name));
                        }
                    } else {
                        self.write_space();
                        self.generate_alter_column_action(action)?;
                    }
                } else if matches!(self.config.dialect, Some(DialectType::Hive))
                    && matches!(action, AlterColumnAction::SetDataType { .. })
                {
                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
                    self.write_keyword("CHANGE COLUMN");
                    self.write_space();
                    self.generate_identifier(name)?;
                    self.write_space();
                    self.generate_identifier(name)?;
                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
                        self.write_space();
                        self.generate_data_type(data_type)?;
                    }
                } else {
                    self.write_keyword("ALTER COLUMN");
                    self.write_space();
                    self.generate_identifier(name)?;
                    self.write_space();
                    self.generate_alter_column_action(action)?;
                }
            }
            AlterTableAction::RenameTable(new_name) => {
                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
                let mysql_like = matches!(
                    self.config.dialect,
                    Some(DialectType::MySQL)
                        | Some(DialectType::Doris)
                        | Some(DialectType::StarRocks)
                        | Some(DialectType::SingleStore)
                );
                if mysql_like {
                    self.write_keyword("RENAME");
                } else {
                    self.write_keyword("RENAME TO");
                }
                self.write_space();
                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
                let rename_table_with_db = !matches!(
                    self.config.dialect,
                    Some(DialectType::Doris)
                        | Some(DialectType::DuckDB)
                        | Some(DialectType::BigQuery)
                        | Some(DialectType::PostgreSQL)
                );
                if !rename_table_with_db {
                    let mut stripped = new_name.clone();
                    stripped.schema = None;
                    stripped.catalog = None;
                    self.generate_table(&stripped)?;
                } else {
                    self.generate_table(new_name)?;
                }
            }
            AlterTableAction::AddConstraint(constraint) => {
                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
                if !is_continuation {
                    self.write_keyword("ADD");
                    self.write_space();
                }
                self.generate_table_constraint(constraint)?;
            }
            AlterTableAction::DropConstraint { name, if_exists } => {
                self.write_keyword("DROP CONSTRAINT");
                if *if_exists {
                    self.write_space();
                    self.write_keyword("IF EXISTS");
                }
                self.write_space();
                self.generate_identifier(name)?;
            }
            AlterTableAction::DropForeignKey { name } => {
                self.write_keyword("DROP FOREIGN KEY");
                self.write_space();
                self.generate_identifier(name)?;
            }
            AlterTableAction::DropPartition {
                partitions,
                if_exists,
            } => {
                self.write_keyword("DROP");
                if *if_exists {
                    self.write_space();
                    self.write_keyword("IF EXISTS");
                }
                for (i, partition) in partitions.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_space();
                    self.write_keyword("PARTITION");
                    // Check for special ClickHouse partition formats
                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
                        // ClickHouse: PARTITION <expression>
                        self.write_space();
                        self.generate_expression(&partition[0].1)?;
                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
                        // ClickHouse: PARTITION ALL
                        self.write_space();
                        self.write_keyword("ALL");
                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
                        // ClickHouse: PARTITION ID 'string'
                        self.write_space();
                        self.write_keyword("ID");
                        self.write_space();
                        self.generate_expression(&partition[0].1)?;
                    } else {
                        // Standard SQL: PARTITION(key=value, ...)
                        self.write("(");
                        for (j, (key, value)) in partition.iter().enumerate() {
                            if j > 0 {
                                self.write(", ");
                            }
                            self.generate_identifier(key)?;
                            self.write(" = ");
                            self.generate_expression(value)?;
                        }
                        self.write(")");
                    }
                }
            }
            AlterTableAction::Delete { where_clause } => {
                self.write_keyword("DELETE");
                self.write_space();
                self.write_keyword("WHERE");
                self.write_space();
                self.generate_expression(where_clause)?;
            }
            AlterTableAction::SwapWith(target) => {
                self.write_keyword("SWAP WITH");
                self.write_space();
                self.generate_table(target)?;
            }
            AlterTableAction::SetProperty { properties } => {
                use crate::dialects::DialectType;
                self.write_keyword("SET");
                // Trino/Presto use SET PROPERTIES syntax with spaces around =
                let is_trino_presto = matches!(
                    self.config.dialect,
                    Some(DialectType::Trino) | Some(DialectType::Presto)
                );
                if is_trino_presto {
                    self.write_space();
                    self.write_keyword("PROPERTIES");
                }
                let eq = if is_trino_presto { " = " } else { "=" };
                for (i, (key, value)) in properties.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_space();
                    // Handle quoted property names for Trino
                    if key.contains(' ') {
                        self.generate_string_literal(key)?;
                    } else {
                        self.write(key);
                    }
                    self.write(eq);
                    self.generate_expression(value)?;
                }
            }
            AlterTableAction::UnsetProperty { properties } => {
                self.write_keyword("UNSET");
                for (i, name) in properties.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_space();
                    self.write(name);
                }
            }
            AlterTableAction::ClusterBy { expressions } => {
                self.write_keyword("CLUSTER BY");
                self.write(" (");
                for (i, expr) in expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
            AlterTableAction::SetTag { expressions } => {
                self.write_keyword("SET TAG");
                for (i, (key, value)) in expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_space();
                    self.write(key);
                    self.write(" = ");
                    self.generate_expression(value)?;
                }
            }
            AlterTableAction::UnsetTag { names } => {
                self.write_keyword("UNSET TAG");
                for (i, name) in names.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                    }
                    self.write_space();
                    self.write(name);
                }
            }
            AlterTableAction::SetOptions { expressions } => {
                self.write_keyword("SET");
                self.write(" (");
                for (i, expr) in expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
            AlterTableAction::AlterIndex { name, visible } => {
                self.write_keyword("ALTER INDEX");
                self.write_space();
                self.generate_identifier(name)?;
                self.write_space();
                if *visible {
                    self.write_keyword("VISIBLE");
                } else {
                    self.write_keyword("INVISIBLE");
                }
            }
            AlterTableAction::SetAttribute { attribute } => {
                self.write_keyword("SET");
                self.write_space();
                self.write_keyword(attribute);
            }
            AlterTableAction::SetStageFileFormat { options } => {
                self.write_keyword("SET");
                self.write_space();
                self.write_keyword("STAGE_FILE_FORMAT");
                self.write(" = (");
                if let Some(opts) = options {
                    self.generate_space_separated_properties(opts)?;
                }
                self.write(")");
            }
            AlterTableAction::SetStageCopyOptions { options } => {
                self.write_keyword("SET");
                self.write_space();
                self.write_keyword("STAGE_COPY_OPTIONS");
                self.write(" = (");
                if let Some(opts) = options {
                    self.generate_space_separated_properties(opts)?;
                }
                self.write(")");
            }
            AlterTableAction::AddColumns { columns, cascade } => {
                // Oracle uses ADD (...) without COLUMNS keyword
                // Hive/Spark uses ADD COLUMNS (...)
                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
                if is_oracle {
                    self.write_keyword("ADD");
                } else {
                    self.write_keyword("ADD COLUMNS");
                }
                self.write(" (");
                for (i, col) in columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_column_def(col)?;
                }
                self.write(")");
                if *cascade {
                    self.write_space();
                    self.write_keyword("CASCADE");
                }
            }
            AlterTableAction::ChangeColumn {
                old_name,
                new_name,
                data_type,
                comment,
                cascade,
            } => {
                use crate::dialects::DialectType;
                let is_spark = matches!(
                    self.config.dialect,
                    Some(DialectType::Spark) | Some(DialectType::Databricks)
                );
                let is_rename = old_name.name != new_name.name;

                if is_spark {
                    if is_rename {
                        // Spark: RENAME COLUMN old TO new
                        self.write_keyword("RENAME COLUMN");
                        self.write_space();
                        self.generate_identifier(old_name)?;
                        self.write_space();
                        self.write_keyword("TO");
                        self.write_space();
                        self.generate_identifier(new_name)?;
                    } else if comment.is_some() {
                        // Spark: ALTER COLUMN old COMMENT 'comment'
                        self.write_keyword("ALTER COLUMN");
                        self.write_space();
                        self.generate_identifier(old_name)?;
                        self.write_space();
                        self.write_keyword("COMMENT");
                        self.write_space();
                        self.write("'");
                        self.write(comment.as_ref().unwrap());
                        self.write("'");
                    } else if data_type.is_some() {
                        // Spark: ALTER COLUMN old TYPE data_type
                        self.write_keyword("ALTER COLUMN");
                        self.write_space();
                        self.generate_identifier(old_name)?;
                        self.write_space();
                        self.write_keyword("TYPE");
                        self.write_space();
                        self.generate_data_type(data_type.as_ref().unwrap())?;
                    } else {
                        // Fallback to CHANGE COLUMN
                        self.write_keyword("CHANGE COLUMN");
                        self.write_space();
                        self.generate_identifier(old_name)?;
                        self.write_space();
                        self.generate_identifier(new_name)?;
                    }
                } else {
                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
                    if data_type.is_some() {
                        self.write_keyword("CHANGE COLUMN");
                    } else {
                        self.write_keyword("CHANGE");
                    }
                    self.write_space();
                    self.generate_identifier(old_name)?;
                    self.write_space();
                    self.generate_identifier(new_name)?;
                    if let Some(ref dt) = data_type {
                        self.write_space();
                        self.generate_data_type(dt)?;
                    }
                    if let Some(ref c) = comment {
                        self.write_space();
                        self.write_keyword("COMMENT");
                        self.write_space();
                        self.write("'");
                        self.write(c);
                        self.write("'");
                    }
                    if *cascade {
                        self.write_space();
                        self.write_keyword("CASCADE");
                    }
                }
            }
            AlterTableAction::AddPartition {
                partition,
                if_not_exists,
                location,
            } => {
                self.write_keyword("ADD");
                self.write_space();
                if *if_not_exists {
                    self.write_keyword("IF NOT EXISTS");
                    self.write_space();
                }
                self.generate_expression(partition)?;
                if let Some(ref loc) = location {
                    self.write_space();
                    self.write_keyword("LOCATION");
                    self.write_space();
                    self.generate_expression(loc)?;
                }
            }
            AlterTableAction::AlterSortKey {
                this,
                expressions,
                compound,
            } => {
                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
                self.write_keyword("ALTER");
                if *compound {
                    self.write_space();
                    self.write_keyword("COMPOUND");
                }
                self.write_space();
                self.write_keyword("SORTKEY");
                self.write_space();
                if let Some(style) = this {
                    self.write_keyword(style);
                } else if !expressions.is_empty() {
                    self.write("(");
                    for (i, expr) in expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(expr)?;
                    }
                    self.write(")");
                }
            }
            AlterTableAction::AlterDistStyle { style, distkey } => {
                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
                self.write_keyword("ALTER");
                self.write_space();
                self.write_keyword("DISTSTYLE");
                self.write_space();
                self.write_keyword(style);
                if let Some(col) = distkey {
                    self.write_space();
                    self.write_keyword("DISTKEY");
                    self.write_space();
                    self.generate_identifier(col)?;
                }
            }
            AlterTableAction::SetTableProperties { properties } => {
                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
                self.write_keyword("SET TABLE PROPERTIES");
                self.write(" (");
                for (i, (key, value)) in properties.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(key)?;
                    self.write(" = ");
                    self.generate_expression(value)?;
                }
                self.write(")");
            }
            AlterTableAction::SetLocation { location } => {
                // Redshift: SET LOCATION 's3://bucket/folder/'
                self.write_keyword("SET LOCATION");
                self.write_space();
                self.write("'");
                self.write(location);
                self.write("'");
            }
            AlterTableAction::SetFileFormat { format } => {
                // Redshift: SET FILE FORMAT AVRO
                self.write_keyword("SET FILE FORMAT");
                self.write_space();
                self.write_keyword(format);
            }
            AlterTableAction::ReplacePartition { partition, source } => {
                // ClickHouse: REPLACE PARTITION expr FROM source
                self.write_keyword("REPLACE PARTITION");
                self.write_space();
                self.generate_expression(partition)?;
                if let Some(src) = source {
                    self.write_space();
                    self.write_keyword("FROM");
                    self.write_space();
                    self.generate_expression(src)?;
                }
            }
        }
        Ok(())
    }

    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
        match action {
            AlterColumnAction::SetDataType {
                data_type,
                using,
                collate,
            } => {
                use crate::dialects::DialectType;
                // Dialect-specific type change syntax:
                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
                let is_no_prefix = matches!(
                    self.config.dialect,
                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
                );
                let is_type_only = matches!(
                    self.config.dialect,
                    Some(DialectType::Redshift)
                        | Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                );
                if is_type_only {
                    self.write_keyword("TYPE");
                    self.write_space();
                } else if !is_no_prefix {
                    self.write_keyword("SET DATA TYPE");
                    self.write_space();
                }
                self.generate_data_type(data_type)?;
                if let Some(ref collation) = collate {
                    self.write_space();
                    self.write_keyword("COLLATE");
                    self.write_space();
                    self.write(collation);
                }
                if let Some(ref using_expr) = using {
                    self.write_space();
                    self.write_keyword("USING");
                    self.write_space();
                    self.generate_expression(using_expr)?;
                }
            }
            AlterColumnAction::SetDefault(expr) => {
                self.write_keyword("SET DEFAULT");
                self.write_space();
                self.generate_expression(expr)?;
            }
            AlterColumnAction::DropDefault => {
                self.write_keyword("DROP DEFAULT");
            }
            AlterColumnAction::SetNotNull => {
                self.write_keyword("SET NOT NULL");
            }
            AlterColumnAction::DropNotNull => {
                self.write_keyword("DROP NOT NULL");
            }
            AlterColumnAction::Comment(comment) => {
                self.write_keyword("COMMENT");
                self.write_space();
                self.generate_string_literal(comment)?;
            }
            AlterColumnAction::SetVisible => {
                self.write_keyword("SET VISIBLE");
            }
            AlterColumnAction::SetInvisible => {
                self.write_keyword("SET INVISIBLE");
            }
        }
        Ok(())
    }

    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
        self.write_keyword("CREATE");

        if ci.unique {
            self.write_space();
            self.write_keyword("UNIQUE");
        }

        // TSQL CLUSTERED/NONCLUSTERED modifier
        if let Some(ref clustered) = ci.clustered {
            self.write_space();
            self.write_keyword(clustered);
        }

        self.write_space();
        self.write_keyword("INDEX");

        // PostgreSQL CONCURRENTLY modifier
        if ci.concurrently {
            self.write_space();
            self.write_keyword("CONCURRENTLY");
        }

        if ci.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
        if !ci.name.name.is_empty() {
            self.write_space();
            self.generate_identifier(&ci.name)?;
        }
        self.write_space();
        self.write_keyword("ON");
        self.write_space();
        self.generate_table(&ci.table)?;

        // Column list (optional for COLUMNSTORE indexes)
        // Standard SQL convention: ON t(a) without space before paren
        if !ci.columns.is_empty() || ci.using.is_some() {
            let space_before_paren = false;

            if let Some(ref using) = ci.using {
                self.write_space();
                self.write_keyword("USING");
                self.write_space();
                self.write(using);
                if space_before_paren {
                    self.write(" (");
                } else {
                    self.write("(");
                }
            } else {
                if space_before_paren {
                    self.write(" (");
                } else {
                    self.write("(");
                }
            }
            for (i, col) in ci.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(&col.column)?;
                if let Some(ref opclass) = col.opclass {
                    self.write_space();
                    self.write(opclass);
                }
                if col.desc {
                    self.write_space();
                    self.write_keyword("DESC");
                } else if col.asc {
                    self.write_space();
                    self.write_keyword("ASC");
                }
                if let Some(nulls_first) = col.nulls_first {
                    self.write_space();
                    self.write_keyword("NULLS");
                    self.write_space();
                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
                }
            }
            self.write(")");
        }

        // PostgreSQL INCLUDE (col1, col2) clause
        if !ci.include_columns.is_empty() {
            self.write_space();
            self.write_keyword("INCLUDE");
            self.write(" (");
            for (i, col) in ci.include_columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(col)?;
            }
            self.write(")");
        }

        // TSQL: WITH (option=value, ...) clause
        if !ci.with_options.is_empty() {
            self.write_space();
            self.write_keyword("WITH");
            self.write(" (");
            for (i, (key, value)) in ci.with_options.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(key);
                self.write("=");
                self.write(value);
            }
            self.write(")");
        }

        // PostgreSQL WHERE clause for partial indexes
        if let Some(ref where_clause) = ci.where_clause {
            self.write_space();
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(where_clause)?;
        }

        // TSQL: ON filegroup or partition scheme clause
        if let Some(ref on_fg) = ci.on_filegroup {
            self.write_space();
            self.write_keyword("ON");
            self.write_space();
            self.write(on_fg);
        }

        Ok(())
    }

    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
        self.write_keyword("DROP INDEX");

        if di.concurrently {
            self.write_space();
            self.write_keyword("CONCURRENTLY");
        }

        if di.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_identifier(&di.name)?;

        if let Some(ref table) = di.table {
            self.write_space();
            self.write_keyword("ON");
            self.write_space();
            self.generate_table(table)?;
        }

        Ok(())
    }

    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
        self.write_keyword("CREATE");

        // MySQL: ALGORITHM=...
        if let Some(ref algorithm) = cv.algorithm {
            self.write_space();
            self.write_keyword("ALGORITHM");
            self.write("=");
            self.write_keyword(algorithm);
        }

        // MySQL: DEFINER=...
        if let Some(ref definer) = cv.definer {
            self.write_space();
            self.write_keyword("DEFINER");
            self.write("=");
            self.write(definer);
        }

        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
        if cv.security_sql_style {
            if let Some(ref security) = cv.security {
                self.write_space();
                self.write_keyword("SQL SECURITY");
                self.write_space();
                match security {
                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
                    FunctionSecurity::None => self.write_keyword("NONE"),
                }
            }
        }

        if cv.or_replace {
            self.write_space();
            self.write_keyword("OR REPLACE");
        }

        if cv.temporary {
            self.write_space();
            self.write_keyword("TEMPORARY");
        }

        if cv.materialized {
            self.write_space();
            self.write_keyword("MATERIALIZED");
        }

        // Snowflake: SECURE VIEW
        if cv.secure {
            self.write_space();
            self.write_keyword("SECURE");
        }

        self.write_space();
        self.write_keyword("VIEW");

        if cv.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_table(&cv.name)?;

        // ClickHouse: ON CLUSTER clause
        if let Some(ref on_cluster) = cv.on_cluster {
            self.write_space();
            self.generate_on_cluster(on_cluster)?;
        }

        // ClickHouse: TO destination_table
        if let Some(ref to_table) = cv.to_table {
            self.write_space();
            self.write_keyword("TO");
            self.write_space();
            self.generate_table(to_table)?;
        }

        // For regular VIEW: columns come before COPY GRANTS
        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
        if !cv.materialized {
            // Regular VIEW: columns first
            if !cv.columns.is_empty() {
                self.write(" (");
                for (i, col) in cv.columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(&col.name)?;
                    // BigQuery: OPTIONS (key=value, ...) on view column
                    if !col.options.is_empty() {
                        self.write_space();
                        self.generate_options_clause(&col.options)?;
                    }
                    if let Some(ref comment) = col.comment {
                        self.write_space();
                        self.write_keyword("COMMENT");
                        self.write_space();
                        self.generate_string_literal(comment)?;
                    }
                }
                self.write(")");
            }

            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
            if !cv.security_sql_style {
                if let Some(ref security) = cv.security {
                    self.write_space();
                    self.write_keyword("SECURITY");
                    self.write_space();
                    match security {
                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
                        FunctionSecurity::None => self.write_keyword("NONE"),
                    }
                }
            }

            // Snowflake: COPY GRANTS
            if cv.copy_grants {
                self.write_space();
                self.write_keyword("COPY GRANTS");
            }
        } else {
            // MATERIALIZED VIEW: COPY GRANTS first
            if cv.copy_grants {
                self.write_space();
                self.write_keyword("COPY GRANTS");
            }

            // Doris: If we have a schema (typed columns), generate that instead
            if let Some(ref schema) = cv.schema {
                self.write(" (");
                for (i, expr) in schema.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            } else if !cv.columns.is_empty() {
                // Then columns (simple column names without types)
                self.write(" (");
                for (i, col) in cv.columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(&col.name)?;
                    // BigQuery: OPTIONS (key=value, ...) on view column
                    if !col.options.is_empty() {
                        self.write_space();
                        self.generate_options_clause(&col.options)?;
                    }
                    if let Some(ref comment) = col.comment {
                        self.write_space();
                        self.write_keyword("COMMENT");
                        self.write_space();
                        self.generate_string_literal(comment)?;
                    }
                }
                self.write(")");
            }

            // Doris: KEY (columns) for materialized views
            if let Some(ref unique_key) = cv.unique_key {
                self.write_space();
                self.write_keyword("KEY");
                self.write(" (");
                for (i, expr) in unique_key.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
        }

        // Snowflake: COMMENT = 'text'
        if let Some(ref comment) = cv.comment {
            self.write_space();
            self.write_keyword("COMMENT");
            self.write("=");
            self.generate_string_literal(comment)?;
        }

        // Snowflake: TAG (name='value', ...)
        if !cv.tags.is_empty() {
            self.write_space();
            self.write_keyword("TAG");
            self.write(" (");
            for (i, (name, value)) in cv.tags.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(name);
                self.write("='");
                self.write(value);
                self.write("'");
            }
            self.write(")");
        }

        // BigQuery: OPTIONS (key=value, ...)
        if !cv.options.is_empty() {
            self.write_space();
            self.generate_options_clause(&cv.options)?;
        }

        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
        if let Some(ref build) = cv.build {
            self.write_space();
            self.write_keyword("BUILD");
            self.write_space();
            self.write_keyword(build);
        }

        // Doris: REFRESH clause for materialized views
        if let Some(ref refresh) = cv.refresh {
            self.write_space();
            self.generate_refresh_trigger_property(refresh)?;
        }

        // Redshift: AUTO REFRESH YES|NO for materialized views
        if let Some(auto_refresh) = cv.auto_refresh {
            self.write_space();
            self.write_keyword("AUTO REFRESH");
            self.write_space();
            if auto_refresh {
                self.write_keyword("YES");
            } else {
                self.write_keyword("NO");
            }
        }

        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
        for prop in &cv.table_properties {
            self.write_space();
            self.generate_expression(prop)?;
        }

        // Only output AS clause if there's a real query (not just NULL placeholder)
        if !matches!(&cv.query, Expression::Null(_)) {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();

            // Teradata: LOCKING clause (between AS and query)
            if let Some(ref mode) = cv.locking_mode {
                self.write_keyword("LOCKING");
                self.write_space();
                self.write_keyword(mode);
                if let Some(ref access) = cv.locking_access {
                    self.write_space();
                    self.write_keyword("FOR");
                    self.write_space();
                    self.write_keyword(access);
                }
                self.write_space();
            }

            if cv.query_parenthesized {
                self.write("(");
            }
            self.generate_expression(&cv.query)?;
            if cv.query_parenthesized {
                self.write(")");
            }
        }

        // Redshift: WITH NO SCHEMA BINDING (after query)
        if cv.no_schema_binding {
            self.write_space();
            self.write_keyword("WITH NO SCHEMA BINDING");
        }

        Ok(())
    }

    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
        self.write_keyword("DROP");

        if dv.materialized {
            self.write_space();
            self.write_keyword("MATERIALIZED");
        }

        self.write_space();
        self.write_keyword("VIEW");

        if dv.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_table(&dv.name)?;

        Ok(())
    }

    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
        match tr.target {
            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
        }
        self.write_space();
        self.generate_table(&tr.table)?;

        // ClickHouse: ON CLUSTER clause
        if let Some(ref on_cluster) = tr.on_cluster {
            self.write_space();
            self.generate_on_cluster(on_cluster)?;
        }

        // Check if first table has a * (multi-table with star)
        if !tr.extra_tables.is_empty() {
            // Check if the first entry matches the main table (star case)
            let skip_first = if let Some(first) = tr.extra_tables.first() {
                first.table.name == tr.table.name && first.star
            } else {
                false
            };

            // PostgreSQL normalizes away the * suffix (it's the default behavior)
            let strip_star = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::PostgreSQL)
                    | Some(crate::dialects::DialectType::Redshift)
            );
            if skip_first && !strip_star {
                self.write("*");
            }

            // Generate additional tables
            for (i, entry) in tr.extra_tables.iter().enumerate() {
                if i == 0 && skip_first {
                    continue; // Already handled the star for first table
                }
                self.write(", ");
                self.generate_table(&entry.table)?;
                if entry.star && !strip_star {
                    self.write("*");
                }
            }
        }

        // RESTART/CONTINUE IDENTITY
        if let Some(identity) = &tr.identity {
            self.write_space();
            match identity {
                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
            }
        }

        if tr.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        if tr.restrict {
            self.write_space();
            self.write_keyword("RESTRICT");
        }

        // Output Hive PARTITION clause
        if let Some(ref partition) = tr.partition {
            self.write_space();
            self.generate_expression(partition)?;
        }

        Ok(())
    }

    fn generate_use(&mut self, u: &Use) -> Result<()> {
        // Teradata uses "DATABASE <name>" instead of "USE <name>"
        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
            self.write_keyword("DATABASE");
            self.write_space();
            self.generate_identifier(&u.this)?;
            return Ok(());
        }

        self.write_keyword("USE");

        if let Some(kind) = &u.kind {
            self.write_space();
            match kind {
                UseKind::Database => self.write_keyword("DATABASE"),
                UseKind::Schema => self.write_keyword("SCHEMA"),
                UseKind::Role => self.write_keyword("ROLE"),
                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
                UseKind::Catalog => self.write_keyword("CATALOG"),
                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
            }
        }

        self.write_space();
        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
        // without quoting, since these are keywords not identifiers
        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
            self.write(&u.this.name);
        } else {
            self.generate_identifier(&u.this)?;
        }
        Ok(())
    }

    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
        self.write_keyword("CACHE");
        if c.lazy {
            self.write_space();
            self.write_keyword("LAZY");
        }
        self.write_space();
        self.write_keyword("TABLE");
        self.write_space();
        self.generate_identifier(&c.table)?;

        // OPTIONS clause
        if !c.options.is_empty() {
            self.write_space();
            self.write_keyword("OPTIONS");
            self.write("(");
            for (i, (key, value)) in c.options.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(key)?;
                self.write(" = ");
                self.generate_expression(value)?;
            }
            self.write(")");
        }

        // AS query
        if let Some(query) = &c.query {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_expression(query)?;
        }

        Ok(())
    }

    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
        self.write_keyword("UNCACHE TABLE");
        if u.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }
        self.write_space();
        self.generate_identifier(&u.table)?;
        Ok(())
    }

    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
        self.write_keyword("LOAD DATA");
        if l.local {
            self.write_space();
            self.write_keyword("LOCAL");
        }
        self.write_space();
        self.write_keyword("INPATH");
        self.write_space();
        self.write("'");
        self.write(&l.inpath);
        self.write("'");

        if l.overwrite {
            self.write_space();
            self.write_keyword("OVERWRITE");
        }

        self.write_space();
        self.write_keyword("INTO TABLE");
        self.write_space();
        self.generate_expression(&l.table)?;

        // PARTITION clause
        if !l.partition.is_empty() {
            self.write_space();
            self.write_keyword("PARTITION");
            self.write("(");
            for (i, (col, val)) in l.partition.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(col)?;
                self.write(" = ");
                self.generate_expression(val)?;
            }
            self.write(")");
        }

        // INPUTFORMAT clause
        if let Some(fmt) = &l.input_format {
            self.write_space();
            self.write_keyword("INPUTFORMAT");
            self.write_space();
            self.write("'");
            self.write(fmt);
            self.write("'");
        }

        // SERDE clause
        if let Some(serde) = &l.serde {
            self.write_space();
            self.write_keyword("SERDE");
            self.write_space();
            self.write("'");
            self.write(serde);
            self.write("'");
        }

        Ok(())
    }

    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
        self.write_keyword("PRAGMA");
        self.write_space();

        // Schema prefix if present
        if let Some(schema) = &p.schema {
            self.generate_identifier(schema)?;
            self.write(".");
        }

        // Pragma name
        self.generate_identifier(&p.name)?;

        // Value assignment or function call
        if let Some(value) = &p.value {
            self.write(" = ");
            self.generate_expression(value)?;
        } else if !p.args.is_empty() {
            self.write("(");
            for (i, arg) in p.args.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(arg)?;
            }
            self.write(")");
        }

        Ok(())
    }

    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
        self.write_keyword("GRANT");
        self.write_space();

        // Privileges (with optional column lists)
        for (i, privilege) in g.privileges.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.write_keyword(&privilege.name);
            // Output column list if present: SELECT(col1, col2)
            if !privilege.columns.is_empty() {
                self.write("(");
                for (j, col) in privilege.columns.iter().enumerate() {
                    if j > 0 {
                        self.write(", ");
                    }
                    self.write(col);
                }
                self.write(")");
            }
        }

        self.write_space();
        self.write_keyword("ON");
        self.write_space();

        // Object kind (TABLE, SCHEMA, etc.)
        if let Some(kind) = &g.kind {
            self.write_keyword(kind);
            self.write_space();
        }

        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
        {
            use crate::dialects::DialectType;
            let should_upper = matches!(
                self.config.dialect,
                Some(DialectType::PostgreSQL)
                    | Some(DialectType::CockroachDB)
                    | Some(DialectType::Materialize)
                    | Some(DialectType::RisingWave)
            ) && (g.kind.as_deref() == Some("FUNCTION")
                || g.kind.as_deref() == Some("PROCEDURE"));
            if should_upper {
                use crate::expressions::Identifier;
                let upper_id = Identifier {
                    name: g.securable.name.to_uppercase(),
                    quoted: g.securable.quoted,
                    ..g.securable.clone()
                };
                self.generate_identifier(&upper_id)?;
            } else {
                self.generate_identifier(&g.securable)?;
            }
        }

        // Function parameter types (if present)
        if !g.function_params.is_empty() {
            self.write("(");
            for (i, param) in g.function_params.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(param);
            }
            self.write(")");
        }

        self.write_space();
        self.write_keyword("TO");
        self.write_space();

        // Principals
        for (i, principal) in g.principals.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            if principal.is_role {
                self.write_keyword("ROLE");
                self.write_space();
            } else if principal.is_group {
                self.write_keyword("GROUP");
                self.write_space();
            }
            self.generate_identifier(&principal.name)?;
        }

        // WITH GRANT OPTION
        if g.grant_option {
            self.write_space();
            self.write_keyword("WITH GRANT OPTION");
        }

        // TSQL: AS principal
        if let Some(ref principal) = g.as_principal {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(principal)?;
        }

        Ok(())
    }

    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
        self.write_keyword("REVOKE");
        self.write_space();

        // GRANT OPTION FOR
        if r.grant_option {
            self.write_keyword("GRANT OPTION FOR");
            self.write_space();
        }

        // Privileges (with optional column lists)
        for (i, privilege) in r.privileges.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.write_keyword(&privilege.name);
            // Output column list if present: SELECT(col1, col2)
            if !privilege.columns.is_empty() {
                self.write("(");
                for (j, col) in privilege.columns.iter().enumerate() {
                    if j > 0 {
                        self.write(", ");
                    }
                    self.write(col);
                }
                self.write(")");
            }
        }

        self.write_space();
        self.write_keyword("ON");
        self.write_space();

        // Object kind
        if let Some(kind) = &r.kind {
            self.write_keyword(kind);
            self.write_space();
        }

        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
        {
            use crate::dialects::DialectType;
            let should_upper = matches!(
                self.config.dialect,
                Some(DialectType::PostgreSQL)
                    | Some(DialectType::CockroachDB)
                    | Some(DialectType::Materialize)
                    | Some(DialectType::RisingWave)
            ) && (r.kind.as_deref() == Some("FUNCTION")
                || r.kind.as_deref() == Some("PROCEDURE"));
            if should_upper {
                use crate::expressions::Identifier;
                let upper_id = Identifier {
                    name: r.securable.name.to_uppercase(),
                    quoted: r.securable.quoted,
                    ..r.securable.clone()
                };
                self.generate_identifier(&upper_id)?;
            } else {
                self.generate_identifier(&r.securable)?;
            }
        }

        // Function parameter types (if present)
        if !r.function_params.is_empty() {
            self.write("(");
            for (i, param) in r.function_params.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(param);
            }
            self.write(")");
        }

        self.write_space();
        self.write_keyword("FROM");
        self.write_space();

        // Principals
        for (i, principal) in r.principals.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            if principal.is_role {
                self.write_keyword("ROLE");
                self.write_space();
            } else if principal.is_group {
                self.write_keyword("GROUP");
                self.write_space();
            }
            self.generate_identifier(&principal.name)?;
        }

        // CASCADE or RESTRICT
        if r.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        } else if r.restrict {
            self.write_space();
            self.write_keyword("RESTRICT");
        }

        Ok(())
    }

    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
        self.write_keyword("COMMENT");

        // IF EXISTS
        if c.exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.write_keyword("ON");

        // MATERIALIZED
        if c.materialized {
            self.write_space();
            self.write_keyword("MATERIALIZED");
        }

        self.write_space();
        self.write_keyword(&c.kind);
        self.write_space();

        // Object name
        self.generate_expression(&c.this)?;

        self.write_space();
        self.write_keyword("IS");
        self.write_space();

        // Comment expression
        self.generate_expression(&c.expression)?;

        Ok(())
    }

    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
        self.write_keyword("SET");

        for (i, item) in s.items.iter().enumerate() {
            if i > 0 {
                self.write(",");
            }
            self.write_space();

            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
            if let Some(ref kind) = item.kind {
                self.write_keyword(kind);
                self.write_space();
            }

            // Check for special SET forms by name
            let name_str = match &item.name {
                Expression::Identifier(id) => Some(id.name.as_str()),
                _ => None,
            };

            let is_transaction = name_str == Some("TRANSACTION");
            let is_character_set = name_str == Some("CHARACTER SET");
            let is_names = name_str == Some("NAMES");
            let is_collate = name_str == Some("COLLATE");
            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
            let name_has_variable_prefix = name_str.is_some_and(|n| n.starts_with("VARIABLE "));
            let is_variable = has_variable_kind || name_has_variable_prefix;
            let is_value_only =
                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());

            if is_transaction {
                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
                self.write_keyword("TRANSACTION");
                if let Expression::Identifier(id) = &item.value {
                    if !id.name.is_empty() {
                        self.write_space();
                        self.write(&id.name);
                    }
                }
            } else if is_character_set {
                // Output: SET CHARACTER SET <charset>
                self.write_keyword("CHARACTER SET");
                self.write_space();
                self.generate_set_value(&item.value)?;
            } else if is_names {
                // Output: SET NAMES <charset>
                self.write_keyword("NAMES");
                self.write_space();
                self.generate_set_value(&item.value)?;
            } else if is_collate {
                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
                self.write_keyword("COLLATE");
                self.write_space();
                self.generate_set_value(&item.value)?;
            } else if is_variable {
                // Output: SET [VARIABLE] <name> = <value>
                // If kind=VARIABLE, the keyword was already written above.
                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
                if name_has_variable_prefix && !has_variable_kind {
                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
                        self.write_keyword("VARIABLE");
                        self.write_space();
                    }
                }
                // Extract actual variable name (strip VARIABLE prefix if present)
                if let Some(ns) = name_str {
                    let var_name = if name_has_variable_prefix {
                        &ns["VARIABLE ".len()..]
                    } else {
                        ns
                    };
                    self.write(var_name);
                } else {
                    self.generate_expression(&item.name)?;
                }
                self.write(" = ");
                self.generate_set_value(&item.value)?;
            } else if is_value_only {
                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
                self.generate_expression(&item.name)?;
            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
                // SET key value without = (TSQL style)
                self.generate_expression(&item.name)?;
                self.write_space();
                self.generate_set_value(&item.value)?;
            } else {
                // Standard: variable = value
                // SET item names should not be quoted (they are config parameter names, not column refs)
                match &item.name {
                    Expression::Identifier(id) => {
                        self.write(&id.name);
                    }
                    _ => {
                        self.generate_expression(&item.name)?;
                    }
                }
                self.write(" = ");
                self.generate_set_value(&item.value)?;
            }
        }

        Ok(())
    }

    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
    /// directly to avoid reserved keyword quoting.
    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
        if let Expression::Identifier(id) = value {
            match id.name.as_str() {
                "DEFAULT" | "ON" | "OFF" => {
                    self.write_keyword(&id.name);
                    return Ok(());
                }
                _ => {}
            }
        }
        self.generate_expression(value)
    }

    // ==================== Phase 4: Additional DDL Generation ====================

    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
        self.write_keyword("ALTER");
        // MySQL modifiers before VIEW
        if let Some(ref algorithm) = av.algorithm {
            self.write_space();
            self.write_keyword("ALGORITHM");
            self.write(" = ");
            self.write_keyword(algorithm);
        }
        if let Some(ref definer) = av.definer {
            self.write_space();
            self.write_keyword("DEFINER");
            self.write(" = ");
            self.write(definer);
        }
        if let Some(ref sql_security) = av.sql_security {
            self.write_space();
            self.write_keyword("SQL SECURITY");
            self.write(" = ");
            self.write_keyword(sql_security);
        }
        self.write_space();
        self.write_keyword("VIEW");
        self.write_space();
        self.generate_table(&av.name)?;

        // Hive: Column aliases with optional COMMENT
        if !av.columns.is_empty() {
            self.write(" (");
            for (i, col) in av.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(&col.name)?;
                if let Some(ref comment) = col.comment {
                    self.write_space();
                    self.write_keyword("COMMENT");
                    self.write(" ");
                    self.generate_string_literal(comment)?;
                }
            }
            self.write(")");
        }

        // TSQL: WITH option before actions
        if let Some(ref opt) = av.with_option {
            self.write_space();
            self.write_keyword("WITH");
            self.write_space();
            self.write_keyword(opt);
        }

        for action in &av.actions {
            self.write_space();
            match action {
                AlterViewAction::Rename(new_name) => {
                    self.write_keyword("RENAME TO");
                    self.write_space();
                    self.generate_table(new_name)?;
                }
                AlterViewAction::OwnerTo(owner) => {
                    self.write_keyword("OWNER TO");
                    self.write_space();
                    self.generate_identifier(owner)?;
                }
                AlterViewAction::SetSchema(schema) => {
                    self.write_keyword("SET SCHEMA");
                    self.write_space();
                    self.generate_identifier(schema)?;
                }
                AlterViewAction::SetAuthorization(auth) => {
                    self.write_keyword("SET AUTHORIZATION");
                    self.write_space();
                    self.write(auth);
                }
                AlterViewAction::AlterColumn { name, action } => {
                    self.write_keyword("ALTER COLUMN");
                    self.write_space();
                    self.generate_identifier(name)?;
                    self.write_space();
                    self.generate_alter_column_action(action)?;
                }
                AlterViewAction::AsSelect(query) => {
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_expression(query)?;
                }
                AlterViewAction::SetTblproperties(props) => {
                    self.write_keyword("SET TBLPROPERTIES");
                    self.write(" (");
                    for (i, (key, value)) in props.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_string_literal(key)?;
                        self.write("=");
                        self.generate_string_literal(value)?;
                    }
                    self.write(")");
                }
                AlterViewAction::UnsetTblproperties(keys) => {
                    self.write_keyword("UNSET TBLPROPERTIES");
                    self.write(" (");
                    for (i, key) in keys.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_string_literal(key)?;
                    }
                    self.write(")");
                }
            }
        }

        Ok(())
    }

    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
        self.write_keyword("ALTER INDEX");
        self.write_space();
        self.generate_identifier(&ai.name)?;

        if let Some(table) = &ai.table {
            self.write_space();
            self.write_keyword("ON");
            self.write_space();
            self.generate_table(table)?;
        }

        for action in &ai.actions {
            self.write_space();
            match action {
                AlterIndexAction::Rename(new_name) => {
                    self.write_keyword("RENAME TO");
                    self.write_space();
                    self.generate_identifier(new_name)?;
                }
                AlterIndexAction::SetTablespace(tablespace) => {
                    self.write_keyword("SET TABLESPACE");
                    self.write_space();
                    self.generate_identifier(tablespace)?;
                }
                AlterIndexAction::Visible(visible) => {
                    if *visible {
                        self.write_keyword("VISIBLE");
                    } else {
                        self.write_keyword("INVISIBLE");
                    }
                }
            }
        }

        Ok(())
    }

    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
        // Output leading comments
        for comment in &cs.leading_comments {
            self.write(comment);
            self.write_space();
        }

        // Athena: CREATE SCHEMA uses Hive engine (backticks)
        let saved_athena_hive_context = self.athena_hive_context;
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Athena)
        ) {
            self.athena_hive_context = true;
        }

        self.write_keyword("CREATE SCHEMA");

        if cs.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_identifier(&cs.name)?;

        if let Some(ref clone_src) = cs.clone_from {
            self.write_keyword(" CLONE ");
            self.generate_identifier(clone_src)?;
        }

        if let Some(ref at_clause) = cs.at_clause {
            self.write_space();
            self.generate_expression(at_clause)?;
        }

        if let Some(auth) = &cs.authorization {
            self.write_space();
            self.write_keyword("AUTHORIZATION");
            self.write_space();
            self.generate_identifier(auth)?;
        }

        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
        // Separate WITH properties from other properties
        let with_properties: Vec<_> = cs
            .properties
            .iter()
            .filter(|p| matches!(p, Expression::Property(_)))
            .collect();
        let other_properties: Vec<_> = cs
            .properties
            .iter()
            .filter(|p| !matches!(p, Expression::Property(_)))
            .collect();

        // Generate WITH (props) if we have Property expressions
        if !with_properties.is_empty() {
            self.write_space();
            self.write_keyword("WITH");
            self.write(" (");
            for (i, prop) in with_properties.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(prop)?;
            }
            self.write(")");
        }

        // Generate other properties (like DEFAULT COLLATE)
        for prop in other_properties {
            self.write_space();
            self.generate_expression(prop)?;
        }

        // Restore Athena Hive context
        self.athena_hive_context = saved_athena_hive_context;

        Ok(())
    }

    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
        self.write_keyword("DROP SCHEMA");

        if ds.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_identifier(&ds.name)?;

        if ds.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
        self.write_keyword("DROP NAMESPACE");

        if dn.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_identifier(&dn.name)?;

        if dn.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
        self.write_keyword("CREATE DATABASE");

        if cd.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_identifier(&cd.name)?;

        if let Some(ref clone_src) = cd.clone_from {
            self.write_keyword(" CLONE ");
            self.generate_identifier(clone_src)?;
        }

        // AT/BEFORE clause for time travel (Snowflake)
        if let Some(ref at_clause) = cd.at_clause {
            self.write_space();
            self.generate_expression(at_clause)?;
        }

        for option in &cd.options {
            self.write_space();
            match option {
                DatabaseOption::CharacterSet(charset) => {
                    self.write_keyword("CHARACTER SET");
                    self.write(" = ");
                    self.write(&format!("'{}'", charset));
                }
                DatabaseOption::Collate(collate) => {
                    self.write_keyword("COLLATE");
                    self.write(" = ");
                    self.write(&format!("'{}'", collate));
                }
                DatabaseOption::Owner(owner) => {
                    self.write_keyword("OWNER");
                    self.write(" = ");
                    self.generate_identifier(owner)?;
                }
                DatabaseOption::Template(template) => {
                    self.write_keyword("TEMPLATE");
                    self.write(" = ");
                    self.generate_identifier(template)?;
                }
                DatabaseOption::Encoding(encoding) => {
                    self.write_keyword("ENCODING");
                    self.write(" = ");
                    self.write(&format!("'{}'", encoding));
                }
                DatabaseOption::Location(location) => {
                    self.write_keyword("LOCATION");
                    self.write(" = ");
                    self.write(&format!("'{}'", location));
                }
            }
        }

        Ok(())
    }

    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
        self.write_keyword("DROP DATABASE");

        if dd.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_identifier(&dd.name)?;

        Ok(())
    }

    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
        self.write_keyword("CREATE");

        if cf.or_replace {
            self.write_space();
            self.write_keyword("OR REPLACE");
        }

        if cf.temporary {
            self.write_space();
            self.write_keyword("TEMPORARY");
        }

        self.write_space();
        if cf.is_table_function {
            self.write_keyword("TABLE FUNCTION");
        } else {
            self.write_keyword("FUNCTION");
        }

        if cf.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_table(&cf.name)?;
        if cf.has_parens {
            let func_multiline = self.config.pretty
                && matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::TSQL)
                        | Some(crate::dialects::DialectType::Fabric)
                )
                && !cf.parameters.is_empty();
            if func_multiline {
                self.write("(\n");
                self.indent_level += 2;
                self.write_indent();
                self.generate_function_parameters(&cf.parameters)?;
                self.write("\n");
                self.indent_level -= 2;
                self.write(")");
            } else {
                self.write("(");
                self.generate_function_parameters(&cf.parameters)?;
                self.write(")");
            }
        }

        // Output RETURNS clause (always comes first after parameters)
        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
        let use_multiline = self.config.pretty
            && matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::BigQuery)
                    | Some(crate::dialects::DialectType::TSQL)
                    | Some(crate::dialects::DialectType::Fabric)
            );

        if cf.language_first {
            // LANGUAGE first, then SQL data access, then RETURNS
            if let Some(lang) = &cf.language {
                if use_multiline {
                    self.write_newline();
                } else {
                    self.write_space();
                }
                self.write_keyword("LANGUAGE");
                self.write_space();
                self.write(lang);
            }

            // SQL data access comes after LANGUAGE in this case
            if let Some(sql_data) = &cf.sql_data_access {
                self.write_space();
                match sql_data {
                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
                }
            }

            if let Some(ref rtb) = cf.returns_table_body {
                if use_multiline {
                    self.write_newline();
                } else {
                    self.write_space();
                }
                self.write_keyword("RETURNS");
                self.write_space();
                self.write(rtb);
            } else if let Some(return_type) = &cf.return_type {
                if use_multiline {
                    self.write_newline();
                } else {
                    self.write_space();
                }
                self.write_keyword("RETURNS");
                self.write_space();
                self.generate_data_type(return_type)?;
            }
        } else {
            // RETURNS first (default)
            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
            let is_duckdb = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::DuckDB)
            );
            if let Some(ref rtb) = cf.returns_table_body {
                if !(is_duckdb && rtb.is_empty()) {
                    if use_multiline {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("RETURNS");
                    self.write_space();
                    self.write(rtb);
                }
            } else if let Some(return_type) = &cf.return_type {
                if use_multiline {
                    self.write_newline();
                } else {
                    self.write_space();
                }
                self.write_keyword("RETURNS");
                self.write_space();
                self.generate_data_type(return_type)?;
            }
        }

        // If we have property_order, use it to output properties in original order
        if !cf.property_order.is_empty() {
            // For BigQuery, OPTIONS must come before AS - reorder if needed
            let is_bigquery = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::BigQuery)
            );
            let property_order = if is_bigquery {
                // Move Options before As if both are present
                let mut reordered = Vec::new();
                let mut has_as = false;
                let mut has_options = false;
                for prop in &cf.property_order {
                    match prop {
                        FunctionPropertyKind::As => has_as = true,
                        FunctionPropertyKind::Options => has_options = true,
                        _ => {}
                    }
                }
                if has_as && has_options {
                    // Output all props except As and Options, then Options, then As
                    for prop in &cf.property_order {
                        if *prop != FunctionPropertyKind::As
                            && *prop != FunctionPropertyKind::Options
                        {
                            reordered.push(*prop);
                        }
                    }
                    reordered.push(FunctionPropertyKind::Options);
                    reordered.push(FunctionPropertyKind::As);
                    reordered
                } else {
                    cf.property_order.clone()
                }
            } else {
                cf.property_order.clone()
            };

            for prop in &property_order {
                match prop {
                    FunctionPropertyKind::Set => {
                        self.generate_function_set_options(cf)?;
                    }
                    FunctionPropertyKind::As => {
                        self.generate_function_body(cf)?;
                    }
                    FunctionPropertyKind::Language => {
                        if !cf.language_first {
                            // Only output here if not already output above
                            if let Some(lang) = &cf.language {
                                // Only BigQuery uses multiline formatting
                                let use_multiline = self.config.pretty
                                    && matches!(
                                        self.config.dialect,
                                        Some(crate::dialects::DialectType::BigQuery)
                                    );
                                if use_multiline {
                                    self.write_newline();
                                } else {
                                    self.write_space();
                                }
                                self.write_keyword("LANGUAGE");
                                self.write_space();
                                self.write(lang);
                            }
                        }
                    }
                    FunctionPropertyKind::Determinism => {
                        self.generate_function_determinism(cf)?;
                    }
                    FunctionPropertyKind::NullInput => {
                        self.generate_function_null_input(cf)?;
                    }
                    FunctionPropertyKind::Security => {
                        self.generate_function_security(cf)?;
                    }
                    FunctionPropertyKind::SqlDataAccess => {
                        if !cf.language_first {
                            // Only output here if not already output above
                            self.generate_function_sql_data_access(cf)?;
                        }
                    }
                    FunctionPropertyKind::Options => {
                        if !cf.options.is_empty() {
                            self.write_space();
                            self.generate_options_clause(&cf.options)?;
                        }
                    }
                    FunctionPropertyKind::Environment => {
                        if !cf.environment.is_empty() {
                            self.write_space();
                            self.generate_environment_clause(&cf.environment)?;
                        }
                    }
                }
            }

            // Output OPTIONS if not tracked in property_order (legacy)
            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
            {
                self.write_space();
                self.generate_options_clause(&cf.options)?;
            }

            // Output ENVIRONMENT if not tracked in property_order (legacy)
            if !cf.environment.is_empty()
                && !cf
                    .property_order
                    .contains(&FunctionPropertyKind::Environment)
            {
                self.write_space();
                self.generate_environment_clause(&cf.environment)?;
            }
        } else {
            // Legacy behavior when property_order is empty
            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
            if matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::BigQuery)
            ) {
                self.generate_function_determinism(cf)?;
            }

            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
            let use_multiline = self.config.pretty
                && matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::BigQuery)
                );

            if !cf.language_first {
                if let Some(lang) = &cf.language {
                    if use_multiline {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("LANGUAGE");
                    self.write_space();
                    self.write(lang);
                }

                // SQL data access characteristic comes after LANGUAGE
                self.generate_function_sql_data_access(cf)?;
            }

            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
            if !matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::BigQuery)
            ) {
                self.generate_function_determinism(cf)?;
            }

            self.generate_function_null_input(cf)?;
            self.generate_function_security(cf)?;
            self.generate_function_set_options(cf)?;

            // BigQuery: OPTIONS (key=value, ...) - comes before AS
            if !cf.options.is_empty() {
                self.write_space();
                self.generate_options_clause(&cf.options)?;
            }

            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
            if !cf.environment.is_empty() {
                self.write_space();
                self.generate_environment_clause(&cf.environment)?;
            }

            self.generate_function_body(cf)?;
        }

        Ok(())
    }

    /// Generate SET options for CREATE FUNCTION
    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
        for opt in &cf.set_options {
            self.write_space();
            self.write_keyword("SET");
            self.write_space();
            self.write(&opt.name);
            match &opt.value {
                FunctionSetValue::Value { value, use_to } => {
                    // PostgreSQL normalizes TO to = (matching Python sqlglot behavior)
                    if *use_to
                        && !matches!(
                            self.config.dialect,
                            Some(crate::dialects::DialectType::PostgreSQL)
                        )
                    {
                        self.write(" TO ");
                    } else {
                        self.write(" = ");
                    }
                    self.write(value);
                }
                FunctionSetValue::FromCurrent => {
                    self.write_space();
                    self.write_keyword("FROM CURRENT");
                }
            }
        }
        Ok(())
    }

    /// Generate function body (AS clause)
    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
        if let Some(body) = &cf.body {
            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
            self.write_space();
            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
            let use_multiline = self.config.pretty
                && matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::BigQuery)
                );
            match body {
                FunctionBody::Block(block) => {
                    self.write_keyword("AS");
                    if matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::TSQL)
                    ) {
                        self.write(" BEGIN ");
                        self.write(block);
                        self.write(" END");
                    } else if matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::PostgreSQL)
                    ) {
                        self.write(" $$");
                        self.write(block);
                        self.write("$$");
                    } else {
                        // Escape content for single-quoted output
                        let escaped = self.escape_block_for_single_quote(block);
                        // In BigQuery pretty mode, body content goes on new line
                        if use_multiline {
                            self.write_newline();
                        } else {
                            self.write(" ");
                        }
                        self.write("'");
                        self.write(&escaped);
                        self.write("'");
                    }
                }
                FunctionBody::StringLiteral(s) => {
                    self.write_keyword("AS");
                    // In BigQuery pretty mode, body content goes on new line
                    if use_multiline {
                        self.write_newline();
                    } else {
                        self.write(" ");
                    }
                    self.write("'");
                    self.write(s);
                    self.write("'");
                }
                FunctionBody::Expression(expr) => {
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_expression(expr)?;
                }
                FunctionBody::External(name) => {
                    self.write_keyword("EXTERNAL NAME");
                    self.write(" '");
                    self.write(name);
                    self.write("'");
                }
                FunctionBody::Return(expr) => {
                    if matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::DuckDB)
                    ) {
                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
                        self.write_keyword("AS");
                        self.write_space();
                        // Empty returns_table_body signals TABLE return
                        if cf.returns_table_body.is_some() {
                            self.write_keyword("TABLE");
                            self.write_space();
                        }
                        self.generate_expression(expr)?;
                    } else {
                        if self.config.create_function_return_as {
                            self.write_keyword("AS");
                            // TSQL pretty: newline between AS and RETURN
                            if self.config.pretty
                                && matches!(
                                    self.config.dialect,
                                    Some(crate::dialects::DialectType::TSQL)
                                        | Some(crate::dialects::DialectType::Fabric)
                                )
                            {
                                self.write_newline();
                            } else {
                                self.write_space();
                            }
                        }
                        self.write_keyword("RETURN");
                        self.write_space();
                        self.generate_expression(expr)?;
                    }
                }
                FunctionBody::Statements(stmts) => {
                    self.write_keyword("AS");
                    self.write(" BEGIN ");
                    for (i, stmt) in stmts.iter().enumerate() {
                        if i > 0 {
                            self.write(" ");
                        }
                        self.generate_expression(stmt)?;
                    }
                    self.write(" END");
                }
                FunctionBody::DollarQuoted { content, tag } => {
                    self.write_keyword("AS");
                    self.write(" ");
                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
                    let supports_dollar_quoting = matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::PostgreSQL)
                            | Some(crate::dialects::DialectType::Databricks)
                            | Some(crate::dialects::DialectType::Redshift)
                            | Some(crate::dialects::DialectType::DuckDB)
                    );
                    if supports_dollar_quoting {
                        // Output in dollar-quoted format
                        self.write("$");
                        if let Some(t) = tag {
                            self.write(t);
                        }
                        self.write("$");
                        self.write(content);
                        self.write("$");
                        if let Some(t) = tag {
                            self.write(t);
                        }
                        self.write("$");
                    } else {
                        // Convert to single-quoted string for other dialects
                        let escaped = self.escape_block_for_single_quote(content);
                        self.write("'");
                        self.write(&escaped);
                        self.write("'");
                    }
                }
            }
        }
        Ok(())
    }

    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
        if let Some(det) = cf.deterministic {
            self.write_space();
            if matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::BigQuery)
            ) {
                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
                if det {
                    self.write_keyword("DETERMINISTIC");
                } else {
                    self.write_keyword("NOT DETERMINISTIC");
                }
            } else {
                // PostgreSQL and others use IMMUTABLE/VOLATILE
                if det {
                    self.write_keyword("IMMUTABLE");
                } else {
                    self.write_keyword("VOLATILE");
                }
            }
        }
        Ok(())
    }

    /// Generate null input handling clause
    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
        if let Some(returns_null) = cf.returns_null_on_null_input {
            self.write_space();
            if returns_null {
                if cf.strict {
                    self.write_keyword("STRICT");
                } else {
                    self.write_keyword("RETURNS NULL ON NULL INPUT");
                }
            } else {
                self.write_keyword("CALLED ON NULL INPUT");
            }
        }
        Ok(())
    }

    /// Generate security clause
    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
        if let Some(security) = &cf.security {
            self.write_space();
            self.write_keyword("SECURITY");
            self.write_space();
            match security {
                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
                FunctionSecurity::None => self.write_keyword("NONE"),
            }
        }
        Ok(())
    }

    /// Generate SQL data access clause
    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
        if let Some(sql_data) = &cf.sql_data_access {
            self.write_space();
            match sql_data {
                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
            }
        }
        Ok(())
    }

    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
        for (i, param) in params.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }

            if let Some(mode) = &param.mode {
                match mode {
                    ParameterMode::In => self.write_keyword("IN"),
                    ParameterMode::Out => self.write_keyword("OUT"),
                    ParameterMode::InOut => self.write_keyword("INOUT"),
                }
                self.write_space();
            }

            if let Some(name) = &param.name {
                self.generate_identifier(name)?;
                // Skip space and type for empty Custom types (e.g., DuckDB macros)
                let skip_type =
                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
                if !skip_type {
                    self.write_space();
                    self.generate_data_type(&param.data_type)?;
                }
            } else {
                self.generate_data_type(&param.data_type)?;
            }

            if let Some(default) = &param.default {
                if self.config.parameter_default_equals {
                    self.write(" = ");
                } else {
                    self.write(" DEFAULT ");
                }
                self.generate_expression(default)?;
            }
        }

        Ok(())
    }

    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
        self.write_keyword("DROP FUNCTION");

        if df.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_table(&df.name)?;

        if let Some(params) = &df.parameters {
            self.write(" (");
            for (i, dt) in params.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_data_type(dt)?;
            }
            self.write(")");
        }

        if df.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
        self.write_keyword("CREATE");

        if cp.or_replace {
            self.write_space();
            self.write_keyword("OR REPLACE");
        }

        self.write_space();
        if cp.use_proc_keyword {
            self.write_keyword("PROC");
        } else {
            self.write_keyword("PROCEDURE");
        }

        if cp.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_table(&cp.name)?;
        if cp.has_parens {
            self.write("(");
            self.generate_function_parameters(&cp.parameters)?;
            self.write(")");
        } else if !cp.parameters.is_empty() {
            // TSQL: unparenthesized parameters
            self.write_space();
            self.generate_function_parameters(&cp.parameters)?;
        }

        // RETURNS clause (Snowflake)
        if let Some(return_type) = &cp.return_type {
            self.write_space();
            self.write_keyword("RETURNS");
            self.write_space();
            self.generate_data_type(return_type)?;
        }

        // EXECUTE AS clause (Snowflake)
        if let Some(execute_as) = &cp.execute_as {
            self.write_space();
            self.write_keyword("EXECUTE AS");
            self.write_space();
            self.write_keyword(execute_as);
        }

        if let Some(lang) = &cp.language {
            self.write_space();
            self.write_keyword("LANGUAGE");
            self.write_space();
            self.write(lang);
        }

        if let Some(security) = &cp.security {
            self.write_space();
            self.write_keyword("SECURITY");
            self.write_space();
            match security {
                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
                FunctionSecurity::None => self.write_keyword("NONE"),
            }
        }

        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
        if !cp.with_options.is_empty() {
            self.write_space();
            self.write_keyword("WITH");
            self.write_space();
            for (i, opt) in cp.with_options.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(opt);
            }
        }

        if let Some(body) = &cp.body {
            self.write_space();
            match body {
                FunctionBody::Block(block) => {
                    self.write_keyword("AS");
                    if matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::TSQL)
                    ) {
                        self.write(" BEGIN ");
                        self.write(block);
                        self.write(" END");
                    } else if matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::PostgreSQL)
                    ) {
                        self.write(" $$");
                        self.write(block);
                        self.write("$$");
                    } else {
                        // Escape content for single-quoted output
                        let escaped = self.escape_block_for_single_quote(block);
                        self.write(" '");
                        self.write(&escaped);
                        self.write("'");
                    }
                }
                FunctionBody::StringLiteral(s) => {
                    self.write_keyword("AS");
                    self.write(" '");
                    self.write(s);
                    self.write("'");
                }
                FunctionBody::Expression(expr) => {
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_expression(expr)?;
                }
                FunctionBody::External(name) => {
                    self.write_keyword("EXTERNAL NAME");
                    self.write(" '");
                    self.write(name);
                    self.write("'");
                }
                FunctionBody::Return(expr) => {
                    self.write_keyword("RETURN");
                    self.write_space();
                    self.generate_expression(expr)?;
                }
                FunctionBody::Statements(stmts) => {
                    self.write_keyword("AS");
                    self.write(" BEGIN ");
                    for (i, stmt) in stmts.iter().enumerate() {
                        if i > 0 {
                            self.write(" ");
                        }
                        self.generate_expression(stmt)?;
                    }
                    self.write(" END");
                }
                FunctionBody::DollarQuoted { content, tag } => {
                    self.write_keyword("AS");
                    self.write(" ");
                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
                    let supports_dollar_quoting = matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::PostgreSQL)
                            | Some(crate::dialects::DialectType::Databricks)
                            | Some(crate::dialects::DialectType::Redshift)
                            | Some(crate::dialects::DialectType::DuckDB)
                    );
                    if supports_dollar_quoting {
                        // Output in dollar-quoted format
                        self.write("$");
                        if let Some(t) = tag {
                            self.write(t);
                        }
                        self.write("$");
                        self.write(content);
                        self.write("$");
                        if let Some(t) = tag {
                            self.write(t);
                        }
                        self.write("$");
                    } else {
                        // Convert to single-quoted string for other dialects
                        let escaped = self.escape_block_for_single_quote(content);
                        self.write("'");
                        self.write(&escaped);
                        self.write("'");
                    }
                }
            }
        }

        Ok(())
    }

    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
        self.write_keyword("DROP PROCEDURE");

        if dp.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_table(&dp.name)?;

        if let Some(params) = &dp.parameters {
            self.write(" (");
            for (i, dt) in params.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_data_type(dt)?;
            }
            self.write(")");
        }

        if dp.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
        self.write_keyword("CREATE");

        if cs.temporary {
            self.write_space();
            self.write_keyword("TEMPORARY");
        }

        self.write_space();
        self.write_keyword("SEQUENCE");

        if cs.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_table(&cs.name)?;

        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
        if let Some(comment) = &cs.comment {
            self.write_space();
            self.write_keyword("COMMENT");
            self.write("=");
            self.generate_string_literal(comment)?;
        }

        // If property_order is available, use it to preserve original order
        if !cs.property_order.is_empty() {
            for prop in &cs.property_order {
                match prop {
                    SeqPropKind::Start => {
                        if let Some(start) = cs.start {
                            self.write_space();
                            self.write_keyword("START WITH");
                            self.write(&format!(" {}", start));
                        }
                    }
                    SeqPropKind::Increment => {
                        if let Some(inc) = cs.increment {
                            self.write_space();
                            self.write_keyword("INCREMENT BY");
                            self.write(&format!(" {}", inc));
                        }
                    }
                    SeqPropKind::Minvalue => {
                        if let Some(min) = &cs.minvalue {
                            self.write_space();
                            match min {
                                SequenceBound::Value(v) => {
                                    self.write_keyword("MINVALUE");
                                    self.write(&format!(" {}", v));
                                }
                                SequenceBound::None => {
                                    self.write_keyword("NO MINVALUE");
                                }
                            }
                        }
                    }
                    SeqPropKind::Maxvalue => {
                        if let Some(max) = &cs.maxvalue {
                            self.write_space();
                            match max {
                                SequenceBound::Value(v) => {
                                    self.write_keyword("MAXVALUE");
                                    self.write(&format!(" {}", v));
                                }
                                SequenceBound::None => {
                                    self.write_keyword("NO MAXVALUE");
                                }
                            }
                        }
                    }
                    SeqPropKind::Cache => {
                        if let Some(cache) = cs.cache {
                            self.write_space();
                            self.write_keyword("CACHE");
                            self.write(&format!(" {}", cache));
                        }
                    }
                    SeqPropKind::Cycle => {
                        self.write_space();
                        self.write_keyword("CYCLE");
                    }
                    SeqPropKind::NoCycle => {
                        self.write_space();
                        self.write_keyword("NO CYCLE");
                    }
                    SeqPropKind::OwnedBy => {
                        if let Some(owned) = &cs.owned_by {
                            self.write_space();
                            self.write_keyword("OWNED BY");
                            self.write_space();
                            self.generate_table(owned)?;
                        }
                    }
                    SeqPropKind::Order => {
                        self.write_space();
                        self.write_keyword("ORDER");
                    }
                    SeqPropKind::NoOrder => {
                        self.write_space();
                        self.write_keyword("NOORDER");
                    }
                    SeqPropKind::Comment => {
                        // Comment is handled above, before property_order iteration
                    }
                }
            }
        } else {
            // Fallback: default order for backwards compatibility
            if let Some(inc) = cs.increment {
                self.write_space();
                self.write_keyword("INCREMENT BY");
                self.write(&format!(" {}", inc));
            }

            if let Some(min) = &cs.minvalue {
                self.write_space();
                match min {
                    SequenceBound::Value(v) => {
                        self.write_keyword("MINVALUE");
                        self.write(&format!(" {}", v));
                    }
                    SequenceBound::None => {
                        self.write_keyword("NO MINVALUE");
                    }
                }
            }

            if let Some(max) = &cs.maxvalue {
                self.write_space();
                match max {
                    SequenceBound::Value(v) => {
                        self.write_keyword("MAXVALUE");
                        self.write(&format!(" {}", v));
                    }
                    SequenceBound::None => {
                        self.write_keyword("NO MAXVALUE");
                    }
                }
            }

            if let Some(start) = cs.start {
                self.write_space();
                self.write_keyword("START WITH");
                self.write(&format!(" {}", start));
            }

            if let Some(cache) = cs.cache {
                self.write_space();
                self.write_keyword("CACHE");
                self.write(&format!(" {}", cache));
            }

            if cs.cycle {
                self.write_space();
                self.write_keyword("CYCLE");
            }

            if let Some(owned) = &cs.owned_by {
                self.write_space();
                self.write_keyword("OWNED BY");
                self.write_space();
                self.generate_table(owned)?;
            }
        }

        Ok(())
    }

    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
        self.write_keyword("DROP SEQUENCE");

        if ds.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_table(&ds.name)?;

        if ds.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
        self.write_keyword("ALTER SEQUENCE");

        if als.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_table(&als.name)?;

        if let Some(inc) = als.increment {
            self.write_space();
            self.write_keyword("INCREMENT BY");
            self.write(&format!(" {}", inc));
        }

        if let Some(min) = &als.minvalue {
            self.write_space();
            match min {
                SequenceBound::Value(v) => {
                    self.write_keyword("MINVALUE");
                    self.write(&format!(" {}", v));
                }
                SequenceBound::None => {
                    self.write_keyword("NO MINVALUE");
                }
            }
        }

        if let Some(max) = &als.maxvalue {
            self.write_space();
            match max {
                SequenceBound::Value(v) => {
                    self.write_keyword("MAXVALUE");
                    self.write(&format!(" {}", v));
                }
                SequenceBound::None => {
                    self.write_keyword("NO MAXVALUE");
                }
            }
        }

        if let Some(start) = als.start {
            self.write_space();
            self.write_keyword("START WITH");
            self.write(&format!(" {}", start));
        }

        if let Some(restart) = &als.restart {
            self.write_space();
            self.write_keyword("RESTART");
            if let Some(val) = restart {
                self.write_keyword(" WITH");
                self.write(&format!(" {}", val));
            }
        }

        if let Some(cache) = als.cache {
            self.write_space();
            self.write_keyword("CACHE");
            self.write(&format!(" {}", cache));
        }

        if let Some(cycle) = als.cycle {
            self.write_space();
            if cycle {
                self.write_keyword("CYCLE");
            } else {
                self.write_keyword("NO CYCLE");
            }
        }

        if let Some(owned) = &als.owned_by {
            self.write_space();
            self.write_keyword("OWNED BY");
            self.write_space();
            if let Some(table) = owned {
                self.generate_table(table)?;
            } else {
                self.write_keyword("NONE");
            }
        }

        Ok(())
    }

    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
        self.write_keyword("CREATE");

        if ct.or_replace {
            self.write_space();
            self.write_keyword("OR REPLACE");
        }

        if ct.constraint {
            self.write_space();
            self.write_keyword("CONSTRAINT");
        }

        self.write_space();
        self.write_keyword("TRIGGER");
        self.write_space();
        self.generate_identifier(&ct.name)?;

        self.write_space();
        match ct.timing {
            TriggerTiming::Before => self.write_keyword("BEFORE"),
            TriggerTiming::After => self.write_keyword("AFTER"),
            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
        }

        // Events
        for (i, event) in ct.events.iter().enumerate() {
            if i > 0 {
                self.write_keyword(" OR");
            }
            self.write_space();
            match event {
                TriggerEvent::Insert => self.write_keyword("INSERT"),
                TriggerEvent::Update(cols) => {
                    self.write_keyword("UPDATE");
                    if let Some(cols) = cols {
                        self.write_space();
                        self.write_keyword("OF");
                        for (j, col) in cols.iter().enumerate() {
                            if j > 0 {
                                self.write(",");
                            }
                            self.write_space();
                            self.generate_identifier(col)?;
                        }
                    }
                }
                TriggerEvent::Delete => self.write_keyword("DELETE"),
                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
            }
        }

        self.write_space();
        self.write_keyword("ON");
        self.write_space();
        self.generate_table(&ct.table)?;

        // Referencing clause
        if let Some(ref_clause) = &ct.referencing {
            self.write_space();
            self.write_keyword("REFERENCING");
            if let Some(old_table) = &ref_clause.old_table {
                self.write_space();
                self.write_keyword("OLD TABLE AS");
                self.write_space();
                self.generate_identifier(old_table)?;
            }
            if let Some(new_table) = &ref_clause.new_table {
                self.write_space();
                self.write_keyword("NEW TABLE AS");
                self.write_space();
                self.generate_identifier(new_table)?;
            }
            if let Some(old_row) = &ref_clause.old_row {
                self.write_space();
                self.write_keyword("OLD ROW AS");
                self.write_space();
                self.generate_identifier(old_row)?;
            }
            if let Some(new_row) = &ref_clause.new_row {
                self.write_space();
                self.write_keyword("NEW ROW AS");
                self.write_space();
                self.generate_identifier(new_row)?;
            }
        }

        // Deferrable options for constraint triggers (must come before FOR EACH)
        if let Some(deferrable) = ct.deferrable {
            self.write_space();
            if deferrable {
                self.write_keyword("DEFERRABLE");
            } else {
                self.write_keyword("NOT DEFERRABLE");
            }
        }

        if let Some(initially) = ct.initially_deferred {
            self.write_space();
            self.write_keyword("INITIALLY");
            self.write_space();
            if initially {
                self.write_keyword("DEFERRED");
            } else {
                self.write_keyword("IMMEDIATE");
            }
        }

        self.write_space();
        self.write_keyword("FOR EACH");
        self.write_space();
        match ct.for_each {
            TriggerForEach::Row => self.write_keyword("ROW"),
            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
        }

        // When clause
        if let Some(when) = &ct.when {
            self.write_space();
            self.write_keyword("WHEN");
            self.write(" (");
            self.generate_expression(when)?;
            self.write(")");
        }

        // Body
        self.write_space();
        match &ct.body {
            TriggerBody::Execute { function, args } => {
                self.write_keyword("EXECUTE FUNCTION");
                self.write_space();
                self.generate_table(function)?;
                self.write("(");
                for (i, arg) in args.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(arg)?;
                }
                self.write(")");
            }
            TriggerBody::Block(block) => {
                self.write_keyword("BEGIN");
                self.write_space();
                self.write(block);
                self.write_space();
                self.write_keyword("END");
            }
        }

        Ok(())
    }

    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
        self.write_keyword("DROP TRIGGER");

        if dt.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_identifier(&dt.name)?;

        if let Some(table) = &dt.table {
            self.write_space();
            self.write_keyword("ON");
            self.write_space();
            self.generate_table(table)?;
        }

        if dt.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
        self.write_keyword("CREATE TYPE");

        if ct.if_not_exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }

        self.write_space();
        self.generate_table(&ct.name)?;

        self.write_space();
        self.write_keyword("AS");
        self.write_space();

        match &ct.definition {
            TypeDefinition::Enum(values) => {
                self.write_keyword("ENUM");
                self.write(" (");
                for (i, val) in values.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write(&format!("'{}'", val));
                }
                self.write(")");
            }
            TypeDefinition::Composite(attrs) => {
                self.write("(");
                for (i, attr) in attrs.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(&attr.name)?;
                    self.write_space();
                    self.generate_data_type(&attr.data_type)?;
                    if let Some(collate) = &attr.collate {
                        self.write_space();
                        self.write_keyword("COLLATE");
                        self.write_space();
                        self.generate_identifier(collate)?;
                    }
                }
                self.write(")");
            }
            TypeDefinition::Range {
                subtype,
                subtype_diff,
                canonical,
            } => {
                self.write_keyword("RANGE");
                self.write(" (");
                self.write_keyword("SUBTYPE");
                self.write(" = ");
                self.generate_data_type(subtype)?;
                if let Some(diff) = subtype_diff {
                    self.write(", ");
                    self.write_keyword("SUBTYPE_DIFF");
                    self.write(" = ");
                    self.write(diff);
                }
                if let Some(canon) = canonical {
                    self.write(", ");
                    self.write_keyword("CANONICAL");
                    self.write(" = ");
                    self.write(canon);
                }
                self.write(")");
            }
            TypeDefinition::Base {
                input,
                output,
                internallength,
            } => {
                self.write("(");
                self.write_keyword("INPUT");
                self.write(" = ");
                self.write(input);
                self.write(", ");
                self.write_keyword("OUTPUT");
                self.write(" = ");
                self.write(output);
                if let Some(len) = internallength {
                    self.write(", ");
                    self.write_keyword("INTERNALLENGTH");
                    self.write(" = ");
                    self.write(&len.to_string());
                }
                self.write(")");
            }
            TypeDefinition::Domain {
                base_type,
                default,
                constraints,
            } => {
                self.generate_data_type(base_type)?;
                if let Some(def) = default {
                    self.write_space();
                    self.write_keyword("DEFAULT");
                    self.write_space();
                    self.generate_expression(def)?;
                }
                for constr in constraints {
                    self.write_space();
                    if let Some(name) = &constr.name {
                        self.write_keyword("CONSTRAINT");
                        self.write_space();
                        self.generate_identifier(name)?;
                        self.write_space();
                    }
                    self.write_keyword("CHECK");
                    self.write(" (");
                    self.generate_expression(&constr.check)?;
                    self.write(")");
                }
            }
        }

        Ok(())
    }

    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
        self.write_keyword("DROP TYPE");

        if dt.if_exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }

        self.write_space();
        self.generate_table(&dt.name)?;

        if dt.cascade {
            self.write_space();
            self.write_keyword("CASCADE");
        }

        Ok(())
    }

    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
        // Athena: DESCRIBE uses Hive engine (backticks)
        let saved_athena_hive_context = self.athena_hive_context;
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Athena)
        ) {
            self.athena_hive_context = true;
        }

        // Output leading comments before DESCRIBE
        for comment in &d.leading_comments {
            self.write_formatted_comment(comment);
            self.write(" ");
        }

        self.write_keyword("DESCRIBE");

        if d.extended {
            self.write_space();
            self.write_keyword("EXTENDED");
        } else if d.formatted {
            self.write_space();
            self.write_keyword("FORMATTED");
        }

        // Output style like ANALYZE, HISTORY
        if let Some(ref style) = d.style {
            self.write_space();
            self.write_keyword(style);
        }

        // Handle object kind (TABLE, VIEW) based on dialect
        let should_output_kind = match self.config.dialect {
            // Spark doesn't use TABLE/VIEW after DESCRIBE
            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
                false
            }
            // Snowflake always includes TABLE
            Some(DialectType::Snowflake) => true,
            _ => d.kind.is_some(),
        };
        if should_output_kind {
            if let Some(ref kind) = d.kind {
                self.write_space();
                self.write_keyword(kind);
            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
                self.write_space();
                self.write_keyword("TABLE");
            }
        }

        self.write_space();
        self.generate_expression(&d.target)?;

        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
        if let Some(ref partition) = d.partition {
            self.write_space();
            self.generate_expression(partition)?;
        }

        // Output properties like type=stage
        for (name, value) in &d.properties {
            self.write_space();
            self.write(name);
            self.write("=");
            self.write(value);
        }

        // Restore Athena Hive context
        self.athena_hive_context = saved_athena_hive_context;

        Ok(())
    }

    /// Generate SHOW statement (Snowflake, MySQL, etc.)
    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
    fn generate_show(&mut self, s: &Show) -> Result<()> {
        self.write_keyword("SHOW");
        self.write_space();

        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
        // where TERSE is syntactically valid but has no effect on output
        let show_terse = s.terse
            && !matches!(
                s.this.as_str(),
                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
            );
        if show_terse {
            self.write_keyword("TERSE");
            self.write_space();
        }

        // Object type (USERS, TABLES, DATABASES, etc.)
        self.write_keyword(&s.this);

        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
        if let Some(ref target_expr) = s.target {
            self.write_space();
            self.generate_expression(target_expr)?;
        }

        // HISTORY keyword
        if s.history {
            self.write_space();
            self.write_keyword("HISTORY");
        }

        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
        if let Some(ref for_target) = s.for_target {
            self.write_space();
            self.write_keyword("FOR");
            self.write_space();
            self.generate_expression(for_target)?;
        }

        // Determine ordering based on dialect:
        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
        // - MySQL: IN, FROM, LIKE (when FROM is present)
        use crate::dialects::DialectType;
        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));

        if !is_snowflake && s.from.is_some() {
            // MySQL ordering: IN, FROM, LIKE

            // IN scope_kind [scope]
            if let Some(ref scope_kind) = s.scope_kind {
                self.write_space();
                self.write_keyword("IN");
                self.write_space();
                self.write_keyword(scope_kind);
                if let Some(ref scope) = s.scope {
                    self.write_space();
                    self.generate_expression(scope)?;
                }
            } else if let Some(ref scope) = s.scope {
                self.write_space();
                self.write_keyword("IN");
                self.write_space();
                self.generate_expression(scope)?;
            }

            // FROM clause
            if let Some(ref from) = s.from {
                self.write_space();
                self.write_keyword("FROM");
                self.write_space();
                self.generate_expression(from)?;
            }

            // Second FROM clause (db name)
            if let Some(ref db) = s.db {
                self.write_space();
                self.write_keyword("FROM");
                self.write_space();
                self.generate_expression(db)?;
            }

            // LIKE pattern
            if let Some(ref like) = s.like {
                self.write_space();
                self.write_keyword("LIKE");
                self.write_space();
                self.generate_expression(like)?;
            }
        } else {
            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM

            // LIKE pattern
            if let Some(ref like) = s.like {
                self.write_space();
                self.write_keyword("LIKE");
                self.write_space();
                self.generate_expression(like)?;
            }

            // IN scope_kind [scope]
            if let Some(ref scope_kind) = s.scope_kind {
                self.write_space();
                self.write_keyword("IN");
                self.write_space();
                self.write_keyword(scope_kind);
                if let Some(ref scope) = s.scope {
                    self.write_space();
                    self.generate_expression(scope)?;
                }
            } else if let Some(ref scope) = s.scope {
                self.write_space();
                self.write_keyword("IN");
                self.write_space();
                self.generate_expression(scope)?;
            }
        }

        // STARTS WITH pattern
        if let Some(ref starts_with) = s.starts_with {
            self.write_space();
            self.write_keyword("STARTS WITH");
            self.write_space();
            self.generate_expression(starts_with)?;
        }

        // LIMIT clause
        if let Some(ref limit) = s.limit {
            self.write_space();
            self.generate_limit(limit)?;
        }

        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
        if is_snowflake {
            if let Some(ref from) = s.from {
                self.write_space();
                self.write_keyword("FROM");
                self.write_space();
                self.generate_expression(from)?;
            }
        }

        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
        if let Some(ref where_clause) = s.where_clause {
            self.write_space();
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(where_clause)?;
        }

        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
        if let Some(is_mutex) = s.mutex {
            self.write_space();
            if is_mutex {
                self.write_keyword("MUTEX");
            } else {
                self.write_keyword("STATUS");
            }
        }

        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
        if !s.privileges.is_empty() {
            self.write_space();
            self.write_keyword("WITH PRIVILEGES");
            self.write_space();
            for (i, priv_name) in s.privileges.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write_keyword(priv_name);
            }
        }

        Ok(())
    }

    // ==================== End DDL Generation ====================

    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
        use crate::dialects::DialectType;
        match lit {
            Literal::String(s) => {
                self.generate_string_literal(s)?;
            }
            Literal::Number(n) => {
                if matches!(self.config.dialect, Some(DialectType::MySQL))
                    && n.len() > 2
                    && (n.starts_with("0x") || n.starts_with("0X"))
                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
                {
                    return self.generate_identifier(&Identifier {
                        name: n.clone(),
                        quoted: true,
                        trailing_comments: Vec::new(),
                    });
                }
                // Normalize numbers starting with decimal point to have leading zero
                // e.g., .25 -> 0.25 (matches sqlglot behavior)
                if n.starts_with('.') {
                    self.write("0");
                    self.write(n);
                } else if n.starts_with("-.") {
                    // Handle negative numbers like -.25 -> -0.25
                    self.write("-0");
                    self.write(&n[1..]);
                } else {
                    self.write(n);
                }
            }
            Literal::HexString(h) => {
                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
                match self.config.dialect {
                    Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Teradata) => self.write("X'"),
                    _ => self.write("x'"),
                }
                self.write(h);
                self.write("'");
            }
            Literal::HexNumber(h) => {
                // Hex number (0xA) - integer in hex notation (from BigQuery)
                // For BigQuery, output as 0xHEX
                // For other dialects, convert to decimal integer
                match self.config.dialect {
                    Some(DialectType::BigQuery) => {
                        self.write("0x");
                        self.write(h);
                    }
                    _ => {
                        // Convert hex to decimal
                        if let Ok(val) = u64::from_str_radix(h, 16) {
                            self.write(&val.to_string());
                        } else {
                            // Fallback: keep as 0x notation
                            self.write("0x");
                            self.write(h);
                        }
                    }
                }
            }
            Literal::BitString(b) => {
                // Bit string B'0101...'
                self.write("B'");
                self.write(b);
                self.write("'");
            }
            Literal::ByteString(b) => {
                // Byte string b'...' (BigQuery style)
                self.write("b'");
                // Escape special characters for output
                self.write_escaped_byte_string(b);
                self.write("'");
            }
            Literal::NationalString(s) => {
                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
                // Other dialects strip the N prefix and output as regular string
                let keep_n_prefix = matches!(
                    self.config.dialect,
                    Some(DialectType::TSQL)
                        | Some(DialectType::Oracle)
                        | Some(DialectType::MySQL)
                        | None
                );
                if keep_n_prefix {
                    self.write("N'");
                } else {
                    self.write("'");
                }
                self.write(s);
                self.write("'");
            }
            Literal::Date(d) => {
                self.generate_date_literal(d)?;
            }
            Literal::Time(t) => {
                self.generate_time_literal(t)?;
            }
            Literal::Timestamp(ts) => {
                self.generate_timestamp_literal(ts)?;
            }
            Literal::Datetime(dt) => {
                self.generate_datetime_literal(dt)?;
            }
            Literal::TripleQuotedString(s, _quote_char) => {
                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
                if matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::BigQuery)
                        | Some(crate::dialects::DialectType::DuckDB)
                        | Some(crate::dialects::DialectType::Snowflake)
                        | Some(crate::dialects::DialectType::Spark)
                        | Some(crate::dialects::DialectType::Hive)
                        | Some(crate::dialects::DialectType::Presto)
                        | Some(crate::dialects::DialectType::Trino)
                        | Some(crate::dialects::DialectType::PostgreSQL)
                        | Some(crate::dialects::DialectType::MySQL)
                        | Some(crate::dialects::DialectType::Redshift)
                        | Some(crate::dialects::DialectType::TSQL)
                        | Some(crate::dialects::DialectType::Oracle)
                        | Some(crate::dialects::DialectType::ClickHouse)
                        | Some(crate::dialects::DialectType::Databricks)
                        | Some(crate::dialects::DialectType::SQLite)
                ) {
                    self.generate_string_literal(s)?;
                } else {
                    // Preserve triple-quoted string syntax for generic/unknown dialects
                    let quotes = format!("{0}{0}{0}", _quote_char);
                    self.write(&quotes);
                    self.write(s);
                    self.write(&quotes);
                }
            }
            Literal::EscapeString(s) => {
                // PostgreSQL escape string: e'...' or E'...'
                // Token text format is "e:content" or "E:content"
                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
                use crate::dialects::DialectType;
                let content = if let Some(c) = s.strip_prefix("e:") {
                    c
                } else if let Some(c) = s.strip_prefix("E:") {
                    c
                } else {
                    s.as_str()
                };

                // MySQL: output the content without quotes or prefix
                if matches!(
                    self.config.dialect,
                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
                ) {
                    self.write(content);
                } else {
                    // Some dialects use lowercase e' prefix
                    let prefix = if matches!(
                        self.config.dialect,
                        Some(DialectType::SingleStore)
                            | Some(DialectType::DuckDB)
                            | Some(DialectType::PostgreSQL)
                            | Some(DialectType::CockroachDB)
                            | Some(DialectType::Materialize)
                            | Some(DialectType::RisingWave)
                    ) {
                        "e'"
                    } else {
                        "E'"
                    };

                    // Normalize \' to '' for output
                    let normalized = content.replace("\\'", "''");
                    self.write(prefix);
                    self.write(&normalized);
                    self.write("'");
                }
            }
            Literal::DollarString(s) => {
                // Convert dollar-quoted strings to single-quoted strings
                // (like Python sqlglot's rawstring_sql)
                use crate::dialects::DialectType;
                // Extract content from tag\x00content format
                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
                // Step 2: Determine quote escaping style
                // Snowflake: ' -> \' (backslash escape)
                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
                let use_backslash_quote =
                    matches!(self.config.dialect, Some(DialectType::Snowflake));

                let mut escaped = String::with_capacity(content.len() + 4);
                for ch in content.chars() {
                    if escape_backslash && ch == '\\' {
                        // Escape backslash first (before quote escaping)
                        escaped.push('\\');
                        escaped.push('\\');
                    } else if ch == '\'' {
                        if use_backslash_quote {
                            escaped.push('\\');
                            escaped.push('\'');
                        } else {
                            escaped.push('\'');
                            escaped.push('\'');
                        }
                    } else {
                        escaped.push(ch);
                    }
                }
                self.write("'");
                self.write(&escaped);
                self.write("'");
            }
            Literal::RawString(s) => {
                // Raw strings (r"..." or r'...') contain literal backslashes.
                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
                // 1. If \\ is in STRING_ESCAPES, double all backslashes
                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
                use crate::dialects::DialectType;

                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
                let escape_backslash = matches!(
                    self.config.dialect,
                    Some(DialectType::BigQuery)
                        | Some(DialectType::MySQL)
                        | Some(DialectType::SingleStore)
                        | Some(DialectType::TiDB)
                        | Some(DialectType::Hive)
                        | Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::Drill)
                        | Some(DialectType::Snowflake)
                        | Some(DialectType::Redshift)
                        | Some(DialectType::ClickHouse)
                );

                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
                // These escape quotes as \' instead of ''
                let backslash_escapes_quote = matches!(
                    self.config.dialect,
                    Some(DialectType::BigQuery)
                        | Some(DialectType::Hive)
                        | Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::Drill)
                        | Some(DialectType::Snowflake)
                        | Some(DialectType::Redshift)
                );

                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
                let supports_escape_sequences = escape_backslash;

                let mut escaped = String::with_capacity(s.len() + 4);
                for ch in s.chars() {
                    if escape_backslash && ch == '\\' {
                        // Double the backslash for the target dialect
                        escaped.push('\\');
                        escaped.push('\\');
                    } else if ch == '\'' {
                        if backslash_escapes_quote {
                            // Use backslash to escape the quote: \'
                            escaped.push('\\');
                            escaped.push('\'');
                        } else {
                            // Use SQL standard quote doubling: ''
                            escaped.push('\'');
                            escaped.push('\'');
                        }
                    } else if supports_escape_sequences {
                        // Apply ESCAPED_SEQUENCES mapping for special chars
                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
                        match ch {
                            '\n' => {
                                escaped.push('\\');
                                escaped.push('n');
                            }
                            '\r' => {
                                escaped.push('\\');
                                escaped.push('r');
                            }
                            '\t' => {
                                escaped.push('\\');
                                escaped.push('t');
                            }
                            '\x07' => {
                                escaped.push('\\');
                                escaped.push('a');
                            }
                            '\x08' => {
                                escaped.push('\\');
                                escaped.push('b');
                            }
                            '\x0C' => {
                                escaped.push('\\');
                                escaped.push('f');
                            }
                            '\x0B' => {
                                escaped.push('\\');
                                escaped.push('v');
                            }
                            _ => escaped.push(ch),
                        }
                    } else {
                        escaped.push(ch);
                    }
                }
                self.write("'");
                self.write(&escaped);
                self.write("'");
            }
        }
        Ok(())
    }

    /// Generate a DATE literal with dialect-specific formatting
    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
        use crate::dialects::DialectType;

        match self.config.dialect {
            // SQL Server uses CONVERT or CAST
            Some(DialectType::TSQL) => {
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            // BigQuery uses CAST syntax for type literals
            // DATE 'value' -> CAST('value' AS DATE)
            Some(DialectType::BigQuery) => {
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            // Exasol uses CAST syntax for DATE literals
            // DATE 'value' -> CAST('value' AS DATE)
            Some(DialectType::Exasol) => {
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            // Snowflake uses CAST syntax for DATE literals
            // DATE 'value' -> CAST('value' AS DATE)
            Some(DialectType::Snowflake) => {
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
            Some(DialectType::PostgreSQL)
            | Some(DialectType::MySQL)
            | Some(DialectType::SingleStore)
            | Some(DialectType::TiDB)
            | Some(DialectType::Redshift) => {
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
            Some(DialectType::DuckDB)
            | Some(DialectType::Presto)
            | Some(DialectType::Trino)
            | Some(DialectType::Athena)
            | Some(DialectType::Spark)
            | Some(DialectType::Databricks)
            | Some(DialectType::Hive) => {
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
            Some(DialectType::Oracle) => {
                self.write("TO_DATE('");
                self.write(d);
                self.write("', 'YYYY-MM-DD')");
            }
            // Standard SQL: DATE '...'
            _ => {
                self.write_keyword("DATE");
                self.write(" '");
                self.write(d);
                self.write("'");
            }
        }
        Ok(())
    }

    /// Generate a TIME literal with dialect-specific formatting
    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
        use crate::dialects::DialectType;

        match self.config.dialect {
            // SQL Server uses CONVERT or CAST
            Some(DialectType::TSQL) => {
                self.write("CAST('");
                self.write(t);
                self.write("' AS TIME)");
            }
            // Standard SQL: TIME '...'
            _ => {
                self.write_keyword("TIME");
                self.write(" '");
                self.write(t);
                self.write("'");
            }
        }
        Ok(())
    }

    /// Generate a date expression for Dremio, converting DATE literals to CAST
    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
        use crate::expressions::Literal;

        match expr {
            Expression::Literal(Literal::Date(d)) => {
                // DATE 'value' -> CAST('value' AS DATE)
                self.write("CAST('");
                self.write(d);
                self.write("' AS DATE)");
            }
            _ => {
                // For all other expressions, generate normally
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    /// Generate a TIMESTAMP literal with dialect-specific formatting
    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
        use crate::dialects::DialectType;

        match self.config.dialect {
            // SQL Server uses CONVERT or CAST
            Some(DialectType::TSQL) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS DATETIME2)");
            }
            // BigQuery uses CAST syntax for type literals
            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
            Some(DialectType::BigQuery) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP)");
            }
            // Snowflake uses CAST syntax for TIMESTAMP literals
            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
            Some(DialectType::Snowflake) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP)");
            }
            // Dremio uses CAST syntax for TIMESTAMP literals
            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
            Some(DialectType::Dremio) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP)");
            }
            // Exasol uses CAST syntax for TIMESTAMP literals
            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
            Some(DialectType::Exasol) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP)");
            }
            // Oracle prefers TO_TIMESTAMP function call
            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
            Some(DialectType::Oracle) => {
                self.write("TO_TIMESTAMP('");
                self.write(ts);
                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
            }
            // Presto/Trino: always use CAST for TIMESTAMP literals
            Some(DialectType::Presto) | Some(DialectType::Trino) => {
                if Self::timestamp_has_timezone(ts) {
                    self.write("CAST('");
                    self.write(ts);
                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
                } else {
                    self.write("CAST('");
                    self.write(ts);
                    self.write("' AS TIMESTAMP)");
                }
            }
            // ClickHouse: CAST('...' AS Nullable(DateTime))
            Some(DialectType::ClickHouse) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS Nullable(DateTime))");
            }
            // Spark: CAST('...' AS TIMESTAMP)
            Some(DialectType::Spark) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP)");
            }
            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
            // but TIMESTAMP '...' for special values like 'epoch'
            Some(DialectType::Redshift) => {
                if ts == "epoch" {
                    self.write_keyword("TIMESTAMP");
                    self.write(" '");
                    self.write(ts);
                    self.write("'");
                } else {
                    self.write("CAST('");
                    self.write(ts);
                    self.write("' AS TIMESTAMP)");
                }
            }
            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
            Some(DialectType::PostgreSQL)
            | Some(DialectType::Hive)
            | Some(DialectType::SQLite)
            | Some(DialectType::DuckDB)
            | Some(DialectType::Athena)
            | Some(DialectType::Drill)
            | Some(DialectType::Teradata) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP)");
            }
            // MySQL/StarRocks: CAST('...' AS DATETIME)
            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS DATETIME)");
            }
            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
            Some(DialectType::Databricks) => {
                self.write("CAST('");
                self.write(ts);
                self.write("' AS TIMESTAMP_NTZ)");
            }
            // Standard SQL: TIMESTAMP '...'
            _ => {
                self.write_keyword("TIMESTAMP");
                self.write(" '");
                self.write(ts);
                self.write("'");
            }
        }
        Ok(())
    }

    /// Check if a timestamp string contains a timezone identifier
    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
    fn timestamp_has_timezone(ts: &str) -> bool {
        // Check for common IANA timezone patterns: Continent/City format
        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
        // Also handles: UTC, GMT, Etc/GMT+0, etc.
        let ts_lower = ts.to_lowercase();

        // Check for Continent/City pattern (most common)
        let continent_prefixes = [
            "africa/",
            "america/",
            "antarctica/",
            "arctic/",
            "asia/",
            "atlantic/",
            "australia/",
            "europe/",
            "indian/",
            "pacific/",
            "etc/",
            "brazil/",
            "canada/",
            "chile/",
            "mexico/",
            "us/",
        ];

        for prefix in &continent_prefixes {
            if ts_lower.contains(prefix) {
                return true;
            }
        }

        // Check for standalone timezone abbreviations at the end
        // These typically appear after the time portion
        let tz_abbrevs = [
            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
            " sgt", " aest", " aedt", " acst", " acdt", " awst",
        ];

        for abbrev in &tz_abbrevs {
            if ts_lower.ends_with(abbrev) {
                return true;
            }
        }

        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
        // Look for pattern: space followed by + or - and digits (optionally with :)
        let trimmed = ts.trim();
        if let Some(last_space) = trimmed.rfind(' ') {
            let suffix = &trimmed[last_space + 1..];
            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
                // Check if rest is numeric (possibly with : for hh:mm format)
                let rest = &suffix[1..];
                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
                    return true;
                }
            }
        }

        false
    }

    /// Generate a DATETIME literal with dialect-specific formatting
    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
        use crate::dialects::DialectType;

        match self.config.dialect {
            // BigQuery uses CAST syntax for type literals
            // DATETIME 'value' -> CAST('value' AS DATETIME)
            Some(DialectType::BigQuery) => {
                self.write("CAST('");
                self.write(dt);
                self.write("' AS DATETIME)");
            }
            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
            Some(DialectType::DuckDB) => {
                self.write("CAST('");
                self.write(dt);
                self.write("' AS TIMESTAMP)");
            }
            // DATETIME is primarily a BigQuery type
            // Output as DATETIME '...' for dialects that support it
            _ => {
                self.write_keyword("DATETIME");
                self.write(" '");
                self.write(dt);
                self.write("'");
            }
        }
        Ok(())
    }

    /// Generate a string literal with dialect-specific escaping
    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
        use crate::dialects::DialectType;

        match self.config.dialect {
            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
            // and backslash escaping for special characters like newlines
            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
            Some(DialectType::Hive)
            | Some(DialectType::Spark)
            | Some(DialectType::Databricks)
            | Some(DialectType::Drill) => {
                // Hive/Spark use backslash escaping for quotes (\') and special chars
                self.write("'");
                for c in s.chars() {
                    match c {
                        '\'' => self.write("\\'"),
                        '\\' => self.write("\\\\"),
                        '\n' => self.write("\\n"),
                        '\r' => self.write("\\r"),
                        '\t' => self.write("\\t"),
                        '\0' => self.write("\\0"),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
                self.write("'");
                for c in s.chars() {
                    match c {
                        // MySQL uses SQL standard quote doubling
                        '\'' => self.write("''"),
                        '\\' => self.write("\\\\"),
                        '\n' => self.write("\\n"),
                        '\r' => self.write("\\r"),
                        '\t' => self.write("\\t"),
                        // sqlglot writes a literal NUL for this case
                        '\0' => self.output.push('\0'),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            // BigQuery: Uses backslash escaping
            Some(DialectType::BigQuery) => {
                self.write("'");
                for c in s.chars() {
                    match c {
                        '\'' => self.write("\\'"),
                        '\\' => self.write("\\\\"),
                        '\n' => self.write("\\n"),
                        '\r' => self.write("\\r"),
                        '\t' => self.write("\\t"),
                        '\0' => self.write("\\0"),
                        '\x07' => self.write("\\a"),
                        '\x08' => self.write("\\b"),
                        '\x0C' => self.write("\\f"),
                        '\x0B' => self.write("\\v"),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
            Some(DialectType::Athena) => {
                if self.athena_hive_context {
                    // Hive-style: backslash escaping
                    self.write("'");
                    for c in s.chars() {
                        match c {
                            '\'' => self.write("\\'"),
                            '\\' => self.write("\\\\"),
                            '\n' => self.write("\\n"),
                            '\r' => self.write("\\r"),
                            '\t' => self.write("\\t"),
                            '\0' => self.write("\\0"),
                            _ => self.output.push(c),
                        }
                    }
                    self.write("'");
                } else {
                    // Trino-style: SQL-standard escaping, preserve backslashes
                    self.write("'");
                    for c in s.chars() {
                        match c {
                            '\'' => self.write("''"),
                            // Preserve backslashes literally (no re-escaping)
                            _ => self.output.push(c),
                        }
                    }
                    self.write("'");
                }
            }
            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
            // becomes string value '\\'), so we should NOT re-escape backslashes.
            // We only need to escape single quotes.
            Some(DialectType::Snowflake) => {
                self.write("'");
                for c in s.chars() {
                    match c {
                        '\'' => self.write("\\'"),
                        // Backslashes are already escaped in the tokenized string, don't re-escape
                        // Only escape special characters that might not have been escaped
                        '\n' => self.write("\\n"),
                        '\r' => self.write("\\r"),
                        '\t' => self.write("\\t"),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
            Some(DialectType::PostgreSQL) => {
                self.write("'");
                for c in s.chars() {
                    match c {
                        '\'' => self.write("''"),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            // Redshift: Uses backslash escaping for single quotes
            Some(DialectType::Redshift) => {
                self.write("'");
                for c in s.chars() {
                    match c {
                        '\'' => self.write("\\'"),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            // Oracle: Uses standard double single-quote escaping
            Some(DialectType::Oracle) => {
                self.write("'");
                self.write(&s.replace('\'', "''"));
                self.write("'");
            }
            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
            // backslash escaping for backslashes and special characters
            Some(DialectType::ClickHouse) => {
                self.write("'");
                for c in s.chars() {
                    match c {
                        '\'' => self.write("''"),
                        '\\' => self.write("\\\\"),
                        '\n' => self.write("\\n"),
                        '\r' => self.write("\\r"),
                        '\t' => self.write("\\t"),
                        '\0' => self.write("\\0"),
                        _ => self.output.push(c),
                    }
                }
                self.write("'");
            }
            // Default: SQL standard double single quotes (works for most dialects)
            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
            _ => {
                self.write("'");
                self.write(&s.replace('\'', "''"));
                self.write("'");
            }
        }
        Ok(())
    }

    /// Write a byte string with proper escaping for BigQuery-style byte literals
    /// Escapes characters as \xNN hex escapes where needed
    fn write_escaped_byte_string(&mut self, s: &str) {
        for c in s.chars() {
            match c {
                // Escape single quotes
                '\'' => self.write("\\'"),
                // Escape backslashes
                '\\' => self.write("\\\\"),
                // Keep all printable characters (including non-ASCII) as-is
                _ if !c.is_control() => self.output.push(c),
                // Escape control characters as hex
                _ => {
                    let byte = c as u32;
                    if byte < 256 {
                        self.write(&format!("\\x{:02x}", byte));
                    } else {
                        // For unicode characters, write each UTF-8 byte
                        for b in c.to_string().as_bytes() {
                            self.write(&format!("\\x{:02x}", b));
                        }
                    }
                }
            }
        }
    }

    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
        use crate::dialects::DialectType;

        // Different dialects have different boolean literal formats
        match self.config.dialect {
            // SQL Server typically uses 1/0 for boolean literals in many contexts
            // However, TRUE/FALSE also works in modern versions
            Some(DialectType::TSQL) => {
                self.write(if b.value { "1" } else { "0" });
            }
            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
            Some(DialectType::Oracle) => {
                self.write(if b.value { "1" } else { "0" });
            }
            // MySQL accepts TRUE/FALSE as aliases for 1/0
            Some(DialectType::MySQL) => {
                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
            }
            // Most other dialects support TRUE/FALSE
            _ => {
                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
            }
        }
        Ok(())
    }

    /// Generate an identifier that's used as an alias name
    /// This quotes reserved keywords in addition to already-quoted identifiers
    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
        let name = &id.name;
        let quote_style = &self.config.identifier_quote_style;

        // For aliases, quote if:
        // 1. The identifier was explicitly quoted in the source
        // 2. The identifier is a reserved keyword for the current dialect
        let needs_quoting = id.quoted || self.is_reserved_keyword(name);

        // Normalize identifier if configured
        let output_name = if self.config.normalize_identifiers && !id.quoted {
            name.to_lowercase()
        } else {
            name.to_string()
        };

        if needs_quoting {
            // Escape any quote characters within the identifier
            let escaped_name = if quote_style.start == quote_style.end {
                output_name.replace(
                    quote_style.end,
                    &format!("{}{}", quote_style.end, quote_style.end),
                )
            } else {
                output_name.replace(
                    quote_style.end,
                    &format!("{}{}", quote_style.end, quote_style.end),
                )
            };
            self.write(&format!(
                "{}{}{}",
                quote_style.start, escaped_name, quote_style.end
            ));
        } else {
            self.write(&output_name);
        }

        // Output trailing comments
        for comment in &id.trailing_comments {
            self.write(" ");
            self.write(comment);
        }
        Ok(())
    }

    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
        use crate::dialects::DialectType;

        let name = &id.name;

        // For Athena, use backticks in Hive context, double quotes in Trino context
        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
            && self.athena_hive_context
        {
            &IdentifierQuoteStyle::BACKTICK
        } else {
            &self.config.identifier_quote_style
        };

        // Quote if:
        // 1. The identifier was explicitly quoted in the source
        // 2. The identifier is a reserved keyword for the current dialect
        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
        // This matches Python sqlglot's identifier_sql behavior
        // Also quote identifiers starting with digits if the target dialect doesn't support them
        let starts_with_digit = name.chars().next().is_some_and(|c| c.is_ascii_digit());
        let needs_digit_quoting = starts_with_digit
            && !self.config.identifiers_can_start_with_digit
            && self.config.dialect.is_some();
        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
            && name.len() > 2
            && (name.starts_with("0x") || name.starts_with("0X"))
            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
        let needs_quoting = id.quoted
            || self.is_reserved_keyword(name)
            || self.config.always_quote_identifiers
            || needs_digit_quoting
            || mysql_invalid_hex_identifier;

        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
        // When quoted, we need to output `name`(16) not `name(16)`
        let (base_name, suffix) = if needs_quoting {
            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
            if let Some(paren_pos) = name.find('(') {
                let base = &name[..paren_pos];
                let rest = &name[paren_pos..];
                // Verify it looks like (digits) or (digits) ASC/DESC
                if rest.starts_with('(')
                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
                {
                    // Check if content between parens is all digits
                    let close_paren = rest.find(')').unwrap_or(rest.len());
                    let inside = &rest[1..close_paren];
                    if inside.chars().all(|c| c.is_ascii_digit()) {
                        (base.to_string(), rest.to_string())
                    } else {
                        (name.to_string(), String::new())
                    }
                } else {
                    (name.to_string(), String::new())
                }
            } else if name.ends_with(" ASC") {
                let base = &name[..name.len() - 4];
                (base.to_string(), " ASC".to_string())
            } else if name.ends_with(" DESC") {
                let base = &name[..name.len() - 5];
                (base.to_string(), " DESC".to_string())
            } else {
                (name.to_string(), String::new())
            }
        } else {
            (name.to_string(), String::new())
        };

        // Normalize identifier if configured, with special handling for Exasol
        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
        // should be uppercased when not already quoted (to match Python sqlglot behavior)
        let output_name = if self.config.normalize_identifiers && !id.quoted {
            base_name.to_lowercase()
        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
            && !id.quoted
            && self.is_reserved_keyword(name)
        {
            // Exasol: uppercase reserved keywords when quoting them
            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
            base_name.to_uppercase()
        } else {
            base_name
        };

        if needs_quoting {
            // Escape any quote characters within the identifier
            let escaped_name = if quote_style.start == quote_style.end {
                // Same start/end char (e.g., " or `) - double the quote char
                output_name.replace(
                    quote_style.end,
                    &format!("{}{}", quote_style.end, quote_style.end),
                )
            } else {
                // Different start/end (e.g., [ and ]) - escape only the end char
                output_name.replace(
                    quote_style.end,
                    &format!("{}{}", quote_style.end, quote_style.end),
                )
            };
            self.write(&format!(
                "{}{}{}{}",
                quote_style.start, escaped_name, quote_style.end, suffix
            ));
        } else {
            self.write(&output_name);
        }

        // Output trailing comments
        for comment in &id.trailing_comments {
            self.write(" ");
            self.write(comment);
        }
        Ok(())
    }

    fn generate_column(&mut self, col: &Column) -> Result<()> {
        use crate::dialects::DialectType;

        if let Some(table) = &col.table {
            // Exasol special case: LOCAL as column table prefix should NOT be quoted
            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
                && !table.quoted
                && table.name.eq_ignore_ascii_case("LOCAL");

            if is_exasol_local_prefix {
                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
                self.write("LOCAL");
            } else {
                self.generate_identifier(table)?;
            }
            self.write(".");
        }
        self.generate_identifier(&col.name)?;
        // Oracle-style join marker (+)
        // Only output if dialect supports it (Oracle, Exasol)
        if col.join_mark && self.config.supports_column_join_marks {
            self.write(" (+)");
        }
        // Output trailing comments
        for comment in &col.trailing_comments {
            self.write_space();
            self.write(comment);
        }
        Ok(())
    }

    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
        use crate::dialects::DialectType;
        use crate::expressions::PseudocolumnType;

        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
        if pc.kind == PseudocolumnType::Sysdate
            && !matches!(
                self.config.dialect,
                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
            )
        {
            self.write_keyword("CURRENT_TIMESTAMP");
            // Add () for dialects that expect it
            if matches!(
                self.config.dialect,
                Some(DialectType::MySQL)
                    | Some(DialectType::ClickHouse)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive)
            ) {
                self.write("()");
            }
        } else {
            self.write(pc.kind.as_str());
        }
        Ok(())
    }

    /// Generate CONNECT BY clause (Oracle hierarchical queries)
    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
        use crate::dialects::DialectType;

        // Generate native CONNECT BY for Oracle and Snowflake
        // For other dialects, add a comment noting manual conversion needed
        let supports_connect_by = matches!(
            self.config.dialect,
            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
        );

        if !supports_connect_by && self.config.dialect.is_some() {
            // Add comment for unsupported dialects
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
        }

        // Generate START WITH if present (before CONNECT BY)
        if let Some(start) = &connect.start {
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("START WITH");
            self.write_space();
            self.generate_expression(start)?;
        }

        // Generate CONNECT BY
        if self.config.pretty {
            self.write_newline();
        } else {
            self.write_space();
        }
        self.write_keyword("CONNECT BY");
        if connect.nocycle {
            self.write_space();
            self.write_keyword("NOCYCLE");
        }
        self.write_space();
        self.generate_expression(&connect.connect)?;

        Ok(())
    }

    /// Generate Connect expression (for Expression::Connect variant)
    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
        self.generate_connect(connect)
    }

    /// Generate PRIOR expression
    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
        self.write_keyword("PRIOR");
        self.write_space();
        self.generate_expression(&prior.this)?;
        Ok(())
    }

    /// Generate CONNECT_BY_ROOT function
    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
        self.write_keyword("CONNECT_BY_ROOT");
        self.write_space();
        self.generate_expression(&cbr.this)?;
        Ok(())
    }

    /// Generate MATCH_RECOGNIZE clause
    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
        use crate::dialects::DialectType;

        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
        let supports_match_recognize = matches!(
            self.config.dialect,
            Some(DialectType::Oracle)
                | Some(DialectType::Snowflake)
                | Some(DialectType::Presto)
                | Some(DialectType::Trino)
        );

        // Generate the source table first
        if let Some(source) = &mr.this {
            self.generate_expression(source)?;
        }

        if !supports_match_recognize {
            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
            return Ok(());
        }

        // In pretty mode, MATCH_RECOGNIZE should be on a new line
        if self.config.pretty {
            self.write_newline();
        } else {
            self.write_space();
        }

        self.write_keyword("MATCH_RECOGNIZE");
        self.write(" (");

        if self.config.pretty {
            self.indent_level += 1;
        }

        let mut needs_separator = false;

        // PARTITION BY
        if let Some(partition_by) = &mr.partition_by {
            if !partition_by.is_empty() {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                }
                self.write_keyword("PARTITION BY");
                self.write_space();
                for (i, expr) in partition_by.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                needs_separator = true;
            }
        }

        // ORDER BY
        if let Some(order_by) = &mr.order_by {
            if !order_by.is_empty() {
                if needs_separator {
                    if self.config.pretty {
                        self.write_newline();
                        self.write_indent();
                    } else {
                        self.write_space();
                    }
                } else if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                }
                self.write_keyword("ORDER BY");
                // In pretty mode, put each ORDER BY column on a new indented line
                if self.config.pretty {
                    self.indent_level += 1;
                    for (i, ordered) in order_by.iter().enumerate() {
                        if i > 0 {
                            self.write(",");
                        }
                        self.write_newline();
                        self.write_indent();
                        self.generate_ordered(ordered)?;
                    }
                    self.indent_level -= 1;
                } else {
                    self.write_space();
                    for (i, ordered) in order_by.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_ordered(ordered)?;
                    }
                }
                needs_separator = true;
            }
        }

        // MEASURES
        if let Some(measures) = &mr.measures {
            if !measures.is_empty() {
                if needs_separator {
                    if self.config.pretty {
                        self.write_newline();
                        self.write_indent();
                    } else {
                        self.write_space();
                    }
                } else if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                }
                self.write_keyword("MEASURES");
                // In pretty mode, put each MEASURE on a new indented line
                if self.config.pretty {
                    self.indent_level += 1;
                    for (i, measure) in measures.iter().enumerate() {
                        if i > 0 {
                            self.write(",");
                        }
                        self.write_newline();
                        self.write_indent();
                        // Handle RUNNING/FINAL prefix
                        if let Some(semantics) = &measure.window_frame {
                            match semantics {
                                MatchRecognizeSemantics::Running => {
                                    self.write_keyword("RUNNING");
                                    self.write_space();
                                }
                                MatchRecognizeSemantics::Final => {
                                    self.write_keyword("FINAL");
                                    self.write_space();
                                }
                            }
                        }
                        self.generate_expression(&measure.this)?;
                    }
                    self.indent_level -= 1;
                } else {
                    self.write_space();
                    for (i, measure) in measures.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        // Handle RUNNING/FINAL prefix
                        if let Some(semantics) = &measure.window_frame {
                            match semantics {
                                MatchRecognizeSemantics::Running => {
                                    self.write_keyword("RUNNING");
                                    self.write_space();
                                }
                                MatchRecognizeSemantics::Final => {
                                    self.write_keyword("FINAL");
                                    self.write_space();
                                }
                            }
                        }
                        self.generate_expression(&measure.this)?;
                    }
                }
                needs_separator = true;
            }
        }

        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
        if let Some(rows) = &mr.rows {
            if needs_separator {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
            } else if self.config.pretty {
                self.write_newline();
                self.write_indent();
            }
            match rows {
                MatchRecognizeRows::OneRowPerMatch => {
                    self.write_keyword("ONE ROW PER MATCH");
                }
                MatchRecognizeRows::AllRowsPerMatch => {
                    self.write_keyword("ALL ROWS PER MATCH");
                }
                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
                }
                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
                }
                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
                }
            }
            needs_separator = true;
        }

        // AFTER MATCH SKIP
        if let Some(after) = &mr.after {
            if needs_separator {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
            } else if self.config.pretty {
                self.write_newline();
                self.write_indent();
            }
            match after {
                MatchRecognizeAfter::PastLastRow => {
                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
                }
                MatchRecognizeAfter::ToNextRow => {
                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
                }
                MatchRecognizeAfter::ToFirst(ident) => {
                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
                    self.write_space();
                    self.generate_identifier(ident)?;
                }
                MatchRecognizeAfter::ToLast(ident) => {
                    self.write_keyword("AFTER MATCH SKIP TO LAST");
                    self.write_space();
                    self.generate_identifier(ident)?;
                }
            }
            needs_separator = true;
        }

        // PATTERN
        if let Some(pattern) = &mr.pattern {
            if needs_separator {
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
            } else if self.config.pretty {
                self.write_newline();
                self.write_indent();
            }
            self.write_keyword("PATTERN");
            self.write_space();
            self.write("(");
            self.write(pattern);
            self.write(")");
            needs_separator = true;
        }

        // DEFINE
        if let Some(define) = &mr.define {
            if !define.is_empty() {
                if needs_separator {
                    if self.config.pretty {
                        self.write_newline();
                        self.write_indent();
                    } else {
                        self.write_space();
                    }
                } else if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                }
                self.write_keyword("DEFINE");
                // In pretty mode, put each DEFINE on a new indented line
                if self.config.pretty {
                    self.indent_level += 1;
                    for (i, (name, expr)) in define.iter().enumerate() {
                        if i > 0 {
                            self.write(",");
                        }
                        self.write_newline();
                        self.write_indent();
                        self.generate_identifier(name)?;
                        self.write(" AS ");
                        self.generate_expression(expr)?;
                    }
                    self.indent_level -= 1;
                } else {
                    self.write_space();
                    for (i, (name, expr)) in define.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(name)?;
                        self.write(" AS ");
                        self.generate_expression(expr)?;
                    }
                }
            }
        }

        if self.config.pretty {
            self.indent_level -= 1;
            self.write_newline();
        }
        self.write(")");

        // Alias - only include AS if it was explicitly present in the input
        if let Some(alias) = &mr.alias {
            self.write(" ");
            if mr.alias_explicit_as {
                self.write_keyword("AS");
                self.write(" ");
            }
            self.generate_identifier(alias)?;
        }

        Ok(())
    }

    /// Generate a query hint /*+ ... */
    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
        use crate::dialects::DialectType;

        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
        let supports_hints = matches!(
            self.config.dialect,
            None |  // No dialect = preserve everything
            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
            Some(DialectType::Spark) | Some(DialectType::Hive) |
            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
        );

        if !supports_hints || hint.expressions.is_empty() {
            return Ok(());
        }

        // First, expand raw hint text into individual hint strings
        // This handles the case where the parser stored multiple hints as a single raw string
        let mut hint_strings: Vec<String> = Vec::new();
        for expr in &hint.expressions {
            match expr {
                HintExpression::Raw(text) => {
                    // Parse raw hint text into individual hint function calls
                    let parsed = self.parse_raw_hint_text(text);
                    hint_strings.extend(parsed);
                }
                _ => {
                    hint_strings.push(self.hint_expression_to_string(expr)?);
                }
            }
        }

        // In pretty mode with multiple hints, always use multiline format
        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
        // always joins with newlines in pretty mode
        let use_multiline = self.config.pretty && hint_strings.len() > 1;

        if use_multiline {
            // Pretty print with each hint on its own line
            self.write(" /*+ ");
            for (i, hint_str) in hint_strings.iter().enumerate() {
                if i > 0 {
                    self.write_newline();
                    self.write("  "); // 2-space indent within hint block
                }
                self.write(hint_str);
            }
            self.write(" */");
        } else {
            // Single line format
            self.write(" /*+ ");
            let sep = match self.config.dialect {
                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
                _ => " ",
            };
            for (i, hint_str) in hint_strings.iter().enumerate() {
                if i > 0 {
                    self.write(sep);
                }
                self.write(hint_str);
            }
            self.write(" */");
        }

        Ok(())
    }

    /// Parse raw hint text into individual hint function calls
    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
        let mut results = Vec::new();
        let chars = text.chars().peekable();
        let mut current = String::new();
        let mut paren_depth = 0;
        let mut has_unparseable_content = false;
        let mut position_after_last_function = 0;
        let mut char_position = 0;

        for c in chars {
            char_position += c.len_utf8();
            match c {
                '(' => {
                    paren_depth += 1;
                    current.push(c);
                }
                ')' => {
                    paren_depth -= 1;
                    current.push(c);
                    // When we close the outer parenthesis, we've completed a hint function
                    if paren_depth == 0 {
                        let trimmed = current.trim().to_string();
                        if !trimmed.is_empty() {
                            // Format this hint for pretty printing if needed
                            let formatted = self.format_hint_function(&trimmed);
                            results.push(formatted);
                        }
                        current.clear();
                        position_after_last_function = char_position;
                    }
                }
                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
                    // Space/comma/whitespace outside parentheses - skip
                }
                _ if paren_depth == 0 => {
                    // Character outside parentheses - accumulate for potential hint name
                    current.push(c);
                }
                _ => {
                    current.push(c);
                }
            }
        }

        // Check if there's remaining text after the last function call
        let remaining_text = text[position_after_last_function..].trim();
        if !remaining_text.is_empty() {
            // Check if it looks like valid hint function names
            // Valid hint identifiers typically are uppercase alphanumeric with underscores
            // If we see multiple words without parens, it's likely unparseable
            let words: Vec<&str> = remaining_text.split_whitespace().collect();
            let looks_like_hint_functions = words.iter().all(|word| {
                // A valid hint name followed by opening paren, or a standalone uppercase identifier
                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
            });

            if !looks_like_hint_functions && words.len() > 1 {
                has_unparseable_content = true;
            }
        }

        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
        if has_unparseable_content {
            return vec![text.trim().to_string()];
        }

        // If we couldn't parse anything, return the original text as a single hint
        if results.is_empty() {
            results.push(text.trim().to_string());
        }

        results
    }

    /// Format a hint function for pretty printing
    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
    fn format_hint_function(&self, hint: &str) -> String {
        if !self.config.pretty {
            return hint.to_string();
        }

        // Try to parse NAME(args) pattern
        if let Some(paren_pos) = hint.find('(') {
            if hint.ends_with(')') {
                let name = &hint[..paren_pos];
                let args_str = &hint[paren_pos + 1..hint.len() - 1];

                // Parse arguments (space-separated for Oracle hints)
                let args: Vec<&str> = args_str.split_whitespace().collect();

                // Calculate total width of arguments
                let total_args_width: usize =
                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args

                // If too wide, format on multiple lines
                if total_args_width > self.config.max_text_width && !args.is_empty() {
                    let mut result = format!("{}(\n", name);
                    for arg in &args {
                        result.push_str("    "); // 4-space indent for args
                        result.push_str(arg);
                        result.push('\n');
                    }
                    result.push_str("  )"); // 2-space indent for closing paren
                    return result;
                }
            }
        }

        hint.to_string()
    }

    /// Convert a hint expression to a string, handling multiline formatting for long arguments
    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
        match expr {
            HintExpression::Function { name, args } => {
                // Generate each argument to a string
                let arg_strings: Vec<String> = args
                    .iter()
                    .map(|arg| {
                        let mut gen = Generator::with_config(self.config.clone());
                        gen.generate_expression(arg)?;
                        Ok(gen.output)
                    })
                    .collect::<Result<Vec<_>>>()?;

                // Oracle hints use space-separated arguments, not comma-separated
                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
                    + arg_strings.len().saturating_sub(1); // spaces between args

                // Check if function args need multiline formatting
                // Use too_wide check for argument formatting
                let args_multiline =
                    self.config.pretty && total_args_width > self.config.max_text_width;

                if args_multiline && !arg_strings.is_empty() {
                    // Multiline format for long argument lists
                    let mut result = format!("{}(\n", name);
                    for arg_str in &arg_strings {
                        result.push_str("    "); // 4-space indent for args
                        result.push_str(arg_str);
                        result.push('\n');
                    }
                    result.push_str("  )"); // 2-space indent for closing paren
                    Ok(result)
                } else {
                    // Single line format with space-separated args (Oracle style)
                    let args_str = arg_strings.join(" ");
                    Ok(format!("{}({})", name, args_str))
                }
            }
            HintExpression::Identifier(name) => Ok(name.clone()),
            HintExpression::Raw(text) => {
                // For pretty printing, try to format the raw text
                if self.config.pretty {
                    Ok(self.format_hint_function(text))
                } else {
                    Ok(text.clone())
                }
            }
        }
    }

    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
        // PostgreSQL ONLY modifier: prevents scanning child tables
        if table.only {
            self.write_keyword("ONLY");
            self.write_space();
        }

        // Check for Snowflake IDENTIFIER() function
        if let Some(ref identifier_func) = table.identifier_func {
            self.generate_expression(identifier_func)?;
        } else {
            if let Some(catalog) = &table.catalog {
                self.generate_identifier(catalog)?;
                self.write(".");
            }
            if let Some(schema) = &table.schema {
                self.generate_identifier(schema)?;
                self.write(".");
            }
            self.generate_identifier(&table.name)?;
        }

        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
        if let Some(changes) = &table.changes {
            self.write(" ");
            self.generate_changes(changes)?;
        }

        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
        if !table.partitions.is_empty() {
            self.write_space();
            self.write_keyword("PARTITION");
            self.write("(");
            for (i, partition) in table.partitions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(partition)?;
            }
            self.write(")");
        }

        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
        if table.changes.is_none() {
            if let Some(when) = &table.when {
                self.write_space();
                self.generate_historical_data(when)?;
            }
        }

        // Output TSQL FOR SYSTEM_TIME temporal clause
        if let Some(ref system_time) = table.system_time {
            self.write_space();
            self.write(system_time);
        }

        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
        if let Some(ref version) = table.version {
            self.write_space();
            self.generate_version(version)?;
        }

        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
        let alias_post_tablesample = self.config.alias_post_tablesample;

        if alias_post_tablesample {
            // TABLESAMPLE before alias (Oracle, Hive, Spark)
            self.generate_table_sample_clause(table)?;
        }

        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
        // For SQLite, INDEXED BY hints come after the alias, so skip here
        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
            && table.hints.iter().any(|h| {
                if let Expression::Identifier(id) = h {
                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
                } else {
                    false
                }
            });
        if !table.hints.is_empty() && !is_sqlite_hint {
            for hint in &table.hints {
                self.write_space();
                self.generate_expression(hint)?;
            }
        }

        if let Some(alias) = &table.alias {
            self.write_space();
            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
            // PostgreSQL/Redshift/Snowflake/BigQuery/Presto/Trino always use AS for table aliases
            let always_use_as = matches!(
                self.config.dialect,
                Some(DialectType::PostgreSQL)
                    | Some(DialectType::Redshift)
                    | Some(DialectType::Snowflake)
                    | Some(DialectType::BigQuery)
                    | Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::TSQL)
                    | Some(DialectType::Fabric)
                    | Some(DialectType::MySQL)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Hive)
            );
            let is_stage_ref = table.name.name.starts_with('@');
            if table.alias_explicit_as || always_use_as || is_stage_ref {
                self.write_keyword("AS");
                self.write_space();
            }
            self.generate_identifier(alias)?;

            // Output column aliases if present: AS t(c1, c2)
            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
                self.write("(");
                for (i, col_alias) in table.column_aliases.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col_alias)?;
                }
                self.write(")");
            }
        }

        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
        if !alias_post_tablesample {
            self.generate_table_sample_clause(table)?;
        }

        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
        if is_sqlite_hint {
            for hint in &table.hints {
                self.write_space();
                self.generate_expression(hint)?;
            }
        }

        // ClickHouse FINAL modifier
        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            self.write_space();
            self.write_keyword("FINAL");
        }

        // Output trailing comments
        for comment in &table.trailing_comments {
            self.write_space();
            self.write_formatted_comment(comment);
        }

        Ok(())
    }

    /// Helper to output TABLESAMPLE clause for a table reference
    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
        if let Some(ref ts) = table.table_sample {
            self.write_space();
            if ts.is_using_sample {
                self.write_keyword("USING SAMPLE");
            } else {
                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
                self.write_keyword(self.config.tablesample_keywords);
            }
            self.generate_sample_body(ts)?;
            // Seed for table-level sample - use dialect's configured keyword
            if let Some(ref seed) = ts.seed {
                self.write_space();
                self.write_keyword(self.config.tablesample_seed_keyword);
                self.write(" (");
                self.generate_expression(seed)?;
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')

        if sr.quoted {
            self.write("'");
        }

        self.write(&sr.name);
        if let Some(path) = &sr.path {
            self.write(path);
        }

        if sr.quoted {
            self.write("'");
        }

        // Output FILE_FORMAT and PATTERN if present
        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
        if has_options {
            self.write(" (");
            let mut first = true;

            if let Some(file_format) = &sr.file_format {
                if !first {
                    self.write(", ");
                }
                self.write_keyword("FILE_FORMAT");
                self.write(" => ");
                self.generate_expression(file_format)?;
                first = false;
            }

            if let Some(pattern) = &sr.pattern {
                if !first {
                    self.write(", ");
                }
                self.write_keyword("PATTERN");
                self.write(" => '");
                self.write(pattern);
                self.write("'");
            }

            self.write(")");
        }
        Ok(())
    }

    fn generate_star(&mut self, star: &Star) -> Result<()> {
        use crate::dialects::DialectType;

        if let Some(table) = &star.table {
            self.generate_identifier(table)?;
            self.write(".");
        }
        self.write("*");

        // Generate EXCLUDE/EXCEPT clause based on dialect
        if let Some(except) = &star.except {
            if !except.is_empty() {
                self.write_space();
                // Use dialect-appropriate keyword
                match self.config.dialect {
                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
                        self.write_keyword("EXCLUDE")
                    }
                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
                }
                self.write(" (");
                for (i, col) in except.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }
        }

        // Generate REPLACE clause
        if let Some(replace) = &star.replace {
            if !replace.is_empty() {
                self.write_space();
                self.write_keyword("REPLACE");
                self.write(" (");
                for (i, alias) in replace.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(&alias.this)?;
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_identifier(&alias.alias)?;
                }
                self.write(")");
            }
        }

        // Generate RENAME clause (Snowflake specific)
        if let Some(rename) = &star.rename {
            if !rename.is_empty() {
                self.write_space();
                self.write_keyword("RENAME");
                self.write(" (");
                for (i, (old_name, new_name)) in rename.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(old_name)?;
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_identifier(new_name)?;
                }
                self.write(")");
            }
        }

        // Output trailing comments
        for comment in &star.trailing_comments {
            self.write_space();
            self.write(comment);
        }

        Ok(())
    }

    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
        self.write("{");
        match expr {
            Expression::Star(star) => {
                // Generate the star (table.* or just * with optional EXCLUDE)
                self.generate_star(star)?;
            }
            Expression::ILike(ilike) => {
                // {* ILIKE 'pattern'} syntax
                self.generate_expression(&ilike.left)?;
                self.write_space();
                self.write_keyword("ILIKE");
                self.write_space();
                self.generate_expression(&ilike.right)?;
            }
            _ => {
                self.generate_expression(expr)?;
            }
        }
        self.write("}");
        Ok(())
    }

    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
        // to avoid duplication (comments are captured as both Column.trailing_comments
        // and Alias.pre_alias_comments during parsing)
        match &alias.this {
            Expression::Column(col) => {
                // Generate column without trailing comments - they're in pre_alias_comments
                if let Some(table) = &col.table {
                    self.generate_identifier(table)?;
                    self.write(".");
                }
                self.generate_identifier(&col.name)?;
            }
            _ => {
                self.generate_expression(&alias.this)?;
            }
        }

        // Output pre-alias comments (comments between expression and AS)
        for comment in &alias.pre_alias_comments {
            self.write_space();
            self.write(comment);
        }

        use crate::dialects::DialectType;

        // Determine if we should skip AS keyword for table-valued function aliases
        // Oracle and some other dialects don't use AS for table aliases
        // Note: We specifically use TableFromRows here, NOT Function, because Function
        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
        let is_table_source = matches!(
            &alias.this,
            Expression::JSONTable(_)
                | Expression::XMLTable(_)
                | Expression::TableFromRows(_)
                | Expression::Unnest(_)
                | Expression::MatchRecognize(_)
                | Expression::Select(_)
                | Expression::Subquery(_)
                | Expression::Paren(_)
        );
        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
        let skip_as = is_table_source && dialect_skips_table_alias_as;

        self.write_space();
        if !skip_as {
            self.write_keyword("AS");
            self.write_space();
        }

        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));

        // Check if we have column aliases only (no table alias name)
        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
            // Generate AS (col1, col2, ...)
            self.write("(");
            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_alias_identifier(col_alias)?;
            }
            self.write(")");
        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
            // Generate AS alias(col1, col2, ...)
            self.generate_alias_identifier(&alias.alias)?;
            self.write("(");
            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_alias_identifier(col_alias)?;
            }
            self.write(")");
        } else {
            // Simple alias (or BigQuery without column aliases)
            self.generate_alias_identifier(&alias.alias)?;
        }

        // Output trailing comments (comments after the alias)
        for comment in &alias.trailing_comments {
            self.write_space();
            self.write(comment);
        }

        Ok(())
    }

    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
        use crate::dialects::DialectType;

        // SingleStore uses :> syntax
        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
            self.generate_expression(&cast.this)?;
            self.write(" :> ");
            self.generate_data_type(&cast.to)?;
            return Ok(());
        }

        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
            let is_unknown_type = matches!(cast.to, DataType::Unknown)
                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
            if is_unknown_type {
                if let Some(format) = &cast.format {
                    self.write_keyword("CAST");
                    self.write("(");
                    self.generate_expression(&cast.this)?;
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.write_keyword("FORMAT");
                    self.write_space();
                    self.generate_expression(format)?;
                    self.write(")");
                    return Ok(());
                }
            }
        }

        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
            if let Some(format) = &cast.format {
                // Check if target type is DATE or TIMESTAMP
                let is_date = matches!(cast.to, DataType::Date);
                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });

                if is_date || is_timestamp {
                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
                    self.write_keyword(func_name);
                    self.write("(");
                    self.generate_expression(&cast.this)?;
                    self.write(", ");

                    // Normalize format string for Oracle (HH -> HH12)
                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
                        let normalized = self.normalize_oracle_format(fmt_str);
                        self.write("'");
                        self.write(&normalized);
                        self.write("'");
                    } else {
                        self.generate_expression(format)?;
                    }

                    self.write(")");
                    return Ok(());
                }
            }
        }

        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
        // This preserves sqlglot's typed inline array literal output.
        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
            if let Expression::Array(arr) = &cast.this {
                self.generate_data_type(&cast.to)?;
                // Output just the bracket content [values] without the ARRAY prefix
                self.write("[");
                for (i, expr) in arr.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write("]");
                return Ok(());
            }
            if matches!(&cast.this, Expression::ArrayFunc(_)) {
                self.generate_data_type(&cast.to)?;
                self.generate_expression(&cast.this)?;
                return Ok(());
            }
        }

        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
        // convert the inner Struct to ROW(values...) format
        if matches!(
            self.config.dialect,
            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
        ) {
            if let Expression::Struct(ref s) = cast.this {
                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
                    self.write_keyword("CAST");
                    self.write("(");
                    self.generate_struct_as_row(s)?;
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_data_type(&cast.to)?;
                    self.write(")");
                    return Ok(());
                }
            }
        }

        // Determine if we should use :: syntax based on dialect
        // PostgreSQL prefers :: for identity, most others prefer CAST()
        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();

        if use_double_colon {
            // PostgreSQL :: syntax: expr::type
            self.generate_expression(&cast.this)?;
            self.write("::");
            self.generate_data_type(&cast.to)?;
        } else {
            // Standard CAST() syntax
            self.write_keyword("CAST");
            self.write("(");
            self.generate_expression(&cast.this)?;
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            // For MySQL/SingleStore/TiDB, map text/blob variant Custom types to CHAR in CAST
            // This matches Python sqlglot's CHAR_CAST_MAPPING behavior
            if matches!(
                self.config.dialect,
                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
            ) {
                if let DataType::Custom { ref name } = cast.to {
                    let upper = name.to_uppercase();
                    match upper.as_str() {
                        "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
                        | "TINYBLOB" => {
                            self.write_keyword("CHAR");
                        }
                        _ => {
                            self.generate_data_type(&cast.to)?;
                        }
                    }
                } else {
                    self.generate_data_type(&cast.to)?;
                }
            } else {
                self.generate_data_type(&cast.to)?;
            }

            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
            if let Some(default) = &cast.default {
                self.write_space();
                self.write_keyword("DEFAULT");
                self.write_space();
                self.generate_expression(default)?;
                self.write_space();
                self.write_keyword("ON");
                self.write_space();
                self.write_keyword("CONVERSION");
                self.write_space();
                self.write_keyword("ERROR");
            }

            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
            if let Some(format) = &cast.format {
                // Check if Oracle dialect - use comma syntax
                if matches!(
                    self.config.dialect,
                    Some(crate::dialects::DialectType::Oracle)
                ) {
                    self.write(", ");
                } else {
                    self.write_space();
                    self.write_keyword("FORMAT");
                    self.write_space();
                }
                self.generate_expression(format)?;
            }

            self.write(")");
            // Output trailing comments
            for comment in &cast.trailing_comments {
                self.write_space();
                self.write(comment);
            }
        }
        Ok(())
    }

    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
        self.write_keyword("ROW");
        self.write("(");
        for (i, (_, expr)) in s.fields.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            // Recursively convert inner Struct to ROW format
            if let Expression::Struct(ref inner_s) = expr {
                self.generate_struct_as_row(inner_s)?;
            } else {
                self.generate_expression(expr)?;
            }
        }
        self.write(")");
        Ok(())
    }

    /// Normalize Oracle date/time format strings
    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
    fn normalize_oracle_format(&self, format: &str) -> String {
        // Replace standalone HH with HH12 (but not HH12 or HH24)
        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
        let mut result = String::new();
        let chars: Vec<char> = format.chars().collect();
        let mut i = 0;

        while i < chars.len() {
            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
                // Check what follows HH
                if i + 2 < chars.len() {
                    let next = chars[i + 2];
                    if next == '1' || next == '2' {
                        // This is HH12 or HH24, keep as is
                        result.push('H');
                        result.push('H');
                        i += 2;
                        continue;
                    }
                }
                // Standalone HH -> HH12
                result.push_str("HH12");
                i += 2;
            } else {
                result.push(chars[i]);
                i += 1;
            }
        }

        result
    }

    /// Check if the current dialect prefers :: cast syntax
    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
    /// So we return false for all dialects to match Python sqlglot's behavior
    fn dialect_prefers_double_colon(&self) -> bool {
        // Python sqlglot normalizes :: syntax to CAST() for all dialects
        // Even PostgreSQL outputs CAST() not ::
        false
    }

    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
        let use_percent_operator = matches!(
            self.config.dialect,
            Some(DialectType::Snowflake)
                | Some(DialectType::MySQL)
                | Some(DialectType::Presto)
                | Some(DialectType::Trino)
                | Some(DialectType::PostgreSQL)
                | Some(DialectType::DuckDB)
        );

        if use_percent_operator {
            self.generate_expression(&f.this)?;
            self.write(" % ");
            self.generate_expression(&f.expression)?;
            Ok(())
        } else {
            self.generate_binary_func("MOD", &f.this, &f.expression)
        }
    }

    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // Snowflake normalizes IFNULL to COALESCE
        let func_name = match self.config.dialect {
            Some(DialectType::Snowflake) => "COALESCE",
            _ => "IFNULL",
        };

        self.generate_binary_func(func_name, &f.this, &f.expression)
    }

    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
        // Use original function name if preserved (for identity tests)
        if let Some(ref original_name) = f.original_name {
            return self.generate_binary_func(original_name, &f.this, &f.expression);
        }

        // Otherwise, use dialect-specific function names
        use crate::dialects::DialectType;
        let func_name = match self.config.dialect {
            Some(DialectType::Snowflake)
            | Some(DialectType::ClickHouse)
            | Some(DialectType::PostgreSQL)
            | Some(DialectType::Presto)
            | Some(DialectType::Trino)
            | Some(DialectType::Athena)
            | Some(DialectType::DuckDB)
            | Some(DialectType::BigQuery)
            | Some(DialectType::Spark)
            | Some(DialectType::Databricks)
            | Some(DialectType::Hive) => "COALESCE",
            Some(DialectType::MySQL)
            | Some(DialectType::Doris)
            | Some(DialectType::StarRocks)
            | Some(DialectType::SingleStore)
            | Some(DialectType::TiDB) => "IFNULL",
            _ => "NVL",
        };

        self.generate_binary_func(func_name, &f.this, &f.expression)
    }

    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // Snowflake normalizes STDDEV_SAMP to STDDEV
        let func_name = match self.config.dialect {
            Some(DialectType::Snowflake) => "STDDEV",
            _ => "STDDEV_SAMP",
        };

        self.generate_agg_func(func_name, f)
    }

    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
        self.generate_expression(&coll.this)?;
        self.write_space();
        self.write_keyword("COLLATE");
        self.write_space();
        if coll.quoted {
            // Single-quoted string: COLLATE 'de_DE'
            self.write("'");
            self.write(&coll.collation);
            self.write("'");
        } else if coll.double_quoted {
            // Double-quoted identifier: COLLATE "de_DE"
            self.write("\"");
            self.write(&coll.collation);
            self.write("\"");
        } else {
            // Unquoted identifier: COLLATE de_DE
            self.write(&coll.collation);
        }
        Ok(())
    }

    fn generate_case(&mut self, case: &Case) -> Result<()> {
        let multiline_case =
            self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake));

        self.write_keyword("CASE");
        if let Some(operand) = &case.operand {
            self.write_space();
            self.generate_expression(operand)?;
        }
        if multiline_case {
            self.indent_level += 1;
        }
        for (condition, result) in &case.whens {
            if multiline_case {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("WHEN");
            self.write_space();
            self.generate_expression(condition)?;
            if multiline_case {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("THEN");
            self.write_space();
            self.generate_expression(result)?;
        }
        if let Some(else_) = &case.else_ {
            if multiline_case {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("ELSE");
            self.write_space();
            self.generate_expression(else_)?;
        }
        if multiline_case {
            self.indent_level -= 1;
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }
        self.write_keyword("END");
        Ok(())
    }

    fn generate_function(&mut self, func: &Function) -> Result<()> {
        // Normalize function name based on dialect settings
        let normalized_name = self.normalize_func_name(&func.name);
        let upper_name = func.name.to_uppercase();

        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
        if upper_name == "STRUCT"
            && !matches!(
                self.config.dialect,
                Some(DialectType::BigQuery)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive)
                    | None
            )
        {
            return self.generate_struct_function_cross_dialect(func);
        }

        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
        // This is an internal marker function for ::? JSON path syntax
        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
            self.generate_expression(&func.args[0])?;
            self.write("::?");
            // Extract the key from the string literal
            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
                self.write(key);
            } else {
                self.generate_expression(&func.args[1])?;
            }
            return Ok(());
        }

        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
            self.generate_expression(&func.args[0])?;
            self.write(" # ");
            self.generate_expression(&func.args[1])?;
            return Ok(());
        }

        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
        if matches!(
            self.config.dialect,
            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
        ) && upper_name == "TRY"
            && func.args.len() == 1
        {
            self.generate_expression(&func.args[0])?;
            return Ok(());
        }

        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
        if self.config.dialect == Some(DialectType::ClickHouse)
            && upper_name == "TOSTARTOFDAY"
            && func.args.len() == 1
        {
            self.write("dateTrunc('DAY', ");
            self.generate_expression(&func.args[0])?;
            self.write(")");
            return Ok(());
        }

        // Redshift: CONCAT(a, b, ...) -> a || b || ...
        if self.config.dialect == Some(DialectType::Redshift)
            && upper_name == "CONCAT"
            && func.args.len() >= 2
        {
            for (i, arg) in func.args.iter().enumerate() {
                if i > 0 {
                    self.write(" || ");
                }
                self.generate_expression(arg)?;
            }
            return Ok(());
        }

        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
        if self.config.dialect == Some(DialectType::Redshift)
            && upper_name == "CONCAT_WS"
            && func.args.len() >= 2
        {
            let sep = &func.args[0];
            for (i, arg) in func.args.iter().skip(1).enumerate() {
                if i > 0 {
                    self.write(" || ");
                    self.generate_expression(sep)?;
                    self.write(" || ");
                }
                self.generate_expression(arg)?;
            }
            return Ok(());
        }

        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
        // Unit should be unquoted uppercase identifier
        if self.config.dialect == Some(DialectType::Redshift)
            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
            && func.args.len() == 3
        {
            self.write_keyword("DATEDIFF");
            self.write("(");
            // First arg is unit - normalize to unquoted uppercase
            self.write_redshift_date_part(&func.args[0]);
            self.write(", ");
            self.generate_expression(&func.args[1])?;
            self.write(", ");
            self.generate_expression(&func.args[2])?;
            self.write(")");
            return Ok(());
        }

        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
        // Unit should be unquoted uppercase identifier
        if self.config.dialect == Some(DialectType::Redshift)
            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
            && func.args.len() == 3
        {
            self.write_keyword("DATEADD");
            self.write("(");
            // First arg is unit - normalize to unquoted uppercase
            self.write_redshift_date_part(&func.args[0]);
            self.write(", ");
            self.generate_expression(&func.args[1])?;
            self.write(", ");
            self.generate_expression(&func.args[2])?;
            self.write(")");
            return Ok(());
        }

        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
        if upper_name == "UUID_STRING"
            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
        {
            let func_name = match self.config.dialect {
                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
                Some(DialectType::BigQuery) => "GENERATE_UUID",
                _ => "UUID",
            };
            self.write_keyword(func_name);
            self.write("()");
            return Ok(());
        }

        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
        // Unit should be quoted uppercase string
        if self.config.dialect == Some(DialectType::Redshift)
            && upper_name == "DATE_TRUNC"
            && func.args.len() == 2
        {
            self.write_keyword("DATE_TRUNC");
            self.write("(");
            // First arg is unit - normalize to quoted uppercase
            self.write_redshift_date_part_quoted(&func.args[0]);
            self.write(", ");
            self.generate_expression(&func.args[1])?;
            self.write(")");
            return Ok(());
        }

        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
        if matches!(
            self.config.dialect,
            Some(DialectType::TSQL) | Some(DialectType::Fabric)
        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
            && func.args.len() == 2
        {
            self.write_keyword("DATEPART");
            self.write("(");
            self.generate_expression(&func.args[0])?;
            self.write(", ");
            self.generate_expression(&func.args[1])?;
            self.write(")");
            return Ok(());
        }

        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
        if matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
            && func.args.len() == 2
        {
            self.write_keyword("EXTRACT");
            self.write("(");
            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
            match &func.args[0] {
                Expression::Literal(crate::expressions::Literal::String(s)) => {
                    self.write(&s.to_lowercase());
                }
                _ => self.generate_expression(&func.args[0])?,
            }
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            self.generate_expression(&func.args[1])?;
            self.write(")");
            return Ok(());
        }

        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
        // Also DATE literals in Dremio should be CAST(...AS DATE)
        if self.config.dialect == Some(DialectType::Dremio)
            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
            && func.args.len() == 2
        {
            self.write_keyword("EXTRACT");
            self.write("(");
            self.generate_expression(&func.args[0])?;
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            // For Dremio, DATE literals should become CAST('value' AS DATE)
            self.generate_dremio_date_expression(&func.args[1])?;
            self.write(")");
            return Ok(());
        }

        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
        if self.config.dialect == Some(DialectType::Dremio)
            && upper_name == "CURRENT_DATE_UTC"
            && func.args.is_empty()
        {
            self.write_keyword("CURRENT_DATE_UTC");
            return Ok(());
        }

        // Dremio: DATETYPE(year, month, day) transformation
        // - If all args are integer literals: DATE('YYYY-MM-DD')
        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
        if self.config.dialect == Some(DialectType::Dremio)
            && upper_name == "DATETYPE"
            && func.args.len() == 3
        {
            // Helper function to extract integer from number literal
            fn get_int_literal(expr: &Expression) -> Option<i64> {
                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
                    s.parse::<i64>().ok()
                } else {
                    None
                }
            }

            // Check if all arguments are integer literals
            if let (Some(year), Some(month), Some(day)) = (
                get_int_literal(&func.args[0]),
                get_int_literal(&func.args[1]),
                get_int_literal(&func.args[2]),
            ) {
                // All are integer literals: DATE('YYYY-MM-DD')
                self.write_keyword("DATE");
                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
                return Ok(());
            }

            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
            self.write_keyword("CAST");
            self.write("(");
            self.write_keyword("CONCAT");
            self.write("(");
            self.generate_expression(&func.args[0])?;
            self.write(", '-', ");
            self.generate_expression(&func.args[1])?;
            self.write(", '-', ");
            self.generate_expression(&func.args[2])?;
            self.write(")");
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.write_keyword("DATE");
            self.write(")");
            return Ok(());
        }

        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
        // when it's not an integer literal
        let is_presto_like = matches!(
            self.config.dialect,
            Some(DialectType::Presto) | Some(DialectType::Trino)
        );
        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
            self.write_keyword("DATE_ADD");
            self.write("(");
            // First arg: unit (pass through as-is, e.g., 'DAY')
            self.generate_expression(&func.args[0])?;
            self.write(", ");
            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
            let interval = &func.args[1];
            let needs_cast = !self.returns_integer_type(interval);
            if needs_cast {
                self.write_keyword("CAST");
                self.write("(");
            }
            self.generate_expression(interval)?;
            if needs_cast {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.write_keyword("BIGINT");
                self.write(")");
            }
            self.write(", ");
            // Third arg: date
            self.generate_expression(&func.args[2])?;
            self.write(")");
            return Ok(());
        }

        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
        let use_brackets = func.use_bracket_syntax;

        // Special case: functions WITH ORDINALITY need special output order
        // Input: FUNC(args) WITH ORDINALITY
        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
        // Output must be: FUNC(args) WITH ORDINALITY
        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
        let output_name = if has_ordinality {
            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
            self.normalize_func_name(base_name)
        } else {
            normalized_name.clone()
        };

        // For qualified names (schema.function or object.method), preserve original case
        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
        if func.name.contains('.') && !has_ordinality {
            // Don't normalize qualified functions - preserve original case
            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
            if func.quoted {
                self.write("`");
                self.write(&func.name);
                self.write("`");
            } else {
                self.write(&func.name);
            }
        } else {
            self.write(&output_name);
        }

        // If no_parens is true and there are no args, output just the function name
        // Unless the target dialect requires parens for this function
        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
            let needs_parens = match upper_name.as_str() {
                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
                    self.config.dialect,
                    Some(DialectType::Snowflake)
                        | Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::Hive)
                ),
                _ => false,
            };
            !needs_parens
        };
        if force_parens {
            // Output trailing comments
            for comment in &func.trailing_comments {
                self.write_space();
                self.write(comment);
            }
            return Ok(());
        }

        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
            self.write(" (");
        } else if use_brackets {
            self.write("[");
        } else {
            self.write("(");
        }
        if func.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }

        // Check if arguments should be split onto multiple lines (pretty + too wide)
        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
            && (upper_name == "TABLE" || upper_name == "FLATTEN");
        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
            // Pre-render arguments to check total width
            let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
            for arg in &func.args {
                let mut temp_gen = Generator::with_config(self.config.clone());
                temp_gen.config.pretty = false; // Don't recurse into pretty
                temp_gen.generate_expression(arg)?;
                expr_strings.push(temp_gen.output);
            }
            self.too_wide(&expr_strings)
        } else {
            false
        };

        if should_split {
            // Split onto multiple lines
            self.write_newline();
            self.indent_level += 1;
            for (i, arg) in func.args.iter().enumerate() {
                self.write_indent();
                self.generate_expression(arg)?;
                if i + 1 < func.args.len() {
                    self.write(",");
                }
                self.write_newline();
            }
            self.indent_level -= 1;
            self.write_indent();
        } else {
            // All on one line
            for (i, arg) in func.args.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(arg)?;
            }
        }

        if use_brackets {
            self.write("]");
        } else {
            self.write(")");
        }
        // Append WITH ORDINALITY after closing paren for table-valued functions
        if has_ordinality {
            self.write_space();
            self.write_keyword("WITH ORDINALITY");
        }
        // Output trailing comments
        for comment in &func.trailing_comments {
            self.write_space();
            self.write(comment);
        }
        Ok(())
    }

    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
        // Normalize function name based on dialect settings
        let mut normalized_name = self.normalize_func_name(&func.name);

        // Dialect-specific name mappings for aggregate functions
        let upper = normalized_name.to_uppercase();
        if upper == "MAX_BY" || upper == "MIN_BY" {
            let is_max = upper == "MAX_BY";
            match self.config.dialect {
                Some(DialectType::ClickHouse) => {
                    normalized_name = if is_max {
                        "argMax".to_string()
                    } else {
                        "argMin".to_string()
                    };
                }
                Some(DialectType::DuckDB) => {
                    normalized_name = if is_max {
                        "ARG_MAX".to_string()
                    } else {
                        "ARG_MIN".to_string()
                    };
                }
                _ => {}
            }
        }
        self.write(&normalized_name);
        self.write("(");
        if func.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }

        // Check if we need to transform multi-arg COUNT DISTINCT
        // When dialect doesn't support multi_arg_distinct, transform:
        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
        let needs_multi_arg_transform =
            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;

        if needs_multi_arg_transform {
            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
            self.write_keyword("CASE");
            for arg in &func.args {
                self.write_space();
                self.write_keyword("WHEN");
                self.write_space();
                self.generate_expression(arg)?;
                self.write_space();
                self.write_keyword("IS NULL THEN NULL");
            }
            self.write_space();
            self.write_keyword("ELSE");
            self.write(" (");
            for (i, arg) in func.args.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(arg)?;
            }
            self.write(")");
            self.write_space();
            self.write_keyword("END");
        } else {
            for (i, arg) in func.args.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(arg)?;
            }
        }

        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
        if self.config.ignore_nulls_in_func
            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
        {
            if let Some(ignore) = func.ignore_nulls {
                self.write_space();
                if ignore {
                    self.write_keyword("IGNORE NULLS");
                } else {
                    self.write_keyword("RESPECT NULLS");
                }
            }
        }

        // ORDER BY inside aggregate
        if !func.order_by.is_empty() {
            self.write_space();
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ord) in func.order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }

        // LIMIT inside aggregate
        if let Some(limit) = &func.limit {
            self.write_space();
            self.write_keyword("LIMIT");
            self.write_space();
            // Check if this is a Tuple representing LIMIT offset, count
            if let Expression::Tuple(t) = limit.as_ref() {
                if t.expressions.len() == 2 {
                    self.generate_expression(&t.expressions[0])?;
                    self.write(", ");
                    self.generate_expression(&t.expressions[1])?;
                } else {
                    self.generate_expression(limit)?;
                }
            } else {
                self.generate_expression(limit)?;
            }
        }

        self.write(")");

        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
        if !self.config.ignore_nulls_in_func
            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
        {
            if let Some(ignore) = func.ignore_nulls {
                self.write_space();
                if ignore {
                    self.write_keyword("IGNORE NULLS");
                } else {
                    self.write_keyword("RESPECT NULLS");
                }
            }
        }

        if let Some(filter) = &func.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }

        Ok(())
    }

    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
        self.generate_expression(&wf.this)?;

        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
        if let Some(keep) = &wf.keep {
            self.write_space();
            self.write_keyword("KEEP");
            self.write(" (");
            self.write_keyword("DENSE_RANK");
            self.write_space();
            if keep.first {
                self.write_keyword("FIRST");
            } else {
                self.write_keyword("LAST");
            }
            self.write_space();
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ord) in keep.order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
            self.write(")");
        }

        // Check if there's any OVER clause content
        let has_over = !wf.over.partition_by.is_empty()
            || !wf.over.order_by.is_empty()
            || wf.over.frame.is_some()
            || wf.over.window_name.is_some();

        // Only output OVER if there's actual window specification (not just KEEP alone)
        if has_over {
            self.write_space();
            self.write_keyword("OVER");

            // Check if this is just a bare named window reference (no parens needed)
            let has_specs = !wf.over.partition_by.is_empty()
                || !wf.over.order_by.is_empty()
                || wf.over.frame.is_some();

            if let Some(window_name) = wf.over.window_name.as_ref().filter(|_| !has_specs) {
                // OVER window_name (without parentheses)
                self.write_space();
                self.write(&window_name.name);
            } else {
                // OVER (...) or OVER (window_name ...)
                self.write(" (");
                self.generate_over(&wf.over)?;
                self.write(")");
            }
        } else if wf.keep.is_none() {
            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
            self.write_space();
            self.write_keyword("OVER");
            self.write(" ()");
        }

        Ok(())
    }

    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
        self.generate_expression(&wg.this)?;
        self.write_space();
        self.write_keyword("WITHIN GROUP");
        self.write(" (");
        self.write_keyword("ORDER BY");
        self.write_space();
        for (i, ord) in wg.order_by.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_ordered(ord)?;
        }
        self.write(")");
        Ok(())
    }

    /// Generate the contents of an OVER clause (without parentheses)
    fn generate_over(&mut self, over: &Over) -> Result<()> {
        let mut has_content = false;

        // Named window reference
        if let Some(name) = &over.window_name {
            self.write(&name.name);
            has_content = true;
        }

        // PARTITION BY
        if !over.partition_by.is_empty() {
            if has_content {
                self.write_space();
            }
            self.write_keyword("PARTITION BY");
            self.write_space();
            for (i, expr) in over.partition_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            has_content = true;
        }

        // ORDER BY
        if !over.order_by.is_empty() {
            if has_content {
                self.write_space();
            }
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ordered) in over.order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ordered)?;
            }
            has_content = true;
        }

        // Window frame
        if let Some(frame) = &over.frame {
            if has_content {
                self.write_space();
            }
            self.generate_window_frame(frame)?;
        }

        Ok(())
    }

    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
        // Exasol uses lowercase for frame kind (rows/range/groups)
        let lowercase_frame = self.config.lowercase_window_frame_keywords;

        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
        if !lowercase_frame {
            if let Some(kind_text) = &frame.kind_text {
                self.write(kind_text);
            } else {
                match frame.kind {
                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
                    WindowFrameKind::Range => self.write_keyword("RANGE"),
                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
                }
            }
        } else {
            match frame.kind {
                WindowFrameKind::Rows => self.write("rows"),
                WindowFrameKind::Range => self.write("range"),
                WindowFrameKind::Groups => self.write("groups"),
            }
        }

        // Use BETWEEN format only when there's an explicit end bound,
        // or when normalize_window_frame_between is enabled and the start is a directional bound
        self.write_space();
        let should_normalize = self.config.normalize_window_frame_between
            && frame.end.is_none()
            && matches!(
                frame.start,
                WindowFrameBound::Preceding(_)
                    | WindowFrameBound::Following(_)
                    | WindowFrameBound::UnboundedPreceding
                    | WindowFrameBound::UnboundedFollowing
            );

        if let Some(end) = &frame.end {
            // BETWEEN format: RANGE BETWEEN start AND end
            self.write_keyword("BETWEEN");
            self.write_space();
            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
            self.write_space();
            self.write_keyword("AND");
            self.write_space();
            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
        } else if should_normalize {
            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
            self.write_keyword("BETWEEN");
            self.write_space();
            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
            self.write_space();
            self.write_keyword("AND");
            self.write_space();
            self.write_keyword("CURRENT ROW");
        } else {
            // Single bound format: RANGE CURRENT ROW
            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
        }

        // EXCLUDE clause
        if let Some(exclude) = &frame.exclude {
            self.write_space();
            self.write_keyword("EXCLUDE");
            self.write_space();
            match exclude {
                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
                WindowFrameExclude::Group => self.write_keyword("GROUP"),
                WindowFrameExclude::Ties => self.write_keyword("TIES"),
                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
            }
        }

        Ok(())
    }

    fn generate_window_frame_bound(
        &mut self,
        bound: &WindowFrameBound,
        side_text: Option<&str>,
    ) -> Result<()> {
        // Exasol uses lowercase for preceding/following
        let lowercase_frame = self.config.lowercase_window_frame_keywords;

        match bound {
            WindowFrameBound::CurrentRow => {
                self.write_keyword("CURRENT ROW");
            }
            WindowFrameBound::UnboundedPreceding => {
                self.write_keyword("UNBOUNDED");
                self.write_space();
                if lowercase_frame {
                    self.write("preceding");
                } else if let Some(text) = side_text {
                    self.write(text);
                } else {
                    self.write_keyword("PRECEDING");
                }
            }
            WindowFrameBound::UnboundedFollowing => {
                self.write_keyword("UNBOUNDED");
                self.write_space();
                if lowercase_frame {
                    self.write("following");
                } else if let Some(text) = side_text {
                    self.write(text);
                } else {
                    self.write_keyword("FOLLOWING");
                }
            }
            WindowFrameBound::Preceding(expr) => {
                self.generate_expression(expr)?;
                self.write_space();
                if lowercase_frame {
                    self.write("preceding");
                } else if let Some(text) = side_text {
                    self.write(text);
                } else {
                    self.write_keyword("PRECEDING");
                }
            }
            WindowFrameBound::Following(expr) => {
                self.generate_expression(expr)?;
                self.write_space();
                if lowercase_frame {
                    self.write("following");
                } else if let Some(text) = side_text {
                    self.write(text);
                } else {
                    self.write_keyword("FOLLOWING");
                }
            }
            WindowFrameBound::BarePreceding => {
                if lowercase_frame {
                    self.write("preceding");
                } else if let Some(text) = side_text {
                    self.write(text);
                } else {
                    self.write_keyword("PRECEDING");
                }
            }
            WindowFrameBound::BareFollowing => {
                if lowercase_frame {
                    self.write("following");
                } else if let Some(text) = side_text {
                    self.write(text);
                } else {
                    self.write_keyword("FOLLOWING");
                }
            }
            WindowFrameBound::Value(expr) => {
                // Bare numeric bound without PRECEDING/FOLLOWING
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
            && !matches!(&interval.this, Some(Expression::Literal(_)));

        if !skip_interval_keyword {
            self.write_keyword("INTERVAL");
        }

        // Generate value if present
        if let Some(ref value) = interval.this {
            if !skip_interval_keyword {
                self.write_space();
            }
            // If the value is a complex expression (not a literal/column/function call)
            // and there's a unit, wrap it in parentheses
            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
            let needs_parens = interval.unit.is_some()
                && matches!(
                    value,
                    Expression::Add(_)
                        | Expression::Sub(_)
                        | Expression::Mul(_)
                        | Expression::Div(_)
                        | Expression::Mod(_)
                        | Expression::BitwiseAnd(_)
                        | Expression::BitwiseOr(_)
                        | Expression::BitwiseXor(_)
                );
            if needs_parens {
                self.write("(");
            }
            self.generate_expression(value)?;
            if needs_parens {
                self.write(")");
            }
        }

        // Generate unit if present
        if let Some(ref unit_spec) = interval.unit {
            self.write_space();
            self.write_interval_unit_spec(unit_spec)?;
        }

        Ok(())
    }

    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
        match unit_spec {
            IntervalUnitSpec::Simple { unit, use_plural } => {
                // If dialect doesn't allow plural forms, force singular
                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
                self.write_simple_interval_unit(unit, effective_plural);
            }
            IntervalUnitSpec::Span(span) => {
                self.write_simple_interval_unit(&span.this, false);
                self.write_space();
                self.write_keyword("TO");
                self.write_space();
                self.write_simple_interval_unit(&span.expression, false);
            }
            IntervalUnitSpec::ExprSpan(span) => {
                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
                self.generate_expression(&span.this)?;
                self.write_space();
                self.write_keyword("TO");
                self.write_space();
                self.generate_expression(&span.expression)?;
            }
            IntervalUnitSpec::Expr(expr) => {
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
        // Output interval unit, respecting plural preference
        match (unit, use_plural) {
            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
        }
    }

    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
    fn write_redshift_date_part(&mut self, expr: &Expression) {
        let part_str = self.extract_date_part_string(expr);
        if let Some(part) = part_str {
            let normalized = self.normalize_date_part(&part);
            self.write_keyword(&normalized);
        } else {
            // If we can't extract a date part string, fall back to generating the expression
            let _ = self.generate_expression(expr);
        }
    }

    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
        let part_str = self.extract_date_part_string(expr);
        if let Some(part) = part_str {
            let normalized = self.normalize_date_part(&part);
            self.write("'");
            self.write(&normalized);
            self.write("'");
        } else {
            // If we can't extract a date part string, fall back to generating the expression
            let _ = self.generate_expression(expr);
        }
    }

    /// Extract date part string from expression (handles string literals and identifiers)
    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
        match expr {
            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
            Expression::Identifier(id) => Some(id.name.clone()),
            Expression::Column(col) if col.table.is_none() => {
                // Simple column reference without table prefix, treat as identifier
                Some(col.name.name.clone())
            }
            _ => None,
        }
    }

    /// Normalize date part to uppercase singular form
    /// days -> DAY, months -> MONTH, etc.
    fn normalize_date_part(&self, part: &str) -> String {
        let lower = part.to_lowercase();
        match lower.as_str() {
            "day" | "days" | "d" => "DAY".to_string(),
            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
            _ => part.to_uppercase(),
        }
    }

    fn write_datetime_field(&mut self, field: &DateTimeField) {
        match field {
            DateTimeField::Year => self.write_keyword("YEAR"),
            DateTimeField::Month => self.write_keyword("MONTH"),
            DateTimeField::Day => self.write_keyword("DAY"),
            DateTimeField::Hour => self.write_keyword("HOUR"),
            DateTimeField::Minute => self.write_keyword("MINUTE"),
            DateTimeField::Second => self.write_keyword("SECOND"),
            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
            DateTimeField::DayOfWeek => {
                let name = match self.config.dialect {
                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
                    _ => "DOW",
                };
                self.write_keyword(name);
            }
            DateTimeField::DayOfYear => {
                let name = match self.config.dialect {
                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
                    _ => "DOY",
                };
                self.write_keyword(name);
            }
            DateTimeField::Week => self.write_keyword("WEEK"),
            DateTimeField::WeekWithModifier(modifier) => {
                self.write_keyword("WEEK");
                self.write("(");
                self.write(modifier);
                self.write(")");
            }
            DateTimeField::Quarter => self.write_keyword("QUARTER"),
            DateTimeField::Epoch => self.write_keyword("EPOCH"),
            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
            DateTimeField::Date => self.write_keyword("DATE"),
            DateTimeField::Time => self.write_keyword("TIME"),
            DateTimeField::Custom(name) => self.write(name),
        }
    }

    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
        match field {
            DateTimeField::Year => self.write("year"),
            DateTimeField::Month => self.write("month"),
            DateTimeField::Day => self.write("day"),
            DateTimeField::Hour => self.write("hour"),
            DateTimeField::Minute => self.write("minute"),
            DateTimeField::Second => self.write("second"),
            DateTimeField::Millisecond => self.write("millisecond"),
            DateTimeField::Microsecond => self.write("microsecond"),
            DateTimeField::DayOfWeek => self.write("dow"),
            DateTimeField::DayOfYear => self.write("doy"),
            DateTimeField::Week => self.write("week"),
            DateTimeField::WeekWithModifier(modifier) => {
                self.write("week(");
                self.write(modifier);
                self.write(")");
            }
            DateTimeField::Quarter => self.write("quarter"),
            DateTimeField::Epoch => self.write("epoch"),
            DateTimeField::Timezone => self.write("timezone"),
            DateTimeField::TimezoneHour => self.write("timezone_hour"),
            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
            DateTimeField::Date => self.write("date"),
            DateTimeField::Time => self.write("time"),
            DateTimeField::Custom(name) => self.write(name),
        }
    }

    // Helper function generators

    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(arg)?;
        self.write(")");
        Ok(())
    }

    /// Generate a unary function, using the original name if available for round-trip preservation
    fn generate_unary_func(
        &mut self,
        default_name: &str,
        f: &crate::expressions::UnaryFunc,
    ) -> Result<()> {
        let name = f.original_name.as_deref().unwrap_or(default_name);
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(")");
        Ok(())
    }

    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
    fn generate_sqrt_cbrt(
        &mut self,
        f: &crate::expressions::UnaryFunc,
        func_name: &str,
        _op: &str,
    ) -> Result<()> {
        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
        // Always use function syntax for consistency
        self.write_keyword(func_name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_binary_func(
        &mut self,
        name: &str,
        arg1: &Expression,
        arg2: &Expression,
    ) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(arg1)?;
        self.write(", ");
        self.generate_expression(arg2)?;
        self.write(")");
        Ok(())
    }

    /// Generate CHAR/CHR function with optional USING charset
    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
        // Use stored name if available, otherwise default to CHAR
        let func_name = f.name.as_deref().unwrap_or("CHAR");
        self.write_keyword(func_name);
        self.write("(");
        for (i, arg) in f.args.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(arg)?;
        }
        if let Some(ref charset) = f.charset {
            self.write(" ");
            self.write_keyword("USING");
            self.write(" ");
            self.write(charset);
        }
        self.write(")");
        Ok(())
    }

    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
        use crate::dialects::DialectType;

        match self.config.dialect {
            Some(DialectType::Teradata) => {
                // Teradata uses ** operator for exponentiation
                self.generate_expression(&f.this)?;
                self.write(" ** ");
                self.generate_expression(&f.expression)?;
                Ok(())
            }
            _ => {
                // Other dialects use POWER function
                self.generate_binary_func("POWER", &f.this, &f.expression)
            }
        }
    }

    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        for (i, arg) in args.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(arg)?;
        }
        self.write(")");
        Ok(())
    }

    // String function generators

    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
        self.write_keyword("CONCAT_WS");
        self.write("(");
        self.generate_expression(&f.separator)?;
        for expr in &f.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
        self.write_keyword("SUBSTRING");
        self.write("(");
        self.generate_expression(&f.this)?;
        // Spark/Hive use comma syntax, not FROM/FOR syntax
        let use_comma_syntax = matches!(
            self.config.dialect,
            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
        );
        if f.from_for_syntax && !use_comma_syntax {
            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            self.generate_expression(&f.start)?;
            if let Some(length) = &f.length {
                self.write_space();
                self.write_keyword("FOR");
                self.write_space();
                self.generate_expression(length)?;
            }
        } else {
            // Comma-separated syntax: SUBSTRING(str, pos, len)
            self.write(", ");
            self.generate_expression(&f.start)?;
            if let Some(length) = &f.length {
                self.write(", ");
                self.generate_expression(length)?;
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
        self.write_keyword("OVERLAY");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write_space();
        self.write_keyword("PLACING");
        self.write_space();
        self.generate_expression(&f.replacement)?;
        self.write_space();
        self.write_keyword("FROM");
        self.write_space();
        self.generate_expression(&f.from)?;
        if let Some(length) = &f.length {
            self.write_space();
            self.write_keyword("FOR");
            self.write_space();
            self.generate_expression(length)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
        // when no characters are specified (PostgreSQL style)
        if f.position_explicit && f.characters.is_none() {
            match f.position {
                TrimPosition::Leading => {
                    self.write_keyword("LTRIM");
                    self.write("(");
                    self.generate_expression(&f.this)?;
                    self.write(")");
                    return Ok(());
                }
                TrimPosition::Trailing => {
                    self.write_keyword("RTRIM");
                    self.write("(");
                    self.generate_expression(&f.this)?;
                    self.write(")");
                    return Ok(());
                }
                TrimPosition::Both => {
                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
                    // Fall through to standard TRIM handling
                }
            }
        }

        self.write_keyword("TRIM");
        self.write("(");
        // When BOTH is specified without trim characters, simplify to just TRIM(str)
        let use_standard = f.sql_standard_syntax
            && !(f.position_explicit
                && f.characters.is_none()
                && matches!(f.position, TrimPosition::Both));
        if use_standard {
            // SQL standard syntax: TRIM(BOTH chars FROM str)
            // Only output position if it was explicitly specified
            if f.position_explicit {
                match f.position {
                    TrimPosition::Both => self.write_keyword("BOTH"),
                    TrimPosition::Leading => self.write_keyword("LEADING"),
                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
                }
                self.write_space();
            }
            if let Some(chars) = &f.characters {
                self.generate_expression(chars)?;
                self.write_space();
            }
            self.write_keyword("FROM");
            self.write_space();
            self.generate_expression(&f.this)?;
        } else {
            // Simple function syntax: TRIM(str) or TRIM(str, chars)
            self.generate_expression(&f.this)?;
            if let Some(chars) = &f.characters {
                self.write(", ");
                self.generate_expression(chars)?;
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
        self.write_keyword("REPLACE");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.old)?;
        self.write(", ");
        self.generate_expression(&f.new)?;
        self.write(")");
        Ok(())
    }

    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.length)?;
        self.write(")");
        Ok(())
    }

    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
        self.write_keyword("REPEAT");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.times)?;
        self.write(")");
        Ok(())
    }

    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.length)?;
        if let Some(fill) = &f.fill {
            self.write(", ");
            self.generate_expression(fill)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
        self.write_keyword("SPLIT");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.delimiter)?;
        self.write(")");
        Ok(())
    }

    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
        use crate::dialects::DialectType;
        // PostgreSQL uses ~ operator for regex matching
        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
            self.generate_expression(&f.this)?;
            self.write(" ~ ");
            self.generate_expression(&f.pattern)?;
        } else if matches!(
            self.config.dialect,
            Some(DialectType::SingleStore)
                | Some(DialectType::Spark)
                | Some(DialectType::Hive)
                | Some(DialectType::Databricks)
        ) && f.flags.is_none()
        {
            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
            self.generate_expression(&f.this)?;
            self.write_keyword(" RLIKE ");
            self.generate_expression(&f.pattern)?;
        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
            // StarRocks uses REGEXP function syntax
            self.write_keyword("REGEXP");
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write(", ");
            self.generate_expression(&f.pattern)?;
            if let Some(flags) = &f.flags {
                self.write(", ");
                self.generate_expression(flags)?;
            }
            self.write(")");
        } else {
            self.write_keyword("REGEXP_LIKE");
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write(", ");
            self.generate_expression(&f.pattern)?;
            if let Some(flags) = &f.flags {
                self.write(", ");
                self.generate_expression(flags)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
        self.write_keyword("REGEXP_REPLACE");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.pattern)?;
        self.write(", ");
        self.generate_expression(&f.replacement)?;
        if let Some(flags) = &f.flags {
            self.write(", ");
            self.generate_expression(flags)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
        self.write_keyword("REGEXP_EXTRACT");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.pattern)?;
        if let Some(group) = &f.group {
            self.write(", ");
            self.generate_expression(group)?;
        }
        self.write(")");
        Ok(())
    }

    // Math function generators

    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
        self.write_keyword("ROUND");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(decimals) = &f.decimals {
            self.write(", ");
            self.generate_expression(decimals)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
        self.write_keyword("FLOOR");
        self.write("(");
        self.generate_expression(&f.this)?;
        // Handle Druid-style FLOOR(time TO unit) syntax
        if let Some(to) = &f.to {
            self.write(" ");
            self.write_keyword("TO");
            self.write(" ");
            self.generate_expression(to)?;
        } else if let Some(scale) = &f.scale {
            self.write(", ");
            self.generate_expression(scale)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
        self.write_keyword("CEIL");
        self.write("(");
        self.generate_expression(&f.this)?;
        // Handle Druid-style CEIL(time TO unit) syntax
        if let Some(to) = &f.to {
            self.write(" ");
            self.write_keyword("TO");
            self.write(" ");
            self.generate_expression(to)?;
        } else if let Some(decimals) = &f.decimals {
            self.write(", ");
            self.generate_expression(decimals)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
        use crate::expressions::Literal;

        if let Some(base) = &f.base {
            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
            if self.is_log_base_none() {
                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
                    self.write_func_name("LOG2");
                    self.write("(");
                    self.generate_expression(&f.this)?;
                    self.write(")");
                    return Ok(());
                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
                    self.write_func_name("LOG10");
                    self.write("(");
                    self.generate_expression(&f.this)?;
                    self.write(")");
                    return Ok(());
                }
                // Other bases: fall through to LOG(base, value) — best effort
            }

            self.write_func_name("LOG");
            self.write("(");
            if self.is_log_value_first() {
                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
                self.generate_expression(&f.this)?;
                self.write(", ");
                self.generate_expression(base)?;
            } else {
                // Default (PostgreSQL, etc.): LOG(base, value)
                self.generate_expression(base)?;
                self.write(", ");
                self.generate_expression(&f.this)?;
            }
            self.write(")");
        } else {
            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
            self.write_func_name("LOG");
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write(")");
        }
        Ok(())
    }

    /// Whether the target dialect uses LOG(value, base) order (value first).
    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
    fn is_log_value_first(&self) -> bool {
        use crate::dialects::DialectType;
        matches!(
            self.config.dialect,
            Some(DialectType::BigQuery)
                | Some(DialectType::TSQL)
                | Some(DialectType::Tableau)
                | Some(DialectType::Fabric)
        )
    }

    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
    /// Presto, Trino, ClickHouse, Athena.
    fn is_log_base_none(&self) -> bool {
        use crate::dialects::DialectType;
        matches!(
            self.config.dialect,
            Some(DialectType::Presto)
                | Some(DialectType::Trino)
                | Some(DialectType::ClickHouse)
                | Some(DialectType::Athena)
        )
    }

    // Date/time function generators

    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
        self.write_keyword("CURRENT_TIME");
        if let Some(precision) = f.precision {
            self.write(&format!("({})", precision));
        }
        Ok(())
    }

    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
        use crate::dialects::DialectType;

        // Oracle/Redshift SYSDATE handling
        if f.sysdate {
            match self.config.dialect {
                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
                    self.write_keyword("SYSDATE");
                    return Ok(());
                }
                Some(DialectType::Snowflake) => {
                    // Snowflake uses SYSDATE() function
                    self.write_keyword("SYSDATE");
                    self.write("()");
                    return Ok(());
                }
                _ => {
                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
                }
            }
        }

        self.write_keyword("CURRENT_TIMESTAMP");
        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
        if let Some(precision) = f.precision {
            self.write(&format!("({})", precision));
        } else if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::MySQL)
                | Some(crate::dialects::DialectType::SingleStore)
                | Some(crate::dialects::DialectType::TiDB)
                | Some(crate::dialects::DialectType::Spark)
                | Some(crate::dialects::DialectType::Hive)
                | Some(crate::dialects::DialectType::Databricks)
                | Some(crate::dialects::DialectType::ClickHouse)
                | Some(crate::dialects::DialectType::BigQuery)
                | Some(crate::dialects::DialectType::Snowflake)
        ) {
            self.write("()");
        }
        Ok(())
    }

    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
        if self.config.dialect == Some(DialectType::Exasol) {
            self.write_keyword("CONVERT_TZ");
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write(", 'UTC', ");
            self.generate_expression(&f.zone)?;
            self.write(")");
            return Ok(());
        }

        self.generate_expression(&f.this)?;
        self.write_space();
        self.write_keyword("AT TIME ZONE");
        self.write_space();
        self.generate_expression(&f.zone)?;
        Ok(())
    }

    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
        use crate::dialects::DialectType;

        // Presto/Trino use DATE_ADD('unit', interval, date) format
        // with the interval cast to BIGINT when needed
        let is_presto_like = matches!(
            self.config.dialect,
            Some(DialectType::Presto) | Some(DialectType::Trino)
        );

        if is_presto_like {
            self.write_keyword(name);
            self.write("(");
            // Unit as string literal
            self.write("'");
            self.write_simple_interval_unit(&f.unit, false);
            self.write("'");
            self.write(", ");
            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
            let needs_cast = !self.returns_integer_type(&f.interval);
            if needs_cast {
                self.write_keyword("CAST");
                self.write("(");
            }
            self.generate_expression(&f.interval)?;
            if needs_cast {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.write_keyword("BIGINT");
                self.write(")");
            }
            self.write(", ");
            self.generate_expression(&f.this)?;
            self.write(")");
        } else {
            self.write_keyword(name);
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write(", ");
            self.write_keyword("INTERVAL");
            self.write_space();
            self.generate_expression(&f.interval)?;
            self.write_space();
            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
            self.write(")");
        }
        Ok(())
    }

    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
    /// This is a heuristic to avoid full type inference
    fn returns_integer_type(&self, expr: &Expression) -> bool {
        use crate::expressions::{DataType, Literal};
        match expr {
            // Integer literals (no decimal point)
            Expression::Literal(Literal::Number(n)) => !n.contains('.'),

            // FLOOR(x) returns integer if x is integer
            Expression::Floor(f) => self.returns_integer_type(&f.this),

            // ROUND(x) returns integer if x is integer
            Expression::Round(f) => {
                // Only if no decimals arg or it's returning an integer
                f.decimals.is_none() && self.returns_integer_type(&f.this)
            }

            // SIGN returns integer if input is integer
            Expression::Sign(f) => self.returns_integer_type(&f.this),

            // ABS returns the same type as input
            Expression::Abs(f) => self.returns_integer_type(&f.this),

            // Arithmetic operations on integers return integers
            Expression::Mul(op) => {
                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
            }
            Expression::Add(op) => {
                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
            }
            Expression::Sub(op) => {
                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
            }
            Expression::Mod(op) => self.returns_integer_type(&op.left),

            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
            Expression::Cast(c) => matches!(
                &c.to,
                DataType::BigInt { .. }
                    | DataType::Int { .. }
                    | DataType::SmallInt { .. }
                    | DataType::TinyInt { .. }
            ),

            // Negation: -x returns integer if x is integer
            Expression::Neg(op) => self.returns_integer_type(&op.this),

            // Parenthesized expression
            Expression::Paren(p) => self.returns_integer_type(&p.this),

            // Column references and most expressions are assumed to need casting
            // since we don't have full type information
            _ => false,
        }
    }

    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
        self.write_keyword("DATEDIFF");
        self.write("(");
        if let Some(unit) = &f.unit {
            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
            self.write(", ");
        }
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
        self.write_keyword("DATE_TRUNC");
        self.write("('");
        self.write_datetime_field(&f.unit);
        self.write("', ");
        self.generate_expression(&f.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
        use crate::dialects::DialectType;
        use crate::expressions::DateTimeField;

        self.write_keyword("LAST_DAY");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(unit) = &f.unit {
            self.write(", ");
            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
            // WEEK(SUNDAY) -> WEEK
            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
                if let DateTimeField::WeekWithModifier(_) = unit {
                    self.write_keyword("WEEK");
                } else {
                    self.write_datetime_field(unit);
                }
            } else {
                self.write_datetime_field(unit);
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
        if matches!(
            self.config.dialect,
            Some(DialectType::TSQL) | Some(DialectType::Fabric)
        ) {
            self.write_keyword("DATEPART");
            self.write("(");
            self.write_datetime_field(&f.field);
            self.write(", ");
            self.generate_expression(&f.this)?;
            self.write(")");
            return Ok(());
        }
        self.write_keyword("EXTRACT");
        self.write("(");
        // Hive/Spark use lowercase datetime fields in EXTRACT
        if matches!(
            self.config.dialect,
            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
        ) {
            self.write_datetime_field_lower(&f.field);
        } else {
            self.write_datetime_field(&f.field);
        }
        self.write_space();
        self.write_keyword("FROM");
        self.write_space();
        self.generate_expression(&f.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
        self.write_keyword("TO_DATE");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(format) = &f.format {
            self.write(", ");
            self.generate_expression(format)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
        self.write_keyword("TO_TIMESTAMP");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(format) = &f.format {
            self.write(", ");
            self.generate_expression(format)?;
        }
        self.write(")");
        Ok(())
    }

    // Control flow function generators

    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
        if self.config.dialect == Some(DialectType::Exasol) {
            self.write_keyword("IF");
            self.write_space();
            self.generate_expression(&f.condition)?;
            self.write_space();
            self.write_keyword("THEN");
            self.write_space();
            self.generate_expression(&f.true_value)?;
            if let Some(false_val) = &f.false_value {
                self.write_space();
                self.write_keyword("ELSE");
                self.write_space();
                self.generate_expression(false_val)?;
            }
            self.write_space();
            self.write_keyword("ENDIF");
            return Ok(());
        }

        // Choose function name based on target dialect
        let func_name = match self.config.dialect {
            Some(DialectType::Snowflake) => "IFF",
            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
            _ => "IF",
        };
        self.write_keyword(func_name);
        self.write("(");
        self.generate_expression(&f.condition)?;
        self.write(", ");
        self.generate_expression(&f.true_value)?;
        if let Some(false_val) = &f.false_value {
            self.write(", ");
            self.generate_expression(false_val)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
        self.write_keyword("NVL2");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.true_value)?;
        self.write(", ");
        self.generate_expression(&f.false_value)?;
        self.write(")");
        Ok(())
    }

    // Typed aggregate function generators

    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
        // Use normalize_functions for COUNT to respect ClickHouse case preservation
        let count_name = match self.config.normalize_functions {
            NormalizeFunctions::Upper => "COUNT".to_string(),
            NormalizeFunctions::Lower => "count".to_string(),
            NormalizeFunctions::None => f
                .original_name
                .clone()
                .unwrap_or_else(|| "COUNT".to_string()),
        };
        self.write(&count_name);
        self.write("(");
        if f.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }
        if f.star {
            self.write("*");
        } else if let Some(ref expr) = f.this {
            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
            if let Expression::Tuple(tuple) = expr {
                // Check if we need to transform multi-arg COUNT DISTINCT
                // When dialect doesn't support multi_arg_distinct, transform:
                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
                let needs_transform =
                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;

                if needs_transform {
                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
                    self.write_keyword("CASE");
                    for e in &tuple.expressions {
                        self.write_space();
                        self.write_keyword("WHEN");
                        self.write_space();
                        self.generate_expression(e)?;
                        self.write_space();
                        self.write_keyword("IS NULL THEN NULL");
                    }
                    self.write_space();
                    self.write_keyword("ELSE");
                    self.write(" (");
                    for (i, e) in tuple.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(e)?;
                    }
                    self.write(")");
                    self.write_space();
                    self.write_keyword("END");
                } else {
                    for (i, e) in tuple.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(e)?;
                    }
                }
            } else {
                self.generate_expression(expr)?;
            }
        }
        // RESPECT NULLS / IGNORE NULLS
        if let Some(ignore) = f.ignore_nulls {
            self.write_space();
            if ignore {
                self.write_keyword("IGNORE NULLS");
            } else {
                self.write_keyword("RESPECT NULLS");
            }
        }
        self.write(")");
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
        // Apply function name normalization based on config
        let func_name = match self.config.normalize_functions {
            NormalizeFunctions::Upper => name.to_uppercase(),
            NormalizeFunctions::Lower => name.to_lowercase(),
            NormalizeFunctions::None => {
                // Use the original function name from parsing if available,
                // otherwise fall back to lowercase of the hardcoded constant
                if let Some(ref original) = f.name {
                    original.clone()
                } else {
                    name.to_lowercase()
                }
            }
        };
        self.write(&func_name);
        self.write("(");
        if f.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }
        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
        if !matches!(f.this, Expression::Null(_)) {
            self.generate_expression(&f.this)?;
        }
        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
        if self.config.ignore_nulls_in_func
            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
        {
            match f.ignore_nulls {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("IGNORE NULLS");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("RESPECT NULLS");
                }
                None => {}
            }
        }
        // Generate HAVING MAX/MIN if present (BigQuery syntax)
        // e.g., ANY_VALUE(fruit HAVING MAX sold)
        if let Some((ref expr, is_max)) = f.having_max {
            self.write_space();
            self.write_keyword("HAVING");
            self.write_space();
            if is_max {
                self.write_keyword("MAX");
            } else {
                self.write_keyword("MIN");
            }
            self.write_space();
            self.generate_expression(expr)?;
        }
        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
        if !f.order_by.is_empty() {
            self.write_space();
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ord) in f.order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
        if let Some(ref limit) = f.limit {
            self.write_space();
            self.write_keyword("LIMIT");
            self.write_space();
            // Check if this is a Tuple representing LIMIT offset, count
            if let Expression::Tuple(t) = limit.as_ref() {
                if t.expressions.len() == 2 {
                    self.generate_expression(&t.expressions[0])?;
                    self.write(", ");
                    self.generate_expression(&t.expressions[1])?;
                } else {
                    self.generate_expression(limit)?;
                }
            } else {
                self.generate_expression(limit)?;
            }
        }
        self.write(")");
        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
        if !self.config.ignore_nulls_in_func
            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
        {
            match f.ignore_nulls {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("IGNORE NULLS");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("RESPECT NULLS");
                }
                None => {}
            }
        }
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
        self.write_keyword("GROUP_CONCAT");
        self.write("(");
        if f.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }
        self.generate_expression(&f.this)?;
        if let Some(ref order_by) = f.order_by {
            self.write_space();
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ord) in order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        if let Some(ref sep) = f.separator {
            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
            if matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::SQLite)
            ) {
                self.write(", ");
                self.generate_expression(sep)?;
            } else {
                self.write_space();
                self.write_keyword("SEPARATOR");
                self.write_space();
                self.generate_expression(sep)?;
            }
        }
        self.write(")");
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
        let is_tsql = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::TSQL)
        );
        self.write_keyword("STRING_AGG");
        self.write("(");
        if f.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }
        self.generate_expression(&f.this)?;
        if let Some(ref separator) = f.separator {
            self.write(", ");
            self.generate_expression(separator)?;
        }
        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
        if !is_tsql {
            if let Some(ref order_by) = f.order_by {
                self.write_space();
                self.write_keyword("ORDER BY");
                self.write_space();
                for (i, ord) in order_by.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
            }
        }
        if let Some(ref limit) = f.limit {
            self.write_space();
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(limit)?;
        }
        self.write(")");
        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
        if is_tsql {
            if let Some(ref order_by) = f.order_by {
                self.write_space();
                self.write_keyword("WITHIN GROUP");
                self.write(" (");
                self.write_keyword("ORDER BY");
                self.write_space();
                for (i, ord) in order_by.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
                self.write(")");
            }
        }
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
        use crate::dialects::DialectType;
        self.write_keyword("LISTAGG");
        self.write("(");
        if f.distinct {
            self.write_keyword("DISTINCT");
            self.write_space();
        }
        self.generate_expression(&f.this)?;
        if let Some(ref sep) = f.separator {
            self.write(", ");
            self.generate_expression(sep)?;
        } else if matches!(
            self.config.dialect,
            Some(DialectType::Trino) | Some(DialectType::Presto)
        ) {
            // Trino/Presto require explicit separator; default to ','
            self.write(", ','");
        }
        if let Some(ref overflow) = f.on_overflow {
            self.write_space();
            self.write_keyword("ON OVERFLOW");
            self.write_space();
            match overflow {
                ListAggOverflow::Error => self.write_keyword("ERROR"),
                ListAggOverflow::Truncate { filler, with_count } => {
                    self.write_keyword("TRUNCATE");
                    if let Some(ref fill) = filler {
                        self.write_space();
                        self.generate_expression(fill)?;
                    }
                    if *with_count {
                        self.write_space();
                        self.write_keyword("WITH COUNT");
                    } else {
                        self.write_space();
                        self.write_keyword("WITHOUT COUNT");
                    }
                }
            }
        }
        self.write(")");
        if let Some(ref order_by) = f.order_by {
            self.write_space();
            self.write_keyword("WITHIN GROUP");
            self.write(" (");
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ord) in order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
            self.write(")");
        }
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
        self.write_keyword("SUM_IF");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.condition)?;
        self.write(")");
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
        self.write_keyword("APPROX_PERCENTILE");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.percentile)?;
        if let Some(ref acc) = f.accuracy {
            self.write(", ");
            self.generate_expression(acc)?;
        }
        self.write(")");
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.percentile)?;
        self.write(")");
        if let Some(ref order_by) = f.order_by {
            self.write_space();
            self.write_keyword("WITHIN GROUP");
            self.write(" (");
            self.write_keyword("ORDER BY");
            self.write_space();
            self.generate_expression(&f.this)?;
            for ord in order_by.iter() {
                if ord.desc {
                    self.write_space();
                    self.write_keyword("DESC");
                }
            }
            self.write(")");
        }
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    // Window function generators

    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
        self.write_keyword("NTILE");
        self.write("(");
        if let Some(num_buckets) = &f.num_buckets {
            self.generate_expression(num_buckets)?;
        }
        if let Some(order_by) = &f.order_by {
            self.write_keyword(" ORDER BY ");
            for (i, ob) in order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ob)?;
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(ref offset) = f.offset {
            self.write(", ");
            self.generate_expression(offset)?;
            if let Some(ref default) = f.default {
                self.write(", ");
                self.generate_expression(default)?;
            }
        }
        // IGNORE NULLS inside parens for dialects like BigQuery
        if f.ignore_nulls && self.config.ignore_nulls_in_func {
            self.write_space();
            self.write_keyword("IGNORE NULLS");
        }
        self.write(")");
        // IGNORE NULLS outside parens for other dialects
        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
            self.write_space();
            self.write_keyword("IGNORE NULLS");
        }
        Ok(())
    }

    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
        if self.config.ignore_nulls_in_func {
            match f.ignore_nulls {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("IGNORE NULLS");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("RESPECT NULLS");
                }
                None => {}
            }
        }
        self.write(")");
        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
        if !self.config.ignore_nulls_in_func {
            match f.ignore_nulls {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("IGNORE NULLS");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("RESPECT NULLS");
                }
                None => {}
            }
        }
        Ok(())
    }

    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
        self.write_keyword("NTH_VALUE");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.offset)?;
        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
        if self.config.ignore_nulls_in_func {
            match f.ignore_nulls {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("IGNORE NULLS");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("RESPECT NULLS");
                }
                None => {}
            }
        }
        self.write(")");
        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
        if !self.config.ignore_nulls_in_func {
            match f.ignore_nulls {
                Some(true) => {
                    self.write_space();
                    self.write_keyword("IGNORE NULLS");
                }
                Some(false) => {
                    self.write_space();
                    self.write_keyword("RESPECT NULLS");
                }
                None => {}
            }
        }
        Ok(())
    }

    // Additional string function generators

    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
        // Standard syntax: POSITION(substr IN str)
        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::ClickHouse)
        ) {
            self.write_keyword("POSITION");
            self.write("(");
            self.generate_expression(&f.string)?;
            self.write(", ");
            self.generate_expression(&f.substring)?;
            if let Some(ref start) = f.start {
                self.write(", ");
                self.generate_expression(start)?;
            }
            self.write(")");
            return Ok(());
        }

        self.write_keyword("POSITION");
        self.write("(");
        self.generate_expression(&f.substring)?;
        self.write_space();
        self.write_keyword("IN");
        self.write_space();
        self.generate_expression(&f.string)?;
        if let Some(ref start) = f.start {
            self.write(", ");
            self.generate_expression(start)?;
        }
        self.write(")");
        Ok(())
    }

    // Additional math function generators

    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
        // Teradata RANDOM(lower, upper)
        if f.lower.is_some() || f.upper.is_some() {
            self.write_keyword("RANDOM");
            self.write("(");
            if let Some(ref lower) = f.lower {
                self.generate_expression(lower)?;
            }
            if let Some(ref upper) = f.upper {
                self.write(", ");
                self.generate_expression(upper)?;
            }
            self.write(")");
            return Ok(());
        }
        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
        let func_name = match self.config.dialect {
            Some(crate::dialects::DialectType::Snowflake)
            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
            _ => "RAND",
        };
        self.write_keyword(func_name);
        self.write("(");
        // DuckDB doesn't support seeded RANDOM, so skip the seed
        if !matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::DuckDB)
        ) {
            if let Some(ref seed) = f.seed {
                self.generate_expression(seed)?;
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
        self.write_keyword("TRUNCATE");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(ref decimals) = f.decimals {
            self.write(", ");
            self.generate_expression(decimals)?;
        }
        self.write(")");
        Ok(())
    }

    // Control flow generators

    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
        self.write_keyword("DECODE");
        self.write("(");
        self.generate_expression(&f.this)?;
        for (search, result) in &f.search_results {
            self.write(", ");
            self.generate_expression(search)?;
            self.write(", ");
            self.generate_expression(result)?;
        }
        if let Some(ref default) = f.default {
            self.write(", ");
            self.generate_expression(default)?;
        }
        self.write(")");
        Ok(())
    }

    // Date/time function generators

    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.format)?;
        self.write(")");
        Ok(())
    }

    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
        self.write_keyword("FROM_UNIXTIME");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(ref format) = f.format {
            self.write(", ");
            self.generate_expression(format)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
        self.write_keyword("UNIX_TIMESTAMP");
        self.write("(");
        if let Some(ref expr) = f.this {
            self.generate_expression(expr)?;
            if let Some(ref format) = f.format {
                self.write(", ");
                self.generate_expression(format)?;
            }
        } else if matches!(
            self.config.dialect,
            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
        ) {
            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
            self.write_keyword("CURRENT_TIMESTAMP");
            self.write("()");
        }
        self.write(")");
        Ok(())
    }

    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
        self.write_keyword("MAKE_DATE");
        self.write("(");
        self.generate_expression(&f.year)?;
        self.write(", ");
        self.generate_expression(&f.month)?;
        self.write(", ");
        self.generate_expression(&f.day)?;
        self.write(")");
        Ok(())
    }

    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
        self.write_keyword("MAKE_TIMESTAMP");
        self.write("(");
        self.generate_expression(&f.year)?;
        self.write(", ");
        self.generate_expression(&f.month)?;
        self.write(", ");
        self.generate_expression(&f.day)?;
        self.write(", ");
        self.generate_expression(&f.hour)?;
        self.write(", ");
        self.generate_expression(&f.minute)?;
        self.write(", ");
        self.generate_expression(&f.second)?;
        if let Some(ref tz) = f.timezone {
            self.write(", ");
            self.generate_expression(tz)?;
        }
        self.write(")");
        Ok(())
    }

    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
        match expr {
            Expression::Struct(s) => {
                if s.fields.iter().all(|(name, _)| name.is_some()) {
                    Some(
                        s.fields
                            .iter()
                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
                            .collect(),
                    )
                } else {
                    None
                }
            }
            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
                // Check if all args are Alias (named fields)
                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
                    Some(
                        f.args
                            .iter()
                            .filter_map(|a| {
                                if let Expression::Alias(alias) = a {
                                    Some(alias.alias.name.clone())
                                } else {
                                    None
                                }
                            })
                            .collect(),
                    )
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    /// Check if a struct expression has any unnamed fields
    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
        match expr {
            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
            }
            _ => false,
        }
    }

    /// Get the field count of a struct expression
    fn struct_field_count(expr: &Expression) -> usize {
        match expr {
            Expression::Struct(s) => s.fields.len(),
            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
            _ => 0,
        }
    }

    /// Apply field names to an unnamed struct expression, producing a new expression with names
    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
        match expr {
            Expression::Struct(s) => {
                let mut new_fields = Vec::with_capacity(s.fields.len());
                for (i, (name, value)) in s.fields.iter().enumerate() {
                    if name.is_none() && i < field_names.len() {
                        new_fields.push((Some(field_names[i].clone()), value.clone()));
                    } else {
                        new_fields.push((name.clone(), value.clone()));
                    }
                }
                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
            }
            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
                let mut new_args = Vec::with_capacity(f.args.len());
                for (i, arg) in f.args.iter().enumerate() {
                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
                        // Wrap the value in an Alias with the inherited name
                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
                            this: arg.clone(),
                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
                            column_aliases: Vec::new(),
                            pre_alias_comments: Vec::new(),
                            trailing_comments: Vec::new(),
                        })));
                    } else {
                        new_args.push(arg.clone());
                    }
                }
                Expression::Function(Box::new(crate::expressions::Function {
                    name: f.name.clone(),
                    args: new_args,
                    distinct: f.distinct,
                    trailing_comments: f.trailing_comments.clone(),
                    use_bracket_syntax: f.use_bracket_syntax,
                    no_parens: f.no_parens,
                    quoted: f.quoted,
                }))
            }
            _ => expr.clone(),
        }
    }

    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
    /// This implements BigQuery's implicit field name inheritance for struct arrays.
    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
        let first = match expressions.first() {
            Some(e) => e,
            None => return expressions.to_vec(),
        };

        let field_names = match Self::extract_struct_field_names(first) {
            Some(names) if !names.is_empty() => names,
            _ => return expressions.to_vec(),
        };

        let mut result = Vec::with_capacity(expressions.len());
        for (idx, expr) in expressions.iter().enumerate() {
            if idx == 0 {
                result.push(expr.clone());
                continue;
            }
            // Check if this is a struct with unnamed fields that needs name propagation
            if Self::struct_field_count(expr) == field_names.len()
                && Self::struct_has_unnamed_fields(expr)
            {
                result.push(Self::apply_struct_field_names(expr, &field_names));
            } else {
                result.push(expr.clone());
            }
        }
        result
    }

    // Array function generators

    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
        // Apply struct name inheritance for target dialects that need it
        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
        let needs_inheritance = matches!(
            self.config.dialect,
            Some(DialectType::DuckDB)
                | Some(DialectType::Spark)
                | Some(DialectType::Databricks)
                | Some(DialectType::Hive)
                | Some(DialectType::Snowflake)
                | Some(DialectType::Presto)
                | Some(DialectType::Trino)
        );
        let propagated: Vec<Expression>;
        let expressions = if needs_inheritance && f.expressions.len() > 1 {
            propagated = Self::inherit_struct_field_names(&f.expressions);
            &propagated
        } else {
            &f.expressions
        };

        // Check if elements should be split onto multiple lines (pretty + too wide)
        let should_split = if self.config.pretty && !expressions.is_empty() {
            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
            for expr in expressions {
                let mut temp_gen = Generator::with_config(self.config.clone());
                temp_gen.config.pretty = false;
                temp_gen.generate_expression(expr)?;
                expr_strings.push(temp_gen.output);
            }
            self.too_wide(&expr_strings)
        } else {
            false
        };

        if f.bracket_notation {
            // For Spark/Databricks, use ARRAY(...) with parens
            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
            // For others (DuckDB, Snowflake), use bare [...]
            let (open, close) = match self.config.dialect {
                Some(DialectType::Spark)
                | Some(DialectType::Databricks)
                | Some(DialectType::Hive) => {
                    self.write_keyword("ARRAY");
                    ("(", ")")
                }
                Some(DialectType::Presto)
                | Some(DialectType::Trino)
                | Some(DialectType::PostgreSQL)
                | Some(DialectType::Redshift)
                | Some(DialectType::Materialize)
                | Some(DialectType::RisingWave)
                | Some(DialectType::CockroachDB) => {
                    self.write_keyword("ARRAY");
                    ("[", "]")
                }
                _ => ("[", "]"),
            };
            self.write(open);
            if should_split {
                self.write_newline();
                self.indent_level += 1;
                for (i, expr) in expressions.iter().enumerate() {
                    self.write_indent();
                    self.generate_expression(expr)?;
                    if i + 1 < expressions.len() {
                        self.write(",");
                    }
                    self.write_newline();
                }
                self.indent_level -= 1;
                self.write_indent();
            } else {
                for (i, expr) in expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            }
            self.write(close);
        } else {
            // Use LIST keyword if that was the original syntax (DuckDB)
            if f.use_list_keyword {
                self.write_keyword("LIST");
            } else {
                self.write_keyword("ARRAY");
            }
            // For Spark/Hive, always use ARRAY(...) with parens
            let (open, close) = if matches!(
                self.config.dialect,
                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
            ) {
                ("(", ")")
            } else {
                ("[", "]")
            };
            self.write(open);
            if should_split {
                self.write_newline();
                self.indent_level += 1;
                for (i, expr) in expressions.iter().enumerate() {
                    self.write_indent();
                    self.generate_expression(expr)?;
                    if i + 1 < expressions.len() {
                        self.write(",");
                    }
                    self.write_newline();
                }
                self.indent_level -= 1;
                self.write_indent();
            } else {
                for (i, expr) in expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            }
            self.write(close);
        }
        Ok(())
    }

    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
        self.write_keyword("ARRAY_SORT");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(ref comp) = f.comparator {
            self.write(", ");
            self.generate_expression(comp)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.separator)?;
        if let Some(ref null_rep) = f.null_replacement {
            self.write(", ");
            self.generate_expression(null_rep)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
        self.write_keyword("UNNEST");
        self.write("(");
        self.generate_expression(&f.this)?;
        for extra in &f.expressions {
            self.write(", ");
            self.generate_expression(extra)?;
        }
        self.write(")");
        if f.with_ordinality {
            self.write_space();
            if self.config.unnest_with_ordinality {
                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
                self.write_keyword("WITH ORDINALITY");
            } else if f.offset_alias.is_some() {
                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
                // Alias (if any) comes BEFORE WITH OFFSET
                if let Some(ref alias) = f.alias {
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_identifier(alias)?;
                    self.write_space();
                }
                self.write_keyword("WITH OFFSET");
                if let Some(ref offset_alias) = f.offset_alias {
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_identifier(offset_alias)?;
                }
            } else {
                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
                self.write_keyword("WITH OFFSET");
                if f.alias.is_none() {
                    self.write(" AS offset");
                }
            }
        }
        if let Some(ref alias) = f.alias {
            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
            let should_add_alias = if !f.with_ordinality {
                true
            } else if self.config.unnest_with_ordinality {
                // Presto/Trino: alias comes after WITH ORDINALITY
                true
            } else if f.offset_alias.is_some() {
                // BigQuery expansion: alias already handled above
                false
            } else {
                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
                true
            };
            if should_add_alias {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.generate_identifier(alias)?;
            }
        }
        Ok(())
    }

    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
        self.write_keyword("FILTER");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.filter)?;
        self.write(")");
        Ok(())
    }

    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
        self.write_keyword("TRANSFORM");
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.transform)?;
        self.write(")");
        Ok(())
    }

    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.start)?;
        self.write(", ");
        self.generate_expression(&f.stop)?;
        if let Some(ref step) = f.step {
            self.write(", ");
            self.generate_expression(step)?;
        }
        self.write(")");
        Ok(())
    }

    // Struct function generators

    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
        self.write_keyword("STRUCT");
        self.write("(");
        for (i, (name, expr)) in f.fields.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            if let Some(ref id) = name {
                self.generate_identifier(id)?;
                self.write(" ");
                self.write_keyword("AS");
                self.write(" ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
        // Extract named/unnamed fields from function args
        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
        let mut names: Vec<Option<String>> = Vec::new();
        let mut values: Vec<&Expression> = Vec::new();
        let mut all_named = true;

        for arg in &func.args {
            match arg {
                Expression::Alias(a) => {
                    names.push(Some(a.alias.name.clone()));
                    values.push(&a.this);
                }
                _ => {
                    names.push(None);
                    values.push(arg);
                    all_named = false;
                }
            }
        }

        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
            self.write("{");
            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                if let Some(n) = name {
                    self.write("'");
                    self.write(n);
                    self.write("'");
                } else {
                    self.write("'_");
                    self.write(&i.to_string());
                    self.write("'");
                }
                self.write(": ");
                self.generate_expression(value)?;
            }
            self.write("}");
            return Ok(());
        }

        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
            self.write_keyword("OBJECT_CONSTRUCT");
            self.write("(");
            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                if let Some(n) = name {
                    self.write("'");
                    self.write(n);
                    self.write("'");
                } else {
                    self.write("'_");
                    self.write(&i.to_string());
                    self.write("'");
                }
                self.write(", ");
                self.generate_expression(value)?;
            }
            self.write(")");
            return Ok(());
        }

        if matches!(
            self.config.dialect,
            Some(DialectType::Presto) | Some(DialectType::Trino)
        ) {
            if all_named && !names.is_empty() {
                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
                // Need to infer types from values
                self.write_keyword("CAST");
                self.write("(");
                self.write_keyword("ROW");
                self.write("(");
                for (i, value) in values.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(value)?;
                }
                self.write(")");
                self.write(" ");
                self.write_keyword("AS");
                self.write(" ");
                self.write_keyword("ROW");
                self.write("(");
                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    if let Some(n) = name {
                        self.write(n);
                    }
                    self.write(" ");
                    let type_str = Self::infer_sql_type_for_presto(value);
                    self.write_keyword(&type_str);
                }
                self.write(")");
                self.write(")");
            } else {
                // Unnamed: ROW(values...)
                self.write_keyword("ROW");
                self.write("(");
                for (i, value) in values.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(value)?;
                }
                self.write(")");
            }
            return Ok(());
        }

        // Default: ROW(values...) for other dialects
        self.write_keyword("ROW");
        self.write("(");
        for (i, value) in values.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(value)?;
        }
        self.write(")");
        Ok(())
    }

    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
    fn infer_sql_type_for_presto(expr: &Expression) -> String {
        match expr {
            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
            Expression::Literal(crate::expressions::Literal::Number(n)) => {
                if n.contains('.') {
                    "DOUBLE".to_string()
                } else {
                    "INTEGER".to_string()
                }
            }
            Expression::Boolean(_) => "BOOLEAN".to_string(),
            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
                "TIMESTAMP".to_string()
            }
            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
                "TIMESTAMP".to_string()
            }
            Expression::Array(_) | Expression::ArrayFunc(_) => {
                // Try to infer element type from first element
                "ARRAY(VARCHAR)".to_string()
            }
            // For nested structs - generate a nested ROW type by inspecting fields
            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
            Expression::Function(f) => {
                let up = f.name.to_uppercase();
                if up == "STRUCT" {
                    "ROW".to_string()
                } else if up == "CURRENT_DATE" {
                    "DATE".to_string()
                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
                    "TIMESTAMP".to_string()
                } else {
                    "VARCHAR".to_string()
                }
            }
            _ => "VARCHAR".to_string(),
        }
    }

    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
        // DuckDB uses STRUCT_EXTRACT function syntax
        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
            self.write_keyword("STRUCT_EXTRACT");
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write(", ");
            // Output field name as string literal
            self.write("'");
            self.write(&f.field.name);
            self.write("'");
            self.write(")");
            return Ok(());
        }
        self.generate_expression(&f.this)?;
        self.write(".");
        self.generate_identifier(&f.field)
    }

    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
        self.write_keyword("NAMED_STRUCT");
        self.write("(");
        for (i, (name, value)) in f.pairs.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(name)?;
            self.write(", ");
            self.generate_expression(value)?;
        }
        self.write(")");
        Ok(())
    }

    // Map function generators

    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
        if f.curly_brace_syntax {
            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
            if f.with_map_keyword {
                self.write_keyword("MAP");
                self.write(" ");
            }
            self.write("{");
            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(key)?;
                self.write(": ");
                self.generate_expression(val)?;
            }
            self.write("}");
        } else {
            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
            self.write_keyword("MAP");
            self.write("(");
            self.write_keyword("ARRAY");
            self.write("[");
            for (i, key) in f.keys.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(key)?;
            }
            self.write("], ");
            self.write_keyword("ARRAY");
            self.write("[");
            for (i, val) in f.values.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(val)?;
            }
            self.write("])");
        }
        Ok(())
    }

    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        self.write(", ");
        self.generate_expression(&f.transform)?;
        self.write(")");
        Ok(())
    }

    // JSON function generators

    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // Check if we should use arrow syntax (-> or ->>)
        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();

        if use_arrow {
            // Output arrow syntax: expr -> path or expr ->> path
            self.generate_expression(&f.this)?;
            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
                self.write(" ->> ");
            } else {
                self.write(" -> ");
            }
            self.generate_expression(&f.path)?;
            return Ok(());
        }

        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
        if f.hash_arrow_syntax
            && matches!(
                self.config.dialect,
                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
            )
        {
            self.generate_expression(&f.this)?;
            self.write(" #>> ");
            self.generate_expression(&f.path)?;
            return Ok(());
        }

        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
            match name {
                "JSON_EXTRACT_SCALAR"
                | "JSON_EXTRACT_PATH_TEXT"
                | "JSON_EXTRACT"
                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
                _ => name,
            }
        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
            match name {
                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
                _ => name,
            }
        } else {
            name
        };

        self.write_keyword(func_name);
        self.write("(");
        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
            if let Expression::Cast(ref cast) = f.this {
                if matches!(cast.to, crate::expressions::DataType::Json) {
                    self.generate_expression(&cast.this)?;
                } else {
                    self.generate_expression(&f.this)?;
                }
            } else {
                self.generate_expression(&f.this)?;
            }
        } else {
            self.generate_expression(&f.this)?;
        }
        self.write(", ");
        self.generate_expression(&f.path)?;

        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
        // These go BEFORE the closing parenthesis
        if let Some(ref wrapper) = f.wrapper_option {
            self.write_space();
            self.write_keyword(wrapper);
        }
        if let Some(ref quotes) = f.quotes_option {
            self.write_space();
            self.write_keyword(quotes);
            if f.on_scalar_string {
                self.write_space();
                self.write_keyword("ON SCALAR STRING");
            }
        }
        if let Some(ref on_err) = f.on_error {
            self.write_space();
            self.write_keyword(on_err);
        }
        if let Some(ref ret_type) = f.returning {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            self.generate_data_type(ret_type)?;
        }

        self.write(")");
        Ok(())
    }

    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
    fn dialect_supports_json_arrow(&self) -> bool {
        use crate::dialects::DialectType;
        match self.config.dialect {
            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
            Some(DialectType::PostgreSQL) => true,
            Some(DialectType::MySQL) => true,
            Some(DialectType::DuckDB) => true,
            Some(DialectType::CockroachDB) => true,
            Some(DialectType::StarRocks) => true,
            Some(DialectType::SQLite) => true,
            // Other dialects use function syntax
            _ => false,
        }
    }

    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // PostgreSQL uses #> operator for JSONB path extraction
        if matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
        ) && name == "JSON_EXTRACT_PATH"
        {
            self.generate_expression(&f.this)?;
            self.write(" #> ");
            if f.paths.len() == 1 {
                self.generate_expression(&f.paths[0])?;
            } else {
                // Multiple paths: ARRAY[path1, path2, ...]
                self.write_keyword("ARRAY");
                self.write("[");
                for (i, path) in f.paths.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(path)?;
                }
                self.write("]");
            }
            return Ok(());
        }

        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        for path in &f.paths {
            self.write(", ");
            self.generate_expression(path)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
        use crate::dialects::DialectType;

        self.write_keyword("JSON_OBJECT");
        self.write("(");
        if f.star {
            self.write("*");
        } else {
            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
            // Also respect the json_key_value_pair_sep config
            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
                || matches!(
                    self.config.dialect,
                    Some(DialectType::BigQuery)
                        | Some(DialectType::MySQL)
                        | Some(DialectType::SQLite)
                );

            for (i, (key, value)) in f.pairs.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(key)?;
                if use_comma_syntax {
                    self.write(", ");
                } else {
                    self.write(": ");
                }
                self.generate_expression(value)?;
            }
        }
        if let Some(null_handling) = f.null_handling {
            self.write_space();
            match null_handling {
                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
            }
        }
        if f.with_unique_keys {
            self.write_space();
            self.write_keyword("WITH UNIQUE KEYS");
        }
        if let Some(ref ret_type) = f.returning_type {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            self.generate_data_type(ret_type)?;
            if f.format_json {
                self.write_space();
                self.write_keyword("FORMAT JSON");
            }
            if let Some(ref enc) = f.encoding {
                self.write_space();
                self.write_keyword("ENCODING");
                self.write_space();
                self.write(enc);
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&f.this)?;
        for (path, value) in &f.path_values {
            self.write(", ");
            self.generate_expression(path)?;
            self.write(", ");
            self.generate_expression(value)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
        self.write_keyword("JSON_ARRAYAGG");
        self.write("(");
        self.generate_expression(&f.this)?;
        if let Some(ref order_by) = f.order_by {
            self.write_space();
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ord) in order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ord)?;
            }
        }
        if let Some(null_handling) = f.null_handling {
            self.write_space();
            match null_handling {
                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
            }
        }
        self.write(")");
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
        self.write_keyword("JSON_OBJECTAGG");
        self.write("(");
        self.generate_expression(&f.key)?;
        self.write(": ");
        self.generate_expression(&f.value)?;
        if let Some(null_handling) = f.null_handling {
            self.write_space();
            match null_handling {
                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
            }
        }
        self.write(")");
        if let Some(ref filter) = f.filter {
            self.write_space();
            self.write_keyword("FILTER");
            self.write("(");
            self.write_keyword("WHERE");
            self.write_space();
            self.generate_expression(filter)?;
            self.write(")");
        }
        Ok(())
    }

    // Type casting/conversion generators

    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
        use crate::dialects::DialectType;

        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
        if self.config.dialect == Some(DialectType::Redshift) {
            self.write_keyword("CAST");
            self.write("(");
            self.generate_expression(&f.this)?;
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_data_type(&f.to)?;
            self.write(")");
            return Ok(());
        }

        self.write_keyword("CONVERT");
        self.write("(");
        self.generate_data_type(&f.to)?;
        self.write(", ");
        self.generate_expression(&f.this)?;
        if let Some(ref style) = f.style {
            self.write(", ");
            self.generate_expression(style)?;
        }
        self.write(")");
        Ok(())
    }

    // Additional expression generators

    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
        if f.colon {
            // DuckDB syntax: LAMBDA x : expr
            self.write_keyword("LAMBDA");
            self.write_space();
            for (i, param) in f.parameters.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(param)?;
            }
            self.write(" : ");
        } else {
            // Standard syntax: x -> expr or (x, y) -> expr
            if f.parameters.len() == 1 {
                self.generate_identifier(&f.parameters[0])?;
            } else {
                self.write("(");
                for (i, param) in f.parameters.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(param)?;
                }
                self.write(")");
            }
            self.write(" -> ");
        }
        self.generate_expression(&f.body)
    }

    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
        self.generate_identifier(&f.name)?;
        match f.separator {
            NamedArgSeparator::DArrow => self.write(" => "),
            NamedArgSeparator::ColonEq => self.write(" := "),
            NamedArgSeparator::Eq => self.write(" = "),
        }
        self.generate_expression(&f.value)
    }

    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
        self.write_keyword(&f.prefix);
        self.write(" ");
        self.generate_expression(&f.this)
    }

    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
        match f.style {
            ParameterStyle::Question => self.write("?"),
            ParameterStyle::Dollar => {
                self.write("$");
                if let Some(idx) = f.index {
                    self.write(&idx.to_string());
                } else if let Some(ref name) = f.name {
                    // Session variable like $x or $query_id
                    self.write(name);
                }
            }
            ParameterStyle::DollarBrace => {
                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
                self.write("${");
                if let Some(ref name) = f.name {
                    self.write(name);
                }
                if let Some(ref expr) = f.expression {
                    self.write(":");
                    self.write(expr);
                }
                self.write("}");
            }
            ParameterStyle::Colon => {
                self.write(":");
                if let Some(idx) = f.index {
                    self.write(&idx.to_string());
                } else if let Some(ref name) = f.name {
                    self.write(name);
                }
            }
            ParameterStyle::At => {
                self.write("@");
                if let Some(ref name) = f.name {
                    if f.quoted {
                        self.write("\"");
                        self.write(name);
                        self.write("\"");
                    } else {
                        self.write(name);
                    }
                }
            }
            ParameterStyle::DoubleAt => {
                self.write("@@");
                if let Some(ref name) = f.name {
                    self.write(name);
                }
            }
            ParameterStyle::DoubleDollar => {
                self.write("$$");
                if let Some(ref name) = f.name {
                    self.write(name);
                }
            }
            ParameterStyle::Percent => {
                if let Some(ref name) = f.name {
                    // %(name)s format
                    self.write("%(");
                    self.write(name);
                    self.write(")s");
                } else {
                    // %s format
                    self.write("%s");
                }
            }
            ParameterStyle::Brace => {
                // Spark/Databricks widget template variable: {name}
                // ClickHouse query parameter may include kind: {name: Type}
                self.write("{");
                if let Some(ref name) = f.name {
                    self.write(name);
                }
                if let Some(ref expr) = f.expression {
                    self.write(": ");
                    self.write(expr);
                }
                self.write("}");
            }
        }
        Ok(())
    }

    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
        self.write("?");
        if let Some(idx) = f.index {
            self.write(&idx.to_string());
        }
        Ok(())
    }

    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
        if f.is_block {
            self.write("/*");
            self.write(&f.text);
            self.write("*/");
        } else {
            self.write("--");
            self.write(&f.text);
        }
        Ok(())
    }

    // Additional predicate generators

    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
        self.generate_expression(&f.this)?;
        if f.not {
            self.write_space();
            self.write_keyword("NOT");
        }
        self.write_space();
        self.write_keyword("SIMILAR TO");
        self.write_space();
        self.generate_expression(&f.pattern)?;
        if let Some(ref escape) = f.escape {
            self.write_space();
            self.write_keyword("ESCAPE");
            self.write_space();
            self.generate_expression(escape)?;
        }
        Ok(())
    }

    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
        self.generate_expression(&f.this)?;
        self.write_space();
        // Output comparison operator if present
        if let Some(op) = &f.op {
            match op {
                QuantifiedOp::Eq => self.write("="),
                QuantifiedOp::Neq => self.write("<>"),
                QuantifiedOp::Lt => self.write("<"),
                QuantifiedOp::Lte => self.write("<="),
                QuantifiedOp::Gt => self.write(">"),
                QuantifiedOp::Gte => self.write(">="),
            }
            self.write_space();
        }
        self.write_keyword(name);
        if self.config.quantified_no_paren_space {
            self.write("(");
        } else {
            self.write(" (");
        }
        self.generate_expression(&f.subquery)?;
        self.write(")");
        Ok(())
    }

    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
        // Check if this is a simple binary form (this OVERLAPS expression)
        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
            self.generate_expression(this)?;
            self.write_space();
            self.write_keyword("OVERLAPS");
            self.write_space();
            self.generate_expression(expr)?;
        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
        {
            // Full ANSI form: (a, b) OVERLAPS (c, d)
            self.write("(");
            self.generate_expression(ls)?;
            self.write(", ");
            self.generate_expression(le)?;
            self.write(")");
            self.write_space();
            self.write_keyword("OVERLAPS");
            self.write_space();
            self.write("(");
            self.generate_expression(rs)?;
            self.write(", ");
            self.generate_expression(re)?;
            self.write(")");
        }
        Ok(())
    }

    // Type conversion generators

    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
        use crate::dialects::DialectType;

        // SingleStore uses !:> syntax for try cast
        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
            self.generate_expression(&cast.this)?;
            self.write(" !:> ");
            self.generate_data_type(&cast.to)?;
            return Ok(());
        }

        // Teradata uses TRYCAST (no underscore)
        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
            self.write_keyword("TRYCAST");
            self.write("(");
            self.generate_expression(&cast.this)?;
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_data_type(&cast.to)?;
            self.write(")");
            return Ok(());
        }

        // Dialects without TRY_CAST: generate as regular CAST
        let keyword = if matches!(
            self.config.dialect,
            Some(DialectType::Hive)
                | Some(DialectType::MySQL)
                | Some(DialectType::SQLite)
                | Some(DialectType::Oracle)
                | Some(DialectType::ClickHouse)
                | Some(DialectType::Redshift)
                | Some(DialectType::PostgreSQL)
                | Some(DialectType::StarRocks)
                | Some(DialectType::Doris)
        ) {
            "CAST"
        } else {
            "TRY_CAST"
        };

        self.write_keyword(keyword);
        self.write("(");
        self.generate_expression(&cast.this)?;
        self.write_space();
        self.write_keyword("AS");
        self.write_space();
        self.generate_data_type(&cast.to)?;

        // Output FORMAT clause if present
        if let Some(format) = &cast.format {
            self.write_space();
            self.write_keyword("FORMAT");
            self.write_space();
            self.generate_expression(format)?;
        }

        self.write(")");
        Ok(())
    }

    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
        self.write_keyword("SAFE_CAST");
        self.write("(");
        self.generate_expression(&cast.this)?;
        self.write_space();
        self.write_keyword("AS");
        self.write_space();
        self.generate_data_type(&cast.to)?;

        // Output FORMAT clause if present
        if let Some(format) = &cast.format {
            self.write_space();
            self.write_keyword("FORMAT");
            self.write_space();
            self.generate_expression(format)?;
        }

        self.write(")");
        Ok(())
    }

    // Array/struct/map access generators

    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
        self.generate_expression(&s.this)?;
        self.write("[");
        self.generate_expression(&s.index)?;
        self.write("]");
        Ok(())
    }

    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
        self.generate_expression(&d.this)?;
        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
            && matches!(
                &d.this,
                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
            );
        if use_colon {
            self.write(":");
        } else {
            self.write(".");
        }
        self.generate_identifier(&d.field)
    }

    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
        self.generate_expression(&m.this)?;
        self.write(".");
        // Method names after a dot should not be quoted based on reserved keywords
        // Only quote if explicitly marked as quoted in the AST
        if m.method.quoted {
            let q = self.config.identifier_quote;
            self.write(&format!("{}{}{}", q, m.method.name, q));
        } else {
            self.write(&m.method.name);
        }
        self.write("(");
        for (i, arg) in m.args.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(arg)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
        // Check if we need to wrap the inner expression in parentheses
        // JSON arrow expressions have lower precedence than array subscript
        let needs_parens = matches!(
            &s.this,
            Expression::JsonExtract(f) if f.arrow_syntax
        ) || matches!(
            &s.this,
            Expression::JsonExtractScalar(f) if f.arrow_syntax
        );

        if needs_parens {
            self.write("(");
        }
        self.generate_expression(&s.this)?;
        if needs_parens {
            self.write(")");
        }
        self.write("[");
        if let Some(start) = &s.start {
            self.generate_expression(start)?;
        }
        self.write(":");
        if let Some(end) = &s.end {
            self.generate_expression(end)?;
        }
        self.write("]");
        Ok(())
    }

    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
        // Generate left expression, but skip trailing comments if they're already in left_comments
        // to avoid duplication (comments are captured as both expr.trailing_comments
        // and BinaryOp.left_comments during parsing)
        match &op.left {
            Expression::Column(col) => {
                // Generate column without trailing comments but include join_mark
                if let Some(table) = &col.table {
                    self.generate_identifier(table)?;
                    self.write(".");
                }
                self.generate_identifier(&col.name)?;
                // Oracle-style join marker (+)
                if col.join_mark && self.config.supports_column_join_marks {
                    self.write(" (+)");
                }
            }
            Expression::Add(inner_op)
            | Expression::Sub(inner_op)
            | Expression::Mul(inner_op)
            | Expression::Div(inner_op)
            | Expression::Concat(inner_op) => {
                // Generate binary op without its trailing comments
                self.generate_binary_op_no_trailing(inner_op, match &op.left {
                    Expression::Add(_) => "+",
                    Expression::Sub(_) => "-",
                    Expression::Mul(_) => "*",
                    Expression::Div(_) => "/",
                    Expression::Concat(_) => "||",
                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
                })?;
            }
            _ => {
                self.generate_expression(&op.left)?;
            }
        }
        // Output comments after left operand
        for comment in &op.left_comments {
            self.write_space();
            self.write(comment);
        }
        if self.config.pretty
            && matches!(self.config.dialect, Some(DialectType::Snowflake))
            && (operator == "AND" || operator == "OR")
        {
            self.write_newline();
            self.write_indent();
            self.write_keyword(operator);
        } else {
            self.write_space();
            if operator.chars().all(|c| c.is_alphabetic()) {
                self.write_keyword(operator);
            } else {
                self.write(operator);
            }
        }
        // Output comments after operator (before right operand)
        for comment in &op.operator_comments {
            self.write_space();
            self.write(comment);
        }
        self.write_space();
        self.generate_expression(&op.right)?;
        // Output trailing comments after right operand
        for comment in &op.trailing_comments {
            self.write_space();
            self.write(comment);
        }
        Ok(())
    }

    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
        self.generate_expression(&op.left)?;
        self.write_space();
        self.write_keyword(operator);
        if let Some(quantifier) = &op.quantifier {
            self.write_space();
            self.write_keyword(quantifier);
        }
        self.write_space();
        self.generate_expression(&op.right)?;
        if let Some(escape) = &op.escape {
            self.write_space();
            self.write_keyword("ESCAPE");
            self.write_space();
            self.generate_expression(escape)?;
        }
        Ok(())
    }

    /// Generate null-safe equality
    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
        use crate::dialects::DialectType;
        self.generate_expression(&op.left)?;
        self.write_space();
        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
            self.write("<=>");
        } else {
            self.write_keyword("IS NOT DISTINCT FROM");
        }
        self.write_space();
        self.generate_expression(&op.right)?;
        Ok(())
    }

    /// Generate IS DISTINCT FROM (null-safe inequality)
    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
        self.generate_expression(&op.left)?;
        self.write_space();
        self.write_keyword("IS DISTINCT FROM");
        self.write_space();
        self.generate_expression(&op.right)?;
        Ok(())
    }

    /// Generate binary op without trailing comments (used when nested inside another binary op)
    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
        // Generate left expression, but skip trailing comments
        match &op.left {
            Expression::Column(col) => {
                if let Some(table) = &col.table {
                    self.generate_identifier(table)?;
                    self.write(".");
                }
                self.generate_identifier(&col.name)?;
                // Oracle-style join marker (+)
                if col.join_mark && self.config.supports_column_join_marks {
                    self.write(" (+)");
                }
            }
            Expression::Add(inner_op)
            | Expression::Sub(inner_op)
            | Expression::Mul(inner_op)
            | Expression::Div(inner_op)
            | Expression::Concat(inner_op) => {
                self.generate_binary_op_no_trailing(inner_op, match &op.left {
                    Expression::Add(_) => "+",
                    Expression::Sub(_) => "-",
                    Expression::Mul(_) => "*",
                    Expression::Div(_) => "/",
                    Expression::Concat(_) => "||",
                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
                })?;
            }
            _ => {
                self.generate_expression(&op.left)?;
            }
        }
        // Output left_comments
        for comment in &op.left_comments {
            self.write_space();
            self.write(comment);
        }
        self.write_space();
        if operator.chars().all(|c| c.is_alphabetic()) {
            self.write_keyword(operator);
        } else {
            self.write(operator);
        }
        // Output operator_comments
        for comment in &op.operator_comments {
            self.write_space();
            self.write(comment);
        }
        self.write_space();
        // Generate right expression, but skip trailing comments if it's a Column
        // (the parent's left_comments will output them)
        match &op.right {
            Expression::Column(col) => {
                if let Some(table) = &col.table {
                    self.generate_identifier(table)?;
                    self.write(".");
                }
                self.generate_identifier(&col.name)?;
                // Oracle-style join marker (+)
                if col.join_mark && self.config.supports_column_join_marks {
                    self.write(" (+)");
                }
            }
            _ => {
                self.generate_expression(&op.right)?;
            }
        }
        // Skip trailing_comments - parent will handle them via its left_comments
        Ok(())
    }

    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
        if operator.chars().all(|c| c.is_alphabetic()) {
            self.write_keyword(operator);
            self.write_space();
        } else {
            self.write(operator);
            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
                self.write_space();
            }
        }
        self.generate_expression(&op.this)
    }

    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
        self.generate_expression(&in_expr.this)?;
        if in_expr.global {
            self.write_space();
            self.write_keyword("GLOBAL");
        }
        if in_expr.not {
            self.write_space();
            self.write_keyword("NOT");
        }
        self.write_space();
        self.write_keyword("IN");

        // BigQuery: IN UNNEST(expr)
        if let Some(unnest_expr) = &in_expr.unnest {
            self.write_space();
            self.write_keyword("UNNEST");
            self.write("(");
            self.generate_expression(unnest_expr)?;
            self.write(")");
            return Ok(());
        }

        if let Some(query) = &in_expr.query {
            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
            // vs a subquery (col IN (SELECT ...))
            let is_bare = in_expr.expressions.is_empty()
                && !matches!(
                    query,
                    Expression::Select(_)
                        | Expression::Union(_)
                        | Expression::Intersect(_)
                        | Expression::Except(_)
                        | Expression::Subquery(_)
                );
            if is_bare {
                // Bare identifier: no parentheses
                self.write_space();
                self.generate_expression(query)?;
            } else {
                // Subquery: with parentheses
                self.write(" (");
                let is_statement = matches!(
                    query,
                    Expression::Select(_)
                        | Expression::Union(_)
                        | Expression::Intersect(_)
                        | Expression::Except(_)
                        | Expression::Subquery(_)
                );
                if self.config.pretty && is_statement {
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                }
                self.generate_expression(query)?;
                if self.config.pretty && is_statement {
                    self.write_newline();
                    self.indent_level -= 1;
                    self.write_indent();
                }
                self.write(")");
            }
        } else {
            // DuckDB: IN without parentheses for single expression that is NOT a literal
            // (array/list membership like 'red' IN tbl.flags)
            // ClickHouse: IN without parentheses for single non-array expressions
            let is_duckdb = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::DuckDB)
            );
            let is_clickhouse = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::ClickHouse)
            );
            let single_expr = in_expr.expressions.len() == 1;
            if is_clickhouse && single_expr {
                if let Expression::Array(arr) = &in_expr.expressions[0] {
                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
                    self.write(" (");
                    for (i, expr) in arr.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(expr)?;
                    }
                    self.write(")");
                } else {
                    self.write_space();
                    self.generate_expression(&in_expr.expressions[0])?;
                }
            } else {
                let is_bare_ref = single_expr
                    && matches!(
                        &in_expr.expressions[0],
                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
                    );
                if is_duckdb && is_bare_ref {
                    self.write_space();
                    self.generate_expression(&in_expr.expressions[0])?;
                } else {
                    // Standard IN (list)
                    self.write(" (");
                    for (i, expr) in in_expr.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(expr)?;
                    }
                    self.write(")");
                }
            }
        }

        Ok(())
    }

    fn generate_between(&mut self, between: &Between) -> Result<()> {
        self.generate_expression(&between.this)?;
        if between.not {
            self.write_space();
            self.write_keyword("NOT");
        }
        self.write_space();
        self.write_keyword("BETWEEN");
        self.write_space();
        self.generate_expression(&between.low)?;
        self.write_space();
        self.write_keyword("AND");
        self.write_space();
        self.generate_expression(&between.high)
    }

    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
        // Python sqlglot normalizes NOTNULL postfix to NOT x IS NULL
        if is_null.not && is_null.postfix_form {
            // NOTNULL → NOT x IS NULL
            self.write_keyword("NOT");
            self.write_space();
            self.generate_expression(&is_null.this)?;
            self.write_space();
            self.write_keyword("IS");
            self.write_space();
            self.write_keyword("NULL");
        } else {
            self.generate_expression(&is_null.this)?;
            self.write_space();
            self.write_keyword("IS");
            if is_null.not {
                self.write_space();
                self.write_keyword("NOT");
            }
            self.write_space();
            self.write_keyword("NULL");
        }
        Ok(())
    }

    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
        self.generate_expression(&is_true.this)?;
        self.write_space();
        self.write_keyword("IS");
        if is_true.not {
            self.write_space();
            self.write_keyword("NOT");
        }
        self.write_space();
        self.write_keyword("TRUE");
        Ok(())
    }

    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
        self.generate_expression(&is_false.this)?;
        self.write_space();
        self.write_keyword("IS");
        if is_false.not {
            self.write_space();
            self.write_keyword("NOT");
        }
        self.write_space();
        self.write_keyword("FALSE");
        Ok(())
    }

    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
        self.generate_expression(&is_json.this)?;
        self.write_space();
        self.write_keyword("IS");
        if is_json.negated {
            self.write_space();
            self.write_keyword("NOT");
        }
        self.write_space();
        self.write_keyword("JSON");

        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
        if let Some(ref json_type) = is_json.json_type {
            self.write_space();
            self.write_keyword(json_type);
        }

        // Output key uniqueness constraint if specified
        match &is_json.unique_keys {
            Some(JsonUniqueKeys::With) => {
                self.write_space();
                self.write_keyword("WITH UNIQUE KEYS");
            }
            Some(JsonUniqueKeys::Without) => {
                self.write_space();
                self.write_keyword("WITHOUT UNIQUE KEYS");
            }
            Some(JsonUniqueKeys::Shorthand) => {
                self.write_space();
                self.write_keyword("UNIQUE KEYS");
            }
            None => {}
        }

        Ok(())
    }

    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
        self.generate_expression(&is_expr.left)?;
        self.write_space();
        self.write_keyword("IS");
        self.write_space();
        self.generate_expression(&is_expr.right)
    }

    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
        if exists.not {
            self.write_keyword("NOT");
            self.write_space();
        }
        self.write_keyword("EXISTS");
        self.write("(");
        self.generate_expression(&exists.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
        self.generate_expression(&op.left)?;
        self.write_space();
        self.write_keyword("MEMBER OF");
        self.write("(");
        self.generate_expression(&op.right)?;
        self.write(")");
        Ok(())
    }

    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
        if subquery.lateral {
            self.write_keyword("LATERAL");
            self.write_space();
        }

        // If the inner expression is a Paren, don't add extra parentheses
        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
        // to carry the LIMIT modifier without adding more parens
        let skip_outer_parens = matches!(&subquery.this, Expression::Paren(_));

        // Check if inner expression is a statement for pretty formatting
        let is_statement = matches!(
            &subquery.this,
            Expression::Select(_)
                | Expression::Union(_)
                | Expression::Intersect(_)
                | Expression::Except(_)
                | Expression::Merge(_)
        );

        if !skip_outer_parens {
            self.write("(");
            if self.config.pretty && is_statement {
                self.write_newline();
                self.indent_level += 1;
                self.write_indent();
            }
        }
        self.generate_expression(&subquery.this)?;

        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
        if subquery.modifiers_inside {
            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
            if let Some(order_by) = &subquery.order_by {
                self.write_space();
                self.write_keyword("ORDER BY");
                self.write_space();
                for (i, ord) in order_by.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
            }

            if let Some(limit) = &subquery.limit {
                self.write_space();
                self.write_keyword("LIMIT");
                self.write_space();
                self.generate_expression(&limit.this)?;
                if limit.percent {
                    self.write_space();
                    self.write_keyword("PERCENT");
                }
            }

            if let Some(offset) = &subquery.offset {
                self.write_space();
                self.write_keyword("OFFSET");
                self.write_space();
                self.generate_expression(&offset.this)?;
            }
        }

        if !skip_outer_parens {
            if self.config.pretty && is_statement {
                self.write_newline();
                self.indent_level -= 1;
                self.write_indent();
            }
            self.write(")");
        }

        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
        if !subquery.modifiers_inside {
            if let Some(order_by) = &subquery.order_by {
                self.write_space();
                self.write_keyword("ORDER BY");
                self.write_space();
                for (i, ord) in order_by.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
            }

            if let Some(limit) = &subquery.limit {
                self.write_space();
                self.write_keyword("LIMIT");
                self.write_space();
                self.generate_expression(&limit.this)?;
                if limit.percent {
                    self.write_space();
                    self.write_keyword("PERCENT");
                }
            }

            if let Some(offset) = &subquery.offset {
                self.write_space();
                self.write_keyword("OFFSET");
                self.write_space();
                self.generate_expression(&offset.this)?;
            }

            // Generate DISTRIBUTE BY (Hive/Spark)
            if let Some(distribute_by) = &subquery.distribute_by {
                self.write_space();
                self.write_keyword("DISTRIBUTE BY");
                self.write_space();
                for (i, expr) in distribute_by.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            }

            // Generate SORT BY (Hive/Spark)
            if let Some(sort_by) = &subquery.sort_by {
                self.write_space();
                self.write_keyword("SORT BY");
                self.write_space();
                for (i, ord) in sort_by.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
            }

            // Generate CLUSTER BY (Hive/Spark)
            if let Some(cluster_by) = &subquery.cluster_by {
                self.write_space();
                self.write_keyword("CLUSTER BY");
                self.write_space();
                for (i, ord) in cluster_by.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
            }
        }

        if let Some(alias) = &subquery.alias {
            self.write_space();
            // Oracle doesn't use AS for subquery aliases
            let skip_as = matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::Oracle)
            );
            if !skip_as {
                self.write_keyword("AS");
                self.write_space();
            }
            self.generate_identifier(alias)?;
            if !subquery.column_aliases.is_empty() {
                self.write("(");
                for (i, col) in subquery.column_aliases.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }
        }
        // Output trailing comments
        for comment in &subquery.trailing_comments {
            self.write(" ");
            self.write(comment);
        }
        Ok(())
    }

    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
        // Generate WITH clause if present
        if let Some(ref with) = pivot.with {
            self.generate_with(with)?;
            self.write_space();
        }

        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };

        // Check for Redshift UNPIVOT in FROM clause:
        // UNPIVOT expr [AS val AT attr]
        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
        let is_redshift_unpivot = pivot.unpivot
            && pivot.expressions.is_empty()
            && pivot.fields.is_empty()
            && pivot.using.is_empty()
            && pivot.into.is_none()
            && !matches!(&pivot.this, Expression::Null(_));

        if is_redshift_unpivot {
            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
            self.write_keyword("UNPIVOT");
            self.write_space();
            self.generate_expression(&pivot.this)?;
            // Alias - for Redshift it can be "val AT attr" format
            if let Some(alias) = &pivot.alias {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                // The alias might contain " AT " for the attr part
                self.write(&alias.name);
            }
            return Ok(());
        }

        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
        let is_simplified = !pivot.using.is_empty()
            || pivot.into.is_some()
            || (pivot.fields.is_empty()
                && !pivot.expressions.is_empty()
                && !matches!(&pivot.this, Expression::Null(_)));

        if is_simplified {
            // DuckDB simplified syntax:
            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
            //   UNPIVOT table ON cols INTO NAME col VALUE col
            self.write_keyword(direction);
            self.write_space();
            self.generate_expression(&pivot.this)?;

            if !pivot.expressions.is_empty() {
                self.write_space();
                self.write_keyword("ON");
                self.write_space();
                for (i, expr) in pivot.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            }

            // INTO (for UNPIVOT)
            if let Some(into) = &pivot.into {
                self.write_space();
                self.write_keyword("INTO");
                self.write_space();
                self.generate_expression(into)?;
            }

            // USING (for PIVOT)
            if !pivot.using.is_empty() {
                self.write_space();
                self.write_keyword("USING");
                self.write_space();
                for (i, expr) in pivot.using.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            }

            // GROUP BY
            if let Some(group) = &pivot.group {
                self.write_space();
                self.generate_expression(group)?;
            }
        } else {
            // Standard syntax:
            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
            if !matches!(&pivot.this, Expression::Null(_)) {
                self.generate_expression(&pivot.this)?;
                self.write_space();
            }
            self.write_keyword(direction);
            self.write("(");

            // Aggregation expressions
            for (i, expr) in pivot.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }

            // FOR...IN fields
            if !pivot.fields.is_empty() {
                if !pivot.expressions.is_empty() {
                    self.write_space();
                }
                self.write_keyword("FOR");
                self.write_space();
                for (i, field) in pivot.fields.iter().enumerate() {
                    if i > 0 {
                        self.write_space();
                    }
                    // field is an In expression: column IN (values)
                    self.generate_expression(field)?;
                }
            }

            // DEFAULT ON NULL
            if let Some(default_val) = &pivot.default_on_null {
                self.write_space();
                self.write_keyword("DEFAULT ON NULL");
                self.write(" (");
                self.generate_expression(default_val)?;
                self.write(")");
            }

            // GROUP BY inside PIVOT parens
            if let Some(group) = &pivot.group {
                self.write_space();
                self.generate_expression(group)?;
            }

            self.write(")");
        }

        // Alias
        if let Some(alias) = &pivot.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(alias)?;
        }

        Ok(())
    }

    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
        self.generate_expression(&unpivot.this)?;
        self.write_space();
        self.write_keyword("UNPIVOT");
        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
        if let Some(include) = unpivot.include_nulls {
            self.write_space();
            if include {
                self.write_keyword("INCLUDE NULLS");
            } else {
                self.write_keyword("EXCLUDE NULLS");
            }
            self.write_space();
        }
        self.write("(");
        if unpivot.value_column_parenthesized {
            self.write("(");
        }
        self.generate_identifier(&unpivot.value_column)?;
        // Output additional value columns if present
        for extra_col in &unpivot.extra_value_columns {
            self.write(", ");
            self.generate_identifier(extra_col)?;
        }
        if unpivot.value_column_parenthesized {
            self.write(")");
        }
        self.write_space();
        self.write_keyword("FOR");
        self.write_space();
        self.generate_identifier(&unpivot.name_column)?;
        self.write_space();
        self.write_keyword("IN");
        self.write(" (");
        for (i, col) in unpivot.columns.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(col)?;
        }
        self.write("))");
        if let Some(alias) = &unpivot.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(alias)?;
        }
        Ok(())
    }

    fn generate_values(&mut self, values: &Values) -> Result<()> {
        self.write_keyword("VALUES");
        for (i, row) in values.expressions.iter().enumerate() {
            if i > 0 {
                self.write(",");
            }
            self.write(" (");
            for (j, expr) in row.expressions.iter().enumerate() {
                if j > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        if let Some(alias) = &values.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(alias)?;
            if !values.column_aliases.is_empty() {
                self.write("(");
                for (i, col) in values.column_aliases.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_array(&mut self, arr: &Array) -> Result<()> {
        // Apply struct name inheritance for target dialects that need it
        let needs_inheritance = matches!(
            self.config.dialect,
            Some(DialectType::DuckDB)
                | Some(DialectType::Spark)
                | Some(DialectType::Databricks)
                | Some(DialectType::Hive)
                | Some(DialectType::Snowflake)
                | Some(DialectType::Presto)
                | Some(DialectType::Trino)
        );
        let propagated: Vec<Expression>;
        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
            propagated = Self::inherit_struct_field_names(&arr.expressions);
            &propagated
        } else {
            &arr.expressions
        };

        if !self.config.array_bracket_only {
            self.write_keyword("ARRAY");
        }
        self.write("[");
        for (i, expr) in expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write("]");
        Ok(())
    }

    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
        if tuple.expressions.len() == 2 {
            if let Expression::TableAlias(_) = &tuple.expressions[1] {
                // First element is the function/expression, second is the TableAlias
                self.generate_expression(&tuple.expressions[0])?;
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.generate_expression(&tuple.expressions[1])?;
                return Ok(());
            }
        }

        // In pretty mode, format long tuples with each element on a new line
        if self.config.pretty && tuple.expressions.len() > 1 {
            self.write("(");
            self.write_newline();
            self.indent_level += 1;
            for (i, expr) in tuple.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_expression(expr)?;
            }
            self.indent_level -= 1;
            self.write_newline();
            self.write(")");
        } else {
            self.write("(");
            for (i, expr) in tuple.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
        self.generate_expression(&pipe.this)?;
        self.write(" |> ");
        self.generate_expression(&pipe.expression)?;
        Ok(())
    }

    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
        self.generate_expression(&ordered.this)?;
        if ordered.desc {
            self.write_space();
            self.write_keyword("DESC");
        } else if ordered.explicit_asc {
            self.write_space();
            self.write_keyword("ASC");
        }
        if let Some(nulls_first) = ordered.nulls_first {
            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
            // for the dialect. Different dialects have different NULL ordering defaults:
            //
            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
            //
            // nulls_are_small (Spark, Hive, BigQuery, most others):
            //   - ASC: NULLS FIRST is default
            //   - DESC: NULLS LAST is default
            //
            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
            //   - NULLS LAST is always the default regardless of sort direction
            let is_asc = !ordered.desc;
            let is_nulls_are_large = matches!(
                self.config.dialect,
                Some(DialectType::Oracle)
                    | Some(DialectType::PostgreSQL)
                    | Some(DialectType::Redshift)
                    | Some(DialectType::Snowflake)
            );
            let is_nulls_are_last = matches!(
                self.config.dialect,
                Some(DialectType::Dremio)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena)
                    | Some(DialectType::ClickHouse)
                    | Some(DialectType::Drill)
                    | Some(DialectType::Exasol)
            );

            // Check if the NULLS ordering matches the default for this dialect
            let is_default_nulls = if is_nulls_are_large {
                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
                (is_asc && !nulls_first) || (!is_asc && nulls_first)
            } else if is_nulls_are_last {
                // For nulls_are_last: NULLS LAST is always default
                !nulls_first
            } else {
                false
            };

            if !is_default_nulls {
                self.write_space();
                self.write_keyword("NULLS");
                self.write_space();
                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
            }
        }
        // WITH FILL clause (ClickHouse)
        if let Some(ref with_fill) = ordered.with_fill {
            self.write_space();
            self.generate_with_fill(with_fill)?;
        }
        Ok(())
    }

    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
        use crate::dialects::DialectType;

        match dt {
            DataType::Boolean => {
                // Dialect-specific boolean type mappings
                match self.config.dialect {
                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
                    Some(DialectType::Oracle) => {
                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
                        self.write_keyword("NUMBER(1)")
                    }
                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
                    _ => self.write_keyword("BOOLEAN"),
                }
            }
            DataType::TinyInt { length } => {
                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
                // Dremio maps TINYINT to INT
                match self.config.dialect {
                    Some(DialectType::PostgreSQL)
                    | Some(DialectType::Redshift)
                    | Some(DialectType::Oracle)
                    | Some(DialectType::Exasol) => {
                        self.write_keyword("SMALLINT");
                    }
                    Some(DialectType::Teradata) => {
                        // Teradata uses BYTEINT for smallest integer
                        self.write_keyword("BYTEINT");
                    }
                    Some(DialectType::Dremio) => {
                        // Dremio maps TINYINT to INT
                        self.write_keyword("INT");
                    }
                    _ => {
                        self.write_keyword("TINYINT");
                    }
                }
                if let Some(n) = length {
                    if !matches!(self.config.dialect, Some(DialectType::Dremio)) {
                        self.write(&format!("({})", n));
                    }
                }
            }
            DataType::SmallInt { length } => {
                // Dremio maps SMALLINT to INT, SQLite maps SMALLINT to INTEGER
                match self.config.dialect {
                    Some(DialectType::Dremio) => {
                        self.write_keyword("INT");
                    }
                    Some(DialectType::SQLite) => {
                        self.write_keyword("INTEGER");
                    }
                    _ => {
                        self.write_keyword("SMALLINT");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::Int {
                length,
                integer_spelling,
            } => {
                // BigQuery uses INT64 for INT
                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
                    self.write_keyword("INT64");
                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
                    self.write("Int32");
                } else {
                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
                    let use_integer = match self.config.dialect {
                        Some(DialectType::TSQL)
                        | Some(DialectType::Fabric)
                        | Some(DialectType::Presto)
                        | Some(DialectType::Trino)
                        | Some(DialectType::SQLite)
                        | Some(DialectType::Redshift) => true,
                        // Databricks preserves the original spelling
                        Some(DialectType::Databricks) => *integer_spelling,
                        _ => false,
                    };
                    if use_integer {
                        self.write_keyword("INTEGER");
                    } else {
                        self.write_keyword("INT");
                    }
                    if let Some(n) = length {
                        self.write(&format!("({})", n));
                    }
                }
            }
            DataType::BigInt { length } => {
                // Dialect-specific bigint type mappings
                match self.config.dialect {
                    Some(DialectType::Oracle) => {
                        // Oracle doesn't have BIGINT, uses NUMBER or INT
                        self.write_keyword("NUMBER(19)");
                    }
                    _ => {
                        self.write_keyword("BIGINT");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::Float {
                precision,
                scale,
                real_spelling,
            } => {
                // Dialect-specific float type mappings
                // If real_spelling is true, preserve REAL; otherwise use dialect default
                // Spark/Hive don't support REAL, always use FLOAT
                if *real_spelling
                    && !matches!(
                        self.config.dialect,
                        Some(DialectType::Spark)
                            | Some(DialectType::Databricks)
                            | Some(DialectType::Hive)
                            | Some(DialectType::Snowflake)
                            | Some(DialectType::MySQL)
                            | Some(DialectType::BigQuery)
                    )
                {
                    self.write_keyword("REAL")
                } else {
                    match self.config.dialect {
                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
                        _ => self.write_keyword("FLOAT"),
                    }
                }
                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
                // Spark/Hive don't support FLOAT(precision)
                if !matches!(
                    self.config.dialect,
                    Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::Hive)
                        | Some(DialectType::Presto)
                        | Some(DialectType::Trino)
                ) {
                    if let Some(p) = precision {
                        self.write(&format!("({}", p));
                        if let Some(s) = scale {
                            self.write(&format!(", {})", s));
                        } else {
                            self.write(")");
                        }
                    }
                }
            }
            DataType::Double { precision, scale } => {
                // Dialect-specific double type mappings
                match self.config.dialect {
                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
                        self.write_keyword("FLOAT")
                    } // SQL Server/Fabric FLOAT is double
                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
                    Some(DialectType::PostgreSQL)
                    | Some(DialectType::Redshift)
                    | Some(DialectType::Teradata) => self.write_keyword("DOUBLE PRECISION"),
                    _ => self.write_keyword("DOUBLE"),
                }
                // MySQL supports DOUBLE(precision, scale)
                if let Some(p) = precision {
                    self.write(&format!("({}", p));
                    if let Some(s) = scale {
                        self.write(&format!(", {})", s));
                    } else {
                        self.write(")");
                    }
                }
            }
            DataType::Decimal { precision, scale } => {
                // Dialect-specific decimal type mappings
                match self.config.dialect {
                    Some(DialectType::ClickHouse) => {
                        self.write("Decimal");
                        if let Some(p) = precision {
                            self.write(&format!("({}", p));
                            if let Some(s) = scale {
                                self.write(&format!(", {}", s));
                            }
                            self.write(")");
                        }
                    }
                    Some(DialectType::Oracle) => {
                        // Oracle uses NUMBER instead of DECIMAL
                        self.write_keyword("NUMBER");
                        if let Some(p) = precision {
                            self.write(&format!("({}", p));
                            if let Some(s) = scale {
                                self.write(&format!(", {}", s));
                            }
                            self.write(")");
                        }
                    }
                    Some(DialectType::BigQuery) => {
                        // BigQuery uses NUMERIC instead of DECIMAL
                        self.write_keyword("NUMERIC");
                        if let Some(p) = precision {
                            self.write(&format!("({}", p));
                            if let Some(s) = scale {
                                self.write(&format!(", {}", s));
                            }
                            self.write(")");
                        }
                    }
                    _ => {
                        self.write_keyword("DECIMAL");
                        if let Some(p) = precision {
                            self.write(&format!("({}", p));
                            if let Some(s) = scale {
                                self.write(&format!(", {}", s));
                            }
                            self.write(")");
                        }
                    }
                }
            }
            DataType::Char { length } => {
                // Dialect-specific char type mappings
                match self.config.dialect {
                    Some(DialectType::DuckDB) => {
                        // DuckDB maps CHAR to TEXT
                        self.write_keyword("TEXT");
                    }
                    Some(DialectType::Dremio) => {
                        // Dremio maps CHAR to VARCHAR
                        self.write_keyword("VARCHAR");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    _ => {
                        self.write_keyword("CHAR");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::VarChar {
                length,
                parenthesized_length,
            } => {
                // Dialect-specific varchar type mappings
                match self.config.dialect {
                    Some(DialectType::Oracle) => {
                        self.write_keyword("VARCHAR2");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    Some(DialectType::DuckDB) => {
                        // DuckDB maps VARCHAR to TEXT
                        self.write_keyword("TEXT");
                    }
                    Some(DialectType::SQLite) => {
                        // SQLite maps VARCHAR to TEXT, preserving length
                        self.write_keyword("TEXT");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    Some(DialectType::MySQL) if length.is_none() => {
                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
                        self.write_keyword("TEXT");
                    }
                    Some(DialectType::Hive)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                        if length.is_none() =>
                    {
                        // Hive/Spark/Databricks: VARCHAR without length → STRING
                        self.write_keyword("STRING");
                    }
                    _ => {
                        self.write_keyword("VARCHAR");
                        if let Some(n) = length {
                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
                            if *parenthesized_length {
                                self.write(&format!("(({}))", n));
                            } else {
                                self.write(&format!("({})", n));
                            }
                        }
                    }
                }
            }
            DataType::Text => {
                // Dialect-specific text type mappings
                match self.config.dialect {
                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
                    Some(DialectType::Snowflake) | Some(DialectType::Dremio) => {
                        self.write_keyword("VARCHAR")
                    }
                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
                    Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
                    Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR"),
                    Some(DialectType::StarRocks) => self.write_keyword("STRING"),
                    _ => self.write_keyword("TEXT"),
                }
            }
            DataType::String { length } => {
                // STRING type with optional length (BigQuery STRING(n))
                match self.config.dialect {
                    Some(DialectType::ClickHouse) => {
                        // ClickHouse uses String with specific casing
                        self.write("String");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    Some(DialectType::BigQuery) => {
                        self.write_keyword("STRING");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
                        if let Some(n) = length {
                            self.write_keyword("VARCHAR");
                            self.write(&format!("({})", n));
                        } else {
                            self.write_keyword("TEXT");
                        }
                    }
                    Some(DialectType::MySQL)
                    | Some(DialectType::StarRocks)
                    | Some(DialectType::Doris) => {
                        // MySQL doesn't have STRING - use VARCHAR or TEXT
                        if let Some(n) = length {
                            self.write_keyword("VARCHAR");
                            self.write(&format!("({})", n));
                        } else {
                            self.write_keyword("TEXT");
                        }
                    }
                    Some(DialectType::TSQL) => {
                        // TSQL doesn't have STRING - use VARCHAR or NVARCHAR(MAX)
                        if let Some(n) = length {
                            self.write_keyword("VARCHAR");
                            self.write(&format!("({})", n));
                        } else {
                            self.write_keyword("NVARCHAR(MAX)");
                        }
                    }
                    Some(DialectType::DuckDB) => {
                        // DuckDB uses TEXT for string types
                        self.write_keyword("TEXT");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
                        // Presto/Trino use VARCHAR for string types
                        self.write_keyword("VARCHAR");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    _ => {
                        // Default: output STRING with optional length
                        self.write_keyword("STRING");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::Binary { length } => {
                // Dialect-specific binary type mappings
                match self.config.dialect {
                    Some(DialectType::PostgreSQL) => {
                        self.write_keyword("BYTEA");
                    }
                    Some(DialectType::Redshift) => {
                        self.write_keyword("VARBYTE");
                    }
                    Some(DialectType::DuckDB) => {
                        // DuckDB maps BINARY to BLOB
                        self.write_keyword("BLOB");
                    }
                    Some(DialectType::Dremio) => {
                        // Dremio maps BINARY to VARBINARY
                        self.write_keyword("VARBINARY");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    _ => {
                        self.write_keyword("BINARY");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::VarBinary { length } => {
                // Dialect-specific varbinary type mappings
                match self.config.dialect {
                    Some(DialectType::PostgreSQL) => {
                        self.write_keyword("BYTEA");
                    }
                    Some(DialectType::Redshift) => {
                        self.write_keyword("VARBYTE");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                    Some(DialectType::DuckDB) => {
                        // DuckDB maps VARBINARY to BLOB
                        self.write_keyword("BLOB");
                    }
                    Some(DialectType::Exasol) => {
                        // Exasol maps VARBINARY to VARCHAR
                        self.write_keyword("VARCHAR");
                    }
                    Some(DialectType::Spark)
                    | Some(DialectType::Hive)
                    | Some(DialectType::Databricks) => {
                        // Spark/Hive use BINARY instead of VARBINARY
                        self.write_keyword("BINARY");
                    }
                    _ => {
                        self.write_keyword("VARBINARY");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::Blob => {
                // Dialect-specific blob type mappings
                match self.config.dialect {
                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
                        self.write_keyword("VARBINARY")
                    }
                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
                    Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
                    Some(DialectType::DuckDB) => {
                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
                        self.write_keyword("VARBINARY");
                    }
                    Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
                    Some(DialectType::ClickHouse) => self.write("Nullable(String)"),
                    _ => self.write_keyword("BLOB"),
                }
            }
            DataType::Bit { length } => {
                // Dialect-specific bit type mappings
                match self.config.dialect {
                    Some(DialectType::Dremio)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive)
                    | Some(DialectType::Snowflake)
                    | Some(DialectType::BigQuery)
                    | Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::ClickHouse)
                    | Some(DialectType::Redshift) => {
                        // These dialects don't support BIT type, use BOOLEAN
                        self.write_keyword("BOOLEAN");
                    }
                    _ => {
                        self.write_keyword("BIT");
                        if let Some(n) = length {
                            self.write(&format!("({})", n));
                        }
                    }
                }
            }
            DataType::VarBit { length } => {
                self.write_keyword("VARBIT");
                if let Some(n) = length {
                    self.write(&format!("({})", n));
                }
            }
            DataType::Date => self.write_keyword("DATE"),
            DataType::Time {
                precision,
                timezone,
            } => {
                if *timezone {
                    // Dialect-specific TIME WITH TIME ZONE output
                    match self.config.dialect {
                        Some(DialectType::DuckDB) => {
                            // DuckDB: TIMETZ (drops precision)
                            self.write_keyword("TIMETZ");
                        }
                        Some(DialectType::PostgreSQL) => {
                            // PostgreSQL: TIMETZ or TIMETZ(p)
                            self.write_keyword("TIMETZ");
                            if let Some(p) = precision {
                                self.write(&format!("({})", p));
                            }
                        }
                        _ => {
                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
                            self.write_keyword("TIME");
                            if let Some(p) = precision {
                                self.write(&format!("({})", p));
                            }
                            self.write_keyword(" WITH TIME ZONE");
                        }
                    }
                } else {
                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
                    if matches!(
                        self.config.dialect,
                        Some(DialectType::Spark)
                            | Some(DialectType::Databricks)
                            | Some(DialectType::Hive)
                    ) {
                        self.write_keyword("TIMESTAMP");
                    } else {
                        self.write_keyword("TIME");
                        if let Some(p) = precision {
                            self.write(&format!("({})", p));
                        }
                    }
                }
            }
            DataType::Timestamp {
                precision,
                timezone,
            } => {
                // Dialect-specific timestamp type mappings
                match self.config.dialect {
                    Some(DialectType::ClickHouse) => {
                        self.write("DateTime");
                        if let Some(p) = precision {
                            self.write(&format!("({})", p));
                        }
                    }
                    Some(DialectType::TSQL) => {
                        if *timezone {
                            self.write_keyword("DATETIMEOFFSET");
                        } else {
                            self.write_keyword("DATETIME2");
                        }
                        if let Some(p) = precision {
                            self.write(&format!("({})", p));
                        }
                    }
                    Some(DialectType::MySQL) => {
                        // MySQL uses TIMESTAMP for both cases
                        // MySQL's TIMESTAMP has implicit timezone behavior
                        self.write_keyword("TIMESTAMP");
                        if let Some(p) = precision {
                            self.write(&format!("({})", p));
                        }
                    }
                    Some(DialectType::BigQuery) => {
                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
                        if *timezone {
                            self.write_keyword("TIMESTAMP");
                        } else {
                            self.write_keyword("DATETIME");
                        }
                    }
                    Some(DialectType::DuckDB) => {
                        // DuckDB: TIMESTAMPTZ shorthand
                        if *timezone {
                            self.write_keyword("TIMESTAMPTZ");
                        } else {
                            self.write_keyword("TIMESTAMP");
                            if let Some(p) = precision {
                                self.write(&format!("({})", p));
                            }
                        }
                    }
                    _ => {
                        if *timezone && !self.config.tz_to_with_time_zone {
                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
                            self.write_keyword("TIMESTAMPTZ");
                            if let Some(p) = precision {
                                self.write(&format!("({})", p));
                            }
                        } else {
                            self.write_keyword("TIMESTAMP");
                            if let Some(p) = precision {
                                self.write(&format!("({})", p));
                            }
                            if *timezone {
                                self.write_space();
                                self.write_keyword("WITH TIME ZONE");
                            }
                        }
                    }
                }
            }
            DataType::Interval { unit, to } => {
                self.write_keyword("INTERVAL");
                if let Some(u) = unit {
                    self.write_space();
                    self.write_keyword(u);
                }
                // Handle range intervals like DAY TO HOUR
                if let Some(t) = to {
                    self.write_space();
                    self.write_keyword("TO");
                    self.write_space();
                    self.write_keyword(t);
                }
            }
            DataType::Json => {
                // Dialect-specific JSON type mappings
                match self.config.dialect {
                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
                    _ => self.write_keyword("JSON"),
                }
            }
            DataType::JsonB => {
                // JSONB is PostgreSQL specific, but Doris also supports it
                match self.config.dialect {
                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
                }
            }
            DataType::Uuid => {
                // Dialect-specific UUID type mappings
                match self.config.dialect {
                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
                    Some(DialectType::BigQuery)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
                    _ => self.write_keyword("UUID"),
                }
            }
            DataType::Array {
                element_type,
                dimension,
            } => {
                // Dialect-specific array syntax
                match self.config.dialect {
                    Some(DialectType::PostgreSQL)
                    | Some(DialectType::Redshift)
                    | Some(DialectType::DuckDB) => {
                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
                        self.generate_data_type(element_type)?;
                        if let Some(dim) = dimension {
                            self.write(&format!("[{}]", dim));
                        } else {
                            self.write("[]");
                        }
                    }
                    Some(DialectType::BigQuery) => {
                        self.write_keyword("ARRAY<");
                        self.generate_data_type(element_type)?;
                        self.write(">");
                    }
                    Some(DialectType::Snowflake)
                    | Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::ClickHouse) => {
                        // These dialects use Array(TYPE) parentheses syntax
                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
                            self.write("Array(");
                        } else {
                            self.write_keyword("ARRAY(");
                        }
                        self.generate_data_type(element_type)?;
                        self.write(")");
                    }
                    Some(DialectType::TSQL)
                    | Some(DialectType::MySQL)
                    | Some(DialectType::Oracle) => {
                        // These dialects don't have native array types
                        // Fall back to JSON or use native workarounds
                        match self.config.dialect {
                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
                            _ => self.write_keyword("JSON"),
                        }
                    }
                    _ => {
                        // Default: use angle bracket syntax (ARRAY<T>)
                        self.write_keyword("ARRAY<");
                        self.generate_data_type(element_type)?;
                        self.write(">");
                    }
                }
            }
            DataType::List { element_type } => {
                // Materialize: element_type LIST (postfix syntax)
                self.generate_data_type(element_type)?;
                self.write_keyword(" LIST");
            }
            DataType::Map {
                key_type,
                value_type,
            } => {
                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
                match self.config.dialect {
                    Some(DialectType::Materialize) => {
                        // Materialize: MAP[key_type => value_type]
                        self.write_keyword("MAP[");
                        self.generate_data_type(key_type)?;
                        self.write(" => ");
                        self.generate_data_type(value_type)?;
                        self.write("]");
                    }
                    Some(DialectType::Snowflake)
                    | Some(DialectType::RisingWave)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::Presto)
                    | Some(DialectType::Trino)
                    | Some(DialectType::Athena) => {
                        self.write_keyword("MAP(");
                        self.generate_data_type(key_type)?;
                        self.write(", ");
                        self.generate_data_type(value_type)?;
                        self.write(")");
                    }
                    _ => {
                        self.write_keyword("MAP<");
                        self.generate_data_type(key_type)?;
                        self.write(", ");
                        self.generate_data_type(value_type)?;
                        self.write(">");
                    }
                }
            }
            DataType::Vector {
                element_type,
                dimension,
            } => {
                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
                    // SingleStore format: VECTOR(dimension, type_alias)
                    self.write_keyword("VECTOR(");
                    if let Some(dim) = dimension {
                        self.write(&dim.to_string());
                    }
                    // Map type back to SingleStore alias
                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
                        DataType::TinyInt { .. } => Some("I8"),
                        DataType::SmallInt { .. } => Some("I16"),
                        DataType::Int { .. } => Some("I32"),
                        DataType::BigInt { .. } => Some("I64"),
                        DataType::Float { .. } => Some("F32"),
                        DataType::Double { .. } => Some("F64"),
                        _ => None,
                    });
                    if let Some(alias) = type_alias {
                        if dimension.is_some() {
                            self.write(", ");
                        }
                        self.write(alias);
                    }
                    self.write(")");
                } else {
                    // Snowflake format: VECTOR(type, dimension)
                    self.write_keyword("VECTOR(");
                    if let Some(ref et) = element_type {
                        self.generate_data_type(et)?;
                        if dimension.is_some() {
                            self.write(", ");
                        }
                    }
                    if let Some(dim) = dimension {
                        self.write(&dim.to_string());
                    }
                    self.write(")");
                }
            }
            DataType::Object { fields, modifier } => {
                self.write_keyword("OBJECT(");
                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write(name);
                    self.write(" ");
                    self.generate_data_type(dt)?;
                    if *not_null {
                        self.write_keyword(" NOT NULL");
                    }
                }
                self.write(")");
                if let Some(mod_str) = modifier {
                    self.write(" ");
                    self.write_keyword(mod_str);
                }
            }
            DataType::Struct { fields, nested } => {
                // Dialect-specific struct type mappings
                match self.config.dialect {
                    Some(DialectType::Snowflake) => {
                        // Snowflake maps STRUCT to OBJECT
                        self.write_keyword("OBJECT(");
                        for (i, field) in fields.iter().enumerate() {
                            if i > 0 {
                                self.write(", ");
                            }
                            if !field.name.is_empty() {
                                self.write(&field.name);
                                self.write(" ");
                            }
                            self.generate_data_type(&field.data_type)?;
                        }
                        self.write(")");
                    }
                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
                        // Presto/Trino use ROW(name TYPE, ...) syntax
                        self.write_keyword("ROW(");
                        for (i, field) in fields.iter().enumerate() {
                            if i > 0 {
                                self.write(", ");
                            }
                            if !field.name.is_empty() {
                                self.write(&field.name);
                                self.write(" ");
                            }
                            self.generate_data_type(&field.data_type)?;
                        }
                        self.write(")");
                    }
                    Some(DialectType::DuckDB) => {
                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
                        self.write_keyword("STRUCT(");
                        for (i, field) in fields.iter().enumerate() {
                            if i > 0 {
                                self.write(", ");
                            }
                            if !field.name.is_empty() {
                                self.write(&field.name);
                                self.write(" ");
                            }
                            self.generate_data_type(&field.data_type)?;
                        }
                        self.write(")");
                    }
                    Some(DialectType::SingleStore) => {
                        // SingleStore uses RECORD(name TYPE, ...) for struct types
                        self.write_keyword("RECORD(");
                        for (i, field) in fields.iter().enumerate() {
                            if i > 0 {
                                self.write(", ");
                            }
                            if !field.name.is_empty() {
                                self.write(&field.name);
                                self.write(" ");
                            }
                            self.generate_data_type(&field.data_type)?;
                        }
                        self.write(")");
                    }
                    _ => {
                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
                        let force_angle_brackets = matches!(
                            self.config.dialect,
                            Some(DialectType::Hive)
                                | Some(DialectType::Spark)
                                | Some(DialectType::Databricks)
                        );
                        if *nested && !force_angle_brackets {
                            self.write_keyword("STRUCT(");
                            for (i, field) in fields.iter().enumerate() {
                                if i > 0 {
                                    self.write(", ");
                                }
                                if !field.name.is_empty() {
                                    self.write(&field.name);
                                    self.write(" ");
                                }
                                self.generate_data_type(&field.data_type)?;
                            }
                            self.write(")");
                        } else {
                            self.write_keyword("STRUCT<");
                            for (i, field) in fields.iter().enumerate() {
                                if i > 0 {
                                    self.write(", ");
                                }
                                if !field.name.is_empty() {
                                    // Named field: name TYPE (with configurable separator for Hive)
                                    self.write(&field.name);
                                    self.write(self.config.struct_field_sep);
                                }
                                // For anonymous fields, just output the type
                                self.generate_data_type(&field.data_type)?;
                                // Spark/Databricks: Output COMMENT clause if present
                                if let Some(comment) = &field.comment {
                                    self.write(" COMMENT '");
                                    self.write(comment);
                                    self.write("'");
                                }
                                // BigQuery: Output OPTIONS clause if present
                                if !field.options.is_empty() {
                                    self.write(" ");
                                    self.generate_options_clause(&field.options)?;
                                }
                            }
                            self.write(">");
                        }
                    }
                }
            }
            DataType::Enum {
                values,
                assignments,
            } => {
                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
                // ClickHouse: Enum('hello' = 1, 'world' = 2)
                if self.config.dialect == Some(DialectType::ClickHouse) {
                    self.write("Enum(");
                } else {
                    self.write_keyword("ENUM(");
                }
                for (i, val) in values.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write("'");
                    self.write(val);
                    self.write("'");
                    if let Some(Some(assignment)) = assignments.get(i) {
                        self.write(" = ");
                        self.write(assignment);
                    }
                }
                self.write(")");
            }
            DataType::Set { values } => {
                // MySQL SET type: SET('a', 'b', 'c')
                self.write_keyword("SET(");
                for (i, val) in values.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write("'");
                    self.write(val);
                    self.write("'");
                }
                self.write(")");
            }
            DataType::Union { fields } => {
                // DuckDB UNION type: UNION(num INT, str TEXT)
                self.write_keyword("UNION(");
                for (i, (name, dt)) in fields.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    if !name.is_empty() {
                        self.write(name);
                        self.write(" ");
                    }
                    self.generate_data_type(dt)?;
                }
                self.write(")");
            }
            DataType::Nullable { inner } => {
                // ClickHouse: Nullable(T), other dialects: just the inner type
                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
                    self.write("Nullable(");
                    self.generate_data_type(inner)?;
                    self.write(")");
                } else {
                    // Map ClickHouse-specific custom type names to standard types
                    match inner.as_ref() {
                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
                            self.generate_data_type(&DataType::Timestamp {
                                precision: None,
                                timezone: false,
                            })?;
                        }
                        _ => {
                            self.generate_data_type(inner)?;
                        }
                    }
                }
            }
            DataType::Custom { name } => {
                // Handle dialect-specific type transformations
                let name_upper = name.to_uppercase();
                match self.config.dialect {
                    Some(DialectType::ClickHouse) => {
                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
                            (name_upper[..idx].to_string(), &name[idx..])
                        } else {
                            (name_upper.clone(), "")
                        };
                        let mapped = match base_upper.as_str() {
                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
                            "DATETIME64" => "DateTime64",
                            "DATE32" => "Date32",
                            "INT" => "Int32",
                            "MEDIUMINT" => "Int32",
                            "INT8" => "Int8",
                            "INT16" => "Int16",
                            "INT32" => "Int32",
                            "INT64" => "Int64",
                            "INT128" => "Int128",
                            "INT256" => "Int256",
                            "UINT8" => "UInt8",
                            "UINT16" => "UInt16",
                            "UINT32" => "UInt32",
                            "UINT64" => "UInt64",
                            "UINT128" => "UInt128",
                            "UINT256" => "UInt256",
                            "FLOAT32" => "Float32",
                            "FLOAT64" => "Float64",
                            "DECIMAL32" => "Decimal32",
                            "DECIMAL64" => "Decimal64",
                            "DECIMAL128" => "Decimal128",
                            "DECIMAL256" => "Decimal256",
                            "ENUM" => "Enum",
                            "ENUM8" => "Enum8",
                            "ENUM16" => "Enum16",
                            "FIXEDSTRING" => "FixedString",
                            "NESTED" => "Nested",
                            "LOWCARDINALITY" => "LowCardinality",
                            "NULLABLE" => "Nullable",
                            "IPV4" => "IPv4",
                            "IPV6" => "IPv6",
                            "POINT" => "Point",
                            "RING" => "Ring",
                            "LINESTRING" => "LineString",
                            "MULTILINESTRING" => "MultiLineString",
                            "POLYGON" => "Polygon",
                            "MULTIPOLYGON" => "MultiPolygon",
                            "AGGREGATEFUNCTION" => "AggregateFunction",
                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
                            "DYNAMIC" => "Dynamic",
                            _ => "",
                        };
                        if mapped.is_empty() {
                            self.write(name);
                        } else {
                            self.write(mapped);
                            self.write(suffix);
                        }
                    }
                    Some(DialectType::MySQL)
                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
                    {
                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
                        self.write_keyword("TIMESTAMP");
                    }
                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
                        self.write_keyword("SQL_VARIANT");
                    }
                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
                        self.write_keyword("DECIMAL(38, 5)");
                    }
                    Some(DialectType::Exasol) => {
                        // Exasol type mappings for custom types
                        match name_upper.as_str() {
                            // Binary types → VARCHAR
                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
                            // Integer types
                            "MEDIUMINT" => self.write_keyword("INT"),
                            // Decimal types → DECIMAL
                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
                                self.write_keyword("DECIMAL")
                            }
                            // Timestamp types
                            "DATETIME" => self.write_keyword("TIMESTAMP"),
                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
                            _ => self.write(name),
                        }
                    }
                    Some(DialectType::Dremio) => {
                        // Dremio type mappings for custom types
                        match name_upper.as_str() {
                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
                            "ARRAY" => self.write_keyword("LIST"),
                            "NCHAR" => self.write_keyword("VARCHAR"),
                            _ => self.write(name),
                        }
                    }
                    // Map dialect-specific custom types to standard SQL types for other dialects
                    _ => {
                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
                            (name_upper[..idx].to_string(), Some(&name[idx..]))
                        } else {
                            (name_upper.clone(), None)
                        };

                        match base_upper.as_str() {
                            "INT64"
                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
                            {
                                self.write_keyword("BIGINT");
                            }
                            "FLOAT64"
                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
                            {
                                self.write_keyword("DOUBLE");
                            }
                            "BOOL"
                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
                            {
                                self.write_keyword("BOOLEAN");
                            }
                            "BYTES"
                                if matches!(
                                    self.config.dialect,
                                    Some(DialectType::Spark)
                                        | Some(DialectType::Hive)
                                        | Some(DialectType::Databricks)
                                ) =>
                            {
                                self.write_keyword("BINARY");
                            }
                            "BYTES"
                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
                            {
                                self.write_keyword("VARBINARY");
                            }
                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
                            "DATETIME2" | "SMALLDATETIME"
                                if !matches!(
                                    self.config.dialect,
                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                                ) =>
                            {
                                // PostgreSQL preserves precision, others don't
                                if matches!(
                                    self.config.dialect,
                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
                                ) {
                                    self.write_keyword("TIMESTAMP");
                                    if let Some(args) = _args_str {
                                        self.write(args);
                                    }
                                } else {
                                    self.write_keyword("TIMESTAMP");
                                }
                            }
                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
                            "DATETIMEOFFSET"
                                if !matches!(
                                    self.config.dialect,
                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                                ) =>
                            {
                                if matches!(
                                    self.config.dialect,
                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
                                ) {
                                    self.write_keyword("TIMESTAMPTZ");
                                    if let Some(args) = _args_str {
                                        self.write(args);
                                    }
                                } else {
                                    self.write_keyword("TIMESTAMPTZ");
                                }
                            }
                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
                            "UNIQUEIDENTIFIER"
                                if !matches!(
                                    self.config.dialect,
                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                                ) =>
                            {
                                match self.config.dialect {
                                    Some(DialectType::Spark)
                                    | Some(DialectType::Databricks)
                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
                                    _ => self.write_keyword("UUID"),
                                }
                            }
                            // TSQL BIT -> BOOLEAN for most dialects
                            "BIT"
                                if !matches!(
                                    self.config.dialect,
                                    Some(DialectType::TSQL)
                                        | Some(DialectType::Fabric)
                                        | Some(DialectType::PostgreSQL)
                                        | Some(DialectType::MySQL)
                                        | Some(DialectType::DuckDB)
                                ) =>
                            {
                                self.write_keyword("BOOLEAN");
                            }
                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
                            "NVARCHAR"
                                if !matches!(
                                    self.config.dialect,
                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                                ) =>
                            {
                                match self.config.dialect {
                                    Some(DialectType::SQLite) => {
                                        self.write_keyword("TEXT");
                                        if let Some(args) = _args_str {
                                            self.write(args);
                                        }
                                    }
                                    Some(DialectType::Spark)
                                    | Some(DialectType::Databricks)
                                    | Some(DialectType::Hive) => {
                                        if let Some(args) = _args_str {
                                            self.write_keyword("VARCHAR");
                                            self.write(args);
                                        } else {
                                            self.write_keyword("VARCHAR(30)");
                                        }
                                    }
                                    _ => {
                                        self.write_keyword("VARCHAR");
                                        if let Some(args) = _args_str {
                                            self.write(args);
                                        }
                                    }
                                }
                            }
                            // TSQL NCHAR -> CHAR
                            "NCHAR"
                                if !matches!(
                                    self.config.dialect,
                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                                ) =>
                            {
                                match self.config.dialect {
                                    Some(DialectType::Spark)
                                    | Some(DialectType::Databricks)
                                    | Some(DialectType::Hive) => {
                                        if let Some(args) = _args_str {
                                            self.write_keyword("CHAR");
                                            self.write(args);
                                        } else {
                                            self.write_keyword("CHAR(30)");
                                        }
                                    }
                                    _ => {
                                        self.write_keyword("CHAR");
                                        if let Some(args) = _args_str {
                                            self.write(args);
                                        }
                                    }
                                }
                            }
                            // MySQL text variant types -> map to appropriate target type
                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
                                Some(DialectType::MySQL)
                                | Some(DialectType::SingleStore)
                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
                                Some(DialectType::Spark)
                                | Some(DialectType::Databricks)
                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
                                Some(DialectType::Presto)
                                | Some(DialectType::Trino)
                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
                                Some(DialectType::Snowflake)
                                | Some(DialectType::Redshift)
                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
                                _ => self.write_keyword("TEXT"),
                            },
                            // MySQL blob variant types -> map to appropriate target type
                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
                                Some(DialectType::MySQL)
                                | Some(DialectType::SingleStore)
                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
                                Some(DialectType::Spark)
                                | Some(DialectType::Databricks)
                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
                                Some(DialectType::Presto)
                                | Some(DialectType::Trino)
                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
                                Some(DialectType::Snowflake)
                                | Some(DialectType::Redshift)
                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
                                _ => self.write_keyword("BLOB"),
                            },
                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
                            "LONGVARCHAR" => match self.config.dialect {
                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
                                _ => self.write_keyword("VARCHAR"),
                            },
                            _ => self.write(name),
                        }
                    }
                }
            }
            DataType::Geometry { subtype, srid } => {
                // Dialect-specific geometry type mappings
                match self.config.dialect {
                    Some(DialectType::MySQL) => {
                        // MySQL uses POINT SRID 4326 syntax for specific types
                        if let Some(sub) = subtype {
                            self.write_keyword(sub);
                            if let Some(s) = srid {
                                self.write(" SRID ");
                                self.write(&s.to_string());
                            }
                        } else {
                            self.write_keyword("GEOMETRY");
                        }
                    }
                    Some(DialectType::BigQuery) => {
                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
                        self.write_keyword("GEOGRAPHY");
                    }
                    Some(DialectType::Teradata) => {
                        // Teradata uses ST_GEOMETRY
                        self.write_keyword("ST_GEOMETRY");
                        if subtype.is_some() || srid.is_some() {
                            self.write("(");
                            if let Some(sub) = subtype {
                                self.write_keyword(sub);
                            }
                            if let Some(s) = srid {
                                if subtype.is_some() {
                                    self.write(", ");
                                }
                                self.write(&s.to_string());
                            }
                            self.write(")");
                        }
                    }
                    _ => {
                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
                        self.write_keyword("GEOMETRY");
                        if subtype.is_some() || srid.is_some() {
                            self.write("(");
                            if let Some(sub) = subtype {
                                self.write_keyword(sub);
                            }
                            if let Some(s) = srid {
                                if subtype.is_some() {
                                    self.write(", ");
                                }
                                self.write(&s.to_string());
                            }
                            self.write(")");
                        }
                    }
                }
            }
            DataType::Geography { subtype, srid } => {
                // Dialect-specific geography type mappings
                match self.config.dialect {
                    Some(DialectType::MySQL) => {
                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
                        if let Some(sub) = subtype {
                            self.write_keyword(sub);
                        } else {
                            self.write_keyword("GEOMETRY");
                        }
                        // Geography implies SRID 4326 (WGS84)
                        let effective_srid = srid.unwrap_or(4326);
                        self.write(" SRID ");
                        self.write(&effective_srid.to_string());
                    }
                    Some(DialectType::BigQuery) => {
                        // BigQuery uses simple GEOGRAPHY without parameters
                        self.write_keyword("GEOGRAPHY");
                    }
                    Some(DialectType::Snowflake) => {
                        // Snowflake uses GEOGRAPHY without parameters
                        self.write_keyword("GEOGRAPHY");
                    }
                    _ => {
                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
                        self.write_keyword("GEOGRAPHY");
                        if subtype.is_some() || srid.is_some() {
                            self.write("(");
                            if let Some(sub) = subtype {
                                self.write_keyword(sub);
                            }
                            if let Some(s) = srid {
                                if subtype.is_some() {
                                    self.write(", ");
                                }
                                self.write(&s.to_string());
                            }
                            self.write(")");
                        }
                    }
                }
            }
            DataType::CharacterSet { name } => {
                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
                self.write_keyword("CHAR CHARACTER SET ");
                self.write(name);
            }
            _ => self.write("UNKNOWN"),
        }
        Ok(())
    }

    // === Helper methods ===

    fn write(&mut self, s: &str) {
        self.output.push_str(s);
    }

    fn write_space(&mut self) {
        self.output.push(' ');
    }

    fn write_keyword(&mut self, keyword: &str) {
        if self.config.uppercase_keywords {
            self.output.push_str(keyword);
        } else {
            self.output.push_str(&keyword.to_lowercase());
        }
    }

    /// Write a function name respecting the normalize_functions config setting
    fn write_func_name(&mut self, name: &str) {
        let normalized = self.normalize_func_name(name);
        self.output.push_str(&normalized);
    }

    /// Convert strptime format string to Exasol format string
    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
    fn convert_strptime_to_exasol_format(format: &str) -> String {
        let mut result = String::new();
        let chars: Vec<char> = format.chars().collect();
        let mut i = 0;
        while i < chars.len() {
            if chars[i] == '%' && i + 1 < chars.len() {
                let spec = chars[i + 1];
                let exasol_spec = match spec {
                    'Y' => "YYYY",
                    'y' => "YY",
                    'm' => "MM",
                    'd' => "DD",
                    'H' => "HH",
                    'M' => "MI",
                    'S' => "SS",
                    'a' => "DY",    // abbreviated weekday name
                    'A' => "DAY",   // full weekday name
                    'b' => "MON",   // abbreviated month name
                    'B' => "MONTH", // full month name
                    'I' => "H12",   // 12-hour format
                    'u' => "ID",    // ISO weekday (1-7)
                    'V' => "IW",    // ISO week number
                    'G' => "IYYY",  // ISO year
                    'W' => "UW",    // Week number (Monday as first day)
                    'U' => "UW",    // Week number (Sunday as first day)
                    'z' => "Z",     // timezone offset
                    _ => {
                        // Unknown specifier, keep as-is
                        result.push('%');
                        result.push(spec);
                        i += 2;
                        continue;
                    }
                };
                result.push_str(exasol_spec);
                i += 2;
            } else {
                result.push(chars[i]);
                i += 1;
            }
        }
        result
    }

    /// Convert strptime format string to PostgreSQL/Redshift format string
    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
    fn convert_strptime_to_postgres_format(format: &str) -> String {
        let mut result = String::new();
        let chars: Vec<char> = format.chars().collect();
        let mut i = 0;
        while i < chars.len() {
            if chars[i] == '%' && i + 1 < chars.len() {
                let spec = chars[i + 1];
                let pg_spec = match spec {
                    'Y' => "YYYY",
                    'y' => "YY",
                    'm' => "MM",
                    'd' => "DD",
                    'H' => "HH24",
                    'I' => "HH12",
                    'M' => "MI",
                    'S' => "SS",
                    'f' => "US",      // microseconds
                    'u' => "D",       // day of week (1=Monday)
                    'j' => "DDD",     // day of year
                    'z' => "OF",      // UTC offset
                    'Z' => "TZ",      // timezone name
                    'A' => "TMDay",   // full weekday name
                    'a' => "TMDy",    // abbreviated weekday name
                    'b' => "TMMon",   // abbreviated month name
                    'B' => "TMMonth", // full month name
                    'U' => "WW",      // week number
                    _ => {
                        // Unknown specifier, keep as-is
                        result.push('%');
                        result.push(spec);
                        i += 2;
                        continue;
                    }
                };
                result.push_str(pg_spec);
                i += 2;
            } else {
                result.push(chars[i]);
                i += 1;
            }
        }
        result
    }

    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
        if self.config.limit_only_literals {
            if let Some(value) = Self::try_evaluate_constant(expr) {
                self.write(&value.to_string());
                return Ok(());
            }
        }
        self.generate_expression(expr)
    }

    /// Format a comment with proper spacing.
    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
    fn write_formatted_comment(&mut self, comment: &str) {
        // Normalize all comments to block comment format /* ... */
        // This matches Python sqlglot behavior which always outputs block comments
        let content = if let Some(inner) = comment
            .strip_prefix("/*")
            .and_then(|s| s.strip_suffix("*/"))
        {
            // Already block comment - extract inner content
            inner.trim()
        } else if let Some(rest) = comment.strip_prefix("--") {
            // Line comment - extract content after --
            rest.trim()
        } else {
            // Raw content (no delimiters)
            comment.trim()
        };
        self.output.push_str("/* ");
        self.output.push_str(content);
        self.output.push_str(" */");
    }

    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
    fn escape_block_for_single_quote(&self, block: &str) -> String {
        let escape_backslash = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Snowflake)
        );
        let mut escaped = String::with_capacity(block.len() + 4);
        for ch in block.chars() {
            if ch == '\'' {
                escaped.push('\\');
                escaped.push('\'');
            } else if escape_backslash && ch == '\\' {
                escaped.push('\\');
                escaped.push('\\');
            } else {
                escaped.push(ch);
            }
        }
        escaped
    }

    fn write_newline(&mut self) {
        self.output.push('\n');
    }

    fn write_indent(&mut self) {
        for _ in 0..self.indent_level {
            self.output.push_str(&self.config.indent);
        }
    }

    // === SQLGlot-style pretty printing helpers ===

    /// Returns the separator string for pretty printing.
    /// Check if the total length of arguments exceeds max_text_width.
    /// Used for dynamic line breaking in expressions() formatting.
    fn too_wide(&self, args: &[String]) -> bool {
        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
    }

    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
    /// In pretty mode: newline + indented keyword + newline + indented condition
    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
        if self.config.pretty {
            self.write_newline();
            self.write_indent();
            self.write_keyword(keyword);
            self.write_newline();
            self.indent_level += 1;
            self.write_indent();
            self.generate_expression(condition)?;
            self.indent_level -= 1;
        } else {
            self.write_space();
            self.write_keyword(keyword);
            self.write_space();
            self.generate_expression(condition)?;
        }
        Ok(())
    }

    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
    /// In pretty mode: each expression on new line with indentation
    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
        if exprs.is_empty() {
            return Ok(());
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
            self.write_keyword(keyword);
            self.write_newline();
            self.indent_level += 1;
            for (i, expr) in exprs.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_expression(expr)?;
            }
            self.indent_level -= 1;
        } else {
            self.write_space();
            self.write_keyword(keyword);
            self.write_space();
            for (i, expr) in exprs.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    /// Writes ORDER BY / SORT BY clause with Ordered expressions
    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
        if orderings.is_empty() {
            return Ok(());
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
            self.write_keyword(keyword);
            self.write_newline();
            self.indent_level += 1;
            for (i, ordered) in orderings.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_ordered(ordered)?;
            }
            self.indent_level -= 1;
        } else {
            self.write_space();
            self.write_keyword(keyword);
            self.write_space();
            for (i, ordered) in orderings.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_ordered(ordered)?;
            }
        }
        Ok(())
    }

    /// Writes WINDOW clause with named window definitions
    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
        if windows.is_empty() {
            return Ok(());
        }

        if self.config.pretty {
            self.write_newline();
            self.write_indent();
            self.write_keyword("WINDOW");
            self.write_newline();
            self.indent_level += 1;
            for (i, named_window) in windows.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_identifier(&named_window.name)?;
                self.write_space();
                self.write_keyword("AS");
                self.write(" (");
                self.generate_over(&named_window.spec)?;
                self.write(")");
            }
            self.indent_level -= 1;
        } else {
            self.write_space();
            self.write_keyword("WINDOW");
            self.write_space();
            for (i, named_window) in windows.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_identifier(&named_window.name)?;
                self.write_space();
                self.write_keyword("AS");
                self.write(" (");
                self.generate_over(&named_window.spec)?;
                self.write(")");
            }
        }
        Ok(())
    }

    // === BATCH-GENERATED STUB METHODS (481 variants) ===
    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
        // AI_AGG(this, expression)
        self.write_keyword("AI_AGG");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
        // AI_CLASSIFY(input, [categories], [config])
        self.write_keyword("AI_CLASSIFY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(categories) = &e.categories {
            self.write(", ");
            self.generate_expression(categories)?;
        }
        if let Some(config) = &e.config {
            self.write(", ");
            self.generate_expression(config)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
        self.write_keyword("ADD");
        self.write_space();
        if e.exists {
            self.write_keyword("IF NOT EXISTS");
            self.write_space();
        }
        self.generate_expression(&e.this)?;
        if let Some(location) = &e.location {
            self.write_space();
            self.generate_expression(location)?;
        }
        Ok(())
    }

    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
        self.write_keyword("ALGORITHM");
        self.write("=");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("AS");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
        self.write_keyword("ALLOWED_VALUES");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
        // Python: complex logic based on dtype, default, comment, visible, etc.
        self.write_keyword("ALTER COLUMN");
        self.write_space();
        self.generate_expression(&e.this)?;

        if let Some(dtype) = &e.dtype {
            self.write_space();
            self.write_keyword("SET DATA TYPE");
            self.write_space();
            self.generate_expression(dtype)?;
            if let Some(collate) = &e.collate {
                self.write_space();
                self.write_keyword("COLLATE");
                self.write_space();
                self.generate_expression(collate)?;
            }
            if let Some(using) = &e.using {
                self.write_space();
                self.write_keyword("USING");
                self.write_space();
                self.generate_expression(using)?;
            }
        } else if let Some(default) = &e.default {
            self.write_space();
            self.write_keyword("SET DEFAULT");
            self.write_space();
            self.generate_expression(default)?;
        } else if let Some(comment) = &e.comment {
            self.write_space();
            self.write_keyword("COMMENT");
            self.write_space();
            self.generate_expression(comment)?;
        } else if let Some(drop) = &e.drop {
            self.write_space();
            self.write_keyword("DROP");
            self.write_space();
            self.generate_expression(drop)?;
        } else if let Some(visible) = &e.visible {
            self.write_space();
            self.generate_expression(visible)?;
        } else if let Some(rename_to) = &e.rename_to {
            self.write_space();
            self.write_keyword("RENAME TO");
            self.write_space();
            self.generate_expression(rename_to)?;
        } else if let Some(allow_null) = &e.allow_null {
            self.write_space();
            self.generate_expression(allow_null)?;
        }
        Ok(())
    }

    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
        self.write_keyword("ALTER SESSION");
        self.write_space();
        if e.unset.is_some() {
            self.write_keyword("UNSET");
        } else {
            self.write_keyword("SET");
        }
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
        self.write_keyword("SET");

        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
        if let Some(opt) = &e.option {
            self.write_space();
            self.generate_expression(opt)?;
        }

        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
        // Check if expressions look like property assignments
        if !e.expressions.is_empty() {
            // Check if this looks like property assignments (for SET PROPERTIES)
            let is_properties = e
                .expressions
                .iter()
                .any(|expr| matches!(expr, Expression::Eq(_)));
            if is_properties && e.option.is_none() {
                self.write_space();
                self.write_keyword("PROPERTIES");
            }
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }

        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
        if let Some(file_format) = &e.file_format {
            self.write(" ");
            self.write_keyword("STAGE_FILE_FORMAT");
            self.write(" = (");
            self.generate_space_separated_properties(file_format)?;
            self.write(")");
        }

        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
        if let Some(copy_options) = &e.copy_options {
            self.write(" ");
            self.write_keyword("STAGE_COPY_OPTIONS");
            self.write(" = (");
            self.generate_space_separated_properties(copy_options)?;
            self.write(")");
        }

        // Generate TAG ...
        if let Some(tag) = &e.tag {
            self.write(" ");
            self.write_keyword("TAG");
            self.write(" ");
            self.generate_expression(tag)?;
        }

        Ok(())
    }

    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Tuple(t) => {
                for (i, prop) in t.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(" ");
                    }
                    self.generate_expression(prop)?;
                }
            }
            _ => {
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
        self.write_keyword("ALTER");
        if e.compound.is_some() {
            self.write_space();
            self.write_keyword("COMPOUND");
        }
        self.write_space();
        self.write_keyword("SORTKEY");
        self.write_space();
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        } else if !e.expressions.is_empty() {
            self.write("(");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
        self.write_keyword("ANALYZE");
        if !e.options.is_empty() {
            self.write_space();
            for (i, opt) in e.options.iter().enumerate() {
                if i > 0 {
                    self.write_space();
                }
                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
                if let Expression::Identifier(id) = opt {
                    self.write_keyword(&id.name);
                } else {
                    self.generate_expression(opt)?;
                }
            }
        }
        if let Some(kind) = &e.kind {
            self.write_space();
            self.write_keyword(kind);
        }
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
        if !e.columns.is_empty() {
            self.write("(");
            for (i, col) in e.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(col);
            }
            self.write(")");
        }
        if let Some(partition) = &e.partition {
            self.write_space();
            self.generate_expression(partition)?;
        }
        if let Some(mode) = &e.mode {
            self.write_space();
            self.generate_expression(mode)?;
        }
        if let Some(expression) = &e.expression {
            self.write_space();
            self.generate_expression(expression)?;
        }
        if !e.properties.is_empty() {
            self.write_space();
            self.write_keyword(self.config.with_properties_prefix);
            self.write(" (");
            for (i, prop) in e.properties.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(prop)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
        // Python: return f"DELETE{kind} STATISTICS"
        self.write_keyword("DELETE");
        if let Some(kind) = &e.kind {
            self.write_space();
            self.write_keyword(kind);
        }
        self.write_space();
        self.write_keyword("STATISTICS");
        Ok(())
    }

    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
        if let Expression::Identifier(id) = e.this.as_ref() {
            self.write_keyword(&id.name);
        } else {
            self.generate_expression(&e.this)?;
        }
        self.write_space();
        self.write_keyword("HISTOGRAM ON");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        if let Some(expression) = &e.expression {
            self.write_space();
            self.generate_expression(expression)?;
        }
        if let Some(update_options) = &e.update_options {
            self.write_space();
            self.generate_expression(update_options)?;
            self.write_space();
            self.write_keyword("UPDATE");
        }
        Ok(())
    }

    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
        // Python: return f"LIST CHAINED ROWS{inner_expression}"
        self.write_keyword("LIST CHAINED ROWS");
        if let Some(expression) = &e.expression {
            self.write_space();
            self.write_keyword("INTO");
            self.write_space();
            self.generate_expression(expression)?;
        }
        Ok(())
    }

    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
        // Python: return f"SAMPLE {sample} {kind}"
        self.write_keyword("SAMPLE");
        self.write_space();
        if let Some(sample) = &e.sample {
            self.generate_expression(sample)?;
            self.write_space();
        }
        self.write_keyword(&e.kind);
        Ok(())
    }

    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
        self.write_keyword(&e.kind);
        if let Some(option) = &e.option {
            self.write_space();
            self.generate_expression(option)?;
        }
        self.write_space();
        self.write_keyword("STATISTICS");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if !e.expressions.is_empty() {
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
        self.write_keyword("VALIDATE");
        self.write_space();
        self.write_keyword(&e.kind);
        if let Some(this) = &e.this {
            self.write_space();
            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
            if let Expression::Identifier(id) = this.as_ref() {
                self.write_keyword(&id.name);
            } else {
                self.generate_expression(this)?;
            }
        }
        if let Some(expression) = &e.expression {
            self.write_space();
            self.write_keyword("INTO");
            self.write_space();
            self.generate_expression(expression)?;
        }
        Ok(())
    }

    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
        // Python: return f"WITH {expressions}"
        self.write_keyword("WITH");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
        // Anonymous represents a generic function call: FUNC_NAME(args...)
        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
        self.generate_expression(&e.this)?;
        self.write("(");
        for (i, arg) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(arg)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
        // Same as Anonymous but for aggregate functions
        self.generate_expression(&e.this)?;
        self.write("(");
        for (i, arg) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(arg)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
        // Python: return f"{this} APPLY({expr})"
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("APPLY");
        self.write("(");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(percentile) = &e.percentile {
            self.write(", ");
            self.generate_expression(percentile)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
        self.write_keyword("APPROX_QUANTILE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(quantile) = &e.quantile {
            self.write(", ");
            self.generate_expression(quantile)?;
        }
        if let Some(accuracy) = &e.accuracy {
            self.write(", ");
            self.generate_expression(accuracy)?;
        }
        if let Some(weight) = &e.weight {
            self.write(", ");
            self.generate_expression(weight)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
        // APPROX_QUANTILES(this, expression)
        self.write_keyword("APPROX_QUANTILES");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
        // APPROX_TOP_K(this[, expression][, counters])
        self.write_keyword("APPROX_TOP_K");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        if let Some(counters) = &e.counters {
            self.write(", ");
            self.generate_expression(counters)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
        // APPROX_TOP_K_ACCUMULATE(this[, expression])
        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
        // APPROX_TOP_K_COMBINE(this[, expression])
        self.write_keyword("APPROX_TOP_K_COMBINE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
        // APPROX_TOP_K_ESTIMATE(this[, expression])
        self.write_keyword("APPROX_TOP_K_ESTIMATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
        // APPROX_TOP_SUM(this, expression[, count])
        self.write_keyword("APPROX_TOP_SUM");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(count) = &e.count {
            self.write(", ");
            self.generate_expression(count)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
        // ARG_MAX(this, expression[, count])
        self.write_keyword("ARG_MAX");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(count) = &e.count {
            self.write(", ");
            self.generate_expression(count)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
        // ARG_MIN(this, expression[, count])
        self.write_keyword("ARG_MIN");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(count) = &e.count {
            self.write(", ");
            self.generate_expression(count)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
        // ARRAY_ALL(this, expression)
        self.write_keyword("ARRAY_ALL");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
        // ARRAY_ANY(this, expression) - fallback implementation
        self.write_keyword("ARRAY_ANY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
        // ARRAY_CONSTRUCT_COMPACT(expressions...)
        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
        // ARRAY_SUM(this[, expression])
        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            self.write("arraySum");
        } else {
            self.write_keyword("ARRAY_SUM");
        }
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
        // Python: return f"{this} AT {index}"
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("AT");
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
        self.write_keyword("ATTACH");
        if e.exists {
            self.write_space();
            self.write_keyword("IF NOT EXISTS");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
        // AttachOption: this [expression]
        // Python sqlglot: no equals sign, just space-separated
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write_space();
            self.generate_expression(expression)?;
        }
        Ok(())
    }

    /// Generate the auto_increment keyword and options for a column definition.
    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
    /// GENERATED AS IDENTITY, etc.
    fn generate_auto_increment_keyword(
        &mut self,
        col: &crate::expressions::ColumnDef,
    ) -> Result<()> {
        use crate::dialects::DialectType;
        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
            self.write_keyword("IDENTITY");
            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
                self.write("(");
                if let Some(ref start) = col.auto_increment_start {
                    self.generate_expression(start)?;
                } else {
                    self.write("0");
                }
                self.write(", ");
                if let Some(ref inc) = col.auto_increment_increment {
                    self.generate_expression(inc)?;
                } else {
                    self.write("1");
                }
                self.write(")");
            }
        } else if matches!(
            self.config.dialect,
            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
        ) {
            self.write_keyword("AUTOINCREMENT");
            if let Some(ref start) = col.auto_increment_start {
                self.write_space();
                self.write_keyword("START");
                self.write_space();
                self.generate_expression(start)?;
            }
            if let Some(ref inc) = col.auto_increment_increment {
                self.write_space();
                self.write_keyword("INCREMENT");
                self.write_space();
                self.generate_expression(inc)?;
            }
            if let Some(order) = col.auto_increment_order {
                self.write_space();
                if order {
                    self.write_keyword("ORDER");
                } else {
                    self.write_keyword("NOORDER");
                }
            }
        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
                self.write(" (");
                let mut first = true;
                if let Some(ref start) = col.auto_increment_start {
                    self.write_keyword("START WITH");
                    self.write_space();
                    self.generate_expression(start)?;
                    first = false;
                }
                if let Some(ref inc) = col.auto_increment_increment {
                    if !first {
                        self.write_space();
                    }
                    self.write_keyword("INCREMENT BY");
                    self.write_space();
                    self.generate_expression(inc)?;
                }
                self.write(")");
            }
        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
                self.write(" (");
                let mut first = true;
                if let Some(ref start) = col.auto_increment_start {
                    self.write_keyword("START WITH");
                    self.write_space();
                    self.generate_expression(start)?;
                    first = false;
                }
                if let Some(ref inc) = col.auto_increment_increment {
                    if !first {
                        self.write_space();
                    }
                    self.write_keyword("INCREMENT BY");
                    self.write_space();
                    self.generate_expression(inc)?;
                }
                self.write(")");
            }
        } else if matches!(
            self.config.dialect,
            Some(DialectType::TSQL) | Some(DialectType::Fabric)
        ) {
            self.write_keyword("IDENTITY");
            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
                self.write("(");
                if let Some(ref start) = col.auto_increment_start {
                    self.generate_expression(start)?;
                } else {
                    self.write("0");
                }
                self.write(", ");
                if let Some(ref inc) = col.auto_increment_increment {
                    self.generate_expression(inc)?;
                } else {
                    self.write("1");
                }
                self.write(")");
            }
        } else {
            self.write_keyword("AUTO_INCREMENT");
            if let Some(ref start) = col.auto_increment_start {
                self.write_space();
                self.write_keyword("START");
                self.write_space();
                self.generate_expression(start)?;
            }
            if let Some(ref inc) = col.auto_increment_increment {
                self.write_space();
                self.write_keyword("INCREMENT");
                self.write_space();
                self.generate_expression(inc)?;
            }
            if let Some(order) = col.auto_increment_order {
                self.write_space();
                if order {
                    self.write_keyword("ORDER");
                } else {
                    self.write_keyword("NOORDER");
                }
            }
        }
        Ok(())
    }

    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
        // AUTO_INCREMENT=value
        self.write_keyword("AUTO_INCREMENT");
        self.write("=");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
        // AUTO_REFRESH=value
        self.write_keyword("AUTO_REFRESH");
        self.write("=");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
        // BACKUP YES|NO (Redshift syntax uses space, not equals)
        self.write_keyword("BACKUP");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
        // BASE64_DECODE_BINARY(this[, alphabet])
        self.write_keyword("BASE64_DECODE_BINARY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(alphabet) = &e.alphabet {
            self.write(", ");
            self.generate_expression(alphabet)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
        // BASE64_DECODE_STRING(this[, alphabet])
        self.write_keyword("BASE64_DECODE_STRING");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(alphabet) = &e.alphabet {
            self.write(", ");
            self.generate_expression(alphabet)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
        // BASE64_ENCODE(this[, max_line_length][, alphabet])
        self.write_keyword("BASE64_ENCODE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(max_line_length) = &e.max_line_length {
            self.write(", ");
            self.generate_expression(max_line_length)?;
        }
        if let Some(alphabet) = &e.alphabet {
            self.write(", ");
            self.generate_expression(alphabet)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
        // BLOCKCOMPRESSION=... (complex Teradata property)
        self.write_keyword("BLOCKCOMPRESSION");
        self.write("=");
        if let Some(autotemp) = &e.autotemp {
            self.write_keyword("AUTOTEMP");
            self.write("(");
            self.generate_expression(autotemp)?;
            self.write(")");
        }
        if let Some(always) = &e.always {
            self.generate_expression(always)?;
        }
        if let Some(default) = &e.default {
            self.generate_expression(default)?;
        }
        if let Some(manual) = &e.manual {
            self.generate_expression(manual)?;
        }
        if let Some(never) = &e.never {
            self.generate_expression(never)?;
        }
        Ok(())
    }

    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
        self.write("((");
        self.generate_expression(&e.this)?;
        self.write(") ");
        self.write_keyword("AND");
        self.write(" (");
        self.generate_expression(&e.expression)?;
        self.write("))");
        Ok(())
    }

    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
        self.write("((");
        self.generate_expression(&e.this)?;
        self.write(") ");
        self.write_keyword("OR");
        self.write(" (");
        self.generate_expression(&e.expression)?;
        self.write("))");
        Ok(())
    }

    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
        self.write_keyword("BUILD");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
        // Byte string literal like B'...' or X'...'
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_case_specific_column_constraint(
        &mut self,
        e: &CaseSpecificColumnConstraint,
    ) -> Result<()> {
        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
        if e.not_.is_some() {
            self.write_keyword("NOT");
            self.write_space();
        }
        self.write_keyword("CASESPECIFIC");
        Ok(())
    }

    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
        // Cast to string type (dialect-specific)
        self.write_keyword("CAST");
        self.write("(");
        self.generate_expression(&e.this)?;
        if self.config.dialect == Some(DialectType::ClickHouse) {
            // ClickHouse: CAST(expr, 'type_string')
            self.write(", ");
        } else {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
        }
        if let Some(to) = &e.to {
            self.generate_expression(to)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
        // Python: f"CHANGES ({information}){at_before}{end}"
        self.write_keyword("CHANGES");
        self.write(" (");
        if let Some(information) = &e.information {
            self.write_keyword("INFORMATION");
            self.write(" => ");
            self.generate_expression(information)?;
        }
        self.write(")");
        // at_before and end are HistoricalData expressions that generate their own keywords
        if let Some(at_before) = &e.at_before {
            self.write(" ");
            self.generate_expression(at_before)?;
        }
        if let Some(end) = &e.end {
            self.write(" ");
            self.generate_expression(end)?;
        }
        Ok(())
    }

    fn generate_character_set_column_constraint(
        &mut self,
        e: &CharacterSetColumnConstraint,
    ) -> Result<()> {
        // CHARACTER SET charset_name
        self.write_keyword("CHARACTER SET");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
        // [DEFAULT] CHARACTER SET=value
        if e.default.is_some() {
            self.write_keyword("DEFAULT");
            self.write_space();
        }
        self.write_keyword("CHARACTER SET");
        self.write("=");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
        self.write_keyword("CHECK");
        self.write(" (");
        self.generate_expression(&e.this)?;
        self.write(")");
        if e.enforced.is_some() {
            self.write_space();
            self.write_keyword("ENFORCED");
        }
        Ok(())
    }

    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
        // CHECK_JSON(this)
        self.write_keyword("CHECK_JSON");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
        // CHECK_XML(this)
        self.write_keyword("CHECK_XML");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
        // CHECKSUM=[ON|OFF|DEFAULT]
        self.write_keyword("CHECKSUM");
        self.write("=");
        if e.on.is_some() {
            self.write_keyword("ON");
        } else if e.default.is_some() {
            self.write_keyword("DEFAULT");
        } else {
            self.write_keyword("OFF");
        }
        Ok(())
    }

    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
        // Python: return f"{shallow}{keyword} {this}"
        if e.shallow.is_some() {
            self.write_keyword("SHALLOW");
            self.write_space();
        }
        if e.copy.is_some() {
            self.write_keyword("COPY");
        } else {
            self.write_keyword("CLONE");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
        // CLUSTER BY (expressions)
        self.write_keyword("CLUSTER BY");
        self.write(" (");
        for (i, ord) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_ordered(ord)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
        self.write_keyword("CLUSTERED BY");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        if let Some(sorted_by) = &e.sorted_by {
            self.write_space();
            self.write_keyword("SORTED BY");
            self.write(" (");
            // Unwrap Tuple to avoid double parentheses
            if let Expression::Tuple(t) = sorted_by.as_ref() {
                for (i, expr) in t.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            } else {
                self.generate_expression(sorted_by)?;
            }
            self.write(")");
        }
        if let Some(buckets) = &e.buckets {
            self.write_space();
            self.write_keyword("INTO");
            self.write_space();
            self.generate_expression(buckets)?;
            self.write_space();
            self.write_keyword("BUCKETS");
        }
        Ok(())
    }

    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
        // [DEFAULT] COLLATE [=] value
        // BigQuery uses space: DEFAULT COLLATE 'en'
        // Others use equals: COLLATE='en'
        if e.default.is_some() {
            self.write_keyword("DEFAULT");
            self.write_space();
        }
        self.write_keyword("COLLATE");
        // BigQuery uses space between COLLATE and value
        match self.config.dialect {
            Some(DialectType::BigQuery) => self.write_space(),
            _ => self.write("="),
        }
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
        // ColumnConstraint is an enum
        match e {
            ColumnConstraint::NotNull => {
                self.write_keyword("NOT NULL");
            }
            ColumnConstraint::Null => {
                self.write_keyword("NULL");
            }
            ColumnConstraint::Unique => {
                self.write_keyword("UNIQUE");
            }
            ColumnConstraint::PrimaryKey => {
                self.write_keyword("PRIMARY KEY");
            }
            ColumnConstraint::Default(expr) => {
                self.write_keyword("DEFAULT");
                self.write_space();
                self.generate_expression(expr)?;
            }
            ColumnConstraint::Check(expr) => {
                self.write_keyword("CHECK");
                self.write(" (");
                self.generate_expression(expr)?;
                self.write(")");
            }
            ColumnConstraint::References(fk_ref) => {
                if fk_ref.has_foreign_key_keywords {
                    self.write_keyword("FOREIGN KEY");
                    self.write_space();
                }
                self.write_keyword("REFERENCES");
                self.write_space();
                self.generate_table(&fk_ref.table)?;
                if !fk_ref.columns.is_empty() {
                    self.write(" (");
                    for (i, col) in fk_ref.columns.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_identifier(col)?;
                    }
                    self.write(")");
                }
            }
            ColumnConstraint::GeneratedAsIdentity(gen) => {
                self.write_keyword("GENERATED");
                self.write_space();
                if gen.always {
                    self.write_keyword("ALWAYS");
                } else {
                    self.write_keyword("BY DEFAULT");
                    if gen.on_null {
                        self.write_space();
                        self.write_keyword("ON NULL");
                    }
                }
                self.write_space();
                self.write_keyword("AS IDENTITY");
            }
            ColumnConstraint::Collate(collation) => {
                self.write_keyword("COLLATE");
                self.write_space();
                self.generate_identifier(collation)?;
            }
            ColumnConstraint::Comment(comment) => {
                self.write_keyword("COMMENT");
                self.write(" '");
                self.write(comment);
                self.write("'");
            }
            ColumnConstraint::ComputedColumn(cc) => {
                self.generate_computed_column_inline(cc)?;
            }
            ColumnConstraint::GeneratedAsRow(gar) => {
                self.generate_generated_as_row_inline(gar)?;
            }
            ColumnConstraint::Tags(tags) => {
                self.write_keyword("TAG");
                self.write(" (");
                for (i, expr) in tags.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
            ColumnConstraint::Path(path_expr) => {
                self.write_keyword("PATH");
                self.write_space();
                self.generate_expression(path_expr)?;
            }
        }
        Ok(())
    }

    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
        // ColumnPosition is an enum
        match e {
            ColumnPosition::First => {
                self.write_keyword("FIRST");
            }
            ColumnPosition::After(ident) => {
                self.write_keyword("AFTER");
                self.write_space();
                self.generate_identifier(ident)?;
            }
        }
        Ok(())
    }

    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
        // column(prefix)
        self.generate_expression(&e.this)?;
        self.write("(");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
        // If unpack is true, this came from * COLUMNS(pattern)
        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
        if let Some(ref unpack) = e.unpack {
            if let Expression::Boolean(b) = unpack.as_ref() {
                if b.value {
                    self.write("*");
                }
            }
        }
        self.write_keyword("COLUMNS");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
        // Combined aggregate: FUNC(args) combined
        self.generate_expression(&e.this)?;
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
        // Combined parameterized aggregate: FUNC(params)(expressions)
        self.generate_expression(&e.this)?;
        self.write("(");
        for (i, param) in e.params.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(param)?;
        }
        self.write(")(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
        self.write_keyword("COMMIT");

        // TSQL always uses COMMIT TRANSACTION
        if e.this.is_none()
            && matches!(
                self.config.dialect,
                Some(DialectType::TSQL) | Some(DialectType::Fabric)
            )
        {
            self.write_space();
            self.write_keyword("TRANSACTION");
        }

        // Check if this has TRANSACTION keyword or transaction name
        if let Some(this) = &e.this {
            // Check if it's just the "TRANSACTION" marker or an actual transaction name
            let is_transaction_marker = matches!(
                this.as_ref(),
                Expression::Identifier(id) if id.name == "TRANSACTION"
            );

            self.write_space();
            self.write_keyword("TRANSACTION");

            // If it's a real transaction name, output it
            if !is_transaction_marker {
                self.write_space();
                self.generate_expression(this)?;
            }
        }

        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
        if let Some(durability) = &e.durability {
            self.write_space();
            self.write_keyword("WITH");
            self.write(" (");
            self.write_keyword("DELAYED_DURABILITY");
            self.write(" = ");
            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
                self.write_keyword("ON");
            } else {
                self.write_keyword("OFF");
            }
            self.write(")");
        }

        // Output AND [NO] CHAIN
        if let Some(chain) = &e.chain {
            self.write_space();
            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
                self.write_keyword("AND NO CHAIN");
            } else {
                self.write_keyword("AND CHAIN");
            }
        }
        Ok(())
    }

    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
        self.write("[");
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("FOR");
        self.write_space();
        self.generate_expression(&e.expression)?;
        // Handle optional position variable (for enumerate-like syntax)
        if let Some(pos) = &e.position {
            self.write(", ");
            self.generate_expression(pos)?;
        }
        if let Some(iterator) = &e.iterator {
            self.write_space();
            self.write_keyword("IN");
            self.write_space();
            self.generate_expression(iterator)?;
        }
        if let Some(condition) = &e.condition {
            self.write_space();
            self.write_keyword("IF");
            self.write_space();
            self.generate_expression(condition)?;
        }
        self.write("]");
        Ok(())
    }

    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
        // COMPRESS(this[, method])
        self.write_keyword("COMPRESS");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(method) = &e.method {
            self.write(", '");
            self.write(method);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
        // Python: return f"COMPRESS {this}"
        self.write_keyword("COMPRESS");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
        // Python: return f"AS {this}{persisted}"
        self.write_keyword("AS");
        self.write_space();
        self.generate_expression(&e.this)?;
        if e.not_null.is_some() {
            self.write_space();
            self.write_keyword("PERSISTED NOT NULL");
        } else if e.persisted.is_some() {
            self.write_space();
            self.write_keyword("PERSISTED");
        }
        Ok(())
    }

    /// Generate a ComputedColumn constraint inline within a column definition.
    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
        let computed_expr = if matches!(
            self.config.dialect,
            Some(DialectType::TSQL) | Some(DialectType::Fabric)
        ) {
            match &*cc.expression {
                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
                {
                    let wrapped = Expression::Cast(Box::new(Cast {
                        this: y.this.clone(),
                        to: DataType::Date,
                        trailing_comments: Vec::new(),
                        double_colon_syntax: false,
                        format: None,
                        default: None,
                    }));
                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
                }
                Expression::Function(f)
                    if f.name.eq_ignore_ascii_case("YEAR")
                        && f.args.len() == 1
                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
                {
                    let wrapped = Expression::Cast(Box::new(Cast {
                        this: f.args[0].clone(),
                        to: DataType::Date,
                        trailing_comments: Vec::new(),
                        double_colon_syntax: false,
                        format: None,
                        default: None,
                    }));
                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
                }
                _ => *cc.expression.clone(),
            }
        } else {
            *cc.expression.clone()
        };

        match cc.persistence_kind.as_deref() {
            Some("STORED") | Some("VIRTUAL") => {
                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
                self.write_keyword("GENERATED ALWAYS AS");
                self.write(" (");
                self.generate_expression(&computed_expr)?;
                self.write(")");
                self.write_space();
                if cc.persisted {
                    self.write_keyword("STORED");
                } else {
                    self.write_keyword("VIRTUAL");
                }
            }
            Some("PERSISTED") => {
                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
                self.write_keyword("AS");
                self.write(" (");
                self.generate_expression(&computed_expr)?;
                self.write(")");
                self.write_space();
                self.write_keyword("PERSISTED");
                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
                if let Some(ref dt) = cc.data_type {
                    self.write_space();
                    self.generate_data_type(dt)?;
                }
                if cc.not_null {
                    self.write_space();
                    self.write_keyword("NOT NULL");
                }
            }
            _ => {
                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
                // TSQL computed column without PERSISTED: AS (expr)
                if matches!(
                    self.config.dialect,
                    Some(DialectType::Spark)
                        | Some(DialectType::Databricks)
                        | Some(DialectType::Hive)
                ) {
                    self.write_keyword("GENERATED ALWAYS AS");
                    self.write(" (");
                    self.generate_expression(&computed_expr)?;
                    self.write(")");
                } else if matches!(
                    self.config.dialect,
                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
                ) {
                    self.write_keyword("AS");
                    let omit_parens = matches!(computed_expr, Expression::Year(_))
                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
                    if omit_parens {
                        self.write_space();
                        self.generate_expression(&computed_expr)?;
                    } else {
                        self.write(" (");
                        self.generate_expression(&computed_expr)?;
                        self.write(")");
                    }
                } else {
                    self.write_keyword("AS");
                    self.write(" (");
                    self.generate_expression(&computed_expr)?;
                    self.write(")");
                }
            }
        }
        Ok(())
    }

    /// Generate a GeneratedAsRow constraint inline within a column definition.
    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
        self.write_keyword("GENERATED ALWAYS AS ROW ");
        if gar.start {
            self.write_keyword("START");
        } else {
            self.write_keyword("END");
        }
        if gar.hidden {
            self.write_space();
            self.write_keyword("HIDDEN");
        }
        Ok(())
    }

    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
    fn generate_system_versioning_content(
        &mut self,
        e: &WithSystemVersioningProperty,
    ) -> Result<()> {
        let mut parts = Vec::new();

        if let Some(this) = &e.this {
            let mut s = String::from("HISTORY_TABLE=");
            let mut gen = Generator::new();
            gen.config = self.config.clone();
            gen.generate_expression(this)?;
            s.push_str(&gen.output);
            parts.push(s);
        }

        if let Some(data_consistency) = &e.data_consistency {
            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
            let mut gen = Generator::new();
            gen.config = self.config.clone();
            gen.generate_expression(data_consistency)?;
            s.push_str(&gen.output);
            parts.push(s);
        }

        if let Some(retention_period) = &e.retention_period {
            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
            let mut gen = Generator::new();
            gen.config = self.config.clone();
            gen.generate_expression(retention_period)?;
            s.push_str(&gen.output);
            parts.push(s);
        }

        self.write_keyword("SYSTEM_VERSIONING");
        self.write("=");

        if !parts.is_empty() {
            self.write_keyword("ON");
            self.write("(");
            self.write(&parts.join(", "));
            self.write(")");
        } else if e.on.is_some() {
            self.write_keyword("ON");
        } else {
            self.write_keyword("OFF");
        }

        Ok(())
    }

    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
        // Conditional INSERT for multi-table inserts
        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
        if e.else_.is_some() {
            self.write_keyword("ELSE");
            self.write_space();
        } else if let Some(expression) = &e.expression {
            self.write_keyword("WHEN");
            self.write_space();
            self.generate_expression(expression)?;
            self.write_space();
            self.write_keyword("THEN");
            self.write_space();
        }

        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
        // without the "INSERT " prefix
        if let Expression::Insert(insert) = e.this.as_ref() {
            self.write_keyword("INTO");
            self.write_space();
            self.generate_table(&insert.table)?;

            // Optional column list
            if !insert.columns.is_empty() {
                self.write(" (");
                for (i, col) in insert.columns.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_identifier(col)?;
                }
                self.write(")");
            }

            // Optional VALUES clause
            if !insert.values.is_empty() {
                self.write_space();
                self.write_keyword("VALUES");
                for (row_idx, row) in insert.values.iter().enumerate() {
                    if row_idx > 0 {
                        self.write(", ");
                    }
                    self.write(" (");
                    for (i, val) in row.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(val)?;
                    }
                    self.write(")");
                }
            }
        } else {
            // Fallback for non-Insert expressions
            self.generate_expression(&e.this)?;
        }
        Ok(())
    }

    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
        // Python: return f"CONSTRAINT {this} {expressions}"
        self.write_keyword("CONSTRAINT");
        self.write_space();
        self.generate_expression(&e.this)?;
        if !e.expressions.is_empty() {
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write_space();
                }
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
        self.write_keyword("CONVERT_TIMEZONE");
        self.write("(");
        let mut first = true;
        if let Some(source_tz) = &e.source_tz {
            self.generate_expression(source_tz)?;
            first = false;
        }
        if let Some(target_tz) = &e.target_tz {
            if !first {
                self.write(", ");
            }
            self.generate_expression(target_tz)?;
            first = false;
        }
        if let Some(timestamp) = &e.timestamp {
            if !first {
                self.write(", ");
            }
            self.generate_expression(timestamp)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
        // CONVERT(this USING dest)
        self.write_keyword("CONVERT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(dest) = &e.dest {
            self.write_space();
            self.write_keyword("USING");
            self.write_space();
            self.generate_expression(dest)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
        self.write_keyword("COPY");
        if e.is_into {
            self.write_space();
            self.write_keyword("INTO");
        }
        self.write_space();

        // Generate target table or query (or stage for COPY INTO @stage)
        if let Expression::Literal(Literal::String(s)) = &e.this {
            if s.starts_with('@') {
                self.write(s);
            } else {
                self.generate_expression(&e.this)?;
            }
        } else {
            self.generate_expression(&e.this)?;
        }

        // FROM or TO based on kind
        if e.kind {
            // kind=true means FROM (loading into table)
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("FROM");
            self.write_space();
        } else if !e.files.is_empty() {
            // kind=false means TO (exporting)
            if self.config.pretty {
                self.write_newline();
            } else {
                self.write_space();
            }
            self.write_keyword("TO");
            self.write_space();
        }

        // Generate source/destination files
        for (i, file) in e.files.iter().enumerate() {
            if i > 0 {
                self.write_space();
            }
            // For stage references (strings starting with @), output without quotes
            if let Expression::Literal(Literal::String(s)) = file {
                if s.starts_with('@') {
                    self.write(s);
                } else {
                    self.generate_expression(file)?;
                }
            } else if let Expression::Identifier(id) = file {
                // Backtick-quoted file path (Databricks style: `s3://link`)
                if id.quoted {
                    self.write("`");
                    self.write(&id.name);
                    self.write("`");
                } else {
                    self.generate_expression(file)?;
                }
            } else {
                self.generate_expression(file)?;
            }
        }

        // Generate credentials if present (Snowflake style - not wrapped in WITH)
        if !e.with_wrapped {
            if let Some(ref creds) = e.credentials {
                if let Some(ref storage) = creds.storage {
                    if self.config.pretty {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("STORAGE_INTEGRATION");
                    self.write(" = ");
                    self.write(storage);
                }
                if creds.credentials.is_empty() {
                    // Empty credentials: CREDENTIALS = ()
                    if self.config.pretty {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("CREDENTIALS");
                    self.write(" = ()");
                } else {
                    if self.config.pretty {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    self.write_keyword("CREDENTIALS");
                    // Check if this is Redshift-style (single value with empty key)
                    // vs Snowflake-style (multiple key=value pairs)
                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
                        // Redshift style: CREDENTIALS 'value'
                        self.write(" '");
                        self.write(&creds.credentials[0].1);
                        self.write("'");
                    } else {
                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
                        self.write(" = (");
                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
                            if i > 0 {
                                self.write_space();
                            }
                            self.write(k);
                            self.write("='");
                            self.write(v);
                            self.write("'");
                        }
                        self.write(")");
                    }
                }
                if let Some(ref encryption) = creds.encryption {
                    self.write_space();
                    self.write_keyword("ENCRYPTION");
                    self.write(" = ");
                    self.write(encryption);
                }
            }
        }

        // Generate parameters
        if !e.params.is_empty() {
            if e.with_wrapped {
                // DuckDB/PostgreSQL/TSQL WITH (...) format
                self.write_space();
                self.write_keyword("WITH");
                self.write(" (");
                for (i, param) in e.params.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_copy_param_with_format(param)?;
                }
                self.write(")");
            } else {
                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
                // For Snowflake: KEY = VALUE
                for param in &e.params {
                    if self.config.pretty {
                        self.write_newline();
                    } else {
                        self.write_space();
                    }
                    // Preserve original case of parameter name (important for Redshift COPY options)
                    self.write(&param.name);
                    if let Some(ref value) = param.value {
                        // Use = only if it was present in the original (param.eq)
                        if param.eq {
                            self.write(" = ");
                        } else {
                            self.write(" ");
                        }
                        if !param.values.is_empty() {
                            self.write("(");
                            for (i, v) in param.values.iter().enumerate() {
                                if i > 0 {
                                    self.write_space();
                                }
                                self.generate_copy_nested_param(v)?;
                            }
                            self.write(")");
                        } else {
                            // For COPY parameter values, output identifiers without quoting
                            self.generate_copy_param_value(value)?;
                        }
                    } else if !param.values.is_empty() {
                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
                        if param.eq {
                            self.write(" = (");
                        } else {
                            self.write(" (");
                        }
                        // Determine separator for values inside parentheses:
                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
                        let is_key_value_pairs = param
                            .values
                            .first()
                            .is_some_and(|v| matches!(v, Expression::Eq(_)));
                        let sep = if is_key_value_pairs && param.eq {
                            " "
                        } else {
                            ", "
                        };
                        for (i, v) in param.values.iter().enumerate() {
                            if i > 0 {
                                self.write(sep);
                            }
                            self.generate_copy_nested_param(v)?;
                        }
                        self.write(")");
                    }
                }
            }
        }

        Ok(())
    }

    /// Generate a COPY parameter in WITH (...) format
    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
        self.write_keyword(&param.name);
        if !param.values.is_empty() {
            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
            self.write(" = (");
            for (i, v) in param.values.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_copy_nested_param(v)?;
            }
            self.write(")");
        } else if let Some(ref value) = param.value {
            if param.eq {
                self.write(" = ");
            } else {
                self.write(" ");
            }
            self.generate_expression(value)?;
        }
        Ok(())
    }

    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Eq(eq) => {
                // Generate key
                match &eq.left {
                    Expression::Column(c) => self.write(&c.name.name),
                    _ => self.generate_expression(&eq.left)?,
                }
                self.write("=");
                // Generate value
                match &eq.right {
                    Expression::Literal(Literal::String(s)) => {
                        self.write("'");
                        self.write(s);
                        self.write("'");
                    }
                    Expression::Tuple(t) => {
                        // For lists like NULL_IF=('', 'str1')
                        self.write("(");
                        if self.config.pretty {
                            self.write_newline();
                            self.indent_level += 1;
                            for (i, item) in t.expressions.iter().enumerate() {
                                if i > 0 {
                                    self.write(", ");
                                }
                                self.write_indent();
                                self.generate_expression(item)?;
                            }
                            self.write_newline();
                            self.indent_level -= 1;
                        } else {
                            for (i, item) in t.expressions.iter().enumerate() {
                                if i > 0 {
                                    self.write(", ");
                                }
                                self.generate_expression(item)?;
                            }
                        }
                        self.write(")");
                    }
                    _ => self.generate_expression(&eq.right)?,
                }
                Ok(())
            }
            Expression::Column(c) => {
                // Standalone keyword like COMPRESSION
                self.write(&c.name.name);
                Ok(())
            }
            _ => self.generate_expression(expr),
        }
    }

    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Column(c) => {
                // Output identifier, preserving quotes if originally quoted
                if c.name.quoted {
                    self.write("\"");
                    self.write(&c.name.name);
                    self.write("\"");
                } else {
                    self.write(&c.name.name);
                }
                Ok(())
            }
            Expression::Identifier(id) => {
                // Output identifier, preserving quotes if originally quoted
                if id.quoted {
                    self.write("\"");
                    self.write(&id.name);
                    self.write("\"");
                } else {
                    self.write(&id.name);
                }
                Ok(())
            }
            Expression::Literal(Literal::String(s)) => {
                // Output string with quotes
                self.write("'");
                self.write(s);
                self.write("'");
                Ok(())
            }
            _ => self.generate_expression(expr),
        }
    }

    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
        self.write_keyword(&e.name);
        if let Some(ref value) = e.value {
            if e.eq {
                self.write(" = ");
            } else {
                self.write(" ");
            }
            self.generate_expression(value)?;
        }
        if !e.values.is_empty() {
            if e.eq {
                self.write(" = ");
            } else {
                self.write(" ");
            }
            self.write("(");
            for (i, v) in e.values.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(v)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
        // CORR(this, expression)
        self.write_keyword("CORR");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
        // COSINE_DISTANCE(this, expression)
        self.write_keyword("COSINE_DISTANCE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
        // COVAR_POP(this, expression)
        self.write_keyword("COVAR_POP");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
        // COVAR_SAMP(this, expression)
        self.write_keyword("COVAR_SAMP");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
        // CREDENTIALS (key1='value1', key2='value2')
        self.write_keyword("CREDENTIALS");
        self.write(" (");
        for (i, (key, value)) in e.credentials.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.write(key);
            self.write("='");
            self.write(value);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
        // CREDENTIALS=(expressions)
        self.write_keyword("CREDENTIALS");
        self.write("=(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
        use crate::dialects::DialectType;

        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
            self.generate_expression(&e.this)?;
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_identifier(&e.alias)?;
            return Ok(());
        }
        self.write(&e.alias.name);

        // BigQuery doesn't support column aliases in CTE definitions
        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));

        if !e.columns.is_empty() && !skip_cte_columns {
            self.write("(");
            for (i, col) in e.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(&col.name);
            }
            self.write(")");
        }
        // USING KEY (columns) for DuckDB recursive CTEs
        if !e.key_expressions.is_empty() {
            self.write_space();
            self.write_keyword("USING KEY");
            self.write(" (");
            for (i, key) in e.key_expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.write(&key.name);
            }
            self.write(")");
        }
        self.write_space();
        self.write_keyword("AS");
        self.write_space();
        if let Some(materialized) = e.materialized {
            if materialized {
                self.write_keyword("MATERIALIZED");
            } else {
                self.write_keyword("NOT MATERIALIZED");
            }
            self.write_space();
        }
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
        if e.expressions.is_empty() {
            self.write_keyword("WITH CUBE");
        } else {
            self.write_keyword("CUBE");
            self.write("(");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
        self.write_keyword("CURRENT_DATETIME");
        if let Some(this) = &e.this {
            self.write("(");
            self.generate_expression(this)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
        // CURRENT_SCHEMA - no arguments
        self.write_keyword("CURRENT_SCHEMA");
        Ok(())
    }

    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
        // CURRENT_SCHEMAS(include_implicit)
        self.write_keyword("CURRENT_SCHEMAS");
        self.write("(");
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
        // CURRENT_USER or CURRENT_USER()
        self.write_keyword("CURRENT_USER");
        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
        let needs_parens = e.this.is_some()
            || matches!(
                self.config.dialect,
                Some(DialectType::Snowflake)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Hive)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::BigQuery)
                    | Some(DialectType::MySQL)
                    | Some(DialectType::Databricks)
            );
        if needs_parens {
            self.write("()");
        }
        Ok(())
    }

    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
        if self.config.dialect == Some(DialectType::Solr) {
            self.generate_expression(&e.this)?;
            self.write(" ");
            self.write_keyword("OR");
            self.write(" ");
            self.generate_expression(&e.expression)?;
        } else {
            // String concatenation: this || expression
            self.generate_expression(&e.this)?;
            self.write(" || ");
            self.generate_expression(&e.expression)?;
        }
        Ok(())
    }

    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
        // DATABLOCKSIZE=... (Teradata)
        self.write_keyword("DATABLOCKSIZE");
        self.write("=");
        if let Some(size) = e.size {
            self.write(&size.to_string());
            if let Some(units) = &e.units {
                self.write_space();
                self.generate_expression(units)?;
            }
        } else if e.minimum.is_some() {
            self.write_keyword("MINIMUM");
        } else if e.maximum.is_some() {
            self.write_keyword("MAXIMUM");
        } else if e.default.is_some() {
            self.write_keyword("DEFAULT");
        }
        Ok(())
    }

    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
        self.write_keyword("DATA_DELETION");
        self.write("=");

        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
        let has_options = e.filter_column.is_some() || e.retention_period.is_some();

        if is_on {
            self.write_keyword("ON");
            if has_options {
                self.write("(");
                let mut first = true;
                if let Some(filter_column) = &e.filter_column {
                    self.write_keyword("FILTER_COLUMN");
                    self.write("=");
                    self.generate_expression(filter_column)?;
                    first = false;
                }
                if let Some(retention_period) = &e.retention_period {
                    if !first {
                        self.write(", ");
                    }
                    self.write_keyword("RETENTION_PERIOD");
                    self.write("=");
                    self.generate_expression(retention_period)?;
                }
                self.write(")");
            }
        } else {
            self.write_keyword("OFF");
        }
        Ok(())
    }

    /// Generate a Date function expression
    /// For Exasol: {d'value'} -> TO_DATE('value')
    /// For other dialects: DATE('value')
    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
        use crate::dialects::DialectType;
        use crate::expressions::Literal;

        match self.config.dialect {
            // Exasol uses TO_DATE for Date expressions
            Some(DialectType::Exasol) => {
                self.write_keyword("TO_DATE");
                self.write("(");
                // Extract the string value from the expression if it's a string literal
                match &e.this {
                    Expression::Literal(Literal::String(s)) => {
                        self.write("'");
                        self.write(s);
                        self.write("'");
                    }
                    _ => {
                        self.generate_expression(&e.this)?;
                    }
                }
                self.write(")");
            }
            // Standard: DATE(value)
            _ => {
                self.write_keyword("DATE");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
        // DATE_BIN(interval, timestamp[, origin])
        self.write_keyword("DATE_BIN");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(origin) = &e.origin {
            self.write(", ");
            self.generate_expression(origin)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_date_format_column_constraint(
        &mut self,
        e: &DateFormatColumnConstraint,
    ) -> Result<()> {
        // FORMAT 'format_string' (Teradata)
        self.write_keyword("FORMAT");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
        self.write_keyword("DATE_FROM_PARTS");
        self.write("(");
        let mut first = true;
        if let Some(year) = &e.year {
            self.generate_expression(year)?;
            first = false;
        }
        if let Some(month) = &e.month {
            if !first {
                self.write(", ");
            }
            self.generate_expression(month)?;
            first = false;
        }
        if let Some(day) = &e.day {
            if !first {
                self.write(", ");
            }
            self.generate_expression(day)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
        // DATETIME(this) or DATETIME(this, expression)
        self.write_keyword("DATETIME");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expr) = &e.expression {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
        // DATETIME_ADD(this, expression, unit)
        self.write_keyword("DATETIME_ADD");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
        // DATETIME_DIFF(this, expression, unit)
        self.write_keyword("DATETIME_DIFF");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
        // DATETIME_SUB(this, expression, unit)
        self.write_keyword("DATETIME_SUB");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
        // DATETIME_TRUNC(this, unit, zone)
        self.write_keyword("DATETIME_TRUNC");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.write_keyword(&e.unit);
        if let Some(zone) = &e.zone {
            self.write(", ");
            self.generate_expression(zone)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
        // DAYNAME(this)
        self.write_keyword("DAYNAME");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
        // DECLARE var1 AS type1, var2 AS type2, ...
        self.write_keyword("DECLARE");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
        use crate::dialects::DialectType;

        // variable TYPE [DEFAULT default]
        self.generate_expression(&e.this)?;
        // BigQuery multi-variable: DECLARE X, Y, Z INT64
        for name in &e.additional_names {
            self.write(", ");
            self.generate_expression(name)?;
        }
        if let Some(kind) = &e.kind {
            self.write_space();
            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
            // TSQL: Always includes AS (normalization)
            // Others: Include AS if present in original
            match self.config.dialect {
                Some(DialectType::BigQuery) => {
                    self.write(kind);
                }
                Some(DialectType::TSQL) => {
                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
                    // NONCLUSTERED, or INDEX constraints
                    let is_complex_table = kind.starts_with("TABLE")
                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));

                    if is_complex_table {
                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
                        self.write(kind);
                    } else {
                        // Simple declarations: add AS (except for CURSOR) and normalize INT
                        if !kind.starts_with("CURSOR") {
                            self.write_keyword("AS");
                            self.write_space();
                        }
                        // Normalize INT to INTEGER for TSQL DECLARE statements
                        if kind == "INT" {
                            self.write("INTEGER");
                        } else if kind.starts_with("TABLE") {
                            // Normalize INT to INTEGER inside TABLE column definitions
                            let normalized = kind
                                .replace(" INT ", " INTEGER ")
                                .replace(" INT,", " INTEGER,")
                                .replace(" INT)", " INTEGER)")
                                .replace("(INT ", "(INTEGER ");
                            self.write(&normalized);
                        } else {
                            self.write(kind);
                        }
                    }
                }
                _ => {
                    if e.has_as {
                        self.write_keyword("AS");
                        self.write_space();
                    }
                    self.write(kind);
                }
            }
        }
        if let Some(default) = &e.default {
            // BigQuery uses DEFAULT, others use =
            match self.config.dialect {
                Some(DialectType::BigQuery) => {
                    self.write_space();
                    self.write_keyword("DEFAULT");
                    self.write_space();
                }
                _ => {
                    self.write(" = ");
                }
            }
            self.generate_expression(default)?;
        }
        Ok(())
    }

    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
        // DECODE(expr, search1, result1, search2, result2, ..., default)
        self.write_keyword("DECODE");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
        // DECOMPRESS(expr, 'method')
        self.write_keyword("DECOMPRESS");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", '");
        self.write(&e.method);
        self.write("')");
        Ok(())
    }

    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
        // DECOMPRESS(expr, 'method')
        self.write_keyword("DECOMPRESS");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", '");
        self.write(&e.method);
        self.write("')");
        Ok(())
    }

    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
        // DECRYPT(value, passphrase [, aad [, algorithm]])
        self.write_keyword("DECRYPT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(passphrase) = &e.passphrase {
            self.write(", ");
            self.generate_expression(passphrase)?;
        }
        if let Some(aad) = &e.aad {
            self.write(", ");
            self.generate_expression(aad)?;
        }
        if let Some(method) = &e.encryption_method {
            self.write(", ");
            self.generate_expression(method)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
        self.write_keyword("DECRYPT_RAW");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(key) = &e.key {
            self.write(", ");
            self.generate_expression(key)?;
        }
        if let Some(iv) = &e.iv {
            self.write(", ");
            self.generate_expression(iv)?;
        }
        if let Some(aad) = &e.aad {
            self.write(", ");
            self.generate_expression(aad)?;
        }
        if let Some(method) = &e.encryption_method {
            self.write(", ");
            self.generate_expression(method)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
        // DEFINER = user
        self.write_keyword("DEFINER");
        self.write(" = ");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
        // Python: DETACH[DATABASE IF EXISTS] this
        self.write_keyword("DETACH");
        if e.exists {
            self.write_keyword(" DATABASE IF EXISTS");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
        let property_name = match e.this.as_ref() {
            Expression::Identifier(id) => id.name.as_str(),
            Expression::Var(v) => v.this.as_str(),
            _ => "DICTIONARY",
        };
        self.write_keyword(property_name);
        self.write("(");
        self.write(&e.kind);
        if let Some(settings) = &e.settings {
            self.write("(");
            if let Expression::Tuple(t) = settings.as_ref() {
                if self.config.pretty && !t.expressions.is_empty() {
                    self.write_newline();
                    self.indent_level += 1;
                    for (i, pair) in t.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(",");
                            self.write_newline();
                        }
                        self.write_indent();
                        if let Expression::Tuple(pair_tuple) = pair {
                            if let Some(k) = pair_tuple.expressions.first() {
                                self.generate_expression(k)?;
                            }
                            if let Some(v) = pair_tuple.expressions.get(1) {
                                self.write(" ");
                                self.generate_expression(v)?;
                            }
                        } else {
                            self.generate_expression(pair)?;
                        }
                    }
                    self.indent_level -= 1;
                    self.write_newline();
                    self.write_indent();
                } else {
                    for (i, pair) in t.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        if let Expression::Tuple(pair_tuple) = pair {
                            if let Some(k) = pair_tuple.expressions.first() {
                                self.generate_expression(k)?;
                            }
                            if let Some(v) = pair_tuple.expressions.get(1) {
                                self.write(" ");
                                self.generate_expression(v)?;
                            }
                        } else {
                            self.generate_expression(pair)?;
                        }
                    }
                }
            } else {
                self.generate_expression(settings)?;
            }
            self.write(")");
        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
            self.write("()");
        }
        self.write(")");
        Ok(())
    }

    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
        let property_name = match e.this.as_ref() {
            Expression::Identifier(id) => id.name.as_str(),
            Expression::Var(v) => v.this.as_str(),
            _ => "RANGE",
        };
        self.write_keyword(property_name);
        self.write("(");
        if let Some(min) = &e.min {
            self.write_keyword("MIN");
            self.write_space();
            self.generate_expression(min)?;
        }
        if let Some(max) = &e.max {
            self.write_space();
            self.write_keyword("MAX");
            self.write_space();
            self.generate_expression(max)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
        // Python: {local}DIRECTORY {this}{row_format}
        if e.local.is_some() {
            self.write_keyword("LOCAL ");
        }
        self.write_keyword("DIRECTORY");
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(row_format) = &e.row_format {
            self.write_space();
            self.generate_expression(row_format)?;
        }
        Ok(())
    }

    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
        // Redshift: DISTKEY(column)
        self.write_keyword("DISTKEY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
        self.write_keyword("DISTSTYLE");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
        // Python: "DISTRIBUTE BY" expressions
        self.write_keyword("DISTRIBUTE BY");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
        self.write_keyword("DISTRIBUTED BY");
        self.write_space();
        self.write(&e.kind);
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        if let Some(buckets) = &e.buckets {
            self.write_space();
            self.write_keyword("BUCKETS");
            self.write_space();
            self.generate_expression(buckets)?;
        }
        if let Some(order) = &e.order {
            self.write_space();
            self.generate_expression(order)?;
        }
        Ok(())
    }

    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
        // DOT_PRODUCT(vector1, vector2)
        self.write_keyword("DOT_PRODUCT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
        // Python: DROP{IF EXISTS }expressions
        self.write_keyword("DROP");
        if e.exists {
            self.write_keyword(" IF EXISTS ");
        } else {
            self.write_space();
        }
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
        // Python: DUPLICATE KEY (expressions)
        self.write_keyword("DUPLICATE KEY");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
        // ELT(index, str1, str2, ...)
        self.write_keyword("ELT");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
        // ENCODE(string, charset)
        self.write_keyword("ENCODE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(charset) = &e.charset {
            self.write(", ");
            self.generate_expression(charset)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
        // Python: [KEY ]ENCODE this [properties]
        if e.key.is_some() {
            self.write_keyword("KEY ");
        }
        self.write_keyword("ENCODE");
        self.write_space();
        self.generate_expression(&e.this)?;
        if !e.properties.is_empty() {
            self.write(" (");
            for (i, prop) in e.properties.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(prop)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
        // ENCRYPT(value, passphrase [, aad [, algorithm]])
        self.write_keyword("ENCRYPT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(passphrase) = &e.passphrase {
            self.write(", ");
            self.generate_expression(passphrase)?;
        }
        if let Some(aad) = &e.aad {
            self.write(", ");
            self.generate_expression(aad)?;
        }
        if let Some(method) = &e.encryption_method {
            self.write(", ");
            self.generate_expression(method)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
        self.write_keyword("ENCRYPT_RAW");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(key) = &e.key {
            self.write(", ");
            self.generate_expression(key)?;
        }
        if let Some(iv) = &e.iv {
            self.write(", ");
            self.generate_expression(iv)?;
        }
        if let Some(aad) = &e.aad {
            self.write(", ");
            self.generate_expression(aad)?;
        }
        if let Some(method) = &e.encryption_method {
            self.write(", ");
            self.generate_expression(method)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
        // MySQL: ENGINE = InnoDB
        self.write_keyword("ENGINE");
        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            self.write("=");
        } else {
            self.write(" = ");
        }
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
        // ENVIRONMENT (expressions)
        self.write_keyword("ENVIRONMENT");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_ephemeral_column_constraint(
        &mut self,
        e: &EphemeralColumnConstraint,
    ) -> Result<()> {
        // MySQL: EPHEMERAL [expr]
        self.write_keyword("EPHEMERAL");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
        // Snowflake: EQUAL_NULL(a, b)
        self.write_keyword("EQUAL_NULL");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
        use crate::dialects::DialectType;

        // PostgreSQL uses <-> operator syntax
        match self.config.dialect {
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
                self.generate_expression(&e.this)?;
                self.write(" <-> ");
                self.generate_expression(&e.expression)?;
            }
            _ => {
                // Other dialects use EUCLIDEAN_DISTANCE function
                self.write_keyword("EUCLIDEAN_DISTANCE");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", ");
                self.generate_expression(&e.expression)?;
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
        // EXECUTE AS CALLER|OWNER|user
        self.write_keyword("EXECUTE AS");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_export(&mut self, e: &Export) -> Result<()> {
        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
        self.write_keyword("EXPORT DATA");
        if let Some(connection) = &e.connection {
            self.write_space();
            self.write_keyword("WITH CONNECTION");
            self.write_space();
            self.generate_expression(connection)?;
        }
        if !e.options.is_empty() {
            self.write_space();
            self.generate_options_clause(&e.options)?;
        }
        self.write_space();
        self.write_keyword("AS");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
        // EXTERNAL [this]
        self.write_keyword("EXTERNAL");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
        // Python: {no}FALLBACK{protection}
        if e.no.is_some() {
            self.write_keyword("NO ");
        }
        self.write_keyword("FALLBACK");
        if e.protection.is_some() {
            self.write_keyword(" PROTECTION");
        }
        Ok(())
    }

    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
        // BigQuery: FARM_FINGERPRINT(value)
        self.write_keyword("FARM_FINGERPRINT");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
        self.write_keyword("FEATURES_AT_TIME");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(time) = &e.time {
            self.write(", ");
            self.generate_expression(time)?;
        }
        if let Some(num_rows) = &e.num_rows {
            self.write(", ");
            self.generate_expression(num_rows)?;
        }
        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
            self.write(", ");
            self.generate_expression(ignore_nulls)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
        let use_limit = !e.percent
            && !e.with_ties
            && e.count.is_some()
            && matches!(
                self.config.dialect,
                Some(DialectType::Spark)
                    | Some(DialectType::Hive)
                    | Some(DialectType::DuckDB)
                    | Some(DialectType::SQLite)
                    | Some(DialectType::MySQL)
                    | Some(DialectType::BigQuery)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::StarRocks)
                    | Some(DialectType::Doris)
                    | Some(DialectType::Athena)
                    | Some(DialectType::ClickHouse)
            );

        if use_limit {
            self.write_keyword("LIMIT");
            self.write_space();
            self.generate_expression(e.count.as_ref().unwrap())?;
            return Ok(());
        }

        // Python: FETCH direction count limit_options
        self.write_keyword("FETCH");
        if !e.direction.is_empty() {
            self.write_space();
            self.write_keyword(&e.direction);
        }
        if let Some(count) = &e.count {
            self.write_space();
            self.generate_expression(count)?;
        }
        // Generate PERCENT, ROWS, WITH TIES/ONLY
        if e.percent {
            self.write_keyword(" PERCENT");
        }
        if e.rows {
            self.write_keyword(" ROWS");
        }
        if e.with_ties {
            self.write_keyword(" WITH TIES");
        } else if e.rows {
            self.write_keyword(" ONLY");
        } else {
            self.write_keyword(" ROWS ONLY");
        }
        Ok(())
    }

    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
        // For Spark/Databricks without hive_format: USING this
        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
        if e.hive_format.is_some() {
            // Hive format: STORED AS ...
            self.write_keyword("STORED AS");
            self.write_space();
            if let Some(this) = &e.this {
                // Uppercase the format name (e.g., parquet -> PARQUET)
                if let Expression::Identifier(id) = this.as_ref() {
                    self.write_keyword(&id.name.to_uppercase());
                } else {
                    self.generate_expression(this)?;
                }
            }
        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
            // Hive: STORED AS format
            self.write_keyword("STORED AS");
            self.write_space();
            if let Some(this) = &e.this {
                if let Expression::Identifier(id) = this.as_ref() {
                    self.write_keyword(&id.name.to_uppercase());
                } else {
                    self.generate_expression(this)?;
                }
            }
        } else if matches!(
            self.config.dialect,
            Some(DialectType::Spark) | Some(DialectType::Databricks)
        ) {
            // Spark/Databricks: USING format (e.g., USING DELTA)
            self.write_keyword("USING");
            self.write_space();
            if let Some(this) = &e.this {
                self.generate_expression(this)?;
            }
        } else {
            // Snowflake/standard format
            self.write_keyword("FILE_FORMAT");
            self.write(" = ");
            if let Some(this) = &e.this {
                self.generate_expression(this)?;
            } else if !e.expressions.is_empty() {
                self.write("(");
                for (i, expr) in e.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
        // agg_func FILTER(WHERE condition)
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("FILTER");
        self.write("(");
        self.write_keyword("WHERE");
        self.write_space();
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
        // FLOAT64(this) or FLOAT64(this, expression)
        self.write_keyword("FLOAT64");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expr) = &e.expression {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
        // FOR this DO expression
        self.write_keyword("FOR");
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("DO");
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
        self.write_keyword("FOREIGN KEY");
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        if let Some(reference) = &e.reference {
            self.write_space();
            self.generate_expression(reference)?;
        }
        if let Some(delete) = &e.delete {
            self.write_space();
            self.write_keyword("ON DELETE");
            self.write_space();
            self.generate_expression(delete)?;
        }
        if let Some(update) = &e.update {
            self.write_space();
            self.write_keyword("ON UPDATE");
            self.write_space();
            self.generate_expression(update)?;
        }
        if !e.options.is_empty() {
            self.write_space();
            for (i, opt) in e.options.iter().enumerate() {
                if i > 0 {
                    self.write_space();
                }
                self.generate_expression(opt)?;
            }
        }
        Ok(())
    }

    fn generate_format(&mut self, e: &Format) -> Result<()> {
        // FORMAT(this, expressions...)
        self.write_keyword("FORMAT");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
        // Teradata: column (FORMAT 'format_string')
        self.generate_expression(&e.this)?;
        self.write(" (");
        self.write_keyword("FORMAT");
        self.write(" '");
        self.write(&e.format);
        self.write("')");
        Ok(())
    }

    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
        // Python: FREESPACE=this[PERCENT]
        self.write_keyword("FREESPACE");
        self.write("=");
        self.generate_expression(&e.this)?;
        if e.percent.is_some() {
            self.write_keyword(" PERCENT");
        }
        Ok(())
    }

    fn generate_from(&mut self, e: &From) -> Result<()> {
        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
        self.write_keyword("FROM");
        self.write_space();

        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
        // But keep commas when TABLESAMPLE is present
        use crate::dialects::DialectType;
        let has_tablesample = e
            .expressions
            .iter()
            .any(|expr| matches!(expr, Expression::TableSample(_)));
        let use_cross_join = !has_tablesample
            && matches!(
                self.config.dialect,
                Some(DialectType::BigQuery)
                    | Some(DialectType::Hive)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::SQLite)
                    | Some(DialectType::ClickHouse)
            );

        // Snowflake wraps standalone VALUES in FROM clause with parentheses
        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));

        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                if use_cross_join {
                    self.write(" CROSS JOIN ");
                } else {
                    self.write(", ");
                }
            }
            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
                self.write("(");
                self.generate_expression(expr)?;
                self.write(")");
            } else {
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
        // FROM_BASE(this, expression) - convert from base N
        self.write_keyword("FROM_BASE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
        self.generate_expression(&e.this)?;
        if let Some(zone) = &e.zone {
            self.write_space();
            self.write_keyword("AT TIME ZONE");
            self.write_space();
            self.generate_expression(zone)?;
            self.write_space();
            self.write_keyword("AT TIME ZONE");
            self.write(" 'UTC'");
        }
        Ok(())
    }

    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
        // GAP_FILL(this, ts_column, bucket_width, ...)
        self.write_keyword("GAP_FILL");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(ts_column) = &e.ts_column {
            self.write(", ");
            self.generate_expression(ts_column)?;
        }
        if let Some(bucket_width) = &e.bucket_width {
            self.write(", ");
            self.generate_expression(bucket_width)?;
        }
        if let Some(partitioning_columns) = &e.partitioning_columns {
            self.write(", ");
            self.generate_expression(partitioning_columns)?;
        }
        if let Some(value_columns) = &e.value_columns {
            self.write(", ");
            self.generate_expression(value_columns)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
        // GENERATE_DATE_ARRAY(start, end, step)
        self.write_keyword("GENERATE_DATE_ARRAY");
        self.write("(");
        let mut first = true;
        if let Some(start) = &e.start {
            self.generate_expression(start)?;
            first = false;
        }
        if let Some(end) = &e.end {
            if !first {
                self.write(", ");
            }
            self.generate_expression(end)?;
            first = false;
        }
        if let Some(step) = &e.step {
            if !first {
                self.write(", ");
            }
            self.generate_expression(step)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
        // ML.GENERATE_EMBEDDING(model, content, params)
        self.write_keyword("ML.GENERATE_EMBEDDING");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(params) = &e.params_struct {
            self.write(", ");
            self.generate_expression(params)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
        // GENERATE_SERIES(start, end, step)
        self.write_keyword("GENERATE_SERIES");
        self.write("(");
        let mut first = true;
        if let Some(start) = &e.start {
            self.generate_expression(start)?;
            first = false;
        }
        if let Some(end) = &e.end {
            if !first {
                self.write(", ");
            }
            self.generate_expression(end)?;
            first = false;
        }
        if let Some(step) = &e.step {
            if !first {
                self.write(", ");
            }
            self.generate_expression(step)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
        self.write("(");
        let mut first = true;
        if let Some(start) = &e.start {
            self.generate_expression(start)?;
            first = false;
        }
        if let Some(end) = &e.end {
            if !first {
                self.write(", ");
            }
            self.generate_expression(end)?;
            first = false;
        }
        if let Some(step) = &e.step {
            if !first {
                self.write(", ");
            }
            self.generate_expression(step)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_generated_as_identity_column_constraint(
        &mut self,
        e: &GeneratedAsIdentityColumnConstraint,
    ) -> Result<()> {
        use crate::dialects::DialectType;

        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
            self.write_keyword("AUTOINCREMENT");
            if let Some(start) = &e.start {
                self.write_keyword(" START ");
                self.generate_expression(start)?;
            }
            if let Some(increment) = &e.increment {
                self.write_keyword(" INCREMENT ");
                self.generate_expression(increment)?;
            }
            return Ok(());
        }

        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
        self.write_keyword("GENERATED");
        if let Some(this) = &e.this {
            // Check if it's a truthy boolean expression
            if let Expression::Boolean(b) = this.as_ref() {
                if b.value {
                    self.write_keyword(" ALWAYS");
                } else {
                    self.write_keyword(" BY DEFAULT");
                    if e.on_null.is_some() {
                        self.write_keyword(" ON NULL");
                    }
                }
            } else {
                self.write_keyword(" ALWAYS");
            }
        }
        self.write_keyword(" AS IDENTITY");
        // Add sequence options if any
        let has_options = e.start.is_some()
            || e.increment.is_some()
            || e.minvalue.is_some()
            || e.maxvalue.is_some();
        if has_options {
            self.write(" (");
            let mut first = true;
            if let Some(start) = &e.start {
                self.write_keyword("START WITH ");
                self.generate_expression(start)?;
                first = false;
            }
            if let Some(increment) = &e.increment {
                if !first {
                    self.write(" ");
                }
                self.write_keyword("INCREMENT BY ");
                self.generate_expression(increment)?;
                first = false;
            }
            if let Some(minvalue) = &e.minvalue {
                if !first {
                    self.write(" ");
                }
                self.write_keyword("MINVALUE ");
                self.generate_expression(minvalue)?;
                first = false;
            }
            if let Some(maxvalue) = &e.maxvalue {
                if !first {
                    self.write(" ");
                }
                self.write_keyword("MAXVALUE ");
                self.generate_expression(maxvalue)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_generated_as_row_column_constraint(
        &mut self,
        e: &GeneratedAsRowColumnConstraint,
    ) -> Result<()> {
        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
        self.write_keyword("GENERATED ALWAYS AS ROW ");
        if e.start.is_some() {
            self.write_keyword("START");
        } else {
            self.write_keyword("END");
        }
        if e.hidden.is_some() {
            self.write_keyword(" HIDDEN");
        }
        Ok(())
    }

    fn generate_get(&mut self, e: &Get) -> Result<()> {
        // GET this target properties
        self.write_keyword("GET");
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(target) = &e.target {
            self.write_space();
            self.generate_expression(target)?;
        }
        for prop in &e.properties {
            self.write_space();
            self.generate_expression(prop)?;
        }
        Ok(())
    }

    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
        // GetExtract generates bracket access: this[expression]
        self.generate_expression(&e.this)?;
        self.write("[");
        self.generate_expression(&e.expression)?;
        self.write("]");
        Ok(())
    }

    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
        // GETBIT(this, expression) or GET_BIT(this, expression)
        self.write_keyword("GETBIT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
        if e.is_role {
            self.write_keyword("ROLE");
            self.write_space();
        } else if e.is_group {
            self.write_keyword("GROUP");
            self.write_space();
        }
        self.write(&e.name.name);
        Ok(())
    }

    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
        // privilege(columns) or just privilege
        self.generate_expression(&e.this)?;
        if !e.expressions.is_empty() {
            self.write("(");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_group(&mut self, e: &Group) -> Result<()> {
        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
        self.write_keyword("GROUP BY");
        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
        match e.all {
            Some(true) => {
                self.write_space();
                self.write_keyword("ALL");
            }
            Some(false) => {
                self.write_space();
                self.write_keyword("DISTINCT");
            }
            None => {}
        }
        if !e.expressions.is_empty() {
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        // Handle CUBE, ROLLUP, GROUPING SETS
        if let Some(cube) = &e.cube {
            if !e.expressions.is_empty() {
                self.write(", ");
            } else {
                self.write_space();
            }
            self.generate_expression(cube)?;
        }
        if let Some(rollup) = &e.rollup {
            if !e.expressions.is_empty() || e.cube.is_some() {
                self.write(", ");
            } else {
                self.write_space();
            }
            self.generate_expression(rollup)?;
        }
        if let Some(grouping_sets) = &e.grouping_sets {
            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
                self.write(", ");
            } else {
                self.write_space();
            }
            self.generate_expression(grouping_sets)?;
        }
        if let Some(totals) = &e.totals {
            self.write_space();
            self.write_keyword("WITH TOTALS");
            self.generate_expression(totals)?;
        }
        Ok(())
    }

    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
        // GROUP BY expressions
        self.write_keyword("GROUP BY");
        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
        match e.all {
            Some(true) => {
                self.write_space();
                self.write_keyword("ALL");
            }
            Some(false) => {
                self.write_space();
                self.write_keyword("DISTINCT");
            }
            None => {}
        }

        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
        // These are represented as Cube/Rollup expressions with empty expressions at the end
        let mut trailing_cube = false;
        let mut trailing_rollup = false;
        let mut regular_expressions: Vec<&Expression> = Vec::new();

        for expr in &e.expressions {
            match expr {
                Expression::Cube(c) if c.expressions.is_empty() => {
                    trailing_cube = true;
                }
                Expression::Rollup(r) if r.expressions.is_empty() => {
                    trailing_rollup = true;
                }
                _ => {
                    regular_expressions.push(expr);
                }
            }
        }

        // In pretty mode, put columns on separate lines
        if self.config.pretty {
            self.write_newline();
            self.indent_level += 1;
            for (i, expr) in regular_expressions.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_expression(expr)?;
            }
            self.indent_level -= 1;
        } else {
            self.write_space();
            for (i, expr) in regular_expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }

        // Output trailing WITH CUBE or WITH ROLLUP
        if trailing_cube {
            self.write_space();
            self.write_keyword("WITH CUBE");
        } else if trailing_rollup {
            self.write_space();
            self.write_keyword("WITH ROLLUP");
        }

        // ClickHouse: WITH TOTALS
        if e.totals {
            self.write_space();
            self.write_keyword("WITH TOTALS");
        }

        Ok(())
    }

    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
        // GROUPING(col1, col2, ...)
        self.write_keyword("GROUPING");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
        // GROUPING_ID(col1, col2, ...)
        self.write_keyword("GROUPING_ID");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
        self.write_keyword("GROUPING SETS");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
        // HASH_AGG(this, expressions...)
        self.write_keyword("HASH_AGG");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_having(&mut self, e: &Having) -> Result<()> {
        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
        self.write_keyword("HAVING");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
        // Python: this HAVING MAX|MIN expression
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("HAVING");
        self.write_space();
        if e.max.is_some() {
            self.write_keyword("MAX");
        } else {
            self.write_keyword("MIN");
        }
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
        use crate::dialects::DialectType;
        // DuckDB: convert dollar-tagged strings to single-quoted
        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
            // Extract the string content and output as single-quoted
            if let Expression::Literal(Literal::String(ref s)) = *e.this {
                return self.generate_string_literal(s);
            }
        }
        // PostgreSQL: preserve dollar-quoting
        if matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
        ) {
            self.write("$");
            if let Some(tag) = &e.tag {
                self.generate_expression(tag)?;
            }
            self.write("$");
            self.generate_expression(&e.this)?;
            self.write("$");
            if let Some(tag) = &e.tag {
                self.generate_expression(tag)?;
            }
            self.write("$");
            return Ok(());
        }
        // Default: output as dollar-tagged
        self.write("$");
        if let Some(tag) = &e.tag {
            self.generate_expression(tag)?;
        }
        self.write("$");
        self.generate_expression(&e.this)?;
        self.write("$");
        if let Some(tag) = &e.tag {
            self.generate_expression(tag)?;
        }
        self.write("$");
        Ok(())
    }

    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
        // HEX_ENCODE(this)
        self.write_keyword("HEX_ENCODE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
        // Python: this (kind => expression)
        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
        match e.this.as_ref() {
            Expression::Identifier(id) => self.write(&id.name),
            other => self.generate_expression(other)?,
        }
        self.write(" (");
        self.write(&e.kind);
        self.write(" => ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
        // HLL(this, expressions...)
        self.write_keyword("HLL");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
        // Python: IN|OUT|IN OUT
        if e.input_.is_some() && e.output.is_some() {
            self.write_keyword("IN OUT");
        } else if e.input_.is_some() {
            self.write_keyword("IN");
        } else if e.output.is_some() {
            self.write_keyword("OUT");
        }
        Ok(())
    }

    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
        // Python: INCLUDE this [column_def] [AS alias]
        self.write_keyword("INCLUDE");
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(column_def) = &e.column_def {
            self.write_space();
            self.generate_expression(column_def)?;
        }
        if let Some(alias) = &e.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.write(alias);
        }
        Ok(())
    }

    fn generate_index(&mut self, e: &Index) -> Result<()> {
        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
        if e.unique {
            self.write_keyword("UNIQUE");
            self.write_space();
        }
        if e.primary.is_some() {
            self.write_keyword("PRIMARY");
            self.write_space();
        }
        if e.amp.is_some() {
            self.write_keyword("AMP");
            self.write_space();
        }
        if e.table.is_none() {
            self.write_keyword("INDEX");
            self.write_space();
        }
        if let Some(name) = &e.this {
            self.generate_expression(name)?;
            self.write_space();
        }
        if let Some(table) = &e.table {
            self.write_keyword("ON");
            self.write_space();
            self.generate_expression(table)?;
        }
        if !e.params.is_empty() {
            self.write("(");
            for (i, param) in e.params.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(param)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
        if let Some(kind) = &e.kind {
            self.write(kind);
            self.write_space();
        }
        self.write_keyword("INDEX");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if let Some(index_type) = &e.index_type {
            self.write_space();
            self.write_keyword("USING");
            self.write_space();
            self.generate_expression(index_type)?;
        }
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        for opt in &e.options {
            self.write_space();
            self.generate_expression(opt)?;
        }
        Ok(())
    }

    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
        if let Some(key_block_size) = &e.key_block_size {
            self.write_keyword("KEY_BLOCK_SIZE");
            self.write(" = ");
            self.generate_expression(key_block_size)?;
        } else if let Some(using) = &e.using {
            self.write_keyword("USING");
            self.write_space();
            self.generate_expression(using)?;
        } else if let Some(parser) = &e.parser {
            self.write_keyword("WITH PARSER");
            self.write_space();
            self.generate_expression(parser)?;
        } else if let Some(comment) = &e.comment {
            self.write_keyword("COMMENT");
            self.write_space();
            self.generate_expression(comment)?;
        } else if let Some(visible) = &e.visible {
            self.generate_expression(visible)?;
        } else if let Some(engine_attr) = &e.engine_attr {
            self.write_keyword("ENGINE_ATTRIBUTE");
            self.write(" = ");
            self.generate_expression(engine_attr)?;
        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
            self.write(" = ");
            self.generate_expression(secondary_engine_attr)?;
        }
        Ok(())
    }

    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
        if let Some(using) = &e.using {
            self.write_keyword("USING");
            self.write_space();
            self.generate_expression(using)?;
        }
        if !e.columns.is_empty() {
            self.write("(");
            for (i, col) in e.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(col)?;
            }
            self.write(")");
        }
        if let Some(partition_by) = &e.partition_by {
            self.write_space();
            self.write_keyword("PARTITION BY");
            self.write_space();
            self.generate_expression(partition_by)?;
        }
        if let Some(where_) = &e.where_ {
            self.write_space();
            self.generate_expression(where_)?;
        }
        if let Some(include) = &e.include {
            self.write_space();
            self.write_keyword("INCLUDE");
            self.write(" (");
            self.generate_expression(include)?;
            self.write(")");
        }
        if let Some(with_storage) = &e.with_storage {
            self.write_space();
            self.write_keyword("WITH");
            self.write(" (");
            self.generate_expression(with_storage)?;
            self.write(")");
        }
        if let Some(tablespace) = &e.tablespace {
            self.write_space();
            self.write_keyword("USING INDEX TABLESPACE");
            self.write_space();
            self.generate_expression(tablespace)?;
        }
        Ok(())
    }

    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
        // Python: this INDEX [FOR target] (expressions)
        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
        if let Expression::Identifier(id) = &*e.this {
            self.write_keyword(&id.name);
        } else {
            self.generate_expression(&e.this)?;
        }
        self.write_space();
        self.write_keyword("INDEX");
        if let Some(target) = &e.target {
            self.write_space();
            self.write_keyword("FOR");
            self.write_space();
            if let Expression::Identifier(id) = &**target {
                self.write_keyword(&id.name);
            } else {
                self.generate_expression(target)?;
            }
        }
        // Always output parentheses (even if empty, e.g. USE INDEX ())
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
        // INHERITS (table1, table2, ...)
        self.write_keyword("INHERITS");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
        // INPUT(model)
        self.write_keyword("INPUT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
        if let Some(input_format) = &e.input_format {
            self.write_keyword("INPUTFORMAT");
            self.write_space();
            self.generate_expression(input_format)?;
        }
        if let Some(output_format) = &e.output_format {
            if e.input_format.is_some() {
                self.write(" ");
            }
            self.write_keyword("OUTPUTFORMAT");
            self.write_space();
            self.generate_expression(output_format)?;
        }
        Ok(())
    }

    fn generate_install(&mut self, e: &Install) -> Result<()> {
        // [FORCE] INSTALL extension [FROM source]
        if e.force.is_some() {
            self.write_keyword("FORCE");
            self.write_space();
        }
        self.write_keyword("INSTALL");
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(from) = &e.from_ {
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            self.generate_expression(from)?;
        }
        Ok(())
    }

    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
        // INTERVAL 'expression' unit
        self.write_keyword("INTERVAL");
        self.write_space();
        // When a unit is specified and the expression is a number,
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write_space();
            self.write(unit);
        }
        Ok(())
    }

    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
        // unit TO unit (e.g., HOUR TO SECOND)
        self.write(&format!("{:?}", e.this).to_uppercase());
        self.write_space();
        self.write_keyword("TO");
        self.write_space();
        self.write(&format!("{:?}", e.expression).to_uppercase());
        Ok(())
    }

    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
        // INTO [TEMPORARY|UNLOGGED] table
        self.write_keyword("INTO");
        if e.temporary {
            self.write_keyword(" TEMPORARY");
        }
        if e.unlogged.is_some() {
            self.write_keyword(" UNLOGGED");
        }
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
        // Python: this expression (e.g., _utf8 'string')
        self.generate_expression(&e.this)?;
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
        self.write_keyword("WITH");
        if e.no.is_some() {
            self.write_keyword(" NO");
        }
        if e.concurrent.is_some() {
            self.write_keyword(" CONCURRENT");
        }
        self.write_keyword(" ISOLATED LOADING");
        if let Some(target) = &e.target {
            self.write_space();
            self.generate_expression(target)?;
        }
        Ok(())
    }

    fn generate_json(&mut self, e: &JSON) -> Result<()> {
        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
        self.write_keyword("JSON");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if let Some(with_) = &e.with_ {
            // Check if it's a truthy boolean
            if let Expression::Boolean(b) = with_.as_ref() {
                if b.value {
                    self.write_keyword(" WITH");
                } else {
                    self.write_keyword(" WITHOUT");
                }
            }
        }
        if e.unique {
            self.write_keyword(" UNIQUE KEYS");
        }
        Ok(())
    }

    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
        self.write_keyword("JSON_ARRAY");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        if let Some(null_handling) = &e.null_handling {
            self.write_space();
            self.generate_expression(null_handling)?;
        }
        if let Some(return_type) = &e.return_type {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            self.generate_expression(return_type)?;
        }
        if e.strict.is_some() {
            self.write_space();
            self.write_keyword("STRICT");
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
        self.write_keyword("JSON_ARRAYAGG");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(order) = &e.order {
            self.write_space();
            // Order is stored as an OrderBy expression
            if let Expression::OrderBy(ob) = order.as_ref() {
                self.write_keyword("ORDER BY");
                self.write_space();
                for (i, ord) in ob.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_ordered(ord)?;
                }
            } else {
                // Fallback: generate the expression directly
                self.generate_expression(order)?;
            }
        }
        if let Some(null_handling) = &e.null_handling {
            self.write_space();
            self.generate_expression(null_handling)?;
        }
        if let Some(return_type) = &e.return_type {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            self.generate_expression(return_type)?;
        }
        if e.strict.is_some() {
            self.write_space();
            self.write_keyword("STRICT");
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
        self.write_keyword("JSON_OBJECTAGG");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        if let Some(null_handling) = &e.null_handling {
            self.write_space();
            self.generate_expression(null_handling)?;
        }
        if let Some(unique_keys) = &e.unique_keys {
            self.write_space();
            if let Expression::Boolean(b) = unique_keys.as_ref() {
                if b.value {
                    self.write_keyword("WITH UNIQUE KEYS");
                } else {
                    self.write_keyword("WITHOUT UNIQUE KEYS");
                }
            }
        }
        if let Some(return_type) = &e.return_type {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            self.generate_expression(return_type)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
        // JSON_ARRAY_APPEND(this, path, value, ...)
        self.write_keyword("JSON_ARRAY_APPEND");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
        // JSON_ARRAY_CONTAINS(this, expression)
        self.write_keyword("JSON_ARRAY_CONTAINS");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
        // JSON_ARRAY_INSERT(this, path, value, ...)
        self.write_keyword("JSON_ARRAY_INSERT");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
        // JSONB_EXISTS(this, path)
        self.write_keyword("JSONB_EXISTS");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(path) = &e.path {
            self.write(", ");
            self.generate_expression(path)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
        // JSONB_EXTRACT_SCALAR(this, expression)
        self.write_keyword("JSONB_EXTRACT_SCALAR");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
        // JSONB_OBJECT_AGG(this, expression)
        self.write_keyword("JSONB_OBJECT_AGG");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
        if let Some(nested_schema) = &e.nested_schema {
            self.write_keyword("NESTED");
            if let Some(path) = &e.path {
                self.write_space();
                self.write_keyword("PATH");
                self.write_space();
                self.generate_expression(path)?;
            }
            self.write_space();
            self.generate_expression(nested_schema)?;
        } else {
            if let Some(this) = &e.this {
                self.generate_expression(this)?;
            }
            if let Some(kind) = &e.kind {
                self.write_space();
                self.write(kind);
            }
            if let Some(path) = &e.path {
                self.write_space();
                self.write_keyword("PATH");
                self.write_space();
                self.generate_expression(path)?;
            }
            if e.ordinality.is_some() {
                self.write_keyword(" FOR ORDINALITY");
            }
        }
        Ok(())
    }

    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
        self.write_keyword("JSON_EXISTS");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(path) = &e.path {
            self.write(", ");
            self.generate_expression(path)?;
        }
        if let Some(passing) = &e.passing {
            self.write_space();
            self.write_keyword("PASSING");
            self.write_space();
            self.generate_expression(passing)?;
        }
        if let Some(on_condition) = &e.on_condition {
            self.write_space();
            self.generate_expression(on_condition)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
        self.generate_expression(&e.this)?;
        self.write(".:");
        self.generate_data_type(&e.to)?;
        Ok(())
    }

    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
        // JSON_EXTRACT_ARRAY(this, expression)
        self.write_keyword("JSON_EXTRACT_ARRAY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expr) = &e.expression {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
        if let Some(option) = &e.option {
            self.generate_expression(option)?;
            self.write_space();
        }
        self.write_keyword("QUOTES");
        if e.scalar.is_some() {
            self.write_keyword(" SCALAR_ONLY");
        }
        Ok(())
    }

    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
        // JSON_EXTRACT_SCALAR(this, expression)
        self.write_keyword("JSON_EXTRACT_SCALAR");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
        // Otherwise output JSON_EXTRACT(this, expression)
        if e.variant_extract.is_some() {
            use crate::dialects::DialectType;
            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
                self.generate_expression(&e.this)?;
                self.write(":");
                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
                // We need to output it without quotes
                match e.expression.as_ref() {
                    Expression::Literal(Literal::String(s)) => {
                        self.write(s);
                    }
                    _ => {
                        // Fallback: generate as-is (shouldn't happen in typical cases)
                        self.generate_expression(&e.expression)?;
                    }
                }
            } else {
                // Snowflake and others: use GET_PATH(col, 'path')
                self.write_keyword("GET_PATH");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", ");
                self.generate_expression(&e.expression)?;
                self.write(")");
            }
        } else {
            self.write_keyword("JSON_EXTRACT");
            self.write("(");
            self.generate_expression(&e.this)?;
            self.write(", ");
            self.generate_expression(&e.expression)?;
            for expr in &e.expressions {
                self.write(", ");
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
        // Output: {expr} FORMAT JSON
        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
            self.write_space();
        }
        self.write_keyword("FORMAT JSON");
        Ok(())
    }

    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
        // key: value (for JSON objects)
        self.generate_expression(&e.this)?;
        self.write(": ");
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
        // JSON_KEYS(this, expression, expressions...)
        self.write_keyword("JSON_KEYS");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expr) = &e.expression {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
        // JSON_KEYS(this, expression)
        self.write_keyword("JSON_KEYS");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expr) = &e.expression {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
        // The path components are concatenated without spaces
        let mut path_str = String::new();
        for expr in &e.expressions {
            match expr {
                Expression::JSONPathRoot(_) => {
                    path_str.push('$');
                }
                Expression::JSONPathKey(k) => {
                    // .key or ."key" (quote if key has special characters)
                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
                        k.this.as_ref()
                    {
                        path_str.push('.');
                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
                        if needs_quoting {
                            path_str.push('"');
                            path_str.push_str(s);
                            path_str.push('"');
                        } else {
                            path_str.push_str(s);
                        }
                    }
                }
                Expression::JSONPathSubscript(s) => {
                    // [index]
                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
                        s.this.as_ref()
                    {
                        path_str.push('[');
                        path_str.push_str(n);
                        path_str.push(']');
                    }
                }
                _ => {
                    // For other path parts, try to generate them
                    let mut temp_gen = Self::with_config(self.config.clone());
                    temp_gen.generate_expression(expr)?;
                    path_str.push_str(&temp_gen.output);
                }
            }
        }
        // Output as quoted string
        self.write("'");
        self.write(&path_str);
        self.write("'");
        Ok(())
    }

    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
        // JSON path filter: ?(predicate)
        self.write("?(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
        // JSON path key: .key or ["key"]
        self.write(".");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
        // JSON path recursive descent: ..
        self.write("..");
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_json_path_root(&mut self) -> Result<()> {
        // JSON path root: $
        self.write("$");
        Ok(())
    }

    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
        // JSON path script: (expression)
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
        // JSON path selector: *
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
        // JSON path slice: [start:end:step]
        self.write("[");
        if let Some(start) = &e.start {
            self.generate_expression(start)?;
        }
        self.write(":");
        if let Some(end) = &e.end {
            self.generate_expression(end)?;
        }
        if let Some(step) = &e.step {
            self.write(":");
            self.generate_expression(step)?;
        }
        self.write("]");
        Ok(())
    }

    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
        // JSON path subscript: [index] or [*]
        self.write("[");
        self.generate_expression(&e.this)?;
        self.write("]");
        Ok(())
    }

    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
        // JSON path union: [key1, key2, ...]
        self.write("[");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write("]");
        Ok(())
    }

    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
        // JSON_REMOVE(this, path1, path2, ...)
        self.write_keyword("JSON_REMOVE");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
        // COLUMNS(col1 type, col2 type, ...)
        // When pretty printing and content is too wide, format with each column on a separate line
        self.write_keyword("COLUMNS");
        self.write("(");

        if self.config.pretty && !e.expressions.is_empty() {
            // First, generate all expressions into strings to check width
            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
            for expr in &e.expressions {
                let mut temp_gen = Generator::with_config(self.config.clone());
                temp_gen.generate_expression(expr)?;
                expr_strings.push(temp_gen.output);
            }

            // Check if total width exceeds max_text_width
            if self.too_wide(&expr_strings) {
                // Pretty print: each column on its own line
                self.write_newline();
                self.indent_level += 1;
                for (i, expr_str) in expr_strings.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                        self.write_newline();
                    }
                    self.write_indent();
                    self.write(expr_str);
                }
                self.write_newline();
                self.indent_level -= 1;
                self.write_indent();
            } else {
                // Compact: all on one line
                for (i, expr_str) in expr_strings.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.write(expr_str);
                }
            }
        } else {
            // Non-pretty mode: compact format
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
        // JSON_SET(this, path, value, ...)
        self.write_keyword("JSON_SET");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
        // JSON_STRIP_NULLS(this, expression)
        self.write_keyword("JSON_STRIP_NULLS");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expr) = &e.expression {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
        self.write_keyword("JSON_TABLE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(path) = &e.path {
            self.write(", ");
            self.generate_expression(path)?;
        }
        if let Some(error_handling) = &e.error_handling {
            self.write_space();
            self.generate_expression(error_handling)?;
        }
        if let Some(empty_handling) = &e.empty_handling {
            self.write_space();
            self.generate_expression(empty_handling)?;
        }
        if let Some(schema) = &e.schema {
            self.write_space();
            self.generate_expression(schema)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
        // JSON_TYPE(this)
        self.write_keyword("JSON_TYPE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
        // JSON_VALUE(this, path RETURNING type ON condition)
        self.write_keyword("JSON_VALUE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(path) = &e.path {
            self.write(", ");
            self.generate_expression(path)?;
        }
        if let Some(returning) = &e.returning {
            self.write_space();
            self.write_keyword("RETURNING");
            self.write_space();
            self.generate_expression(returning)?;
        }
        if let Some(on_condition) = &e.on_condition {
            self.write_space();
            self.generate_expression(on_condition)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
        // JSON_VALUE_ARRAY(this)
        self.write_keyword("JSON_VALUE_ARRAY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
        // JAROWINKLER_SIMILARITY(str1, str2)
        self.write_keyword("JAROWINKLER_SIMILARITY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
        // Python: this(expressions)
        self.generate_expression(&e.this)?;
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
        // Python: {no}{local}{dual}{before}{after}JOURNAL
        if e.no.is_some() {
            self.write_keyword("NO ");
        }
        if let Some(local) = &e.local {
            self.generate_expression(local)?;
            self.write_space();
        }
        if e.dual.is_some() {
            self.write_keyword("DUAL ");
        }
        if e.before.is_some() {
            self.write_keyword("BEFORE ");
        }
        if e.after.is_some() {
            self.write_keyword("AFTER ");
        }
        self.write_keyword("JOURNAL");
        Ok(())
    }

    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
        // LANGUAGE language_name
        self.write_keyword("LANGUAGE");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
        if e.view.is_some() {
            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
            self.write_keyword("LATERAL VIEW");
            if e.outer.is_some() {
                self.write_space();
                self.write_keyword("OUTER");
            }
            self.write_space();
            self.generate_expression(&e.this)?;
            if let Some(alias) = &e.alias {
                self.write_space();
                self.write(alias);
            }
        } else {
            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
            self.write_keyword("LATERAL");
            self.write_space();
            self.generate_expression(&e.this)?;
            if e.ordinality.is_some() {
                self.write_space();
                self.write_keyword("WITH ORDINALITY");
            }
            if let Some(alias) = &e.alias {
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.write(alias);
                if !e.column_aliases.is_empty() {
                    self.write("(");
                    for (i, col) in e.column_aliases.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.write(col);
                    }
                    self.write(")");
                }
            }
        }
        Ok(())
    }

    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
        // Python: LIKE this [options]
        self.write_keyword("LIKE");
        self.write_space();
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write_space();
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
        self.write_keyword("LIMIT");
        self.write_space();
        self.write_limit_expr(&e.this)?;
        if e.percent {
            self.write_space();
            self.write_keyword("PERCENT");
        }
        Ok(())
    }

    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
        if e.percent.is_some() {
            self.write_keyword(" PERCENT");
        }
        if e.rows.is_some() {
            self.write_keyword(" ROWS");
        }
        if e.with_ties.is_some() {
            self.write_keyword(" WITH TIES");
        } else if e.rows.is_some() {
            self.write_keyword(" ONLY");
        }
        Ok(())
    }

    fn generate_list(&mut self, e: &List) -> Result<()> {
        use crate::dialects::DialectType;
        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));

        // Check if this is a subquery-based list (LIST(SELECT ...))
        if e.expressions.len() == 1 {
            if let Expression::Select(_) = &e.expressions[0] {
                self.write_keyword("LIST");
                self.write("(");
                self.generate_expression(&e.expressions[0])?;
                self.write(")");
                return Ok(());
            }
        }

        // For Materialize, output as LIST[expr, expr, ...]
        if is_materialize {
            self.write_keyword("LIST");
            self.write("[");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write("]");
        } else {
            // For other dialects, output as LIST(expr, expr, ...)
            self.write_keyword("LIST");
            self.write("(");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
        // Check if this is a subquery-based map (MAP(SELECT ...))
        if let Expression::Select(_) = &*e.this {
            self.write_keyword("MAP");
            self.write("(");
            self.generate_expression(&e.this)?;
            self.write(")");
            return Ok(());
        }

        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));

        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
        self.write_keyword("MAP");
        if is_duckdb {
            self.write(" {");
        } else {
            self.write("[");
        }
        if let Expression::Struct(s) = &*e.this {
            for (i, (_, expr)) in s.fields.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                if let Expression::PropertyEQ(op) = expr {
                    self.generate_expression(&op.left)?;
                    if is_duckdb {
                        self.write(": ");
                    } else {
                        self.write(" => ");
                    }
                    self.generate_expression(&op.right)?;
                } else {
                    self.generate_expression(expr)?;
                }
            }
        }
        if is_duckdb {
            self.write("}");
        } else {
            self.write("]");
        }
        Ok(())
    }

    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
        // Python: LOCALTIME or LOCALTIME(precision)
        self.write_keyword("LOCALTIME");
        if let Some(precision) = &e.this {
            self.write("(");
            self.generate_expression(precision)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
        self.write_keyword("LOCALTIMESTAMP");
        if let Some(precision) = &e.this {
            self.write("(");
            self.generate_expression(precision)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
        // LOCATION 'path'
        self.write_keyword("LOCATION");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
        if e.update.is_some() {
            if e.key.is_some() {
                self.write_keyword("FOR NO KEY UPDATE");
            } else {
                self.write_keyword("FOR UPDATE");
            }
        } else {
            if e.key.is_some() {
                self.write_keyword("FOR KEY SHARE");
            } else {
                self.write_keyword("FOR SHARE");
            }
        }
        if !e.expressions.is_empty() {
            self.write_keyword(" OF ");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        // Handle wait option following Python sqlglot convention:
        // - Boolean(true) -> NOWAIT
        // - Boolean(false) -> SKIP LOCKED
        // - Literal (number) -> WAIT n
        if let Some(wait) = &e.wait {
            match wait.as_ref() {
                Expression::Boolean(b) => {
                    if b.value {
                        self.write_keyword(" NOWAIT");
                    } else {
                        self.write_keyword(" SKIP LOCKED");
                    }
                }
                _ => {
                    // It's a literal (number), output WAIT n
                    self.write_keyword(" WAIT ");
                    self.generate_expression(wait)?;
                }
            }
        }
        Ok(())
    }

    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
        // LOCK property
        self.write_keyword("LOCK");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
        self.write_keyword("LOCKING");
        self.write_space();
        self.write(&e.kind);
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if let Some(for_or_in) = &e.for_or_in {
            self.write_space();
            self.generate_expression(for_or_in)?;
        }
        if let Some(lock_type) = &e.lock_type {
            self.write_space();
            self.generate_expression(lock_type)?;
        }
        if e.override_.is_some() {
            self.write_keyword(" OVERRIDE");
        }
        Ok(())
    }

    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
        // this expression
        self.generate_expression(&e.this)?;
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
        // [NO] LOG
        if e.no.is_some() {
            self.write_keyword("NO ");
        }
        self.write_keyword("LOG");
        Ok(())
    }

    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
        // MD5(this, expressions...)
        self.write_keyword("MD5");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
        // ML.FORECAST(model, [params])
        self.write_keyword("ML.FORECAST");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        if let Some(params) = &e.params_struct {
            self.write(", ");
            self.generate_expression(params)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
        // ML.TRANSLATE(model, input, [params])
        self.write_keyword("ML.TRANSLATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(params) = &e.params_struct {
            self.write(", ");
            self.generate_expression(params)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
        // MAKE_INTERVAL(years => x, months => y, ...)
        self.write_keyword("MAKE_INTERVAL");
        self.write("(");
        let mut first = true;
        if let Some(year) = &e.year {
            self.write("years => ");
            self.generate_expression(year)?;
            first = false;
        }
        if let Some(month) = &e.month {
            if !first {
                self.write(", ");
            }
            self.write("months => ");
            self.generate_expression(month)?;
            first = false;
        }
        if let Some(week) = &e.week {
            if !first {
                self.write(", ");
            }
            self.write("weeks => ");
            self.generate_expression(week)?;
            first = false;
        }
        if let Some(day) = &e.day {
            if !first {
                self.write(", ");
            }
            self.write("days => ");
            self.generate_expression(day)?;
            first = false;
        }
        if let Some(hour) = &e.hour {
            if !first {
                self.write(", ");
            }
            self.write("hours => ");
            self.generate_expression(hour)?;
            first = false;
        }
        if let Some(minute) = &e.minute {
            if !first {
                self.write(", ");
            }
            self.write("mins => ");
            self.generate_expression(minute)?;
            first = false;
        }
        if let Some(second) = &e.second {
            if !first {
                self.write(", ");
            }
            self.write("secs => ");
            self.generate_expression(second)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
        // MANHATTAN_DISTANCE(vector1, vector2)
        self.write_keyword("MANHATTAN_DISTANCE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_map(&mut self, e: &Map) -> Result<()> {
        // MAP(key1, value1, key2, value2, ...)
        self.write_keyword("MAP");
        self.write("(");
        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(key)?;
            self.write(", ");
            self.generate_expression(value)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
        // MAP_CAT(map1, map2)
        self.write_keyword("MAP_CAT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
        // MAP_DELETE(map, key1, key2, ...)
        self.write_keyword("MAP_DELETE");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
        // MAP_INSERT(map, key, value, [update_flag])
        self.write_keyword("MAP_INSERT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(key) = &e.key {
            self.write(", ");
            self.generate_expression(key)?;
        }
        if let Some(value) = &e.value {
            self.write(", ");
            self.generate_expression(value)?;
        }
        if let Some(update_flag) = &e.update_flag {
            self.write(", ");
            self.generate_expression(update_flag)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
        // MAP_PICK(map, key1, key2, ...)
        self.write_keyword("MAP_PICK");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_masking_policy_column_constraint(
        &mut self,
        e: &MaskingPolicyColumnConstraint,
    ) -> Result<()> {
        // Python: MASKING POLICY name [USING (cols)]
        self.write_keyword("MASKING POLICY");
        self.write_space();
        self.generate_expression(&e.this)?;
        if !e.expressions.is_empty() {
            self.write_keyword(" USING");
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
        if matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
        ) {
            if e.expressions.len() > 1 {
                self.write("(");
            }
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write_keyword(" OR ");
                }
                self.generate_expression(expr)?;
                self.write_space();
                self.write("@@");
                self.write_space();
                self.generate_expression(&e.this)?;
            }
            if e.expressions.len() > 1 {
                self.write(")");
            }
            return Ok(());
        }

        // MATCH(columns) AGAINST(expr [modifier])
        self.write_keyword("MATCH");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        self.write_keyword(" AGAINST");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(modifier) = &e.modifier {
            self.write_space();
            self.generate_expression(modifier)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
        // Python: [window_frame] this
        if let Some(window_frame) = &e.window_frame {
            self.write(&format!("{:?}", window_frame).to_uppercase());
            self.write_space();
        }
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
        // MATERIALIZED [this]
        self.write_keyword("MATERIALIZED");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
        // MERGE INTO target USING source ON condition WHEN ...
        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
        if let Some(with_) = &e.with_ {
            self.generate_expression(with_)?;
            self.write_space();
        }
        self.write_keyword("MERGE INTO");
        self.write_space();
        self.generate_expression(&e.this)?;

        // USING clause - newline before in pretty mode
        if self.config.pretty {
            self.write_newline();
            self.write_indent();
        } else {
            self.write_space();
        }
        self.write_keyword("USING");
        self.write_space();
        self.generate_expression(&e.using)?;

        // ON clause - newline before in pretty mode
        if let Some(on) = &e.on {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.write_keyword("ON");
            self.write_space();
            self.generate_expression(on)?;
        }
        // DuckDB USING (key_columns) clause
        if let Some(using_cond) = &e.using_cond {
            self.write_space();
            self.write_keyword("USING");
            self.write_space();
            self.write("(");
            // using_cond is a Tuple containing the column identifiers
            if let Expression::Tuple(tuple) = using_cond.as_ref() {
                for (i, col) in tuple.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(col)?;
                }
            } else {
                self.generate_expression(using_cond)?;
            }
            self.write(")");
        }
        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
        if matches!(
            self.config.dialect,
            Some(crate::DialectType::PostgreSQL)
                | Some(crate::DialectType::Redshift)
                | Some(crate::DialectType::Trino)
                | Some(crate::DialectType::Presto)
                | Some(crate::DialectType::Athena)
        ) {
            let mut names = Vec::new();
            match e.this.as_ref() {
                Expression::Alias(a) => {
                    // e.g., "x AS z" -> strip both "x" and "z"
                    if let Expression::Table(t) = &a.this {
                        names.push(t.name.name.clone());
                    } else if let Expression::Identifier(id) = &a.this {
                        names.push(id.name.clone());
                    }
                    names.push(a.alias.name.clone());
                }
                Expression::Table(t) => {
                    names.push(t.name.name.clone());
                }
                Expression::Identifier(id) => {
                    names.push(id.name.clone());
                }
                _ => {}
            }
            self.merge_strip_qualifiers = names;
        }

        // WHEN clauses - newline before each in pretty mode
        if let Some(whens) = &e.whens {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.generate_expression(whens)?;
        }

        // Restore merge_strip_qualifiers
        self.merge_strip_qualifiers = saved_merge_strip;

        // OUTPUT/RETURNING clause - newline before in pretty mode
        if let Some(returning) = &e.returning {
            if self.config.pretty {
                self.write_newline();
                self.write_indent();
            } else {
                self.write_space();
            }
            self.generate_expression(returning)?;
        }
        Ok(())
    }

    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
        if e.no.is_some() {
            self.write_keyword("NO MERGEBLOCKRATIO");
        } else if e.default.is_some() {
            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
        } else {
            self.write_keyword("MERGEBLOCKRATIO");
            self.write("=");
            if let Some(this) = &e.this {
                self.generate_expression(this)?;
            }
            if e.percent.is_some() {
                self.write_keyword(" PERCENT");
            }
        }
        Ok(())
    }

    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
        self.write_keyword("TTL");
        let pretty_clickhouse = self.config.pretty
            && matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::ClickHouse)
            );

        if pretty_clickhouse {
            self.write_newline();
            self.indent_level += 1;
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_expression(expr)?;
            }
            self.indent_level -= 1;
        } else {
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }

        if let Some(where_) = &e.where_ {
            if pretty_clickhouse {
                self.write_newline();
                if let Expression::Where(w) = where_.as_ref() {
                    self.write_indent();
                    self.write_keyword("WHERE");
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                    self.generate_expression(&w.this)?;
                    self.indent_level -= 1;
                } else {
                    self.write_indent();
                    self.generate_expression(where_)?;
                }
            } else {
                self.write_space();
                self.generate_expression(where_)?;
            }
        }
        if let Some(group) = &e.group {
            if pretty_clickhouse {
                self.write_newline();
                if let Expression::Group(g) = group.as_ref() {
                    self.write_indent();
                    self.write_keyword("GROUP BY");
                    self.write_newline();
                    self.indent_level += 1;
                    for (i, expr) in g.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(",");
                            self.write_newline();
                        }
                        self.write_indent();
                        self.generate_expression(expr)?;
                    }
                    self.indent_level -= 1;
                } else {
                    self.write_indent();
                    self.generate_expression(group)?;
                }
            } else {
                self.write_space();
                self.generate_expression(group)?;
            }
        }
        if let Some(aggregates) = &e.aggregates {
            if pretty_clickhouse {
                self.write_newline();
                self.write_indent();
                self.write_keyword("SET");
                self.write_newline();
                self.indent_level += 1;
                if let Expression::Tuple(t) = aggregates.as_ref() {
                    for (i, agg) in t.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(",");
                            self.write_newline();
                        }
                        self.write_indent();
                        self.generate_expression(agg)?;
                    }
                } else {
                    self.write_indent();
                    self.generate_expression(aggregates)?;
                }
                self.indent_level -= 1;
            } else {
                self.write_space();
                self.write_keyword("SET");
                self.write_space();
                self.generate_expression(aggregates)?;
            }
        }
        Ok(())
    }

    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
        self.generate_expression(&e.this)?;
        if e.delete.is_some() {
            self.write_keyword(" DELETE");
        }
        if let Some(recompress) = &e.recompress {
            self.write_keyword(" RECOMPRESS ");
            self.generate_expression(recompress)?;
        }
        if let Some(to_disk) = &e.to_disk {
            self.write_keyword(" TO DISK ");
            self.generate_expression(to_disk)?;
        }
        if let Some(to_volume) = &e.to_volume {
            self.write_keyword(" TO VOLUME ");
            self.generate_expression(to_volume)?;
        }
        Ok(())
    }

    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
        // MINHASH(this, expressions...)
        self.write_keyword("MINHASH");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
        // model!attribute - Snowflake syntax
        self.generate_expression(&e.this)?;
        self.write("!");
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
        // MONTHNAME(this)
        self.write_keyword("MONTHNAME");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
        // Output leading comments
        for comment in &e.leading_comments {
            self.write_formatted_comment(comment);
            self.write_space();
        }
        // Python: INSERT kind expressions source
        self.write_keyword("INSERT");
        self.write_space();
        self.write(&e.kind);
        for expr in &e.expressions {
            self.write_space();
            self.generate_expression(expr)?;
        }
        if let Some(source) = &e.source {
            self.write_space();
            self.generate_expression(source)?;
        }
        Ok(())
    }

    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
        // Python: NEXT VALUE FOR this [OVER (order)]
        self.write_keyword("NEXT VALUE FOR");
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(order) = &e.order {
            self.write_space();
            self.write_keyword("OVER");
            self.write(" (");
            self.generate_expression(order)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
        // NORMAL(mean, stddev, gen)
        self.write_keyword("NORMAL");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(stddev) = &e.stddev {
            self.write(", ");
            self.generate_expression(stddev)?;
        }
        if let Some(gen) = &e.gen {
            self.write(", ");
            self.generate_expression(gen)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
        // NORMALIZE(this, form) or CASEFOLD version
        if e.is_casefold.is_some() {
            self.write_keyword("NORMALIZE_AND_CASEFOLD");
        } else {
            self.write_keyword("NORMALIZE");
        }
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(form) = &e.form {
            self.write(", ");
            self.generate_expression(form)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
        // Python: [NOT ]NULL
        if e.allow_null.is_none() {
            self.write_keyword("NOT ");
        }
        self.write_keyword("NULL");
        Ok(())
    }

    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
        // NULLIF(this, expression)
        self.write_keyword("NULLIF");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
        // FORMAT(this, format, culture)
        self.write_keyword("FORMAT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", '");
        self.write(&e.format);
        self.write("'");
        if let Some(culture) = &e.culture {
            self.write(", ");
            self.generate_expression(culture)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
        // OBJECT_AGG(key, value)
        self.write_keyword("OBJECT_AGG");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
        // Python: Just returns the name
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
        // OBJECT_INSERT(obj, key, value, [update_flag])
        self.write_keyword("OBJECT_INSERT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(key) = &e.key {
            self.write(", ");
            self.generate_expression(key)?;
        }
        if let Some(value) = &e.value {
            self.write(", ");
            self.generate_expression(value)?;
        }
        if let Some(update_flag) = &e.update_flag {
            self.write(", ");
            self.generate_expression(update_flag)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
        // OFFSET value [ROW|ROWS]
        self.write_keyword("OFFSET");
        self.write_space();
        self.generate_expression(&e.this)?;
        // Output ROWS keyword only for TSQL/Oracle targets
        if e.rows == Some(true)
            && matches!(
                self.config.dialect,
                Some(crate::dialects::DialectType::TSQL)
                    | Some(crate::dialects::DialectType::Oracle)
            )
        {
            self.write_space();
            self.write_keyword("ROWS");
        }
        Ok(())
    }

    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
        // QUALIFY condition (Snowflake/BigQuery)
        self.write_keyword("QUALIFY");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
        // ON CLUSTER cluster_name
        self.write_keyword("ON CLUSTER");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
        self.write_keyword("ON COMMIT");
        if e.delete.is_some() {
            self.write_keyword(" DELETE ROWS");
        } else {
            self.write_keyword(" PRESERVE ROWS");
        }
        Ok(())
    }

    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
        // Python: error/empty/null handling
        if let Some(empty) = &e.empty {
            self.generate_expression(empty)?;
            self.write_keyword(" ON EMPTY");
        }
        if let Some(error) = &e.error {
            if e.empty.is_some() {
                self.write_space();
            }
            self.generate_expression(error)?;
            self.write_keyword(" ON ERROR");
        }
        if let Some(null) = &e.null {
            if e.empty.is_some() || e.error.is_some() {
                self.write_space();
            }
            self.generate_expression(null)?;
            self.write_keyword(" ON NULL");
        }
        Ok(())
    }

    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
        // Materialize doesn't support ON CONFLICT - skip entirely
        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
            return Ok(());
        }
        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
        if e.duplicate.is_some() {
            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
            self.write_keyword("ON DUPLICATE KEY UPDATE");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                }
                self.write_space();
                self.generate_expression(expr)?;
            }
            return Ok(());
        } else {
            self.write_keyword("ON CONFLICT");
        }
        if let Some(constraint) = &e.constraint {
            self.write_keyword(" ON CONSTRAINT ");
            self.generate_expression(constraint)?;
        }
        if let Some(conflict_keys) = &e.conflict_keys {
            // conflict_keys can be a Tuple containing expressions
            if let Expression::Tuple(t) = conflict_keys.as_ref() {
                self.write("(");
                for (i, expr) in t.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            } else {
                self.write("(");
                self.generate_expression(conflict_keys)?;
                self.write(")");
            }
        }
        if let Some(index_predicate) = &e.index_predicate {
            self.write_keyword(" WHERE ");
            self.generate_expression(index_predicate)?;
        }
        if let Some(action) = &e.action {
            // Check if action is "NOTHING" or an UPDATE set
            if let Expression::Identifier(id) = action.as_ref() {
                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
                    self.write_keyword(" DO NOTHING");
                } else {
                    self.write_keyword(" DO ");
                    self.generate_expression(action)?;
                }
            } else if let Expression::Tuple(t) = action.as_ref() {
                // DO UPDATE SET col1 = val1, col2 = val2
                self.write_keyword(" DO UPDATE SET ");
                for (i, expr) in t.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            } else {
                self.write_keyword(" DO ");
                self.generate_expression(action)?;
            }
        }
        // WHERE clause for the UPDATE action
        if let Some(where_) = &e.where_ {
            self.write_keyword(" WHERE ");
            self.generate_expression(where_)?;
        }
        Ok(())
    }

    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
        // ON property_value
        self.write_keyword("ON");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
        // Python: this expression (e.g., column opclass)
        self.generate_expression(&e.this)?;
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
        // Python: OPENJSON(this[, path]) [WITH (columns)]
        self.write_keyword("OPENJSON");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(path) = &e.path {
            self.write(", ");
            self.generate_expression(path)?;
        }
        self.write(")");
        if !e.expressions.is_empty() {
            self.write_keyword(" WITH");
            if self.config.pretty {
                self.write(" (\n");
                self.indent_level += 2;
                for (i, expr) in e.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(",\n");
                    }
                    self.write_indent();
                    self.generate_expression(expr)?;
                }
                self.write("\n");
                self.indent_level -= 2;
                self.write(")");
            } else {
                self.write(" (");
                for (i, expr) in e.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
        // Python: this kind [path] [AS JSON]
        self.generate_expression(&e.this)?;
        self.write_space();
        // Use parsed data_type if available, otherwise fall back to kind string
        if let Some(ref dt) = e.data_type {
            self.generate_data_type(dt)?;
        } else if !e.kind.is_empty() {
            self.write(&e.kind);
        }
        if let Some(path) = &e.path {
            self.write_space();
            self.generate_expression(path)?;
        }
        if e.as_json.is_some() {
            self.write_keyword(" AS JSON");
        }
        Ok(())
    }

    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
        // this OPERATOR(op) expression
        self.generate_expression(&e.this)?;
        self.write_space();
        if let Some(op) = &e.operator {
            self.write_keyword("OPERATOR");
            self.write("(");
            self.generate_expression(op)?;
            self.write(")");
        }
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
        self.write_keyword("ORDER BY");
        let pretty_clickhouse_single_paren = self.config.pretty
            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
            && e.expressions.len() == 1
            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
            && e.expressions.len() == 1
            && matches!(e.expressions[0].this, Expression::Tuple(_))
            && !e.expressions[0].desc
            && e.expressions[0].nulls_first.is_none();

        if pretty_clickhouse_single_paren {
            self.write_space();
            if let Expression::Paren(p) = &e.expressions[0].this {
                self.write("(");
                self.write_newline();
                self.indent_level += 1;
                self.write_indent();
                self.generate_expression(&p.this)?;
                self.indent_level -= 1;
                self.write_newline();
                self.write(")");
            }
            return Ok(());
        }

        if clickhouse_single_tuple {
            self.write_space();
            if let Expression::Tuple(t) = &e.expressions[0].this {
                self.write("(");
                for (i, expr) in t.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
                self.write(")");
            }
            return Ok(());
        }

        self.write_space();
        for (i, ordered) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(&ordered.this)?;
            if ordered.desc {
                self.write_space();
                self.write_keyword("DESC");
            } else if ordered.explicit_asc {
                self.write_space();
                self.write_keyword("ASC");
            }
            if let Some(nulls_first) = ordered.nulls_first {
                // In Dremio, NULLS LAST is the default, so skip generating it
                let skip_nulls_last =
                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
                if !skip_nulls_last {
                    self.write_space();
                    self.write_keyword("NULLS");
                    self.write_space();
                    if nulls_first {
                        self.write_keyword("FIRST");
                    } else {
                        self.write_keyword("LAST");
                    }
                }
            }
        }
        Ok(())
    }

    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
        // OUTPUT(model)
        self.write_keyword("OUTPUT");
        self.write("(");
        if self.config.pretty {
            self.indent_level += 1;
            self.write_newline();
            self.write_indent();
            self.generate_expression(&e.this)?;
            self.indent_level -= 1;
            self.write_newline();
        } else {
            self.generate_expression(&e.this)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
        self.write_keyword("TRUNCATE");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if e.with_count.is_some() {
            self.write_keyword(" WITH COUNT");
        } else {
            self.write_keyword(" WITHOUT COUNT");
        }
        Ok(())
    }

    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
        // Python: name(expressions)(params)
        self.generate_expression(&e.this)?;
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")(");
        for (i, param) in e.params.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(param)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
        // PARSE_DATETIME(format, this) or similar
        self.write_keyword("PARSE_DATETIME");
        self.write("(");
        if let Some(format) = &e.format {
            self.write("'");
            self.write(format);
            self.write("', ");
        }
        self.generate_expression(&e.this)?;
        if let Some(zone) = &e.zone {
            self.write(", ");
            self.generate_expression(zone)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
        // PARSE_IP(this, type, permissive)
        self.write_keyword("PARSE_IP");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(type_) = &e.type_ {
            self.write(", ");
            self.generate_expression(type_)?;
        }
        if let Some(permissive) = &e.permissive {
            self.write(", ");
            self.generate_expression(permissive)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
        // PARSE_JSON(this, [expression])
        self.write_keyword("PARSE_JSON");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
        self.write_keyword("PARSE_TIME");
        self.write("(");
        self.write(&format!("'{}'", e.format));
        self.write(", ");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
        // PARSE_URL(this, [part_to_extract], [key], [permissive])
        self.write_keyword("PARSE_URL");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(part) = &e.part_to_extract {
            self.write(", ");
            self.generate_expression(part)?;
        }
        if let Some(key) = &e.key {
            self.write(", ");
            self.generate_expression(key)?;
        }
        if let Some(permissive) = &e.permissive {
            self.write(", ");
            self.generate_expression(permissive)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
        if e.subpartition {
            self.write_keyword("SUBPARTITION");
        } else {
            self.write_keyword("PARTITION");
        }
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
        if let Some(this) = &e.this {
            if let Some(expression) = &e.expression {
                // WITH (MODULUS this, REMAINDER expression)
                self.write_keyword("WITH");
                self.write(" (");
                self.write_keyword("MODULUS");
                self.write_space();
                self.generate_expression(this)?;
                self.write(", ");
                self.write_keyword("REMAINDER");
                self.write_space();
                self.generate_expression(expression)?;
                self.write(")");
            } else {
                // IN (this) - this could be a list
                self.write_keyword("IN");
                self.write(" (");
                self.generate_partition_bound_values(this)?;
                self.write(")");
            }
        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
            // FROM (from_expressions) TO (to_expressions)
            self.write_keyword("FROM");
            self.write(" (");
            self.generate_partition_bound_values(from)?;
            self.write(") ");
            self.write_keyword("TO");
            self.write(" (");
            self.generate_partition_bound_values(to)?;
            self.write(")");
        }
        Ok(())
    }

    /// Generate partition bound values - handles Tuple expressions by outputting
    /// contents without wrapping parens (since caller provides the parens)
    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
        if let Expression::Tuple(t) = expr {
            for (i, e) in t.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(e)?;
            }
            Ok(())
        } else {
            self.generate_expression(expr)
        }
    }

    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
        // PARTITION BY LIST (partition_expressions) (create_expressions)
        self.write_keyword("PARTITION BY LIST");
        if let Some(partition_exprs) = &e.partition_expressions {
            self.write(" (");
            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
            self.generate_doris_partition_expressions(partition_exprs)?;
            self.write(")");
        }
        if let Some(create_exprs) = &e.create_expressions {
            self.write(" (");
            // Unwrap Tuple for partition definitions
            self.generate_doris_partition_definitions(create_exprs)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
        // PARTITION BY RANGE (partition_expressions) (create_expressions)
        self.write_keyword("PARTITION BY RANGE");
        if let Some(partition_exprs) = &e.partition_expressions {
            self.write(" (");
            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
            self.generate_doris_partition_expressions(partition_exprs)?;
            self.write(")");
        }
        if let Some(create_exprs) = &e.create_expressions {
            self.write(" (");
            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
            self.generate_doris_partition_definitions(create_exprs)?;
            self.write(")");
        }
        Ok(())
    }

    /// Generate Doris partition column expressions (unwrap Tuple)
    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
        if let Expression::Tuple(t) = expr {
            for (i, e) in t.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(e)?;
            }
        } else {
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    /// Generate Doris partition definitions (comma-separated Partition expressions)
    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Tuple(t) => {
                // Multiple partitions, comma-separated
                for (i, part) in t.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
                    if let Expression::Partition(p) = part {
                        for (j, inner) in p.expressions.iter().enumerate() {
                            if j > 0 {
                                self.write(", ");
                            }
                            self.generate_expression(inner)?;
                        }
                    } else {
                        self.generate_expression(part)?;
                    }
                }
            }
            Expression::PartitionByRangePropertyDynamic(_) => {
                // Dynamic partition - FROM/TO/INTERVAL
                self.generate_expression(expr)?;
            }
            _ => {
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_partition_by_range_property_dynamic(
        &mut self,
        e: &PartitionByRangePropertyDynamic,
    ) -> Result<()> {
        // Doris: FROM (start) TO (end) INTERVAL n UNIT
        if let Some(start) = &e.start {
            self.write_keyword("FROM");
            self.write(" (");
            self.generate_expression(start)?;
            self.write(")");
        }
        if let Some(end) = &e.end {
            self.write_space();
            self.write_keyword("TO");
            self.write(" (");
            self.generate_expression(end)?;
            self.write(")");
        }
        if let Some(every) = &e.every {
            self.write_space();
            // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
            self.generate_doris_interval(every)?;
        }
        Ok(())
    }

    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
        if let Expression::Interval(interval) = expr {
            self.write_keyword("INTERVAL");
            if let Some(ref value) = interval.this {
                self.write_space();
                // Don't quote numbers for Doris partition interval
                self.generate_expression(value)?;
            }
            if let Some(ref unit_spec) = interval.unit {
                self.write_space();
                self.write_interval_unit_spec(unit_spec)?;
            }
            Ok(())
        } else {
            self.generate_expression(expr)
        }
    }

    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
        // TRUNCATE(expression, this)
        self.write_keyword("TRUNCATE");
        self.write("(");
        self.generate_expression(&e.expression)?;
        self.write(", ");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
        // Doris: PARTITION name VALUES IN (val1, val2)
        self.write_keyword("PARTITION");
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("VALUES IN");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
        // TSQL ranges have no expressions and just use `this TO expression`
        if let Some(expression) = e.expression.as_ref().filter(|_| e.expressions.is_empty()) {
            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
            self.generate_expression(&e.this)?;
            self.write_space();
            self.write_keyword("TO");
            self.write_space();
            self.generate_expression(expression)?;
            return Ok(());
        }

        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
        self.write_keyword("PARTITION");
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write_space();

        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
        if e.expressions.len() == 1 {
            // Single value: VALUES LESS THAN (val)
            self.write_keyword("VALUES LESS THAN");
            self.write(" (");
            self.generate_expression(&e.expressions[0])?;
            self.write(")");
        } else if !e.expressions.is_empty() {
            // Multiple values with Tuple: VALUES [(val1), (val2))
            self.write_keyword("VALUES");
            self.write(" [");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                // If the expr is a Tuple, generate its contents wrapped in parens
                if let Expression::Tuple(t) = expr {
                    self.write("(");
                    for (j, inner) in t.expressions.iter().enumerate() {
                        if j > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(inner)?;
                    }
                    self.write(")");
                } else {
                    self.write("(");
                    self.generate_expression(expr)?;
                    self.write(")");
                }
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
        // BUCKET(this, expression)
        self.write_keyword("BUCKET");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Teradata)
                | Some(crate::dialects::DialectType::ClickHouse)
        ) {
            self.write_keyword("PARTITION BY");
        } else {
            self.write_keyword("PARTITIONED BY");
        }
        self.write_space();
        // In pretty mode, always use multiline tuple format for PARTITIONED BY
        if self.config.pretty {
            if let Expression::Tuple(ref tuple) = *e.this {
                self.write("(");
                self.write_newline();
                self.indent_level += 1;
                for (i, expr) in tuple.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(",");
                        self.write_newline();
                    }
                    self.write_indent();
                    self.generate_expression(expr)?;
                }
                self.indent_level -= 1;
                self.write_newline();
                self.write(")");
            } else {
                self.generate_expression(&e.this)?;
            }
        } else {
            self.generate_expression(&e.this)?;
        }
        Ok(())
    }

    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
        self.write_keyword("PARTITION OF");
        self.write_space();
        self.generate_expression(&e.this)?;
        // Check if expression is a PartitionBoundSpec
        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
            self.write_space();
            self.write_keyword("FOR VALUES");
            self.write_space();
            self.generate_expression(&e.expression)?;
        } else {
            self.write_space();
            self.write_keyword("DEFAULT");
        }
        Ok(())
    }

    fn generate_period_for_system_time_constraint(
        &mut self,
        e: &PeriodForSystemTimeConstraint,
    ) -> Result<()> {
        // PERIOD FOR SYSTEM_TIME (this, expression)
        self.write_keyword("PERIOD FOR SYSTEM_TIME");
        self.write(" (");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
        // value AS alias
        // The alias can be an identifier or an expression (e.g., string concatenation)
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("AS");
        self.write_space();
        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
        if self.config.unpivot_aliases_are_identifiers {
            match &e.alias {
                Expression::Literal(Literal::String(s)) => {
                    // Convert string literal to identifier
                    self.generate_identifier(&Identifier::new(s.clone()))?;
                }
                Expression::Literal(Literal::Number(n)) => {
                    // Convert number literal to quoted identifier
                    let mut id = Identifier::new(n.clone());
                    id.quoted = true;
                    self.generate_identifier(&id)?;
                }
                other => {
                    self.generate_expression(other)?;
                }
            }
        } else {
            self.generate_expression(&e.alias)?;
        }
        Ok(())
    }

    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
        // ANY or ANY [expression]
        self.write_keyword("ANY");
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
        // ML.PREDICT(MODEL this, expression, [params_struct])
        self.write_keyword("ML.PREDICT");
        self.write("(");
        self.write_keyword("MODEL");
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(params) = &e.params_struct {
            self.write(", ");
            self.generate_expression(params)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
        // PREVIOUS_DAY(this, expression)
        self.write_keyword("PREVIOUS_DAY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
        self.write_keyword("PRIMARY KEY");
        if let Some(name) = &e.this {
            self.write_space();
            self.generate_expression(name)?;
        }
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        if let Some(include) = &e.include {
            self.write_space();
            self.generate_expression(include)?;
        }
        if !e.options.is_empty() {
            self.write_space();
            for (i, opt) in e.options.iter().enumerate() {
                if i > 0 {
                    self.write_space();
                }
                self.generate_expression(opt)?;
            }
        }
        Ok(())
    }

    fn generate_primary_key_column_constraint(
        &mut self,
        _e: &PrimaryKeyColumnConstraint,
    ) -> Result<()> {
        // PRIMARY KEY constraint at column level
        self.write_keyword("PRIMARY KEY");
        Ok(())
    }

    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
        self.write_keyword("PATH");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
        // PROJECTION this (expression)
        self.write_keyword("PROJECTION");
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write(" (");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
        // Properties list
        for (i, prop) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(prop)?;
        }
        Ok(())
    }

    fn generate_property(&mut self, e: &Property) -> Result<()> {
        // name=value
        self.generate_expression(&e.this)?;
        if let Some(value) = &e.value {
            self.write("=");
            self.generate_expression(value)?;
        }
        Ok(())
    }

    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
        self.write_keyword("OPTIONS");
        self.write(" (");
        for (i, opt) in options.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_option_expression(opt)?;
        }
        self.write(")");
        Ok(())
    }

    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
        self.write_keyword("PROPERTIES");
        self.write(" (");
        for (i, prop) in properties.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_option_expression(prop)?;
        }
        self.write(")");
        Ok(())
    }

    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
        self.write_keyword("ENVIRONMENT");
        self.write(" (");
        for (i, env_item) in environment.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_environment_expression(env_item)?;
        }
        self.write(")");
        Ok(())
    }

    /// Generate an environment expression with spaces around =
    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Eq(eq) => {
                // Generate key = value with spaces (Databricks ENVIRONMENT style)
                self.generate_expression(&eq.left)?;
                self.write(" = ");
                self.generate_expression(&eq.right)?;
                Ok(())
            }
            _ => self.generate_expression(expr),
        }
    }

    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
        self.write_keyword("TBLPROPERTIES");
        if self.config.pretty {
            self.write(" (");
            self.write_newline();
            self.indent_level += 1;
            for (i, opt) in options.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                    self.write_newline();
                }
                self.write_indent();
                self.generate_option_expression(opt)?;
            }
            self.indent_level -= 1;
            self.write_newline();
            self.write(")");
        } else {
            self.write(" (");
            for (i, opt) in options.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_option_expression(opt)?;
            }
            self.write(")");
        }
        Ok(())
    }

    /// Generate an option expression without spaces around =
    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Eq(eq) => {
                // Generate key=value without spaces
                self.generate_expression(&eq.left)?;
                self.write("=");
                self.generate_expression(&eq.right)?;
                Ok(())
            }
            _ => self.generate_expression(expr),
        }
    }

    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
        // Just output the name
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
        // PUT source_file @stage [options]
        self.write_keyword("PUT");
        self.write_space();

        // Source file path - preserve original quoting
        if e.source_quoted {
            self.write("'");
            self.write(&e.source);
            self.write("'");
        } else {
            self.write(&e.source);
        }

        self.write_space();

        // Target stage reference - output the string directly (includes @)
        if let Expression::Literal(Literal::String(s)) = &e.target {
            self.write(s);
        } else {
            self.generate_expression(&e.target)?;
        }

        // Optional parameters: KEY=VALUE
        for param in &e.params {
            self.write_space();
            self.write(&param.name);
            if let Some(ref value) = param.value {
                self.write("=");
                self.generate_expression(value)?;
            }
        }

        Ok(())
    }

    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
        // QUANTILE(this, quantile)
        self.write_keyword("QUANTILE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(quantile) = &e.quantile {
            self.write(", ");
            self.generate_expression(quantile)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
        // QUERY_BAND = this [UPDATE] [FOR scope]
        if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Teradata)
        ) {
            self.write_keyword("SET");
            self.write_space();
        }
        self.write_keyword("QUERY_BAND");
        self.write(" = ");
        self.generate_expression(&e.this)?;
        if e.update.is_some() {
            self.write_space();
            self.write_keyword("UPDATE");
        }
        if let Some(scope) = &e.scope {
            self.write_space();
            self.write_keyword("FOR");
            self.write_space();
            self.generate_expression(scope)?;
        }
        Ok(())
    }

    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
        // this = expression
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(" = ");
            self.generate_expression(expression)?;
        }
        Ok(())
    }

    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
        self.write_keyword("TRANSFORM");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        if let Some(row_format_before) = &e.row_format_before {
            self.write_space();
            self.generate_expression(row_format_before)?;
        }
        if let Some(record_writer) = &e.record_writer {
            self.write_space();
            self.write_keyword("RECORDWRITER");
            self.write_space();
            self.generate_expression(record_writer)?;
        }
        if let Some(command_script) = &e.command_script {
            self.write_space();
            self.write_keyword("USING");
            self.write_space();
            self.generate_expression(command_script)?;
        }
        if let Some(schema) = &e.schema {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_expression(schema)?;
        }
        if let Some(row_format_after) = &e.row_format_after {
            self.write_space();
            self.generate_expression(row_format_after)?;
        }
        if let Some(record_reader) = &e.record_reader {
            self.write_space();
            self.write_keyword("RECORDREADER");
            self.write_space();
            self.generate_expression(record_reader)?;
        }
        Ok(())
    }

    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
        // RANDN([seed])
        self.write_keyword("RANDN");
        self.write("(");
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
        // RANDSTR(this, [generator])
        self.write_keyword("RANDSTR");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(generator) = &e.generator {
            self.write(", ");
            self.generate_expression(generator)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
        // RANGE_BUCKET(this, expression)
        self.write_keyword("RANGE_BUCKET");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
        // RANGE_N(this BETWEEN expressions [EACH each])
        self.write_keyword("RANGE_N");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("BETWEEN");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        if let Some(each) = &e.each {
            self.write_space();
            self.write_keyword("EACH");
            self.write_space();
            self.generate_expression(each)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
        // READ_CSV(this, expressions...)
        self.write_keyword("READ_CSV");
        self.write("(");
        self.generate_expression(&e.this)?;
        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
        // READ_PARQUET(expressions...)
        self.write_keyword("READ_PARQUET");
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
        // SEARCH kind FIRST BY this SET expression [USING using]
        // or CYCLE this SET expression [USING using]
        if e.kind == "CYCLE" {
            self.write_keyword("CYCLE");
        } else {
            self.write_keyword("SEARCH");
            self.write_space();
            self.write(&e.kind);
            self.write_space();
            self.write_keyword("FIRST BY");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("SET");
        self.write_space();
        self.generate_expression(&e.expression)?;
        if let Some(using) = &e.using {
            self.write_space();
            self.write_keyword("USING");
            self.write_space();
            self.generate_expression(using)?;
        }
        Ok(())
    }

    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
        // REDUCE(this, initial, merge, [finish])
        self.write_keyword("REDUCE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(initial) = &e.initial {
            self.write(", ");
            self.generate_expression(initial)?;
        }
        if let Some(merge) = &e.merge {
            self.write(", ");
            self.generate_expression(merge)?;
        }
        if let Some(finish) = &e.finish {
            self.write(", ");
            self.generate_expression(finish)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
        // REFERENCES this (expressions) [options]
        self.write_keyword("REFERENCES");
        self.write_space();
        self.generate_expression(&e.this)?;
        if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        for opt in &e.options {
            self.write_space();
            self.generate_expression(opt)?;
        }
        Ok(())
    }

    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
        // REFRESH [kind] this
        self.write_keyword("REFRESH");
        if !e.kind.is_empty() {
            self.write_space();
            self.write_keyword(&e.kind);
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
        self.write_keyword("REFRESH");
        self.write_space();
        self.write_keyword(&e.method);

        if let Some(ref kind) = e.kind {
            self.write_space();
            self.write_keyword("ON");
            self.write_space();
            self.write_keyword(kind);

            // EVERY n UNIT
            if let Some(ref every) = e.every {
                self.write_space();
                self.write_keyword("EVERY");
                self.write_space();
                self.generate_expression(every)?;
                if let Some(ref unit) = e.unit {
                    self.write_space();
                    self.write_keyword(unit);
                }
            }

            // STARTS 'datetime'
            if let Some(ref starts) = e.starts {
                self.write_space();
                self.write_keyword("STARTS");
                self.write_space();
                self.generate_expression(starts)?;
            }
        }
        Ok(())
    }

    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
        // REGEXP_COUNT(this, expression, position, parameters)
        self.write_keyword("REGEXP_COUNT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(position) = &e.position {
            self.write(", ");
            self.generate_expression(position)?;
        }
        if let Some(parameters) = &e.parameters {
            self.write(", ");
            self.generate_expression(parameters)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
        self.write_keyword("REGEXP_EXTRACT_ALL");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(group) = &e.group {
            self.write(", ");
            self.generate_expression(group)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
        // REGEXP_FULL_MATCH(this, expression)
        self.write_keyword("REGEXP_FULL_MATCH");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
        use crate::dialects::DialectType;
        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
        if matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
        ) && e.flag.is_none()
        {
            self.generate_expression(&e.this)?;
            self.write(" ~* ");
            self.generate_expression(&e.expression)?;
        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
            self.write_keyword("REGEXP_LIKE");
            self.write("(");
            self.generate_expression(&e.this)?;
            self.write(", ");
            self.generate_expression(&e.expression)?;
            self.write(", ");
            if let Some(flag) = &e.flag {
                self.generate_expression(flag)?;
            } else {
                self.write("'i'");
            }
            self.write(")");
        } else {
            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
            self.generate_expression(&e.this)?;
            self.write_space();
            self.write_keyword("REGEXP_ILIKE");
            self.write_space();
            self.generate_expression(&e.expression)?;
            if let Some(flag) = &e.flag {
                self.write(", ");
                self.generate_expression(flag)?;
            }
        }
        Ok(())
    }

    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
        self.write_keyword("REGEXP_INSTR");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(position) = &e.position {
            self.write(", ");
            self.generate_expression(position)?;
        }
        if let Some(occurrence) = &e.occurrence {
            self.write(", ");
            self.generate_expression(occurrence)?;
        }
        if let Some(option) = &e.option {
            self.write(", ");
            self.generate_expression(option)?;
        }
        if let Some(parameters) = &e.parameters {
            self.write(", ");
            self.generate_expression(parameters)?;
        }
        if let Some(group) = &e.group {
            self.write(", ");
            self.generate_expression(group)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
        // REGEXP_SPLIT(this, expression, limit)
        self.write_keyword("REGEXP_SPLIT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(limit) = &e.limit {
            self.write(", ");
            self.generate_expression(limit)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
        // REGR_AVGX(this, expression)
        self.write_keyword("REGR_AVGX");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
        // REGR_AVGY(this, expression)
        self.write_keyword("REGR_AVGY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
        // REGR_COUNT(this, expression)
        self.write_keyword("REGR_COUNT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
        // REGR_INTERCEPT(this, expression)
        self.write_keyword("REGR_INTERCEPT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
        // REGR_R2(this, expression)
        self.write_keyword("REGR_R2");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
        // REGR_SLOPE(this, expression)
        self.write_keyword("REGR_SLOPE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
        // REGR_SXX(this, expression)
        self.write_keyword("REGR_SXX");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
        // REGR_SXY(this, expression)
        self.write_keyword("REGR_SXY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
        // REGR_SYY(this, expression)
        self.write_keyword("REGR_SYY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
        // REGR_VALX(this, expression)
        self.write_keyword("REGR_VALX");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
        // REGR_VALY(this, expression)
        self.write_keyword("REGR_VALY");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_remote_with_connection_model_property(
        &mut self,
        e: &RemoteWithConnectionModelProperty,
    ) -> Result<()> {
        // REMOTE WITH CONNECTION this
        self.write_keyword("REMOTE WITH CONNECTION");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
        // RENAME COLUMN [IF EXISTS] this TO new_name
        self.write_keyword("RENAME COLUMN");
        if e.exists {
            self.write_space();
            self.write_keyword("IF EXISTS");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(to) = &e.to {
            self.write_space();
            self.write_keyword("TO");
            self.write_space();
            self.generate_expression(to)?;
        }
        Ok(())
    }

    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
        // REPLACE PARTITION expression [FROM source]
        self.write_keyword("REPLACE PARTITION");
        self.write_space();
        self.generate_expression(&e.expression)?;
        if let Some(source) = &e.source {
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            self.generate_expression(source)?;
        }
        Ok(())
    }

    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
        // RETURNING expressions [INTO into]
        // TSQL and Fabric use OUTPUT instead of RETURNING
        let keyword = match self.config.dialect {
            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
            _ => "RETURNING",
        };
        self.write_keyword(keyword);
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        if let Some(into) = &e.into {
            self.write_space();
            self.write_keyword("INTO");
            self.write_space();
            self.generate_expression(into)?;
        }
        Ok(())
    }

    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
        // OUTPUT expressions [INTO into_table]
        self.write_space();
        self.write_keyword("OUTPUT");
        self.write_space();
        for (i, expr) in output.columns.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        if let Some(into_table) = &output.into_table {
            self.write_space();
            self.write_keyword("INTO");
            self.write_space();
            self.generate_expression(into_table)?;
        }
        Ok(())
    }

    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
        self.write_keyword("RETURNS");
        if e.is_table.is_some() {
            self.write_space();
            self.write_keyword("TABLE");
        }
        if let Some(table) = &e.table {
            self.write_space();
            self.generate_expression(table)?;
        } else if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if e.null.is_some() {
            self.write_space();
            self.write_keyword("NULL ON NULL INPUT");
        }
        Ok(())
    }

    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
        self.write_keyword("ROLLBACK");

        // TSQL always uses ROLLBACK TRANSACTION
        if e.this.is_none()
            && matches!(
                self.config.dialect,
                Some(DialectType::TSQL) | Some(DialectType::Fabric)
            )
        {
            self.write_space();
            self.write_keyword("TRANSACTION");
        }

        // Check if this has TRANSACTION keyword or transaction name
        if let Some(this) = &e.this {
            // Check if it's just the "TRANSACTION" marker or an actual transaction name
            let is_transaction_marker = matches!(
                this.as_ref(),
                Expression::Identifier(id) if id.name == "TRANSACTION"
            );

            self.write_space();
            self.write_keyword("TRANSACTION");

            // If it's a real transaction name, output it
            if !is_transaction_marker {
                self.write_space();
                self.generate_expression(this)?;
            }
        }

        // Output TO savepoint
        if let Some(savepoint) = &e.savepoint {
            self.write_space();
            self.write_keyword("TO");
            self.write_space();
            self.generate_expression(savepoint)?;
        }
        Ok(())
    }

    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
        if e.expressions.is_empty() {
            self.write_keyword("WITH ROLLUP");
        } else {
            self.write_keyword("ROLLUP");
            self.write("(");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_row_format_delimited_property(
        &mut self,
        e: &RowFormatDelimitedProperty,
    ) -> Result<()> {
        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
        self.write_keyword("ROW FORMAT DELIMITED");
        if let Some(fields) = &e.fields {
            self.write_space();
            self.write_keyword("FIELDS TERMINATED BY");
            self.write_space();
            self.generate_expression(fields)?;
        }
        if let Some(escaped) = &e.escaped {
            self.write_space();
            self.write_keyword("ESCAPED BY");
            self.write_space();
            self.generate_expression(escaped)?;
        }
        if let Some(items) = &e.collection_items {
            self.write_space();
            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
            self.write_space();
            self.generate_expression(items)?;
        }
        if let Some(keys) = &e.map_keys {
            self.write_space();
            self.write_keyword("MAP KEYS TERMINATED BY");
            self.write_space();
            self.generate_expression(keys)?;
        }
        if let Some(lines) = &e.lines {
            self.write_space();
            self.write_keyword("LINES TERMINATED BY");
            self.write_space();
            self.generate_expression(lines)?;
        }
        if let Some(null) = &e.null {
            self.write_space();
            self.write_keyword("NULL DEFINED AS");
            self.write_space();
            self.generate_expression(null)?;
        }
        if let Some(serde) = &e.serde {
            self.write_space();
            self.generate_expression(serde)?;
        }
        Ok(())
    }

    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
        // ROW FORMAT this
        self.write_keyword("ROW FORMAT");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
        self.write_keyword("ROW FORMAT SERDE");
        self.write_space();
        self.generate_expression(&e.this)?;
        if let Some(props) = &e.serde_properties {
            self.write_space();
            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
            self.generate_expression(props)?;
        }
        Ok(())
    }

    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
        // SHA2(this, length)
        self.write_keyword("SHA2");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(length) = e.length {
            self.write(", ");
            self.write(&length.to_string());
        }
        self.write(")");
        Ok(())
    }

    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
        // SHA2_DIGEST(this, length)
        self.write_keyword("SHA2_DIGEST");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(length) = e.length {
            self.write(", ");
            self.write(&length.to_string());
        }
        self.write(")");
        Ok(())
    }

    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
        let name = if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Spark)
                | Some(crate::dialects::DialectType::Databricks)
        ) {
            "TRY_ADD"
        } else {
            "SAFE_ADD"
        };
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
        // SAFE_DIVIDE(this, expression)
        self.write_keyword("SAFE_DIVIDE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
        let name = if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Spark)
                | Some(crate::dialects::DialectType::Databricks)
        ) {
            "TRY_MULTIPLY"
        } else {
            "SAFE_MULTIPLY"
        };
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
        let name = if matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Spark)
                | Some(crate::dialects::DialectType::Databricks)
        ) {
            "TRY_SUBTRACT"
        } else {
            "SAFE_SUBTRACT"
        };
        self.write_keyword(name);
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
    /// METHOD (size UNIT) [REPEATABLE (seed)]
    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
        if matches!(sample.method, SampleMethod::Bucket) {
            self.write(" (");
            self.write_keyword("BUCKET");
            self.write_space();
            if let Some(ref num) = sample.bucket_numerator {
                self.generate_expression(num)?;
            }
            self.write_space();
            self.write_keyword("OUT OF");
            self.write_space();
            if let Some(ref denom) = sample.bucket_denominator {
                self.generate_expression(denom)?;
            }
            if let Some(ref field) = sample.bucket_field {
                self.write_space();
                self.write_keyword("ON");
                self.write_space();
                self.generate_expression(field)?;
            }
            self.write(")");
            return Ok(());
        }

        // Output method name if explicitly specified, or for dialects that always require it
        let is_snowflake = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Snowflake)
        );
        let is_postgres = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::PostgreSQL)
                | Some(crate::dialects::DialectType::Redshift)
        );
        // Databricks and Spark don't output method names
        let is_databricks = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Databricks)
        );
        let is_spark = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Spark)
        );
        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
            self.write_space();
            if !sample.explicit_method && (is_snowflake || force_method) {
                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
                self.write_keyword("BERNOULLI");
            } else {
                match sample.method {
                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
                    SampleMethod::System => self.write_keyword("SYSTEM"),
                    SampleMethod::Block => self.write_keyword("BLOCK"),
                    SampleMethod::Row => self.write_keyword("ROW"),
                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
                    SampleMethod::Bucket => {} // handled above
                }
            }
        }

        // Output size, with or without parentheses depending on dialect
        let emit_size_no_parens = !self.config.tablesample_requires_parens;
        if emit_size_no_parens {
            self.write_space();
            match &sample.size {
                Expression::Tuple(tuple) => {
                    for (i, expr) in tuple.expressions.iter().enumerate() {
                        if i > 0 {
                            self.write(", ");
                        }
                        self.generate_expression(expr)?;
                    }
                }
                expr => self.generate_expression(expr)?,
            }
        } else {
            self.write(" (");
            self.generate_expression(&sample.size)?;
        }

        // Determine unit
        let is_rows_method = matches!(
            sample.method,
            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
        );
        let is_percent = matches!(
            sample.method,
            SampleMethod::Percent
                | SampleMethod::System
                | SampleMethod::Bernoulli
                | SampleMethod::Block
        );

        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
        // For Databricks and Spark, always output PERCENT for percentage samples.
        let is_presto = matches!(
            self.config.dialect,
            Some(crate::dialects::DialectType::Presto)
                | Some(crate::dialects::DialectType::Trino)
                | Some(crate::dialects::DialectType::Athena)
        );
        let should_output_unit = if is_databricks || is_spark {
            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
            is_percent || is_rows_method || sample.unit_after_size
        } else if is_snowflake || is_postgres || is_presto {
            sample.unit_after_size
        } else {
            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
        };

        if should_output_unit {
            self.write_space();
            if sample.is_percent {
                self.write_keyword("PERCENT");
            } else if is_rows_method && !sample.unit_after_size {
                self.write_keyword("ROWS");
            } else if sample.unit_after_size {
                match sample.method {
                    SampleMethod::Percent
                    | SampleMethod::System
                    | SampleMethod::Bernoulli
                    | SampleMethod::Block => {
                        self.write_keyword("PERCENT");
                    }
                    SampleMethod::Row | SampleMethod::Reservoir => {
                        self.write_keyword("ROWS");
                    }
                    _ => self.write_keyword("ROWS"),
                }
            } else {
                self.write_keyword("PERCENT");
            }
        }

        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            if let Some(ref offset) = sample.offset {
                self.write_space();
                self.write_keyword("OFFSET");
                self.write_space();
                self.generate_expression(offset)?;
            }
        }
        if !emit_size_no_parens {
            self.write(")");
        }

        Ok(())
    }

    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
        // SAMPLE this (ClickHouse uses SAMPLE BY)
        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            self.write_keyword("SAMPLE BY");
        } else {
            self.write_keyword("SAMPLE");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
        // this (expressions...)
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        if !e.expressions.is_empty() {
            // Add space before column list if there's a preceding expression
            if e.this.is_some() {
                self.write_space();
            }
            self.write("(");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
        // COMMENT this
        self.write_keyword("COMMENT");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
        // [this::]expression
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
            self.write("::");
        }
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_search(&mut self, e: &Search) -> Result<()> {
        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
        self.write_keyword("SEARCH");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(json_scope) = &e.json_scope {
            self.write(", ");
            self.generate_expression(json_scope)?;
        }
        if let Some(analyzer) = &e.analyzer {
            self.write(", ");
            self.generate_expression(analyzer)?;
        }
        if let Some(analyzer_options) = &e.analyzer_options {
            self.write(", ");
            self.generate_expression(analyzer_options)?;
        }
        if let Some(search_mode) = &e.search_mode {
            self.write(", ");
            self.generate_expression(search_mode)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
        // SEARCH_IP(this, expression)
        self.write_keyword("SEARCH_IP");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
        // SECURITY this
        self.write_keyword("SECURITY");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
        self.write("SEMANTIC_VIEW(");

        if self.config.pretty {
            // Pretty print: each clause on its own line
            self.write_newline();
            self.indent_level += 1;
            self.write_indent();
            self.generate_expression(&e.this)?;

            if let Some(metrics) = &e.metrics {
                self.write_newline();
                self.write_indent();
                self.write_keyword("METRICS");
                self.write_space();
                self.generate_semantic_view_tuple(metrics)?;
            }
            if let Some(dimensions) = &e.dimensions {
                self.write_newline();
                self.write_indent();
                self.write_keyword("DIMENSIONS");
                self.write_space();
                self.generate_semantic_view_tuple(dimensions)?;
            }
            if let Some(facts) = &e.facts {
                self.write_newline();
                self.write_indent();
                self.write_keyword("FACTS");
                self.write_space();
                self.generate_semantic_view_tuple(facts)?;
            }
            if let Some(where_) = &e.where_ {
                self.write_newline();
                self.write_indent();
                self.write_keyword("WHERE");
                self.write_space();
                self.generate_expression(where_)?;
            }
            self.write_newline();
            self.indent_level -= 1;
            self.write_indent();
        } else {
            // Compact: all on one line
            self.generate_expression(&e.this)?;
            if let Some(metrics) = &e.metrics {
                self.write_space();
                self.write_keyword("METRICS");
                self.write_space();
                self.generate_semantic_view_tuple(metrics)?;
            }
            if let Some(dimensions) = &e.dimensions {
                self.write_space();
                self.write_keyword("DIMENSIONS");
                self.write_space();
                self.generate_semantic_view_tuple(dimensions)?;
            }
            if let Some(facts) = &e.facts {
                self.write_space();
                self.write_keyword("FACTS");
                self.write_space();
                self.generate_semantic_view_tuple(facts)?;
            }
            if let Some(where_) = &e.where_ {
                self.write_space();
                self.write_keyword("WHERE");
                self.write_space();
                self.generate_expression(where_)?;
            }
        }
        self.write(")");
        Ok(())
    }

    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
        if let Expression::Tuple(t) = expr {
            for (i, e) in t.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(e)?;
            }
        } else {
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
        if let Some(start) = &e.start {
            self.write_keyword("START WITH");
            self.write_space();
            self.generate_expression(start)?;
        }
        if let Some(increment) = &e.increment {
            self.write_space();
            self.write_keyword("INCREMENT BY");
            self.write_space();
            self.generate_expression(increment)?;
        }
        if let Some(minvalue) = &e.minvalue {
            self.write_space();
            self.write_keyword("MINVALUE");
            self.write_space();
            self.generate_expression(minvalue)?;
        }
        if let Some(maxvalue) = &e.maxvalue {
            self.write_space();
            self.write_keyword("MAXVALUE");
            self.write_space();
            self.generate_expression(maxvalue)?;
        }
        if let Some(cache) = &e.cache {
            self.write_space();
            self.write_keyword("CACHE");
            self.write_space();
            self.generate_expression(cache)?;
        }
        if let Some(owned) = &e.owned {
            self.write_space();
            self.write_keyword("OWNED BY");
            self.write_space();
            self.generate_expression(owned)?;
        }
        for opt in &e.options {
            self.write_space();
            self.generate_expression(opt)?;
        }
        Ok(())
    }

    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
        // [WITH] SERDEPROPERTIES (expressions)
        if e.with_.is_some() {
            self.write_keyword("WITH");
            self.write_space();
        }
        self.write_keyword("SERDEPROPERTIES");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            // Generate key=value without spaces around =
            match expr {
                Expression::Eq(eq) => {
                    self.generate_expression(&eq.left)?;
                    self.write("=");
                    self.generate_expression(&eq.right)?;
                }
                _ => self.generate_expression(expr)?,
            }
        }
        self.write(")");
        Ok(())
    }

    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
        // @@[kind.]this
        self.write("@@");
        if let Some(kind) = &e.kind {
            self.write(kind);
            self.write(".");
        }
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_set(&mut self, e: &Set) -> Result<()> {
        // SET/UNSET [TAG] expressions
        if e.unset.is_some() {
            self.write_keyword("UNSET");
        } else {
            self.write_keyword("SET");
        }
        if e.tag.is_some() {
            self.write_space();
            self.write_keyword("TAG");
        }
        if !e.expressions.is_empty() {
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
        // SET this or SETCONFIG this
        self.write_keyword("SET");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
        // [kind] name = value
        if let Some(kind) = &e.kind {
            self.write_keyword(kind);
            self.write_space();
        }
        self.generate_expression(&e.name)?;
        self.write(" = ");
        self.generate_expression(&e.value)?;
        Ok(())
    }

    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
        if let Some(with_) = &e.with_ {
            self.generate_expression(with_)?;
            self.write_space();
        }
        self.generate_expression(&e.this)?;
        self.write_space();
        // kind should be UNION, INTERSECT, EXCEPT, etc.
        if let Some(kind) = &e.kind {
            self.write_keyword(kind);
        }
        if e.distinct {
            self.write_space();
            self.write_keyword("DISTINCT");
        } else {
            self.write_space();
            self.write_keyword("ALL");
        }
        if e.by_name.is_some() {
            self.write_space();
            self.write_keyword("BY NAME");
        }
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
        // SET or MULTISET
        if e.multi.is_some() {
            self.write_keyword("MULTISET");
        } else {
            self.write_keyword("SET");
        }
        Ok(())
    }

    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
        // SETTINGS expressions
        self.write_keyword("SETTINGS");
        if self.config.pretty && e.expressions.len() > 1 {
            // Pretty print: each setting on its own line, indented
            self.indent_level += 1;
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                }
                self.write_newline();
                self.write_indent();
                self.generate_expression(expr)?;
            }
            self.indent_level -= 1;
        } else {
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
        // SHARING = this
        self.write_keyword("SHARING");
        if let Some(this) = &e.this {
            self.write(" = ");
            self.generate_expression(this)?;
        }
        Ok(())
    }

    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
        // Python array slicing: begin:end:step
        if let Some(begin) = &e.this {
            self.generate_expression(begin)?;
        }
        self.write(":");
        if let Some(end) = &e.expression {
            self.generate_expression(end)?;
        }
        if let Some(step) = &e.step {
            self.write(":");
            self.generate_expression(step)?;
        }
        Ok(())
    }

    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
        // SORT_ARRAY(this, asc)
        self.write_keyword("SORT_ARRAY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(asc) = &e.asc {
            self.write(", ");
            self.generate_expression(asc)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
        // SORT BY expressions
        self.write_keyword("SORT BY");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_ordered(expr)?;
        }
        Ok(())
    }

    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
        if e.compound.is_some() {
            self.write_keyword("COMPOUND");
            self.write_space();
        }
        self.write_keyword("SORTKEY");
        self.write("(");
        // If this is a Tuple, unwrap its contents to avoid double parentheses
        if let Expression::Tuple(t) = e.this.as_ref() {
            for (i, expr) in t.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        } else {
            self.generate_expression(&e.this)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
        // SPLIT_PART(this, delimiter, part_index)
        self.write_keyword("SPLIT_PART");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(delimiter) = &e.delimiter {
            self.write(", ");
            self.generate_expression(delimiter)?;
        }
        if let Some(part_index) = &e.part_index {
            self.write(", ");
            self.generate_expression(part_index)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
        // READS SQL DATA or MODIFIES SQL DATA, etc.
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
        self.write_keyword("SQL SECURITY");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
        // ST_DISTANCE(this, expression, [use_spheroid])
        self.write_keyword("ST_DISTANCE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(use_spheroid) = &e.use_spheroid {
            self.write(", ");
            self.generate_expression(use_spheroid)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
        // ST_POINT(this, expression)
        self.write_keyword("ST_POINT");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
        // IMMUTABLE, STABLE, VOLATILE
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
        // STANDARD_HASH(this, [expression])
        self.write_keyword("STANDARD_HASH");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
        // STORED BY this
        self.write_keyword("STORED BY");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
        // STRPOS(this, substr) or STRPOS(this, substr, position)
        // Different dialects have different function names
        use crate::dialects::DialectType;
        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
            // Snowflake: CHARINDEX(substr, str[, position])
            self.write_keyword("CHARINDEX");
            self.write("(");
            if let Some(substr) = &e.substr {
                self.generate_expression(substr)?;
                self.write(", ");
            }
            self.generate_expression(&e.this)?;
            if let Some(position) = &e.position {
                self.write(", ");
                self.generate_expression(position)?;
            }
            self.write(")");
        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
            self.write_keyword("POSITION");
            self.write("(");
            self.generate_expression(&e.this)?;
            if let Some(substr) = &e.substr {
                self.write(", ");
                self.generate_expression(substr)?;
            }
            if let Some(position) = &e.position {
                self.write(", ");
                self.generate_expression(position)?;
            }
            if let Some(occurrence) = &e.occurrence {
                self.write(", ");
                self.generate_expression(occurrence)?;
            }
            self.write(")");
        } else if matches!(
            self.config.dialect,
            Some(DialectType::SQLite)
                | Some(DialectType::Oracle)
                | Some(DialectType::BigQuery)
                | Some(DialectType::Teradata)
        ) {
            self.write_keyword("INSTR");
            self.write("(");
            self.generate_expression(&e.this)?;
            if let Some(substr) = &e.substr {
                self.write(", ");
                self.generate_expression(substr)?;
            }
            if let Some(position) = &e.position {
                self.write(", ");
                self.generate_expression(position)?;
            } else if e.occurrence.is_some() {
                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
                // Default start position is 1
                self.write(", 1");
            }
            if let Some(occurrence) = &e.occurrence {
                self.write(", ");
                self.generate_expression(occurrence)?;
            }
            self.write(")");
        } else if matches!(
            self.config.dialect,
            Some(DialectType::MySQL)
                | Some(DialectType::SingleStore)
                | Some(DialectType::Doris)
                | Some(DialectType::StarRocks)
                | Some(DialectType::Hive)
                | Some(DialectType::Spark)
                | Some(DialectType::Databricks)
        ) {
            // LOCATE(substr, str[, position]) - substr first
            self.write_keyword("LOCATE");
            self.write("(");
            if let Some(substr) = &e.substr {
                self.generate_expression(substr)?;
                self.write(", ");
            }
            self.generate_expression(&e.this)?;
            if let Some(position) = &e.position {
                self.write(", ");
                self.generate_expression(position)?;
            }
            self.write(")");
        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
            // CHARINDEX(substr, str[, position])
            self.write_keyword("CHARINDEX");
            self.write("(");
            if let Some(substr) = &e.substr {
                self.generate_expression(substr)?;
                self.write(", ");
            }
            self.generate_expression(&e.this)?;
            if let Some(position) = &e.position {
                self.write(", ");
                self.generate_expression(position)?;
            }
            self.write(")");
        } else {
            self.write_keyword("STRPOS");
            self.write("(");
            self.generate_expression(&e.this)?;
            if let Some(substr) = &e.substr {
                self.write(", ");
                self.generate_expression(substr)?;
            }
            if let Some(position) = &e.position {
                self.write(", ");
                self.generate_expression(position)?;
            }
            if let Some(occurrence) = &e.occurrence {
                self.write(", ");
                self.generate_expression(occurrence)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
        match self.config.dialect {
            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
                // TO_DATE(this, java_format)
                self.write_keyword("TO_DATE");
                self.write("(");
                self.generate_expression(&e.this)?;
                if let Some(format) = &e.format {
                    self.write(", '");
                    self.write(&Self::strftime_to_java_format(format));
                    self.write("'");
                }
                self.write(")");
            }
            Some(DialectType::DuckDB) => {
                // CAST(STRPTIME(this, format) AS DATE)
                self.write_keyword("CAST");
                self.write("(");
                self.write_keyword("STRPTIME");
                self.write("(");
                self.generate_expression(&e.this)?;
                if let Some(format) = &e.format {
                    self.write(", '");
                    self.write(format);
                    self.write("'");
                }
                self.write(")");
                self.write_keyword(" AS ");
                self.write_keyword("DATE");
                self.write(")");
            }
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
                // TO_DATE(this, pg_format)
                self.write_keyword("TO_DATE");
                self.write("(");
                self.generate_expression(&e.this)?;
                if let Some(format) = &e.format {
                    self.write(", '");
                    self.write(&Self::strftime_to_postgres_format(format));
                    self.write("'");
                }
                self.write(")");
            }
            Some(DialectType::BigQuery) => {
                // PARSE_DATE(format, this) - note: format comes first for BigQuery
                self.write_keyword("PARSE_DATE");
                self.write("(");
                if let Some(format) = &e.format {
                    self.write("'");
                    self.write(format);
                    self.write("'");
                    self.write(", ");
                }
                self.generate_expression(&e.this)?;
                self.write(")");
            }
            Some(DialectType::Teradata) => {
                // CAST(this AS DATE FORMAT 'teradata_fmt')
                self.write_keyword("CAST");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write_keyword(" AS ");
                self.write_keyword("DATE");
                if let Some(format) = &e.format {
                    self.write_keyword(" FORMAT ");
                    self.write("'");
                    self.write(&Self::strftime_to_teradata_format(format));
                    self.write("'");
                }
                self.write(")");
            }
            _ => {
                // STR_TO_DATE(this, format) - MySQL default
                self.write_keyword("STR_TO_DATE");
                self.write("(");
                self.generate_expression(&e.this)?;
                if let Some(format) = &e.format {
                    self.write(", '");
                    self.write(format);
                    self.write("'");
                }
                self.write(")");
            }
        }
        Ok(())
    }

    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
    fn strftime_to_teradata_format(fmt: &str) -> String {
        let mut result = fmt.to_string();
        result = result.replace("%Y", "YYYY");
        result = result.replace("%y", "YY");
        result = result.replace("%m", "MM");
        result = result.replace("%B", "MMMM");
        result = result.replace("%b", "MMM");
        result = result.replace("%d", "DD");
        result = result.replace("%j", "DDD");
        result = result.replace("%H", "HH");
        result = result.replace("%M", "MI");
        result = result.replace("%S", "SS");
        result = result.replace("%f", "SSSSSS");
        result = result.replace("%A", "EEEE");
        result = result.replace("%a", "EEE");
        result
    }

    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
    fn strftime_to_java_format(fmt: &str) -> String {
        let mut result = fmt.to_string();
        result = result.replace("%Y", "yyyy");
        result = result.replace("%y", "yy");
        result = result.replace("%m", "MM");
        result = result.replace("%B", "MMMM");
        result = result.replace("%b", "MMM");
        result = result.replace("%d", "dd");
        result = result.replace("%j", "DDD");
        result = result.replace("%H", "HH");
        result = result.replace("%M", "mm");
        result = result.replace("%S", "ss");
        result = result.replace("%f", "SSSSSS");
        result = result.replace("%A", "EEEE");
        result = result.replace("%a", "EEE");
        result
    }

    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
    fn strftime_to_postgres_format(fmt: &str) -> String {
        let mut result = fmt.to_string();
        result = result.replace("%Y", "YYYY");
        result = result.replace("%y", "YY");
        result = result.replace("%m", "MM");
        result = result.replace("%B", "Month");
        result = result.replace("%b", "Mon");
        result = result.replace("%d", "DD");
        result = result.replace("%j", "DDD");
        result = result.replace("%H", "HH24");
        result = result.replace("%M", "MI");
        result = result.replace("%S", "SS");
        result = result.replace("%f", "US");
        result = result.replace("%A", "Day");
        result = result.replace("%a", "Dy");
        result
    }

    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
        // STR_TO_MAP(this, pair_delim, key_value_delim)
        self.write_keyword("STR_TO_MAP");
        self.write("(");
        self.generate_expression(&e.this)?;
        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
        let needs_defaults = matches!(
            self.config.dialect,
            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
        );
        if let Some(pair_delim) = &e.pair_delim {
            self.write(", ");
            self.generate_expression(pair_delim)?;
        } else if needs_defaults {
            self.write(", ','");
        }
        if let Some(key_value_delim) = &e.key_value_delim {
            self.write(", ");
            self.generate_expression(key_value_delim)?;
        } else if needs_defaults {
            self.write(", ':'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
        match self.config.dialect {
            Some(DialectType::Exasol) => {
                self.write_keyword("TO_DATE");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", '");
                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
                self.write("'");
                self.write(")");
            }
            Some(DialectType::BigQuery) => {
                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
                let fmt = Self::snowflake_format_to_strftime(&e.format);
                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
                self.write_keyword("PARSE_TIMESTAMP");
                self.write("('");
                self.write(&fmt);
                self.write("', ");
                self.generate_expression(&e.this)?;
                self.write(")");
            }
            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
                // Spark: TO_TIMESTAMP(value, format)
                let fmt = Self::snowflake_format_to_spark(&e.format);
                self.write_keyword("TO_TIMESTAMP");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", '");
                self.write(&fmt);
                self.write("')");
            }
            Some(DialectType::Presto) | Some(DialectType::Trino) => {
                // Presto: DATE_PARSE(value, format)
                let fmt = Self::snowflake_format_to_strftime(&e.format);
                self.write_keyword("DATE_PARSE");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", '");
                self.write(&fmt);
                self.write("')");
            }
            Some(DialectType::DuckDB) => {
                // DuckDB: STRPTIME(value, format)
                let fmt = Self::snowflake_format_to_strftime(&e.format);
                self.write_keyword("STRPTIME");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", '");
                self.write(&fmt);
                self.write("')");
            }
            Some(DialectType::Snowflake) => {
                // Snowflake: TO_TIMESTAMP(value, format)
                self.write_keyword("TO_TIMESTAMP");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", '");
                self.write(&e.format);
                self.write("')");
            }
            _ => {
                // Default: STR_TO_TIME(this, format)
                self.write_keyword("STR_TO_TIME");
                self.write("(");
                self.generate_expression(&e.this)?;
                self.write(", '");
                self.write(&e.format);
                self.write("'");
                self.write(")");
            }
        }
        Ok(())
    }

    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
    fn snowflake_format_to_strftime(format: &str) -> String {
        let mut result = String::new();
        let chars: Vec<char> = format.chars().collect();
        let mut i = 0;
        while i < chars.len() {
            let remaining = &format[i..];
            if remaining.starts_with("yyyy") {
                result.push_str("%Y");
                i += 4;
            } else if remaining.starts_with("yy") {
                result.push_str("%y");
                i += 2;
            } else if remaining.starts_with("mmmm") {
                result.push_str("%B"); // full month name
                i += 4;
            } else if remaining.starts_with("mon") {
                result.push_str("%b"); // abbreviated month
                i += 3;
            } else if remaining.starts_with("mm") {
                result.push_str("%m");
                i += 2;
            } else if remaining.starts_with("DD") {
                result.push_str("%d");
                i += 2;
            } else if remaining.starts_with("dy") {
                result.push_str("%a"); // abbreviated day name
                i += 2;
            } else if remaining.starts_with("hh24") {
                result.push_str("%H");
                i += 4;
            } else if remaining.starts_with("hh12") {
                result.push_str("%I");
                i += 4;
            } else if remaining.starts_with("hh") {
                result.push_str("%H");
                i += 2;
            } else if remaining.starts_with("mi") {
                result.push_str("%M");
                i += 2;
            } else if remaining.starts_with("ss") {
                result.push_str("%S");
                i += 2;
            } else if remaining.starts_with("ff") {
                // Fractional seconds
                result.push_str("%f");
                i += 2;
                // Skip digits after ff (ff3, ff6, ff9)
                while i < chars.len() && chars[i].is_ascii_digit() {
                    i += 1;
                }
            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
                result.push_str("%p");
                i += 2;
            } else if remaining.starts_with("tz") {
                result.push_str("%Z");
                i += 2;
            } else {
                result.push(chars[i]);
                i += 1;
            }
        }
        result
    }

    /// Convert Snowflake normalized format to Spark format (Java-style)
    fn snowflake_format_to_spark(format: &str) -> String {
        let mut result = String::new();
        let chars: Vec<char> = format.chars().collect();
        let mut i = 0;
        while i < chars.len() {
            let remaining = &format[i..];
            if remaining.starts_with("yyyy") {
                result.push_str("yyyy");
                i += 4;
            } else if remaining.starts_with("yy") {
                result.push_str("yy");
                i += 2;
            } else if remaining.starts_with("mmmm") {
                result.push_str("MMMM"); // full month name
                i += 4;
            } else if remaining.starts_with("mon") {
                result.push_str("MMM"); // abbreviated month
                i += 3;
            } else if remaining.starts_with("mm") {
                result.push_str("MM");
                i += 2;
            } else if remaining.starts_with("DD") {
                result.push_str("dd");
                i += 2;
            } else if remaining.starts_with("dy") {
                result.push_str("EEE"); // abbreviated day name
                i += 2;
            } else if remaining.starts_with("hh24") {
                result.push_str("HH");
                i += 4;
            } else if remaining.starts_with("hh12") {
                result.push_str("hh");
                i += 4;
            } else if remaining.starts_with("hh") {
                result.push_str("HH");
                i += 2;
            } else if remaining.starts_with("mi") {
                result.push_str("mm");
                i += 2;
            } else if remaining.starts_with("ss") {
                result.push_str("ss");
                i += 2;
            } else if remaining.starts_with("ff") {
                result.push_str("SSS"); // milliseconds
                i += 2;
                // Skip digits after ff
                while i < chars.len() && chars[i].is_ascii_digit() {
                    i += 1;
                }
            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
                result.push('a');
                i += 2;
            } else if remaining.starts_with("tz") {
                result.push('z');
                i += 2;
            } else {
                result.push(chars[i]);
                i += 1;
            }
        }
        result
    }

    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
        // STR_TO_UNIX(this, format)
        self.write_keyword("STR_TO_UNIX");
        self.write("(");
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
        // STRING_TO_ARRAY(this, delimiter, null_string)
        self.write_keyword("STRING_TO_ARRAY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        if let Some(null_val) = &e.null {
            self.write(", ");
            self.generate_expression(null_val)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
            self.write_keyword("OBJECT_CONSTRUCT");
            self.write("(");
            for (i, (name, expr)) in e.fields.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                if let Some(name) = name {
                    self.write("'");
                    self.write(name);
                    self.write("'");
                    self.write(", ");
                } else {
                    self.write("'_");
                    self.write(&i.to_string());
                    self.write("'");
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        } else if self.config.struct_curly_brace_notation {
            // DuckDB-style: {'key': value, ...}
            self.write("{");
            for (i, (name, expr)) in e.fields.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                if let Some(name) = name {
                    // Quote the key as a string literal
                    self.write("'");
                    self.write(name);
                    self.write("'");
                    self.write(": ");
                } else {
                    // Unnamed field: use positional key
                    self.write("'_");
                    self.write(&i.to_string());
                    self.write("'");
                    self.write(": ");
                }
                self.generate_expression(expr)?;
            }
            self.write("}");
        } else {
            // Standard SQL struct notation
            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
            let value_as_name = matches!(
                self.config.dialect,
                Some(DialectType::BigQuery)
                    | Some(DialectType::Spark)
                    | Some(DialectType::Databricks)
                    | Some(DialectType::Hive)
            );
            self.write_keyword("STRUCT");
            self.write("(");
            for (i, (name, expr)) in e.fields.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                if let Some(name) = name {
                    if value_as_name {
                        // STRUCT(value AS name)
                        self.generate_expression(expr)?;
                        self.write_space();
                        self.write_keyword("AS");
                        self.write_space();
                        // Quote name if it contains spaces or special chars
                        let needs_quoting = name.contains(' ') || name.contains('-');
                        if needs_quoting {
                            if matches!(
                                self.config.dialect,
                                Some(DialectType::Spark)
                                    | Some(DialectType::Databricks)
                                    | Some(DialectType::Hive)
                            ) {
                                self.write("`");
                                self.write(name);
                                self.write("`");
                            } else {
                                self.write(name);
                            }
                        } else {
                            self.write(name);
                        }
                    } else {
                        // STRUCT(name AS value)
                        self.write(name);
                        self.write_space();
                        self.write_keyword("AS");
                        self.write_space();
                        self.generate_expression(expr)?;
                    }
                } else {
                    self.generate_expression(expr)?;
                }
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
        // STUFF(this, start, length, expression)
        self.write_keyword("STUFF");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(start) = &e.start {
            self.write(", ");
            self.generate_expression(start)?;
        }
        if let Some(length) = e.length {
            self.write(", ");
            self.write(&length.to_string());
        }
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
        // SUBSTRING_INDEX(this, delimiter, count)
        self.write_keyword("SUBSTRING_INDEX");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(delimiter) = &e.delimiter {
            self.write(", ");
            self.generate_expression(delimiter)?;
        }
        if let Some(count) = &e.count {
            self.write(", ");
            self.generate_expression(count)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
        // SUMMARIZE [TABLE] this
        self.write_keyword("SUMMARIZE");
        if e.table.is_some() {
            self.write_space();
            self.write_keyword("TABLE");
        }
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
        // SYSTIMESTAMP
        self.write_keyword("SYSTIMESTAMP");
        Ok(())
    }

    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
        // alias (columns...)
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        if !e.columns.is_empty() {
            self.write("(");
            for (i, col) in e.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(col)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
        // TABLE(this) [AS alias]
        self.write_keyword("TABLE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        if let Some(alias) = &e.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.write(alias);
        }
        Ok(())
    }

    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
        self.write_keyword("ROWS FROM");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            // Each expression is either:
            // - A plain function (no alias)
            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
            match expr {
                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
                    // First element is the function, second is the TableAlias
                    self.generate_expression(&tuple.expressions[0])?;
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_expression(&tuple.expressions[1])?;
                }
                _ => {
                    self.generate_expression(expr)?;
                }
            }
        }
        self.write(")");
        if e.ordinality {
            self.write_space();
            self.write_keyword("WITH ORDINALITY");
        }
        if let Some(alias) = &e.alias {
            self.write_space();
            self.write_keyword("AS");
            self.write_space();
            self.generate_expression(alias)?;
        }
        Ok(())
    }

    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
        use crate::dialects::DialectType;

        // New wrapper pattern: expression + Sample struct
        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
            if self.config.alias_post_tablesample {
                // Handle Subquery with alias and Alias wrapper
                if let Expression::Subquery(ref s) = **this {
                    if let Some(ref alias) = s.alias {
                        // Create a clone without alias for output
                        let mut subquery_no_alias = (**s).clone();
                        subquery_no_alias.alias = None;
                        subquery_no_alias.column_aliases = Vec::new();
                        self.generate_expression(&Expression::Subquery(Box::new(
                            subquery_no_alias,
                        )))?;
                        self.write_space();
                        self.write_keyword("TABLESAMPLE");
                        self.generate_sample_body(sample)?;
                        if let Some(ref seed) = sample.seed {
                            self.write_space();
                            let use_seed = sample.use_seed_keyword
                                && !matches!(
                                    self.config.dialect,
                                    Some(crate::dialects::DialectType::Databricks)
                                        | Some(crate::dialects::DialectType::Spark)
                                );
                            if use_seed {
                                self.write_keyword("SEED");
                            } else {
                                self.write_keyword("REPEATABLE");
                            }
                            self.write(" (");
                            self.generate_expression(seed)?;
                            self.write(")");
                        }
                        self.write_space();
                        self.write_keyword("AS");
                        self.write_space();
                        self.generate_identifier(alias)?;
                        return Ok(());
                    }
                } else if let Expression::Alias(ref a) = **this {
                    // Output the base expression without alias
                    self.generate_expression(&a.this)?;
                    self.write_space();
                    self.write_keyword("TABLESAMPLE");
                    self.generate_sample_body(sample)?;
                    if let Some(ref seed) = sample.seed {
                        self.write_space();
                        let use_seed = sample.use_seed_keyword
                            && !matches!(
                                self.config.dialect,
                                Some(crate::dialects::DialectType::Databricks)
                                    | Some(crate::dialects::DialectType::Spark)
                            );
                        if use_seed {
                            self.write_keyword("SEED");
                        } else {
                            self.write_keyword("REPEATABLE");
                        }
                        self.write(" (");
                        self.generate_expression(seed)?;
                        self.write(")");
                    }
                    // Output alias after TABLESAMPLE
                    self.write_space();
                    self.write_keyword("AS");
                    self.write_space();
                    self.generate_identifier(&a.alias)?;
                    return Ok(());
                }
            }
            // Default: generate wrapped expression first, then TABLESAMPLE
            self.generate_expression(this)?;
            self.write_space();
            self.write_keyword("TABLESAMPLE");
            self.generate_sample_body(sample)?;
            // Seed for table-level sample
            if let Some(ref seed) = sample.seed {
                self.write_space();
                // Databricks uses REPEATABLE, not SEED
                let use_seed = sample.use_seed_keyword
                    && !matches!(
                        self.config.dialect,
                        Some(crate::dialects::DialectType::Databricks)
                            | Some(crate::dialects::DialectType::Spark)
                    );
                if use_seed {
                    self.write_keyword("SEED");
                } else {
                    self.write_keyword("REPEATABLE");
                }
                self.write(" (");
                self.generate_expression(seed)?;
                self.write(")");
            }
            return Ok(());
        }

        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
        self.write_keyword("TABLESAMPLE");
        if let Some(method) = &e.method {
            self.write_space();
            self.write_keyword(method);
        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
            // Snowflake defaults to BERNOULLI when no method is specified
            self.write_space();
            self.write_keyword("BERNOULLI");
        }
        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
            self.write_space();
            self.write_keyword("BUCKET");
            self.write_space();
            self.generate_expression(numerator)?;
            self.write_space();
            self.write_keyword("OUT OF");
            self.write_space();
            self.generate_expression(denominator)?;
            if let Some(field) = &e.bucket_field {
                self.write_space();
                self.write_keyword("ON");
                self.write_space();
                self.generate_expression(field)?;
            }
        } else if !e.expressions.is_empty() {
            self.write(" (");
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        } else if let Some(percent) = &e.percent {
            self.write(" (");
            self.generate_expression(percent)?;
            self.write_space();
            self.write_keyword("PERCENT");
            self.write(")");
        }
        Ok(())
    }

    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
        // [prefix]this[postfix]
        if let Some(prefix) = &e.prefix {
            self.generate_expression(prefix)?;
        }
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        if let Some(postfix) = &e.postfix {
            self.generate_expression(postfix)?;
        }
        Ok(())
    }

    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
        // TAG (expressions)
        self.write_keyword("TAG");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
        // TEMPORARY or TEMP or [this] TEMPORARY
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
            self.write_space();
        }
        self.write_keyword("TEMPORARY");
        Ok(())
    }

    /// Generate a Time function expression
    /// For most dialects: TIME('value')
    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
        // Standard: TIME(value)
        self.write_keyword("TIME");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
        // TIME_ADD(this, expression, unit)
        self.write_keyword("TIME_ADD");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
        // TIME_DIFF(this, expression, unit)
        self.write_keyword("TIME_DIFF");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
        self.write_keyword("TIME_FROM_PARTS");
        self.write("(");
        let mut first = true;
        if let Some(hour) = &e.hour {
            self.generate_expression(hour)?;
            first = false;
        }
        if let Some(minute) = &e.min {
            if !first {
                self.write(", ");
            }
            self.generate_expression(minute)?;
            first = false;
        }
        if let Some(second) = &e.sec {
            if !first {
                self.write(", ");
            }
            self.generate_expression(second)?;
            first = false;
        }
        if let Some(ns) = &e.nano {
            if !first {
                self.write(", ");
            }
            self.generate_expression(ns)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
        // TIME_SLICE(this, expression, unit)
        self.write_keyword("TIME_SLICE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(", ");
        self.write_keyword(&e.unit);
        self.write(")");
        Ok(())
    }

    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
        // TIME_STR_TO_TIME(this)
        self.write_keyword("TIME_STR_TO_TIME");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
        // TIME_SUB(this, expression, unit)
        self.write_keyword("TIME_SUB");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
        // Exasol uses TO_CHAR with Exasol-specific format
        if self.config.dialect == Some(DialectType::Exasol) {
            self.write_keyword("TO_CHAR");
            self.write("(");
            self.generate_expression(&e.this)?;
            self.write(", '");
            self.write(&Self::convert_strptime_to_exasol_format(&e.format));
            self.write("'");
            self.write(")");
            return Ok(());
        }

        // PostgreSQL/Redshift uses TO_CHAR with PG-specific format
        if matches!(
            self.config.dialect,
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
        ) {
            self.write_keyword("TO_CHAR");
            self.write("(");
            self.generate_expression(&e.this)?;
            self.write(", '");
            self.write(&Self::convert_strptime_to_postgres_format(&e.format));
            self.write("'");
            self.write(")");
            return Ok(());
        }

        // TIME_TO_STR(this, format)
        self.write_keyword("TIME_TO_STR");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", '");
        self.write(&e.format);
        self.write("'");
        self.write(")");
        Ok(())
    }

    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
        // TIME_TRUNC(this, unit)
        self.write_keyword("TIME_TRUNC");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.write_keyword(&e.unit);
        self.write(")");
        Ok(())
    }

    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
        // Just output the unit name
        if let Some(unit) = &e.unit {
            self.write_keyword(unit);
        }
        Ok(())
    }

    /// Generate a Timestamp function expression
    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
    /// For other dialects: TIMESTAMP('value')
    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
        use crate::dialects::DialectType;
        use crate::expressions::Literal;

        match self.config.dialect {
            // Exasol uses TO_TIMESTAMP for Timestamp expressions
            Some(DialectType::Exasol) => {
                self.write_keyword("TO_TIMESTAMP");
                self.write("(");
                // Extract the string value from the expression if it's a string literal
                if let Some(this) = &e.this {
                    match this.as_ref() {
                        Expression::Literal(Literal::String(s)) => {
                            self.write("'");
                            self.write(s);
                            self.write("'");
                        }
                        _ => {
                            self.generate_expression(this)?;
                        }
                    }
                }
                self.write(")");
            }
            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
            _ => {
                self.write_keyword("TIMESTAMP");
                self.write("(");
                if let Some(this) = &e.this {
                    self.generate_expression(this)?;
                }
                if let Some(zone) = &e.zone {
                    self.write(", ");
                    self.generate_expression(zone)?;
                }
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
        // TIMESTAMP_ADD(this, expression, unit)
        self.write_keyword("TIMESTAMP_ADD");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
        // TIMESTAMP_DIFF(this, expression, unit)
        self.write_keyword("TIMESTAMP_DIFF");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
        // TIMESTAMP_FROM_PARTS(this, expression)
        self.write_keyword("TIMESTAMP_FROM_PARTS");
        self.write("(");
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        if let Some(zone) = &e.zone {
            self.write(", ");
            self.generate_expression(zone)?;
        }
        if let Some(milli) = &e.milli {
            self.write(", ");
            self.generate_expression(milli)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
        // TIMESTAMP_SUB(this, INTERVAL expression unit)
        self.write_keyword("TIMESTAMP_SUB");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.write_keyword("INTERVAL");
        self.write_space();
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write_space();
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
        // TIMESTAMP_TZ_FROM_PARTS(...)
        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
        self.write("(");
        if let Some(zone) = &e.zone {
            self.generate_expression(zone)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
        // TO_BINARY(this, [format])
        self.write_keyword("TO_BINARY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
        // TO_BOOLEAN(this)
        self.write_keyword("TO_BOOLEAN");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(")");
        Ok(())
    }

    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
        // TO_CHAR(this, [format], [nlsparam])
        self.write_keyword("TO_CHAR");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        if let Some(nlsparam) = &e.nlsparam {
            self.write(", ");
            self.generate_expression(nlsparam)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
        // TO_DECFLOAT(this, [format])
        self.write_keyword("TO_DECFLOAT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
        // TO_DOUBLE(this, [format])
        self.write_keyword("TO_DOUBLE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
        // TO_FILE(this, path)
        self.write_keyword("TO_FILE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(path) = &e.path {
            self.write(", ");
            self.generate_expression(path)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
        // If safe flag is set, output TRY_TO_NUMBER
        let is_safe = e.safe.is_some();
        if is_safe {
            self.write_keyword("TRY_TO_NUMBER");
        } else {
            self.write_keyword("TO_NUMBER");
        }
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", ");
            self.generate_expression(format)?;
        }
        if let Some(nlsparam) = &e.nlsparam {
            self.write(", ");
            self.generate_expression(nlsparam)?;
        }
        if let Some(precision) = &e.precision {
            self.write(", ");
            self.generate_expression(precision)?;
        }
        if let Some(scale) = &e.scale {
            self.write(", ");
            self.generate_expression(scale)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
        // TO_TABLE this
        self.write_keyword("TO_TABLE");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
        // Check mark to determine the format
        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
            Expression::Identifier(id) => id.name.clone(),
            Expression::Literal(Literal::String(s)) => s.clone(),
            _ => String::new(),
        });

        let is_start = mark_text.as_ref().is_some_and(|s| s == "START");
        let has_transaction_keyword = mark_text.as_ref().is_some_and(|s| s == "TRANSACTION");
        let has_with_mark = e
            .mark
            .as_ref()
            .is_some_and(|m| matches!(m.as_ref(), Expression::Literal(Literal::String(_))));

        if is_start {
            // START TRANSACTION [modes]
            self.write_keyword("START TRANSACTION");
            if let Some(modes) = &e.modes {
                self.write_space();
                self.generate_expression(modes)?;
            }
        } else {
            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
            self.write_keyword("BEGIN");

            // Output TRANSACTION keyword if it was present
            if has_transaction_keyword || has_with_mark {
                self.write_space();
                self.write_keyword("TRANSACTION");
            }

            // Output transaction name or kind (e.g., DEFERRED)
            if let Some(this) = &e.this {
                self.write_space();
                self.generate_expression(this)?;
            }

            // Output WITH MARK 'description' for TSQL
            if has_with_mark {
                self.write_space();
                self.write_keyword("WITH MARK");
                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
                    if !desc.is_empty() {
                        self.write_space();
                        self.write(&format!("'{}'", desc));
                    }
                }
            }

            // Output modes (isolation levels, etc.)
            if let Some(modes) = &e.modes {
                self.write_space();
                self.generate_expression(modes)?;
            }
        }
        Ok(())
    }

    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
        // TRANSFORM(this, expression)
        self.write_keyword("TRANSFORM");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        self.write(")");
        Ok(())
    }

    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
        // TRANSFORM(expressions)
        self.write_keyword("TRANSFORM");
        self.write("(");
        if self.config.pretty && !e.expressions.is_empty() {
            self.indent_level += 1;
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(",");
                }
                self.write_newline();
                self.write_indent();
                self.generate_expression(expr)?;
            }
            self.indent_level -= 1;
            self.write_newline();
            self.write(")");
        } else {
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            self.write(")");
        }
        Ok(())
    }

    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
        use crate::dialects::DialectType;
        // TRANSIENT is Snowflake-specific; skip for other dialects
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
                self.write_space();
            }
        }
        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
            self.write_keyword("TRANSIENT");
        }
        Ok(())
    }

    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
        // TRANSLATE(this, from_, to)
        self.write_keyword("TRANSLATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(from) = &e.from_ {
            self.write(", ");
            self.generate_expression(from)?;
        }
        if let Some(to) = &e.to {
            self.write(", ");
            self.generate_expression(to)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
        // TRANSLATE(this USING expression)
        self.write_keyword("TRANSLATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("USING");
        self.write_space();
        self.generate_expression(&e.expression)?;
        if e.with_error.is_some() {
            self.write_space();
            self.write_keyword("WITH ERROR");
        }
        self.write(")");
        Ok(())
    }

    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
        // TRUNCATE TABLE table1, table2, ...
        self.write_keyword("TRUNCATE TABLE");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
        self.write_keyword("TRY_BASE64_DECODE_BINARY");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(alphabet) = &e.alphabet {
            self.write(", ");
            self.generate_expression(alphabet)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
        // TRY_BASE64_DECODE_STRING(this, [alphabet])
        self.write_keyword("TRY_BASE64_DECODE_STRING");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(alphabet) = &e.alphabet {
            self.write(", ");
            self.generate_expression(alphabet)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
        // TRY_TO_DECFLOAT(this, [format])
        self.write_keyword("TRY_TO_DECFLOAT");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
        self.write_keyword("TS_OR_DS_ADD");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        if let Some(return_type) = &e.return_type {
            self.write(", ");
            self.generate_expression(return_type)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
        // TS_OR_DS_DIFF(this, expression, [unit])
        self.write_keyword("TS_OR_DS_DIFF");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(unit) = &e.unit {
            self.write(", ");
            self.write_keyword(unit);
        }
        self.write(")");
        Ok(())
    }

    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
        // TS_OR_DS_TO_DATE(this, [format])
        self.write_keyword("TS_OR_DS_TO_DATE");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
        // TS_OR_DS_TO_TIME(this, [format])
        self.write_keyword("TS_OR_DS_TO_TIME");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
        // UNHEX(this, [expression])
        self.write_keyword("UNHEX");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write(", ");
            self.generate_expression(expression)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
        // U&this [UESCAPE escape]
        self.write("U&");
        self.generate_expression(&e.this)?;
        if let Some(escape) = &e.escape {
            self.write_space();
            self.write_keyword("UESCAPE");
            self.write_space();
            self.generate_expression(escape)?;
        }
        Ok(())
    }

    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
        // UNIFORM(this, expression, [gen], [seed])
        self.write_keyword("UNIFORM");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(gen) = &e.gen {
            self.write(", ");
            self.generate_expression(gen)?;
        }
        if let Some(seed) = &e.seed {
            self.write(", ");
            self.generate_expression(seed)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
        self.write_keyword("UNIQUE");
        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
        if e.nulls.is_some() {
            self.write(" NULLS NOT DISTINCT");
        }
        if let Some(this) = &e.this {
            self.write_space();
            self.generate_expression(this)?;
        }
        if let Some(index_type) = &e.index_type {
            self.write(" USING ");
            self.generate_expression(index_type)?;
        }
        if let Some(on_conflict) = &e.on_conflict {
            self.write_space();
            self.generate_expression(on_conflict)?;
        }
        for opt in &e.options {
            self.write_space();
            self.generate_expression(opt)?;
        }
        Ok(())
    }

    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
        // UNIQUE KEY (expressions)
        self.write_keyword("UNIQUE KEY");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
        // ROLLUP (r1(col1, col2), r2(col1))
        self.write_keyword("ROLLUP");
        self.write(" (");
        for (i, index) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_identifier(&index.name)?;
            self.write("(");
            for (j, col) in index.expressions.iter().enumerate() {
                if j > 0 {
                    self.write(", ");
                }
                self.generate_identifier(col)?;
            }
            self.write(")");
        }
        self.write(")");
        Ok(())
    }

    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
        // UNIX_TO_STR(this, [format])
        self.write_keyword("UNIX_TO_STR");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(format) = &e.format {
            self.write(", '");
            self.write(format);
            self.write("'");
        }
        self.write(")");
        Ok(())
    }

    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
        use crate::dialects::DialectType;
        let scale = e.scale.unwrap_or(0); // 0 = seconds

        match self.config.dialect {
            Some(DialectType::Snowflake) => {
                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
                self.write_keyword("TO_TIMESTAMP");
                self.write("(");
                self.generate_expression(&e.this)?;
                if let Some(s) = e.scale {
                    if s > 0 {
                        self.write(", ");
                        self.write(&s.to_string());
                    }
                }
                self.write(")");
            }
            Some(DialectType::BigQuery) => {
                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
                match scale {
                    0 => {
                        self.write_keyword("TIMESTAMP_SECONDS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    3 => {
                        self.write_keyword("TIMESTAMP_MILLIS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    6 => {
                        self.write_keyword("TIMESTAMP_MICROS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    _ => {
                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
                        self.write_keyword("TIMESTAMP_SECONDS");
                        self.write("(CAST(");
                        self.generate_expression(&e.this)?;
                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
                    }
                }
            }
            Some(DialectType::Spark) => {
                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
                // TIMESTAMP_MILLIS(value) for scale=3
                // TIMESTAMP_MICROS(value) for scale=6
                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
                match scale {
                    0 => {
                        self.write_keyword("CAST");
                        self.write("(");
                        self.write_keyword("FROM_UNIXTIME");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(") ");
                        self.write_keyword("AS TIMESTAMP");
                        self.write(")");
                    }
                    3 => {
                        self.write_keyword("TIMESTAMP_MILLIS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    6 => {
                        self.write_keyword("TIMESTAMP_MICROS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    _ => {
                        self.write_keyword("TIMESTAMP_SECONDS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(&format!(" / POWER(10, {}))", scale));
                    }
                }
            }
            Some(DialectType::Databricks) => {
                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
                // TIMESTAMP_MILLIS(value) for scale=3
                // TIMESTAMP_MICROS(value) for scale=6
                match scale {
                    0 => {
                        self.write_keyword("CAST");
                        self.write("(");
                        self.write_keyword("FROM_UNIXTIME");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(") ");
                        self.write_keyword("AS TIMESTAMP");
                        self.write(")");
                    }
                    3 => {
                        self.write_keyword("TIMESTAMP_MILLIS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    6 => {
                        self.write_keyword("TIMESTAMP_MICROS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    _ => {
                        self.write_keyword("TIMESTAMP_SECONDS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(&format!(" / POWER(10, {}))", scale));
                    }
                }
            }
            Some(DialectType::Presto) | Some(DialectType::Trino) => {
                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
                // FROM_UNIXTIME(value) for scale=0
                if scale == 0 {
                    self.write_keyword("FROM_UNIXTIME");
                    self.write("(");
                    self.generate_expression(&e.this)?;
                    self.write(")");
                } else {
                    self.write_keyword("FROM_UNIXTIME");
                    self.write("(CAST(");
                    self.generate_expression(&e.this)?;
                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
                }
            }
            Some(DialectType::DuckDB) => {
                // DuckDB: TO_TIMESTAMP(value) for scale=0
                // EPOCH_MS(value) for scale=3
                // MAKE_TIMESTAMP(value) for scale=6
                match scale {
                    0 => {
                        self.write_keyword("TO_TIMESTAMP");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    3 => {
                        self.write_keyword("EPOCH_MS");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    6 => {
                        self.write_keyword("MAKE_TIMESTAMP");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(")");
                    }
                    _ => {
                        self.write_keyword("TO_TIMESTAMP");
                        self.write("(");
                        self.generate_expression(&e.this)?;
                        self.write(&format!(" / POWER(10, {}))", scale));
                        self.write_keyword(" AT TIME ZONE");
                        self.write(" 'UTC'");
                    }
                }
            }
            Some(DialectType::Redshift) => {
                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
                self.write("(TIMESTAMP 'epoch' + ");
                if scale == 0 {
                    self.generate_expression(&e.this)?;
                } else {
                    self.write("(");
                    self.generate_expression(&e.this)?;
                    self.write(&format!(" / POWER(10, {}))", scale));
                }
                self.write(" * INTERVAL '1 SECOND')");
            }
            _ => {
                // Default: TO_TIMESTAMP(value[, scale])
                self.write_keyword("TO_TIMESTAMP");
                self.write("(");
                self.generate_expression(&e.this)?;
                if let Some(s) = e.scale {
                    self.write(", ");
                    self.write(&s.to_string());
                }
                self.write(")");
            }
        }
        Ok(())
    }

    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
        // NAME col VALUE col1, col2, ...
        if !matches!(&*e.this, Expression::Null(_)) {
            self.write_keyword("NAME");
            self.write_space();
            self.generate_expression(&e.this)?;
        }
        if !e.expressions.is_empty() {
            self.write_space();
            self.write_keyword("VALUE");
            self.write_space();
            for (i, expr) in e.expressions.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
        }
        Ok(())
    }

    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
        // this(expressions) or (this)(expressions)
        if e.wrapped.is_some() {
            self.write("(");
        }
        self.generate_expression(&e.this)?;
        if e.wrapped.is_some() {
            self.write(")");
        }
        self.write("(");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
        // USING TEMPLATE this
        self.write_keyword("USING TEMPLATE");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
        // UTC_TIME
        self.write_keyword("UTC_TIME");
        Ok(())
    }

    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
        // UTC_TIMESTAMP
        self.write_keyword("UTC_TIMESTAMP");
        Ok(())
    }

    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
        use crate::dialects::DialectType;
        // Choose UUID function name based on target dialect
        let func_name = match self.config.dialect {
            Some(DialectType::Snowflake) => "UUID_STRING",
            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
            Some(DialectType::BigQuery) => "GENERATE_UUID",
            _ => {
                if let Some(name) = &e.name {
                    name.as_str()
                } else {
                    "UUID"
                }
            }
        };
        self.write_keyword(func_name);
        self.write("(");
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
        // MAP(key1, value1, key2, value2, ...)
        self.write_keyword("MAP");
        self.write("(");
        let mut first = true;
        for (k, v) in e.keys.iter().zip(e.values.iter()) {
            if !first {
                self.write(", ");
            }
            self.generate_expression(k)?;
            self.write(", ");
            self.generate_expression(v)?;
            first = false;
        }
        self.write(")");
        Ok(())
    }

    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
        self.write_keyword("VECTOR_SEARCH");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(col) = &e.column_to_search {
            self.write(", ");
            self.generate_expression(col)?;
        }
        if let Some(query_table) = &e.query_table {
            self.write(", ");
            self.generate_expression(query_table)?;
        }
        if let Some(query_col) = &e.query_column_to_search {
            self.write(", ");
            self.generate_expression(query_col)?;
        }
        if let Some(top_k) = &e.top_k {
            self.write(", ");
            self.generate_expression(top_k)?;
        }
        if let Some(dist_type) = &e.distance_type {
            self.write(", ");
            self.generate_expression(dist_type)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_version(&mut self, e: &Version) -> Result<()> {
        // Python: f"FOR {expression.name} {kind} {expr}"
        // e.this = Identifier("TIMESTAMP" or "VERSION")
        // e.kind = "AS OF" (or "BETWEEN", etc.)
        // e.expression = the value expression
        // Hive does NOT use the FOR prefix for time travel
        use crate::dialects::DialectType;
        let skip_for = matches!(
            self.config.dialect,
            Some(DialectType::Hive) | Some(DialectType::Spark)
        );
        if !skip_for {
            self.write_keyword("FOR");
            self.write_space();
        }
        // Extract the name from this (which is an Identifier expression)
        match e.this.as_ref() {
            Expression::Identifier(ident) => {
                self.write_keyword(&ident.name);
            }
            _ => {
                self.generate_expression(&e.this)?;
            }
        }
        self.write_space();
        self.write_keyword(&e.kind);
        if let Some(expression) = &e.expression {
            self.write_space();
            self.generate_expression(expression)?;
        }
        Ok(())
    }

    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
        // Python: return self.sql(expression, "this")
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
        if e.this.is_some() {
            self.write_keyword("NOT VOLATILE");
        } else {
            self.write_keyword("VOLATILE");
        }
        Ok(())
    }

    fn generate_watermark_column_constraint(
        &mut self,
        e: &WatermarkColumnConstraint,
    ) -> Result<()> {
        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
        self.write_keyword("WATERMARK FOR");
        self.write_space();
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("AS");
        self.write_space();
        self.generate_expression(&e.expression)?;
        Ok(())
    }

    fn generate_week(&mut self, e: &Week) -> Result<()> {
        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
        self.write_keyword("WEEK");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(mode) = &e.mode {
            self.write(", ");
            self.generate_expression(mode)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_when(&mut self, e: &When) -> Result<()> {
        // Python: WHEN {matched}{source}{condition} THEN {then}
        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
        self.write_keyword("WHEN");
        self.write_space();

        // Check if matched
        if let Some(matched) = &e.matched {
            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
            match matched.as_ref() {
                Expression::Boolean(b) if b.value => {
                    self.write_keyword("MATCHED");
                }
                _ => {
                    self.write_keyword("NOT MATCHED");
                }
            }
        } else {
            self.write_keyword("NOT MATCHED");
        }

        // BY SOURCE / BY TARGET
        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
        // BY TARGET is the default and typically omitted in output
        // Only emit if the dialect supports BY SOURCE syntax
        if self.config.matched_by_source {
            if let Some(source) = &e.source {
                if let Expression::Boolean(b) = source.as_ref() {
                    if b.value {
                        // BY SOURCE
                        self.write_space();
                        self.write_keyword("BY SOURCE");
                    }
                    // BY TARGET (b.value == false) is omitted as it's the default
                } else {
                    // For non-boolean source, output as BY SOURCE (legacy behavior)
                    self.write_space();
                    self.write_keyword("BY SOURCE");
                }
            }
        }

        // Condition
        if let Some(condition) = &e.condition {
            self.write_space();
            self.write_keyword("AND");
            self.write_space();
            self.generate_expression(condition)?;
        }

        self.write_space();
        self.write_keyword("THEN");
        self.write_space();

        // Generate the then expression (could be INSERT, UPDATE, DELETE)
        // MERGE actions are stored as Tuples with the action keyword as first element
        self.generate_merge_action(&e.then)?;

        Ok(())
    }

    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
        match action {
            Expression::Tuple(tuple) => {
                let elements = &tuple.expressions;
                if elements.is_empty() {
                    return self.generate_expression(action);
                }
                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
                match &elements[0] {
                    Expression::Var(v) if v.this == "INSERT" => {
                        self.write_keyword("INSERT");
                        let mut values_idx = 1;
                        // Check if second element is column list (Tuple)
                        if elements.len() > 1 {
                            if let Expression::Tuple(cols) = &elements[1] {
                                // Could be columns or values - if there's a third element, second is columns
                                if elements.len() > 2 {
                                    // Second is columns, third is values
                                    self.write(" (");
                                    for (i, col) in cols.expressions.iter().enumerate() {
                                        if i > 0 {
                                            self.write(", ");
                                        }
                                        self.generate_expression(col)?;
                                    }
                                    self.write(")");
                                    values_idx = 2;
                                } else {
                                    // Only two elements: INSERT + values (no explicit columns)
                                    values_idx = 1;
                                }
                            }
                        }
                        // Generate VALUES clause
                        if values_idx < elements.len() {
                            // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
                            let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
                            if !is_row {
                                self.write_space();
                                self.write_keyword("VALUES");
                            }
                            self.write(" ");
                            if let Expression::Tuple(vals) = &elements[values_idx] {
                                self.write("(");
                                for (i, val) in vals.expressions.iter().enumerate() {
                                    if i > 0 {
                                        self.write(", ");
                                    }
                                    self.generate_expression(val)?;
                                }
                                self.write(")");
                            } else {
                                self.generate_expression(&elements[values_idx])?;
                            }
                        }
                    }
                    Expression::Var(v) if v.this == "UPDATE" => {
                        self.write_keyword("UPDATE");
                        if elements.len() > 1 {
                            self.write_space();
                            self.write_keyword("SET");
                            // In pretty mode, put assignments on next line with extra indent
                            if self.config.pretty {
                                self.write_newline();
                                self.indent_level += 1;
                                self.write_indent();
                            } else {
                                self.write_space();
                            }
                            if let Expression::Tuple(assignments) = &elements[1] {
                                for (i, assignment) in assignments.expressions.iter().enumerate() {
                                    if i > 0 {
                                        self.write(", ");
                                    }
                                    // Strip MERGE target qualifiers from left side of UPDATE SET
                                    if !self.merge_strip_qualifiers.is_empty() {
                                        self.generate_merge_set_assignment(assignment)?;
                                    } else {
                                        self.generate_expression(assignment)?;
                                    }
                                }
                            } else {
                                self.generate_expression(&elements[1])?;
                            }
                            if self.config.pretty {
                                self.indent_level -= 1;
                            }
                        }
                    }
                    _ => {
                        // Fallback: generic tuple generation
                        self.generate_expression(action)?;
                    }
                }
            }
            Expression::Var(v)
                if v.this == "INSERT"
                    || v.this == "UPDATE"
                    || v.this == "DELETE"
                    || v.this == "DO NOTHING" =>
            {
                self.write_keyword(&v.this);
            }
            _ => {
                self.generate_expression(action)?;
            }
        }
        Ok(())
    }

    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
        match assignment {
            Expression::Eq(eq) => {
                // Strip qualifier from the left side if it matches a MERGE target name
                let stripped_left = self.strip_merge_qualifier(&eq.left);
                self.generate_expression(&stripped_left)?;
                self.write(" = ");
                self.generate_expression(&eq.right)?;
                Ok(())
            }
            other => self.generate_expression(other),
        }
    }

    /// Strip table qualifier from a column reference if it matches a MERGE target name
    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
        match expr {
            Expression::Column(col) => {
                if let Some(ref table_ident) = col.table {
                    if self
                        .merge_strip_qualifiers
                        .iter()
                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
                    {
                        // Strip the table qualifier
                        let mut col = col.clone();
                        col.table = None;
                        return Expression::Column(col);
                    }
                }
                expr.clone()
            }
            Expression::Dot(dot) => {
                // table.column -> column (strip qualifier)
                if let Expression::Identifier(id) = &dot.this {
                    if self
                        .merge_strip_qualifiers
                        .iter()
                        .any(|n| n.eq_ignore_ascii_case(&id.name))
                    {
                        return Expression::Identifier(dot.field.clone());
                    }
                }
                expr.clone()
            }
            _ => expr.clone(),
        }
    }

    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
        // Python: return self.expressions(expression, sep=" ", indent=False)
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                // In pretty mode, each WHEN clause on its own line
                if self.config.pretty {
                    self.write_newline();
                    self.write_indent();
                } else {
                    self.write_space();
                }
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_where(&mut self, e: &Where) -> Result<()> {
        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
        self.write_keyword("WHERE");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
        self.write_keyword("WIDTH_BUCKET");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(min_value) = &e.min_value {
            self.write(", ");
            self.generate_expression(min_value)?;
        }
        if let Some(max_value) = &e.max_value {
            self.write(", ");
            self.generate_expression(max_value)?;
        }
        if let Some(num_buckets) = &e.num_buckets {
            self.write(", ");
            self.generate_expression(num_buckets)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
        // Window specification: PARTITION BY ... ORDER BY ... frame
        self.generate_window_spec(e)
    }

    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
        // Window specification: PARTITION BY ... ORDER BY ... frame
        let mut has_content = false;

        // PARTITION BY
        if !e.partition_by.is_empty() {
            self.write_keyword("PARTITION BY");
            self.write_space();
            for (i, expr) in e.partition_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(expr)?;
            }
            has_content = true;
        }

        // ORDER BY
        if !e.order_by.is_empty() {
            if has_content {
                self.write_space();
            }
            self.write_keyword("ORDER BY");
            self.write_space();
            for (i, ordered) in e.order_by.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(&ordered.this)?;
                if ordered.desc {
                    self.write_space();
                    self.write_keyword("DESC");
                } else if ordered.explicit_asc {
                    self.write_space();
                    self.write_keyword("ASC");
                }
                if let Some(nulls_first) = ordered.nulls_first {
                    self.write_space();
                    self.write_keyword("NULLS");
                    self.write_space();
                    if nulls_first {
                        self.write_keyword("FIRST");
                    } else {
                        self.write_keyword("LAST");
                    }
                }
            }
            has_content = true;
        }

        // Frame specification
        if let Some(frame) = &e.frame {
            if has_content {
                self.write_space();
            }
            self.generate_window_frame(frame)?;
        }

        Ok(())
    }

    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
        self.write_keyword("WITH");
        self.write_space();
        if e.no.is_some() {
            self.write_keyword("NO");
            self.write_space();
        }
        self.write_keyword("DATA");

        // statistics
        if let Some(statistics) = &e.statistics {
            self.write_space();
            self.write_keyword("AND");
            self.write_space();
            // Check if statistics is true or false
            match statistics.as_ref() {
                Expression::Boolean(b) if !b.value => {
                    self.write_keyword("NO");
                    self.write_space();
                }
                _ => {}
            }
            self.write_keyword("STATISTICS");
        }
        Ok(())
    }

    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
        self.write_keyword("WITH FILL");

        if let Some(from_) = &e.from_ {
            self.write_space();
            self.write_keyword("FROM");
            self.write_space();
            self.generate_expression(from_)?;
        }

        if let Some(to) = &e.to {
            self.write_space();
            self.write_keyword("TO");
            self.write_space();
            self.generate_expression(to)?;
        }

        if let Some(step) = &e.step {
            self.write_space();
            self.write_keyword("STEP");
            self.write_space();
            self.generate_expression(step)?;
        }

        if let Some(interpolate) = &e.interpolate {
            self.write_space();
            self.write_keyword("INTERPOLATE");
            self.write(" (");
            // INTERPOLATE items use reversed alias format: name AS expression
            self.generate_interpolate_item(interpolate)?;
            self.write(")");
        }

        Ok(())
    }

    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
        match expr {
            Expression::Alias(alias) => {
                // Output as: alias_name AS expression
                self.generate_identifier(&alias.alias)?;
                self.write_space();
                self.write_keyword("AS");
                self.write_space();
                self.generate_expression(&alias.this)?;
            }
            Expression::Tuple(tuple) => {
                for (i, item) in tuple.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_interpolate_item(item)?;
                }
            }
            other => {
                self.generate_expression(other)?;
            }
        }
        Ok(())
    }

    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
        self.write_keyword("WITH JOURNAL TABLE");
        self.write("=");
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
        self.generate_expression(&e.this)?;
        self.write_space();
        self.write_keyword("WITH");
        self.write_space();
        self.write_keyword(&e.op);
        Ok(())
    }

    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
        // Python: return f"WITH {self.expressions(expression, flat=True)}"
        self.write_keyword("WITH");
        self.write_space();
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_with_schema_binding_property(
        &mut self,
        e: &WithSchemaBindingProperty,
    ) -> Result<()> {
        // Python: return f"WITH {self.sql(expression, 'this')}"
        self.write_keyword("WITH");
        self.write_space();
        self.generate_expression(&e.this)?;
        Ok(())
    }

    fn generate_with_system_versioning_property(
        &mut self,
        e: &WithSystemVersioningProperty,
    ) -> Result<()> {
        // Python: complex logic for SYSTEM_VERSIONING with options
        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
        // or SYSTEM_VERSIONING=ON/OFF
        // with WITH(...) wrapper if with_ is set

        let mut parts = Vec::new();

        if let Some(this) = &e.this {
            // HISTORY_TABLE=...
            let mut s = String::from("HISTORY_TABLE=");
            let mut gen = Generator::new();
            gen.generate_expression(this)?;
            s.push_str(&gen.output);
            parts.push(s);
        }

        if let Some(data_consistency) = &e.data_consistency {
            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
            let mut gen = Generator::new();
            gen.generate_expression(data_consistency)?;
            s.push_str(&gen.output);
            parts.push(s);
        }

        if let Some(retention_period) = &e.retention_period {
            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
            let mut gen = Generator::new();
            gen.generate_expression(retention_period)?;
            s.push_str(&gen.output);
            parts.push(s);
        }

        self.write_keyword("SYSTEM_VERSIONING");
        self.write("=");

        if !parts.is_empty() {
            self.write_keyword("ON");
            self.write("(");
            self.write(&parts.join(", "));
            self.write(")");
        } else if e.on.is_some() {
            self.write_keyword("ON");
        } else {
            self.write_keyword("OFF");
        }

        // Wrap in WITH(...) if with_ is set
        if e.with_.is_some() {
            let inner = self.output.clone();
            self.output.clear();
            self.write("WITH(");
            self.write(&inner);
            self.write(")");
        }

        Ok(())
    }

    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
        // Python: f"WITH ({self.expressions(expression, flat=True)})"
        self.write_keyword("WITH");
        self.write(" (");
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
        // return self.func("XMLELEMENT", name, *expression.expressions)
        self.write_keyword("XMLELEMENT");
        self.write("(");

        if e.evalname.is_some() {
            self.write_keyword("EVALNAME");
        } else {
            self.write_keyword("NAME");
        }
        self.write_space();
        self.generate_expression(&e.this)?;

        for expr in &e.expressions {
            self.write(", ");
            self.generate_expression(expr)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
        // XMLGET(this, expression [, instance])
        self.write_keyword("XMLGET");
        self.write("(");
        self.generate_expression(&e.this)?;
        self.write(", ");
        self.generate_expression(&e.expression)?;
        if let Some(instance) = &e.instance {
            self.write(", ");
            self.generate_expression(instance)?;
        }
        self.write(")");
        Ok(())
    }

    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
        // Python: this + optional (expr)
        self.generate_expression(&e.this)?;
        if let Some(expression) = &e.expression {
            self.write("(");
            self.generate_expression(expression)?;
            self.write(")");
        }
        Ok(())
    }

    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
        self.write_keyword("XMLTABLE");
        self.write("(");

        if self.config.pretty {
            self.indent_level += 1;
            self.write_newline();
            self.write_indent();
            self.generate_expression(&e.this)?;

            if let Some(passing) = &e.passing {
                self.write_newline();
                self.write_indent();
                self.write_keyword("PASSING");
                if let Expression::Tuple(tuple) = passing.as_ref() {
                    for expr in &tuple.expressions {
                        self.write_newline();
                        self.indent_level += 1;
                        self.write_indent();
                        self.generate_expression(expr)?;
                        self.indent_level -= 1;
                    }
                } else {
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                    self.generate_expression(passing)?;
                    self.indent_level -= 1;
                }
            }

            if e.by_ref.is_some() {
                self.write_newline();
                self.write_indent();
                self.write_keyword("RETURNING SEQUENCE BY REF");
            }

            if !e.columns.is_empty() {
                self.write_newline();
                self.write_indent();
                self.write_keyword("COLUMNS");
                for (i, col) in e.columns.iter().enumerate() {
                    self.write_newline();
                    self.indent_level += 1;
                    self.write_indent();
                    self.generate_expression(col)?;
                    self.indent_level -= 1;
                    if i < e.columns.len() - 1 {
                        self.write(",");
                    }
                }
            }

            self.indent_level -= 1;
            self.write_newline();
            self.write_indent();
            self.write(")");
            return Ok(());
        }

        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
        if let Some(namespaces) = &e.namespaces {
            self.write_keyword("XMLNAMESPACES");
            self.write("(");
            // Unwrap Tuple if present to avoid extra parentheses
            if let Expression::Tuple(tuple) = namespaces.as_ref() {
                for (i, expr) in tuple.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
                    // See xmlnamespace_sql in generator.py
                    if !matches!(expr, Expression::Alias(_)) {
                        self.write_keyword("DEFAULT");
                        self.write_space();
                    }
                    self.generate_expression(expr)?;
                }
            } else {
                // Single namespace - check if DEFAULT
                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
                    self.write_keyword("DEFAULT");
                    self.write_space();
                }
                self.generate_expression(namespaces)?;
            }
            self.write("), ");
        }

        // XPath expression
        self.generate_expression(&e.this)?;

        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
        if let Some(passing) = &e.passing {
            self.write_space();
            self.write_keyword("PASSING");
            self.write_space();
            // Unwrap Tuple if present to avoid extra parentheses
            if let Expression::Tuple(tuple) = passing.as_ref() {
                for (i, expr) in tuple.expressions.iter().enumerate() {
                    if i > 0 {
                        self.write(", ");
                    }
                    self.generate_expression(expr)?;
                }
            } else {
                self.generate_expression(passing)?;
            }
        }

        // RETURNING SEQUENCE BY REF
        if e.by_ref.is_some() {
            self.write_space();
            self.write_keyword("RETURNING SEQUENCE BY REF");
        }

        // COLUMNS clause
        if !e.columns.is_empty() {
            self.write_space();
            self.write_keyword("COLUMNS");
            self.write_space();
            for (i, col) in e.columns.iter().enumerate() {
                if i > 0 {
                    self.write(", ");
                }
                self.generate_expression(col)?;
            }
        }

        self.write(")");
        Ok(())
    }

    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
        // Python: return self.connector_sql(expression, "XOR", stack)
        // Handles: this XOR expression or expressions joined by XOR
        if let Some(this) = &e.this {
            self.generate_expression(this)?;
            if let Some(expression) = &e.expression {
                self.write_space();
                self.write_keyword("XOR");
                self.write_space();
                self.generate_expression(expression)?;
            }
        }

        // Handle multiple expressions
        for (i, expr) in e.expressions.iter().enumerate() {
            if i > 0 || e.this.is_some() {
                self.write_space();
                self.write_keyword("XOR");
                self.write_space();
            }
            self.generate_expression(expr)?;
        }
        Ok(())
    }

    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
        // ZIPF(this, elementcount [, gen])
        self.write_keyword("ZIPF");
        self.write("(");
        self.generate_expression(&e.this)?;
        if let Some(elementcount) = &e.elementcount {
            self.write(", ");
            self.generate_expression(elementcount)?;
        }
        if let Some(gen) = &e.gen {
            self.write(", ");
            self.generate_expression(gen)?;
        }
        self.write(")");
        Ok(())
    }
}

impl Default for Generator {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parser::Parser;

    fn roundtrip(sql: &str) -> String {
        let ast = Parser::parse_sql(sql).unwrap();
        Generator::sql(&ast[0]).unwrap()
    }

    #[test]
    fn test_simple_select() {
        let result = roundtrip("SELECT 1");
        assert_eq!(result, "SELECT 1");
    }

    #[test]
    fn test_select_from() {
        let result = roundtrip("SELECT a, b FROM t");
        assert_eq!(result, "SELECT a, b FROM t");
    }

    #[test]
    fn test_select_where() {
        let result = roundtrip("SELECT * FROM t WHERE x = 1");
        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
    }

    #[test]
    fn test_select_join() {
        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
    }

    #[test]
    fn test_insert() {
        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
    }

    #[test]
    fn test_pretty_print() {
        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
        let result = Generator::pretty_sql(&ast[0]).unwrap();
        assert!(result.contains('\n'));
    }

    #[test]
    fn test_window_function() {
        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
        assert_eq!(
            result,
            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
        );
    }

    #[test]
    fn test_window_function_with_frame() {
        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
    }

    #[test]
    fn test_aggregate_with_filter() {
        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
        assert_eq!(
            result,
            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
        );
    }

    #[test]
    fn test_subscript() {
        let result = roundtrip("SELECT arr[0]");
        assert_eq!(result, "SELECT arr[0]");
    }

    // DDL tests
    #[test]
    fn test_create_table() {
        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
    }

    #[test]
    fn test_create_table_with_constraints() {
        let result = roundtrip(
            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
        );
        assert_eq!(
            result,
            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
        );
    }

    #[test]
    fn test_create_table_if_not_exists() {
        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
    }

    #[test]
    fn test_drop_table() {
        let result = roundtrip("DROP TABLE users");
        assert_eq!(result, "DROP TABLE users");
    }

    #[test]
    fn test_drop_table_if_exists_cascade() {
        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
    }

    #[test]
    fn test_alter_table_add_column() {
        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
    }

    #[test]
    fn test_alter_table_drop_column() {
        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
    }

    #[test]
    fn test_create_index() {
        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
    }

    #[test]
    fn test_create_unique_index() {
        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
    }

    #[test]
    fn test_drop_index() {
        let result = roundtrip("DROP INDEX idx_name");
        assert_eq!(result, "DROP INDEX idx_name");
    }

    #[test]
    fn test_create_view() {
        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
        assert_eq!(
            result,
            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
        );
    }

    #[test]
    fn test_drop_view() {
        let result = roundtrip("DROP VIEW active_users");
        assert_eq!(result, "DROP VIEW active_users");
    }

    #[test]
    fn test_truncate() {
        let result = roundtrip("TRUNCATE TABLE users");
        assert_eq!(result, "TRUNCATE TABLE users");
    }

    #[test]
    fn test_string_literal_escaping_default() {
        // Default: double single quotes
        let result = roundtrip("SELECT 'hello'");
        assert_eq!(result, "SELECT 'hello'");

        // Single quotes are doubled
        let result = roundtrip("SELECT 'it''s a test'");
        assert_eq!(result, "SELECT 'it''s a test'");
    }

    #[test]
    fn test_string_literal_escaping_mysql() {
        use crate::dialects::DialectType;

        let config = GeneratorConfig {
            dialect: Some(DialectType::MySQL),
            ..Default::default()
        };

        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
        let mut gen = Generator::with_config(config.clone());
        let result = gen.generate(&ast[0]).unwrap();
        assert_eq!(result, "SELECT 'hello'");

        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
        let mut gen = Generator::with_config(config.clone());
        let result = gen.generate(&ast[0]).unwrap();
        assert_eq!(result, "SELECT 'it''s'");
    }

    #[test]
    fn test_string_literal_escaping_postgres() {
        use crate::dialects::DialectType;

        let config = GeneratorConfig {
            dialect: Some(DialectType::PostgreSQL),
            ..Default::default()
        };

        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
        let mut gen = Generator::with_config(config.clone());
        let result = gen.generate(&ast[0]).unwrap();
        assert_eq!(result, "SELECT 'hello'");

        // PostgreSQL uses doubled quotes for regular strings
        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
        let mut gen = Generator::with_config(config.clone());
        let result = gen.generate(&ast[0]).unwrap();
        assert_eq!(result, "SELECT 'it''s'");
    }

    #[test]
    fn test_string_literal_escaping_bigquery() {
        use crate::dialects::DialectType;

        let config = GeneratorConfig {
            dialect: Some(DialectType::BigQuery),
            ..Default::default()
        };

        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
        let mut gen = Generator::with_config(config.clone());
        let result = gen.generate(&ast[0]).unwrap();
        assert_eq!(result, "SELECT 'hello'");

        // BigQuery escapes single quotes with backslash
        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
        let mut gen = Generator::with_config(config.clone());
        let result = gen.generate(&ast[0]).unwrap();
        assert_eq!(result, "SELECT 'it\\'s'");
    }
}
