Visitar URL original
sqlglot.generator API documentation
Edit on GitHub

sqlglot.generator

   1from __future__ import annotations
   2
   3import logging
   4import re
   5import typing as t
   6from collections import defaultdict
   7from decimal import Decimal
   8from functools import reduce, wraps
   9
  10from sqlglot import exp
  11from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
  12from sqlglot.expressions import apply_index_offset
  13from sqlglot.expressions.core import maybe_parse
  14from sqlglot.helper import csv, name_sequence, seq_get
  15from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS
  16from sqlglot.time import format_time
  17from sqlglot.tokens import TokenType
  18
  19if t.TYPE_CHECKING:
  20    from sqlglot._typing import E
  21    from sqlglot.dialects.dialect import DialectType
  22
  23    G = t.TypeVar("G", bound="Generator")
  24    GeneratorMethod = t.Callable[[G, E], str]
  25
  26logger = logging.getLogger("sqlglot")
  27
  28_HIGH_BYTE_RE = re.compile(r"[\x80-\xff]")
  29
  30ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
  31UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
  32
  33
  34def unsupported_args(
  35    *args: str | tuple[str, str],
  36) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
  37    """
  38    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
  39    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
  40    """
  41    diagnostic_by_arg: dict[str, str | None] = {}
  42    for arg in args:
  43        if isinstance(arg, str):
  44            diagnostic_by_arg[arg] = None
  45        else:
  46            diagnostic_by_arg[arg[0]] = arg[1]
  47
  48    def decorator(func: GeneratorMethod) -> GeneratorMethod:
  49        @wraps(func)
  50        def _func(generator: G, expression: E) -> str:
  51            expression_name = expression.__class__.__name__
  52            dialect_name = generator.dialect.__class__.__name__
  53
  54            for arg_name, diagnostic in diagnostic_by_arg.items():
  55                if expression.args.get(arg_name):
  56                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
  57                        arg_name, expression_name, dialect_name
  58                    )
  59                    generator.unsupported(diagnostic)
  60
  61            return func(generator, expression)
  62
  63        return _func
  64
  65    return decorator
  66
  67
  68AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = {
  69    "windows": lambda self, e: (
  70        self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
  71        if e.args.get("windows")
  72        else ""
  73    ),
  74    "qualify": lambda self, e: self.sql(e, "qualify"),
  75}
  76
  77
  78_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {}
  79
  80
  81def _build_dispatch(
  82    cls: type[Generator],
  83) -> dict[type[exp.Expr], t.Callable[..., str]]:
  84    dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS)
  85
  86    for attr_name in dir(cls):
  87        if not attr_name.endswith("_sql") or attr_name.startswith("_"):
  88            continue
  89
  90        expr_key = attr_name[:-4]
  91        expr_cls = exp.EXPR_CLASSES.get(expr_key)
  92
  93        if expr_cls and expr_cls not in dispatch:
  94            dispatch[expr_cls] = getattr(cls, attr_name)
  95
  96    return dispatch
  97
  98
  99class Generator:
 100    """
 101    Generator converts a given syntax tree to the corresponding SQL string.
 102
 103    Args:
 104        pretty: Whether to format the produced SQL string.
 105            Default: False.
 106        identify: Determines when an identifier should be quoted. Possible values are:
 107            False (default): Never quote, except in cases where it's mandatory by the dialect.
 108            True: Always quote except for specials cases.
 109            'safe': Only quote identifiers that are case insensitive.
 110        normalize: Whether to normalize identifiers to lowercase.
 111            Default: False.
 112        pad: The pad size in a formatted string. For example, this affects the indentation of
 113            a projection in a query, relative to its nesting level.
 114            Default: 2.
 115        indent: The indentation size in a formatted string. For example, this affects the
 116            indentation of subqueries and filters under a `WHERE` clause.
 117            Default: 2.
 118        normalize_functions: How to normalize function names. Possible values are:
 119            "upper" or True (default): Convert names to uppercase.
 120            "lower": Convert names to lowercase.
 121            False: Disables function name normalization.
 122        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 123            Default ErrorLevel.WARN.
 124        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 125            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 126            Default: 3
 127        leading_comma: Whether the comma is leading or trailing in select expressions.
 128            This is only relevant when generating in pretty mode.
 129            Default: False
 130        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 131            The default is on the smaller end because the length only represents a segment and not the true
 132            line length.
 133            Default: 80
 134        comments: Whether to preserve comments in the output SQL code.
 135            Default: True
 136    """
 137
 138    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 139        **JSON_PATH_PART_TRANSFORMS,
 140        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 141        exp.AllowedValuesProperty: lambda self, e: (
 142            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 143        ),
 144        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 145        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 146        exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
 147        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 148        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 149        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 150        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 151        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 152        exp.BinaryColumnConstraint: lambda *_: "BINARY",
 153        exp.CaseSpecificColumnConstraint: lambda _, e: (
 154            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 155        ),
 156        exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
 157        exp.Ceil: lambda self, e: self.ceil_floor(e),
 158        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 159        exp.CharacterSetProperty: lambda self, e: (
 160            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 161        ),
 162        exp.ClusteredColumnConstraint: lambda self, e: (
 163            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 164        ),
 165        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 166        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 167        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 168        exp.ConvertToCharset: lambda self, e: self.func(
 169            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 170        ),
 171        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 172        exp.CredentialsProperty: lambda self, e: (
 173            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 174        ),
 175        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 176        exp.SessionUser: lambda *_: "SESSION_USER",
 177        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 178        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 179        exp.ApiProperty: lambda *_: "API",
 180        exp.ApplicationProperty: lambda *_: "APPLICATION",
 181        exp.CatalogProperty: lambda *_: "CATALOG",
 182        exp.ComputeProperty: lambda *_: "COMPUTE",
 183        exp.DatabaseProperty: lambda *_: "DATABASE",
 184        exp.DynamicProperty: lambda *_: "DYNAMIC",
 185        exp.EmptyProperty: lambda *_: "EMPTY",
 186        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 187        exp.EndStatement: lambda *_: "END",
 188        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 189        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 190        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 191        exp.EphemeralColumnConstraint: lambda self, e: (
 192            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 193        ),
 194        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 195        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 196        exp.Except: lambda self, e: self.set_operations(e),
 197        exp.ExternalProperty: lambda *_: "EXTERNAL",
 198        exp.Floor: lambda self, e: self.ceil_floor(e),
 199        exp.Get: lambda self, e: self.get_put_sql(e),
 200        exp.GlobalProperty: lambda *_: "GLOBAL",
 201        exp.HeapProperty: lambda *_: "HEAP",
 202        exp.HybridProperty: lambda *_: "HYBRID",
 203        exp.IcebergProperty: lambda *_: "ICEBERG",
 204        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 205        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 206        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 207        exp.Intersect: lambda self, e: self.set_operations(e),
 208        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 209        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 210        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 211        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 212        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 213        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 214        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 215        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 216        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 217        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 218        exp.LocationProperty: lambda self, e: self.naked_property(e),
 219        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 220        exp.MaskingProperty: lambda *_: "MASKING",
 221        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 222        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 223        exp.NetworkProperty: lambda *_: "NETWORK",
 224        exp.NonClusteredColumnConstraint: lambda self, e: (
 225            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 226        ),
 227        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 228        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 229        exp.OnCommitProperty: lambda _, e: (
 230            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 231        ),
 232        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 233        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 234        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 235        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 236        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 237        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 238        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 239        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 240        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 241        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 242        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 243        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 244            f"PROJECTION POLICY {self.sql(e, 'this')}"
 245        ),
 246        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 247        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 248        exp.Put: lambda self, e: self.get_put_sql(e),
 249        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 250            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 251        ),
 252        exp.ReturnsProperty: lambda self, e: (
 253            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 254        ),
 255        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 256        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 257        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 258        exp.SecureProperty: lambda *_: "SECURE",
 259        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 260        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 261        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 262        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 263        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 264        exp.SqlReadWriteProperty: lambda _, e: e.name,
 265        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 266        exp.StabilityProperty: lambda _, e: e.name,
 267        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 268        exp.StreamingTableProperty: lambda *_: "STREAMING",
 269        exp.StrictProperty: lambda *_: "STRICT",
 270        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 271        exp.TableColumn: lambda self, e: self.sql(e.this),
 272        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 273        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 274        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 275        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 276        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 277        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 278        exp.TransientProperty: lambda *_: "TRANSIENT",
 279        exp.VirtualProperty: lambda *_: "VIRTUAL",
 280        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 281        exp.Union: lambda self, e: self.set_operations(e),
 282        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 283        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 284        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 285        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 286        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 287        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 288        exp.UtcTimestamp: lambda self, e: self.sql(
 289            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 290        ),
 291        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 292        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 293        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 294        exp.VolatileProperty: lambda *_: "VOLATILE",
 295        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 296        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 297        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 298        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 299        exp.ForceProperty: lambda *_: "FORCE",
 300    }
 301
 302    # Whether null ordering is supported in order by
 303    # True: Full Support, None: No support, False: No support for certain cases
 304    # such as window specifications, aggregate functions etc
 305    NULL_ORDERING_SUPPORTED: bool | None = True
 306
 307    # Window functions that support NULLS FIRST/LAST
 308    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 309
 310    # Whether ignore nulls is inside the agg or outside.
 311    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 312    IGNORE_NULLS_IN_FUNC = False
 313
 314    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 315    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 316    IGNORE_NULLS_BEFORE_ORDER = True
 317
 318    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 319    LOCKING_READS_SUPPORTED = False
 320
 321    # Whether the EXCEPT and INTERSECT operations can return duplicates
 322    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 323
 324    # Wrap derived values in parens, usually standard but spark doesn't support it
 325    WRAP_DERIVED_VALUES = True
 326
 327    # Whether create function uses an AS before the RETURN
 328    CREATE_FUNCTION_RETURN_AS = True
 329
 330    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 331    MATCHED_BY_SOURCE = True
 332
 333    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 334    SUPPORTS_MERGE_WHERE = False
 335
 336    # Whether the INTERVAL expression works only with values like '1 day'
 337    SINGLE_STRING_INTERVAL = False
 338
 339    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 340    INTERVAL_ALLOWS_PLURAL_FORM = True
 341
 342    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 343    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 344    AUTO_REFRESH_BARE_INTERVALS = False
 345
 346    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 347    LIMIT_FETCH = "ALL"
 348
 349    # Whether limit and fetch allows expresions or just limits
 350    LIMIT_ONLY_LITERALS = False
 351
 352    # Whether a table is allowed to be renamed with a db
 353    RENAME_TABLE_WITH_DB = True
 354
 355    # The separator for grouping sets and rollups
 356    GROUPINGS_SEP = ","
 357
 358    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 359    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 360
 361    # The string used for creating an index on a table
 362    INDEX_ON = "ON"
 363
 364    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 365    INOUT_SEPARATOR = " "
 366
 367    # Whether join hints should be generated
 368    JOIN_HINTS = True
 369
 370    # Whether directed joins are supported
 371    DIRECTED_JOINS = False
 372
 373    # Whether table hints should be generated
 374    TABLE_HINTS = True
 375
 376    # Whether query hints should be generated
 377    QUERY_HINTS = True
 378
 379    # What kind of separator to use for query hints
 380    QUERY_HINT_SEP = ", "
 381
 382    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 383    IS_BOOL_ALLOWED = True
 384
 385    # Whether casting a fractional number to an integer rounds it instead of truncating it
 386    CAST_TO_INT_ROUNDS = False
 387
 388    # Whether the native integer division operator returns NULL for a zero divisor instead of raising
 389    SAFE_INT_DIVISION = False
 390
 391    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 392    DUPLICATE_KEY_UPDATE_WITH_SET = True
 393
 394    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 395    LIMIT_IS_TOP = False
 396
 397    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 398    RETURNING_END = True
 399
 400    # Whether to generate an unquoted value for EXTRACT's date part argument
 401    EXTRACT_ALLOWS_QUOTES = True
 402
 403    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 404    TZ_TO_WITH_TIME_ZONE = False
 405
 406    # Whether the NVL2 function is supported
 407    NVL2_SUPPORTED = True
 408
 409    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 410    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 411
 412    # Whether VALUES statements can be used as derived tables.
 413    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 414    # SELECT * VALUES into SELECT UNION
 415    VALUES_AS_TABLE = True
 416
 417    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 418    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 419
 420    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 421    UNNEST_WITH_ORDINALITY = True
 422
 423    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 424    SEMI_ANTI_JOIN_WITH_SIDE = True
 425
 426    # Whether to include the type of a computed column in the CREATE DDL
 427    COMPUTED_COLUMN_WITH_TYPE = True
 428
 429    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 430    SUPPORTS_TABLE_COPY = True
 431
 432    # Whether parentheses are required around the table sample's expression
 433    TABLESAMPLE_REQUIRES_PARENS = True
 434
 435    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 436    TABLESAMPLE_SIZE_IS_ROWS = True
 437
 438    # The keyword(s) to use when generating a sample clause
 439    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 440
 441    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 442    TABLESAMPLE_WITH_METHOD = True
 443
 444    # The keyword to use when specifying the seed of a sample clause
 445    TABLESAMPLE_SEED_KEYWORD = "SEED"
 446
 447    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 448    HISTORICAL_DATA_POST_ALIAS = False
 449
 450    # Whether COLLATE is a function instead of a binary operator
 451    COLLATE_IS_FUNC = False
 452
 453    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 454    DATA_TYPE_SPECIFIERS_ALLOWED = False
 455
 456    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 457    ENSURE_BOOLS = False
 458
 459    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 460    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 461
 462    # Whether CONCAT requires >1 arguments
 463    SUPPORTS_SINGLE_ARG_CONCAT = True
 464
 465    # Whether LAST_DAY function supports a date part argument
 466    LAST_DAY_SUPPORTS_DATE_PART = True
 467
 468    # Whether named columns are allowed in table aliases
 469    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 470
 471    # Whether named columns are allowed in CTE definitions
 472    SUPPORTS_NAMED_CTE_COLUMNS = True
 473
 474    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 475    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 476
 477    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 478    PIVOT_ALIAS_WITH_AS = True
 479
 480    # What delimiter to use for separating JSON key/value pairs
 481    JSON_KEY_VALUE_PAIR_SEP = ":"
 482
 483    # INSERT OVERWRITE TABLE x override
 484    INSERT_OVERWRITE = " OVERWRITE TABLE"
 485
 486    # Whether the SELECT .. INTO syntax is used instead of CTAS
 487    SUPPORTS_SELECT_INTO = False
 488
 489    # Whether UNLOGGED tables can be created
 490    SUPPORTS_UNLOGGED_TABLES = False
 491
 492    # Whether the CREATE TABLE LIKE statement is supported
 493    SUPPORTS_CREATE_TABLE_LIKE = True
 494
 495    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 496    SUPPORTS_MODIFY_COLUMN = False
 497
 498    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 499    SUPPORTS_CHANGE_COLUMN = False
 500
 501    # Whether ALTER COLUMN can set a column's nullability together with its type
 502    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 503
 504    # Whether ALTER COLUMN IF EXISTS is supported
 505    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 506
 507    # Whether the LikeProperty needs to be specified inside of the schema clause
 508    LIKE_PROPERTY_INSIDE_SCHEMA = False
 509
 510    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 511    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 512    MULTI_ARG_DISTINCT = True
 513
 514    # Whether the JSON extraction operators expect a value of type JSON
 515    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 516
 517    # Whether bracketed keys like ["foo"] are supported in JSON paths
 518    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 519
 520    # Whether to escape keys using single quotes in JSON paths
 521    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 522
 523    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 524    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 525    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 526    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 527    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 528
 529    # The JSONPathPart expressions supported by this dialect
 530    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 531
 532    # Whether any(f(x) for x in array) can be implemented by this dialect
 533    CAN_IMPLEMENT_ARRAY_ANY = False
 534
 535    # Whether the function TO_NUMBER is supported
 536    SUPPORTS_TO_NUMBER = True
 537
 538    # Whether EXCLUDE in window specification is supported
 539    SUPPORTS_WINDOW_EXCLUDE = False
 540
 541    # Whether or not set op modifiers apply to the outer set op or select.
 542    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 543    # True means limit 1 happens after the set op, False means it it happens on y.
 544    SET_OP_MODIFIERS = True
 545
 546    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 547    SET_OP_LIMITS = False
 548
 549    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 550    SET_OP_PARENTHESIZED_OPERANDS = True
 551
 552    # Whether parameters from COPY statement are wrapped in parentheses
 553    COPY_PARAMS_ARE_WRAPPED = True
 554
 555    # Whether values of params are set with "=" token or empty space
 556    COPY_PARAMS_EQ_REQUIRED = False
 557
 558    # Whether COPY statement has INTO keyword
 559    COPY_HAS_INTO_KEYWORD = True
 560
 561    # Whether the conditional TRY(expression) function is supported
 562    TRY_SUPPORTED = True
 563
 564    # Whether the UESCAPE syntax in unicode strings is supported
 565    SUPPORTS_UESCAPE = True
 566
 567    # Function used to replace escaped unicode codes in unicode strings
 568    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 569
 570    # The keyword to use when generating a star projection with excluded columns
 571    STAR_EXCEPT = "EXCEPT"
 572
 573    # The HEX function name
 574    HEX_FUNC = "HEX"
 575
 576    # Function that constructs a binary value from a hex-encoded byte string.
 577    BYTE_STRING_FUNCTION = ""
 578
 579    # The keywords to use when prefixing & separating WITH based properties
 580    WITH_PROPERTIES_PREFIX = "WITH"
 581
 582    # Whether to quote the generated expression of exp.JsonPath
 583    QUOTE_JSON_PATH = True
 584
 585    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 586    PAD_FILL_PATTERN_IS_REQUIRED = False
 587
 588    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 589    SUPPORTS_EXPLODING_PROJECTIONS = True
 590
 591    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 592    ARRAY_CONCAT_IS_VAR_LEN = True
 593
 594    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 595    SUPPORTS_CONVERT_TIMEZONE = False
 596
 597    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 598    SUPPORTS_MEDIAN = True
 599
 600    # Whether UNIX_SECONDS(timestamp) is supported
 601    SUPPORTS_UNIX_SECONDS = False
 602
 603    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 604    ALTER_SET_WRAPPED = False
 605
 606    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 607    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 608    # TODO: The normalization should be done by default once we've tested it across all dialects.
 609    NORMALIZE_EXTRACT_DATE_PARTS = False
 610
 611    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 612    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 613
 614    # The function name of the exp.ArraySize expression
 615    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 616
 617    # The syntax to use when altering the type of a column
 618    ALTER_SET_TYPE = "SET DATA TYPE"
 619
 620    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 621    # None -> Doesn't support it at all
 622    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 623    # True (Postgres) -> Explicitly requires it
 624    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 625
 626    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 627    SUPPORTS_DECODE_CASE = True
 628
 629    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 630    SUPPORTS_BETWEEN_FLAGS = False
 631
 632    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 633    SUPPORTS_LIKE_QUANTIFIERS = True
 634
 635    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 636    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 637
 638    # Whether to include the VARIABLE keyword for SET assignments
 639    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 640
 641    # The keyword to use for default value assignment in DECLARE statements
 642    DECLARE_DEFAULT_ASSIGNMENT = "="
 643
 644    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 645    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 646    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 647    UPDATE_STATEMENT_SUPPORTS_FROM = True
 648
 649    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 650    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 651
 652    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 653    # - Snowflake: DROP ICEBERG TABLE a.b;
 654    # - DuckDB:    DROP TABLE a.b;
 655    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 656
 657    TYPE_MAPPING: t.ClassVar = {
 658        exp.DType.DATETIME2: "TIMESTAMP",
 659        exp.DType.NCHAR: "CHAR",
 660        exp.DType.NVARCHAR: "VARCHAR",
 661        exp.DType.MEDIUMTEXT: "TEXT",
 662        exp.DType.LONGTEXT: "TEXT",
 663        exp.DType.TINYTEXT: "TEXT",
 664        exp.DType.BLOB: "VARBINARY",
 665        exp.DType.MEDIUMBLOB: "BLOB",
 666        exp.DType.LONGBLOB: "BLOB",
 667        exp.DType.TINYBLOB: "BLOB",
 668        exp.DType.INET: "INET",
 669        exp.DType.ROWVERSION: "VARBINARY",
 670        exp.DType.SMALLDATETIME: "TIMESTAMP",
 671    }
 672
 673    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 674
 675    # mapping of DType to its default parameters, bounds
 676    TYPE_PARAM_SETTINGS: t.ClassVar[
 677        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 678    ] = {}
 679
 680    TIME_PART_SINGULARS: t.ClassVar = {
 681        "MICROSECONDS": "MICROSECOND",
 682        "SECONDS": "SECOND",
 683        "MINUTES": "MINUTE",
 684        "HOURS": "HOUR",
 685        "DAYS": "DAY",
 686        "WEEKS": "WEEK",
 687        "MONTHS": "MONTH",
 688        "QUARTERS": "QUARTER",
 689        "YEARS": "YEAR",
 690    }
 691
 692    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 693        "cluster": lambda self, e: self.sql(e, "cluster"),
 694        "distribute": lambda self, e: self.sql(e, "distribute"),
 695        "sort": lambda self, e: self.sql(e, "sort"),
 696        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 697    }
 698
 699    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 700
 701    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 702
 703    PARAMETER_TOKEN = "@"
 704    NAMED_PLACEHOLDER_TOKEN = ":"
 705
 706    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 707
 708    PROPERTIES_LOCATION: t.ClassVar = {
 709        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 710        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 711        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 712        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 713        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 714        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 717        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 718        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 719        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 720        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 721        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 723        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 724        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 725        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 730        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 731        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 732        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 733        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 734        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 735        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 736        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 737        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 740        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 746        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 747        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 748        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 749        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 750        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 751        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 752        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 753        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 754        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 757        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 758        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 759        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 760        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 761        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 762        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 763        exp.LogProperty: exp.Properties.Location.POST_NAME,
 764        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 765        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 766        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 767        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 769        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 770        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 771        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 772        exp.Order: exp.Properties.Location.POST_SCHEMA,
 773        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 775        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 776        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 777        exp.Property: exp.Properties.Location.POST_WITH,
 778        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 779        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 780        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 781        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 782        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 783        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 784        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 785        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 786        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 787        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 788        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 789        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 790        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 791        exp.Set: exp.Properties.Location.POST_SCHEMA,
 792        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 793        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 794        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 795        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 796        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 797        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 798        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 799        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 800        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 801        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 802        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 803        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 804        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 805        exp.Tags: exp.Properties.Location.POST_WITH,
 806        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 807        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 808        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 809        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 810        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 811        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 812        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 813        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 814        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 815        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 816        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 817        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 818        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 819        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 820        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 821        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 822        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 823    }
 824
 825    # Keywords that can't be used as unquoted identifier names
 826    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 827
 828    # Exprs whose comments are separated from them for better formatting
 829    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 830        exp.Command,
 831        exp.Create,
 832        exp.Describe,
 833        exp.Delete,
 834        exp.Drop,
 835        exp.From,
 836        exp.Insert,
 837        exp.Join,
 838        exp.MultitableInserts,
 839        exp.Order,
 840        exp.Group,
 841        exp.Having,
 842        exp.Select,
 843        exp.SetOperation,
 844        exp.Update,
 845        exp.Where,
 846        exp.With,
 847    )
 848
 849    # Exprs that should not have their comments generated in maybe_comment
 850    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 851        exp.Binary,
 852        exp.SetOperation,
 853    )
 854
 855    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 856    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 857        exp.Column,
 858        exp.Literal,
 859        exp.Neg,
 860        exp.Paren,
 861    )
 862
 863    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 864        exp.DType.NVARCHAR,
 865        exp.DType.VARCHAR,
 866        exp.DType.CHAR,
 867        exp.DType.NCHAR,
 868    }
 869
 870    # Exprs that need to have all CTEs under them bubbled up to them
 871    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 872
 873    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 874
 875    MOD_OPERATOR = "%"
 876
 877    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 878    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 879        exp.Mul,
 880        exp.Div,
 881        exp.IntDiv,
 882        exp.Mod,
 883    )
 884
 885    # ALTER options that follow the others without a comma, e.g. MySQL's
 886    # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
 887    ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 888
 889    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 890
 891    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 892
 893    __slots__ = (
 894        "pretty",
 895        "identify",
 896        "normalize",
 897        "pad",
 898        "_indent",
 899        "normalize_functions",
 900        "unsupported_level",
 901        "max_unsupported",
 902        "leading_comma",
 903        "max_text_width",
 904        "comments",
 905        "dialect",
 906        "unsupported_messages",
 907        "_escaped_quote_end",
 908        "_escaped_byte_quote_end",
 909        "_escaped_identifier_end",
 910        "_identifier_escaped_sequences",
 911        "_next_name",
 912        "_identifier_start",
 913        "_identifier_end",
 914        "_quote_json_path_key_using_brackets",
 915        "_dispatch",
 916    )
 917
 918    def __init__(
 919        self,
 920        pretty: bool | int | None = None,
 921        identify: str | bool = False,
 922        normalize: bool = False,
 923        pad: int = 2,
 924        indent: int = 2,
 925        normalize_functions: str | bool | None = None,
 926        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 927        max_unsupported: int = 3,
 928        leading_comma: bool = False,
 929        max_text_width: int = 80,
 930        comments: bool = True,
 931        dialect: DialectType = None,
 932    ):
 933        import sqlglot
 934        import sqlglot.dialects.dialect
 935
 936        self.pretty = pretty if pretty is not None else sqlglot.pretty
 937        self.identify = identify
 938        self.normalize = normalize
 939        self.pad = pad
 940        self._indent = indent
 941        self.unsupported_level = unsupported_level
 942        self.max_unsupported = max_unsupported
 943        self.leading_comma = leading_comma
 944        self.max_text_width = max_text_width
 945        self.comments = comments
 946        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 947
 948        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 949        self.normalize_functions = (
 950            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 951        )
 952
 953        self.unsupported_messages: list[str] = []
 954        self._escaped_quote_end: str = (
 955            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 956        )
 957        self._escaped_byte_quote_end: str = (
 958            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 959            if self.dialect.BYTE_END
 960            else ""
 961        )
 962        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 963        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
 964
 965        self._next_name = name_sequence("_t")
 966
 967        self._identifier_start = self.dialect.IDENTIFIER_START
 968        self._identifier_end = self.dialect.IDENTIFIER_END
 969
 970        self._quote_json_path_key_using_brackets = True
 971
 972        cls = type(self)
 973        dispatch = _DISPATCH_CACHE.get(cls)
 974        if dispatch is None:
 975            dispatch = _build_dispatch(cls)
 976            _DISPATCH_CACHE[cls] = dispatch
 977        self._dispatch = dispatch
 978
 979    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 980        """
 981        Generates the SQL string corresponding to the given syntax tree.
 982
 983        Args:
 984            expression: The syntax tree.
 985            copy: Whether to copy the expression. The generator performs mutations so
 986                it is safer to copy.
 987
 988        Returns:
 989            The SQL string corresponding to `expression`.
 990        """
 991        if copy:
 992            expression = expression.copy()
 993
 994        expression = self.preprocess(expression)
 995
 996        self.unsupported_messages = []
 997        sql = self.sql(expression).strip()
 998
 999        if self.pretty:
1000            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1001
1002        if self.unsupported_level == ErrorLevel.IGNORE:
1003            return sql
1004
1005        if self.unsupported_level == ErrorLevel.WARN:
1006            for msg in self.unsupported_messages:
1007                logger.warning(msg)
1008        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1009            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1010
1011        return sql
1012
1013    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1014        """Apply generic preprocessing transformations to a given expression."""
1015        expression = self._move_ctes_to_top_level(expression)
1016
1017        if self.ENSURE_BOOLS:
1018            import sqlglot.transforms
1019
1020            expression = sqlglot.transforms.ensure_bools(expression)
1021
1022        return expression
1023
1024    def _move_ctes_to_top_level(self, expression: E) -> E:
1025        if (
1026            not expression.parent
1027            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1028            and any(node.parent is not expression for node in expression.find_all(exp.With))
1029        ):
1030            import sqlglot.transforms
1031
1032            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1033        return expression
1034
1035    def unsupported(self, message: str) -> None:
1036        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1037            raise UnsupportedError(message)
1038        self.unsupported_messages.append(message)
1039
1040    def sep(self, sep: str = " ") -> str:
1041        return f"{sep.strip()}\n" if self.pretty else sep
1042
1043    def seg(self, sql: str, sep: str = " ") -> str:
1044        return f"{self.sep(sep)}{sql}"
1045
1046    def sanitize_comment(self, comment: str) -> str:
1047        comment = " " + comment if comment[0].strip() else comment
1048        comment = comment + " " if comment[-1].strip() else comment
1049
1050        # Escape block comment markers to prevent premature closure or unintended nesting.
1051        # This is necessary because single-line comments (--) are converted to block comments
1052        # (/* */) on output, and any */ in the original text would close the comment early.
1053        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1054
1055        return comment
1056
1057    def maybe_comment(
1058        self,
1059        sql: str,
1060        expression: exp.Expr | None = None,
1061        comments: list[str] | None = None,
1062        separated: bool = False,
1063    ) -> str:
1064        comments = (
1065            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1066            if self.comments
1067            else None
1068        )
1069
1070        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1071            return sql
1072
1073        comments_list = [
1074            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1075            for comment in comments
1076            if comment
1077        ]
1078
1079        if not comments_list:
1080            return sql
1081
1082        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1083            comments_sql = self.sep().join(comments_list)
1084            return (
1085                f"{self.sep()}{comments_sql}{sql}"
1086                if not sql or sql[0].isspace()
1087                else f"{comments_sql}{self.sep()}{sql}"
1088            )
1089
1090        return f"{sql} {' '.join(comments_list)}"
1091
1092    def wrap(self, expression: exp.Expr | str) -> str:
1093        this_sql = (
1094            self.sql(expression)
1095            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1096            else self.sql(expression, "this")
1097        )
1098        if not this_sql:
1099            return "()"
1100
1101        this_sql = self.indent(this_sql, level=1, pad=0)
1102        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1103
1104    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1105        original = self.identify
1106        self.identify = False
1107        result = func(*args, **kwargs)
1108        self.identify = original
1109        return result
1110
1111    def normalize_func(self, name: str) -> str:
1112        if self.normalize_functions == "upper" or self.normalize_functions is True:
1113            return name.upper()
1114        if self.normalize_functions == "lower":
1115            return name.lower()
1116        return name
1117
1118    def indent(
1119        self,
1120        sql: str,
1121        level: int = 0,
1122        pad: int | None = None,
1123        skip_first: bool = False,
1124        skip_last: bool = False,
1125    ) -> str:
1126        if not self.pretty or not sql:
1127            return sql
1128
1129        pad = self.pad if pad is None else pad
1130        lines = sql.split("\n")
1131
1132        return "\n".join(
1133            (
1134                line
1135                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1136                else f"{' ' * (level * self._indent + pad)}{line}"
1137            )
1138            for i, line in enumerate(lines)
1139        )
1140
1141    def sql(
1142        self,
1143        expression: str | exp.Expr | None,
1144        key: str | None = None,
1145        comment: bool = True,
1146    ) -> str:
1147        if not expression:
1148            return ""
1149
1150        if isinstance(expression, str):
1151            return expression
1152
1153        if key:
1154            value = expression.args.get(key)
1155            if value:
1156                return self.sql(value)
1157            return ""
1158
1159        handler = self._dispatch.get(expression.__class__)
1160
1161        if handler:
1162            sql = handler(self, expression)
1163        elif isinstance(expression, exp.Func):
1164            sql = self.function_fallback_sql(expression)
1165        elif isinstance(expression, exp.Property):
1166            sql = self.property_sql(expression)
1167        else:
1168            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1169
1170        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1171
1172    def uncache_sql(self, expression: exp.Uncache) -> str:
1173        table = self.sql(expression, "this")
1174        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1175        return f"UNCACHE TABLE{exists_sql} {table}"
1176
1177    def cache_sql(self, expression: exp.Cache) -> str:
1178        lazy = " LAZY" if expression.args.get("lazy") else ""
1179        table = self.sql(expression, "this")
1180        options = expression.args.get("options")
1181        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1182        sql = self.sql(expression, "expression")
1183        sql = f" AS{self.sep()}{sql}" if sql else ""
1184        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1185        return self.prepend_ctes(expression, sql)
1186
1187    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1188        default = "DEFAULT " if expression.args.get("default") else ""
1189        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1190
1191    def column_parts(self, expression: exp.Column) -> str:
1192        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1193            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1194            return self.sql(expression, "this")
1195
1196        return ".".join(
1197            self.sql(part)
1198            for part in (
1199                expression.args.get("catalog"),
1200                expression.args.get("db"),
1201                expression.args.get("table"),
1202                expression.args.get("this"),
1203            )
1204            if part
1205        )
1206
1207    def column_sql(self, expression: exp.Column) -> str:
1208        join_mark = " (+)" if expression.args.get("join_mark") else ""
1209
1210        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1211            join_mark = ""
1212            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1213
1214        return f"{self.column_parts(expression)}{join_mark}"
1215
1216    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1217        return self.column_sql(expression)
1218
1219    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1220        this = self.sql(expression, "this")
1221        this = f" {this}" if this else ""
1222        position = self.sql(expression, "position")
1223        return f"{position}{this}"
1224
1225    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1226        column = self.sql(expression, "this")
1227        kind = self.sql(expression, "kind")
1228        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1229        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1230        kind = f"{sep}{kind}" if kind else ""
1231        constraints = f" {constraints}" if constraints else ""
1232        position = self.sql(expression, "position")
1233        position = f" {position}" if position else ""
1234
1235        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1236            kind = ""
1237
1238        return f"{exists}{column}{kind}{constraints}{position}"
1239
1240    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1241        this = self.sql(expression, "this")
1242        kind_sql = self.sql(expression, "kind").strip()
1243        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1244
1245    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1246        this = self.sql(expression, "this")
1247        if expression.args.get("not_null"):
1248            persisted = " PERSISTED NOT NULL"
1249        elif expression.args.get("persisted"):
1250            persisted = " PERSISTED"
1251        else:
1252            persisted = ""
1253
1254        return f"AS {this}{persisted}"
1255
1256    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1257        return self.token_sql(TokenType.AUTO_INCREMENT)
1258
1259    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1260        if isinstance(expression.this, list):
1261            this = self.wrap(self.expressions(expression, key="this", flat=True))
1262        else:
1263            this = self.sql(expression, "this")
1264
1265        return f"COMPRESS {this}"
1266
1267    def generatedasidentitycolumnconstraint_sql(
1268        self, expression: exp.GeneratedAsIdentityColumnConstraint
1269    ) -> str:
1270        this = ""
1271        if expression.this is not None:
1272            on_null = " ON NULL" if expression.args.get("on_null") else ""
1273            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1274
1275        start = expression.args.get("start")
1276        start = f"START WITH {start}" if start else ""
1277        increment = expression.args.get("increment")
1278        increment = f" INCREMENT BY {increment}" if increment else ""
1279        minvalue = expression.args.get("minvalue")
1280        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1281        maxvalue = expression.args.get("maxvalue")
1282        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1283        cycle = expression.args.get("cycle")
1284        cycle_sql = ""
1285
1286        if cycle is not None:
1287            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1288            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1289
1290        sequence_opts = ""
1291        if start or increment or cycle_sql:
1292            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1293            sequence_opts = f" ({sequence_opts.strip()})"
1294
1295        expr = self.sql(expression, "expression")
1296        expr = f"({expr})" if expr else "IDENTITY"
1297
1298        return f"GENERATED{this} AS {expr}{sequence_opts}"
1299
1300    def generatedasrowcolumnconstraint_sql(
1301        self, expression: exp.GeneratedAsRowColumnConstraint
1302    ) -> str:
1303        start = "START" if expression.args.get("start") else "END"
1304        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1305        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1306
1307    def periodforsystemtimeconstraint_sql(
1308        self, expression: exp.PeriodForSystemTimeConstraint
1309    ) -> str:
1310        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1311
1312    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1313        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1314
1315    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1316        desc = expression.args.get("desc")
1317        if desc is not None:
1318            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1319        options = self.expressions(expression, key="options", flat=True, sep=" ")
1320        options = f" {options}" if options else ""
1321        return f"PRIMARY KEY{options}"
1322
1323    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1324        this = self.sql(expression, "this")
1325        this = f" {this}" if this else ""
1326        index_type = expression.args.get("index_type")
1327        index_type = f" USING {index_type}" if index_type else ""
1328        on_conflict = self.sql(expression, "on_conflict")
1329        on_conflict = f" {on_conflict}" if on_conflict else ""
1330        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1331        options = self.expressions(expression, key="options", flat=True, sep=" ")
1332        options = f" {options}" if options else ""
1333        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1334
1335    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1336        input_ = expression.args.get("input_")
1337        output = expression.args.get("output")
1338        variadic = expression.args.get("variadic")
1339
1340        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1341        if variadic:
1342            return "VARIADIC"
1343
1344        if input_ and output:
1345            return f"IN{self.INOUT_SEPARATOR}OUT"
1346        if input_:
1347            return "IN"
1348        if output:
1349            return "OUT"
1350
1351        return ""
1352
1353    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1354        return self.sql(expression, "this")
1355
1356    def create_sql(self, expression: exp.Create) -> str:
1357        kind = self.sql(expression, "kind")
1358        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1359
1360        properties = expression.args.get("properties")
1361
1362        if (
1363            kind == "TRIGGER"
1364            and properties
1365            and properties.expressions
1366            and isinstance(properties.expressions[0], exp.TriggerProperties)
1367            and properties.expressions[0].args.get("constraint")
1368        ):
1369            kind = f"CONSTRAINT {kind}"
1370
1371        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1372
1373        this = self.createable_sql(expression, properties_locs)
1374
1375        properties_sql = ""
1376        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1377            exp.Properties.Location.POST_WITH
1378        ):
1379            props_ast = exp.Properties(
1380                expressions=[
1381                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1382                    *properties_locs[exp.Properties.Location.POST_WITH],
1383                ]
1384            )
1385            props_ast.parent = expression
1386            properties_sql = self.sql(props_ast)
1387
1388            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1389                properties_sql = self.sep() + properties_sql
1390            elif not self.pretty:
1391                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1392                properties_sql = f" {properties_sql}"
1393
1394        begin = " BEGIN" if expression.args.get("begin") else ""
1395
1396        expression_sql = self.sql(expression, "expression")
1397        if expression_sql:
1398            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1399
1400            if not isinstance(expression.expression, exp.MacroOverloads) and (
1401                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1402            ):
1403                postalias_props_sql = ""
1404                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1405                    postalias_props_sql = self.properties(
1406                        exp.Properties(
1407                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1408                        ),
1409                        wrapped=False,
1410                    )
1411                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1412                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1413
1414        postindex_props_sql = ""
1415        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1416            postindex_props_sql = self.properties(
1417                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1418                wrapped=False,
1419                prefix=" ",
1420            )
1421
1422        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1423        indexes = f" {indexes}" if indexes else ""
1424        index_sql = indexes + postindex_props_sql
1425
1426        replace = " OR REPLACE" if expression.args.get("replace") else ""
1427        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1428        unique = " UNIQUE" if expression.args.get("unique") else ""
1429
1430        clustered = expression.args.get("clustered")
1431        if clustered is None:
1432            clustered_sql = ""
1433        elif clustered:
1434            clustered_sql = " CLUSTERED COLUMNSTORE"
1435        else:
1436            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1437
1438        postcreate_props_sql = ""
1439        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1440            postcreate_props_sql = self.properties(
1441                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1442                sep=" ",
1443                prefix=" ",
1444                wrapped=False,
1445            )
1446
1447        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1448
1449        postexpression_props_sql = ""
1450        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1451            postexpression_props_sql = self.properties(
1452                exp.Properties(
1453                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1454                ),
1455                sep=" ",
1456                prefix=" ",
1457                wrapped=False,
1458            )
1459
1460        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1461        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1462        no_schema_binding = (
1463            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1464        )
1465
1466        clone = self.sql(expression, "clone")
1467        clone = f" {clone}" if clone else ""
1468
1469        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1470            properties_expression = f"{expression_sql}{properties_sql}"
1471        else:
1472            properties_expression = f"{properties_sql}{expression_sql}"
1473
1474        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1475        return self.prepend_ctes(expression, expression_sql)
1476
1477    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1478        start = self.sql(expression, "start")
1479        start = f"START WITH {start}" if start else ""
1480        increment = self.sql(expression, "increment")
1481        increment = f" INCREMENT BY {increment}" if increment else ""
1482        minvalue = self.sql(expression, "minvalue")
1483        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1484        maxvalue = self.sql(expression, "maxvalue")
1485        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1486        owned = self.sql(expression, "owned")
1487        owned = f" OWNED BY {owned}" if owned else ""
1488
1489        cache = expression.args.get("cache")
1490        if cache is None:
1491            cache_str = ""
1492        elif cache is True:
1493            cache_str = " CACHE"
1494        else:
1495            cache_str = f" CACHE {cache}"
1496
1497        options = self.expressions(expression, key="options", flat=True, sep=" ")
1498        options = f" {options}" if options else ""
1499
1500        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1501
1502    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1503        timing = expression.args.get("timing", "")
1504        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1505        timing_events = f"{timing} {events}".strip() if timing or events else ""
1506
1507        parts = [timing_events, "ON", self.sql(expression, "table")]
1508
1509        if referenced_table := expression.args.get("referenced_table"):
1510            parts.extend(["FROM", self.sql(referenced_table)])
1511
1512        if deferrable := expression.args.get("deferrable"):
1513            parts.append(deferrable)
1514
1515        if initially := expression.args.get("initially"):
1516            parts.append(f"INITIALLY {initially}")
1517
1518        if referencing := expression.args.get("referencing"):
1519            parts.append(self.sql(referencing))
1520
1521        if for_each := expression.args.get("for_each"):
1522            parts.append(f"FOR EACH {for_each}")
1523
1524        if when := expression.args.get("when"):
1525            parts.append(f"WHEN ({self.sql(when)})")
1526
1527        parts.append(self.sql(expression, "execute"))
1528
1529        return self.sep().join(parts)
1530
1531    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1532        parts = []
1533
1534        if old_alias := expression.args.get("old"):
1535            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1536
1537        if new_alias := expression.args.get("new"):
1538            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1539
1540        return f"REFERENCING {' '.join(parts)}"
1541
1542    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1543        columns = expression.args.get("columns")
1544        if columns:
1545            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1546
1547        return self.sql(expression, "this")
1548
1549    def clone_sql(self, expression: exp.Clone) -> str:
1550        this = self.sql(expression, "this")
1551        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1552        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1553        return f"{shallow}{keyword} {this}"
1554
1555    def describe_sql(self, expression: exp.Describe) -> str:
1556        style = expression.args.get("style")
1557        style = f" {style}" if style else ""
1558        partition = self.sql(expression, "partition")
1559        partition = f" {partition}" if partition else ""
1560        format = self.sql(expression, "format")
1561        format = f" {format}" if format else ""
1562        as_json = " AS JSON" if expression.args.get("as_json") else ""
1563
1564        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1565
1566    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1567        tag = self.sql(expression, "tag")
1568        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1569
1570    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1571        with_ = self.sql(expression, "with_")
1572        if with_:
1573            sql = f"{with_}{self.sep()}{sql}"
1574        return sql
1575
1576    def with_sql(self, expression: exp.With) -> str:
1577        udfs = self.expressions(expression, key="udfs", flat=True)
1578        udfs = f"WITH {udfs}" if udfs else ""
1579
1580        sql = self.expressions(expression, flat=True)
1581
1582        recursive = (
1583            "RECURSIVE "
1584            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1585            else ""
1586        )
1587        search = self.sql(expression, "search")
1588        search = f" {search}" if search else ""
1589        cycle = self.sql(expression, "cycle")
1590        cycle = f" {cycle}" if cycle else ""
1591
1592        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1593        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1594
1595    def cte_sql(self, expression: exp.CTE) -> str:
1596        alias = expression.args.get("alias")
1597        if alias:
1598            alias.add_comments(expression.pop_comments())
1599
1600        alias_sql = self.sql(expression, "alias")
1601
1602        materialized = expression.args.get("materialized")
1603        if materialized is False:
1604            materialized = "NOT MATERIALIZED "
1605        elif materialized:
1606            materialized = "MATERIALIZED "
1607
1608        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1609        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1610
1611        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1612
1613    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1614        alias = self.sql(expression, "this")
1615        columns = self.expressions(expression, key="columns", flat=True)
1616        columns = f"({columns})" if columns else ""
1617
1618        if (
1619            columns
1620            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1621            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1622        ):
1623            columns = ""
1624            self.unsupported("Named columns are not supported in table alias.")
1625
1626        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1627            alias = self._next_name()
1628
1629        return f"{alias}{columns}"
1630
1631    def bitstring_sql(self, expression: exp.BitString) -> str:
1632        this = self.sql(expression, "this")
1633        if self.dialect.BIT_START:
1634            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1635        return f"{int(this, 2)}"
1636
1637    def hexstring_sql(
1638        self, expression: exp.HexString, binary_function_repr: str | None = None
1639    ) -> str:
1640        this = self.sql(expression, "this")
1641        is_integer_type = expression.args.get("is_integer")
1642
1643        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1644            not self.dialect.HEX_START and not binary_function_repr
1645        ):
1646            # Integer representation will be returned if:
1647            # - The read dialect treats the hex value as integer literal but not the write
1648            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1649            return f"{int(this, 16)}"
1650
1651        if not is_integer_type:
1652            # Read dialect treats the hex value as BINARY/BLOB
1653            if binary_function_repr:
1654                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1655                return self.func(binary_function_repr, exp.Literal.string(this))
1656            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1657                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1658                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1659
1660        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1661
1662    def bytestring_sql(self, expression: exp.ByteString) -> str:
1663        this = self.sql(expression, "this")
1664        is_bytes = bool(expression.args.get("is_bytes"))
1665        if is_bytes and self.BYTE_STRING_FUNCTION:
1666            # Hex digits can be quoted directly without escaping.
1667            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1668
1669        if self.dialect.BYTE_START:
1670            escaped_byte_string = self.escape_str(
1671                this,
1672                escape_backslash=bool(
1673                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1674                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1675                ),
1676                delimiter=self.dialect.BYTE_END,
1677                escaped_delimiter=self._escaped_byte_quote_end,
1678                is_byte_string=True,
1679                is_bytes=is_bytes,
1680            )
1681            delimited_byte_string = (
1682                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1683            )
1684            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1685                return self.sql(
1686                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1687                )
1688            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1689                return self.sql(
1690                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1691                )
1692
1693            return delimited_byte_string
1694
1695        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1696            return self.sql(exp.Literal.string(this))
1697
1698        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1699        return ""
1700
1701    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1702        this = self.sql(expression, "this")
1703        escape = expression.args.get("escape")
1704        unicode_start = self.dialect.UNICODE_START
1705
1706        if unicode_start:
1707            escape_substitute = r"\\\1"
1708            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1709        else:
1710            escape_substitute = r"\\u\1"
1711            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1712
1713        if escape:
1714            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1715            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1716        else:
1717            escape_pattern = ESCAPED_UNICODE_RE
1718            escape_sql = ""
1719
1720        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1721            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1722
1723        if unicode_start:
1724            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1725            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1726            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1727        else:
1728            this = self.escape_str(this, escape_backslash=False)
1729
1730        return f"{left_quote}{this}{right_quote}{escape_sql}"
1731
1732    def rawstring_sql(self, expression: exp.RawString) -> str:
1733        string = expression.this
1734        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1735            string = string.replace("\\", "\\\\")
1736
1737        string = self.escape_str(string, escape_backslash=False)
1738        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1739
1740    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1741        this = self.sql(expression, "this")
1742        specifier = self.sql(expression, "expression")
1743        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1744        return f"{this}{specifier}"
1745
1746    def datatype_param_bound_limiter(
1747        self,
1748        expression: exp.DataType,
1749        type_value: exp.DType,
1750        defaults: tuple[int, ...],
1751        bounds: tuple[int | None, ...],
1752    ) -> exp.DataType:
1753        params = expression.expressions
1754
1755        if not params:
1756            if defaults:
1757                expression.set(
1758                    "expressions",
1759                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1760                )
1761            return expression
1762
1763        if not bounds:
1764            return expression
1765
1766        for i, param in enumerate(params):
1767            bound = bounds[i] if i < len(bounds) else None
1768            if bound is None:
1769                continue
1770
1771            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1772            value = (
1773                param_value.to_py()
1774                if isinstance(param_value, exp.Literal) and param_value.is_number
1775                else None
1776            )
1777            if isinstance(value, (int, Decimal)) and value > bound:
1778                self.unsupported(
1779                    f"{type_value.value} parameter {param_value.name} exceeds "
1780                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1781                )
1782                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1783
1784        return expression
1785
1786    def datatype_sql(self, expression: exp.DataType) -> str:
1787        nested = ""
1788        values = ""
1789
1790        expr_nested = expression.args.get("nested")
1791        type_value = expression.this
1792
1793        if (
1794            not expr_nested
1795            and isinstance(type_value, exp.DType)
1796            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1797        ):
1798            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1799
1800        interior = (
1801            self.expressions(
1802                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1803            )
1804            if expr_nested and self.pretty
1805            else self.expressions(expression, flat=True)
1806        )
1807
1808        if type_value in self.UNSUPPORTED_TYPES:
1809            self.unsupported(
1810                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1811            )
1812
1813        type_sql: t.Any = ""
1814        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1815            type_sql = self.sql(expression, "kind")
1816        elif type_value == exp.DType.CHARACTER_SET:
1817            char = f"CHAR({interior})" if interior else "CHAR"
1818            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1819        else:
1820            type_sql = (
1821                self.TYPE_MAPPING.get(type_value, type_value.value)
1822                if isinstance(type_value, exp.DType)
1823                else type_value
1824            )
1825
1826        if interior:
1827            if expr_nested:
1828                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1829                if expression.args.get("values") is not None:
1830                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1831                    values = self.expressions(expression, key="values", flat=True)
1832                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1833            elif type_value == exp.DType.INTERVAL:
1834                nested = f" {interior}"
1835            else:
1836                nested = f"({interior})"
1837
1838        type_sql = f"{type_sql}{nested}{values}"
1839        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1840            exp.DType.TIMETZ,
1841            exp.DType.TIMESTAMPTZ,
1842        ):
1843            type_sql = f"{type_sql} WITH TIME ZONE"
1844
1845        collate = self.sql(expression, "collate")
1846        if collate:
1847            type_sql = f"{type_sql} COLLATE {collate}"
1848
1849        return type_sql
1850
1851    def directory_sql(self, expression: exp.Directory) -> str:
1852        local = "LOCAL " if expression.args.get("local") else ""
1853        row_format = self.sql(expression, "row_format")
1854        row_format = f" {row_format}" if row_format else ""
1855        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1856
1857    def delete_sql(self, expression: exp.Delete) -> str:
1858        hint = self.sql(expression, "hint")
1859        this = self.sql(expression, "this")
1860        this = f" FROM {this}" if this else ""
1861        using = self.expressions(expression, key="using")
1862        using = f" USING {using}" if using else ""
1863        cluster = self.sql(expression, "cluster")
1864        cluster = f" {cluster}" if cluster else ""
1865        where = self.sql(expression, "where")
1866        returning = self.sql(expression, "returning")
1867        order = self.sql(expression, "order")
1868        limit = self.sql(expression, "limit")
1869        tables = self.expressions(expression, key="tables")
1870        tables = f" {tables}" if tables else ""
1871        if self.RETURNING_END:
1872            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1873        else:
1874            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1875        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1876
1877    def drop_sql(self, expression: exp.Drop) -> str:
1878        tables = self.expressions(expression, key="tables", flat=True)
1879        expressions = self.expressions(expression, flat=True)
1880        expressions = f" ({expressions})" if expressions else ""
1881        kind = expression.args["kind"]
1882        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1883        iceberg = (
1884            " ICEBERG"
1885            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1886            else ""
1887        )
1888        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1889        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1890        on_cluster = self.sql(expression, "cluster")
1891        on_cluster = f" {on_cluster}" if on_cluster else ""
1892        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1893        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1894        cascade = " CASCADE" if expression.args.get("cascade") else ""
1895        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1896        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1897        purge = " PURGE" if expression.args.get("purge") else ""
1898        sync = " SYNC" if expression.args.get("sync") else ""
1899        force = " FORCE" if expression.args.get("force") else ""
1900        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1901
1902    def set_operation(self, expression: exp.SetOperation) -> str:
1903        op_type = type(expression)
1904        op_name = op_type.key.upper()
1905
1906        distinct = expression.args.get("distinct")
1907        if (
1908            distinct is False
1909            and op_type in (exp.Except, exp.Intersect)
1910            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1911        ):
1912            self.unsupported(f"{op_name} ALL is not supported")
1913
1914        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1915
1916        if distinct is None:
1917            distinct = default_distinct
1918            if distinct is None:
1919                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1920
1921        if distinct is default_distinct:
1922            distinct_or_all = ""
1923        else:
1924            distinct_or_all = " DISTINCT" if distinct else " ALL"
1925
1926        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1927        side_kind = f"{side_kind} " if side_kind else ""
1928
1929        by_name = " BY NAME" if expression.args.get("by_name") else ""
1930        on = self.expressions(expression, key="on", flat=True)
1931        on = f" ON ({on})" if on else ""
1932
1933        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1934
1935    def set_operations(self, expression: exp.SetOperation) -> str:
1936        if not self.SET_OP_MODIFIERS:
1937            limit = expression.args.get("limit")
1938            order = expression.args.get("order")
1939            offset = expression.args.get("offset")
1940
1941            if limit or order or offset:
1942                select = self._move_ctes_to_top_level(
1943                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1944                )
1945
1946                for arg in ("limit", "order", "offset"):
1947                    if value := expression.args.get(arg):
1948                        select.set(arg, value.pop())
1949                return self.sql(select)
1950
1951        sqls: list[str] = []
1952        stack: list[str | exp.Expr] = [expression]
1953
1954        while stack:
1955            node = stack.pop()
1956
1957            if isinstance(node, exp.SetOperation):
1958                stack.append(node.expression)
1959                stack.append(
1960                    self.maybe_comment(
1961                        self.set_operation(node), comments=node.comments, separated=True
1962                    )
1963                )
1964                stack.append(node.this)
1965            else:
1966                if (
1967                    not self.SET_OP_LIMITS
1968                    and isinstance(node, exp.Select)
1969                    and node.args.get("limit")
1970                ):
1971                    node = node.subquery(copy=False)
1972                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1973                        node = exp.select("*").from_(node, copy=False)
1974                sqls.append(self.sql(node))
1975
1976        this = self.sep().join(sqls)
1977        this = self.query_modifiers(expression, this)
1978        return self.prepend_ctes(expression, this)
1979
1980    def fetch_sql(self, expression: exp.Fetch) -> str:
1981        direction = expression.args.get("direction")
1982        direction = f" {direction}" if direction else ""
1983        count = self.sql(expression, "count")
1984        count = f" {count}" if count else ""
1985        limit_options = self.sql(expression, "limit_options")
1986        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1987        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1988
1989    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1990        percent = " PERCENT" if expression.args.get("percent") else ""
1991        rows = " ROWS" if expression.args.get("rows") else ""
1992        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1993        if not with_ties and rows:
1994            with_ties = " ONLY"
1995        return f"{percent}{rows}{with_ties}"
1996
1997    def filter_sql(self, expression: exp.Filter) -> str:
1998        this = self.sql(expression, "this")
1999        where = self.sql(expression, "expression").strip()
2000        return f"{this} FILTER({where})"
2001
2002    def hint_sql(self, expression: exp.Hint) -> str:
2003        if not self.QUERY_HINTS:
2004            self.unsupported("Hints are not supported")
2005            return ""
2006
2007        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
2008
2009    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2010        using = self.sql(expression, "using")
2011        using = f" USING {using}" if using else ""
2012        columns = self.expressions(expression, key="columns", flat=True)
2013        columns = f"({columns})" if columns else ""
2014        partition_by = self.expressions(expression, key="partition_by", flat=True)
2015        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2016        where = self.sql(expression, "where")
2017        include = self.expressions(expression, key="include", flat=True)
2018        if include:
2019            include = f" INCLUDE ({include})"
2020        with_storage = self.expressions(expression, key="with_storage", flat=True)
2021        with_storage = f" WITH ({with_storage})" if with_storage else ""
2022        tablespace = self.sql(expression, "tablespace")
2023        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
2024        on = self.sql(expression, "on")
2025        on = f" ON {on}" if on else ""
2026
2027        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2028
2029    def index_sql(self, expression: exp.Index) -> str:
2030        unique = "UNIQUE " if expression.args.get("unique") else ""
2031        primary = "PRIMARY " if expression.args.get("primary") else ""
2032        amp = "AMP " if expression.args.get("amp") else ""
2033        name = self.sql(expression, "this")
2034        name = f"{name} " if name else ""
2035        table = self.sql(expression, "table")
2036        table = f"{self.INDEX_ON} {table}" if table else ""
2037
2038        index = "INDEX " if not table else ""
2039
2040        params = self.sql(expression, "params")
2041        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2042
2043    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2044        this = expression.this
2045        if this and this.is_string:
2046            resolved = maybe_parse(this.name).sql(self.dialect)
2047            if "expressions" in expression.args:
2048                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2049                # We can't safely emit the call to other dialects since name/arg semantics may differ
2050                self.unsupported(
2051                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2052                )
2053            return resolved
2054        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2055        return self.func("IDENTIFIER", this)
2056
2057    def identifier_sql(self, expression: exp.Identifier) -> str:
2058        text = expression.name
2059        lower = text.lower()
2060        quoted = expression.quoted
2061        text = lower if self.normalize and not quoted else text
2062        if self._identifier_escaped_sequences:
2063            for char, escaped in self._identifier_escaped_sequences.items():
2064                text = text.replace(char, escaped)
2065        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2066        if (
2067            quoted
2068            or self.dialect.can_quote(expression, self.identify)
2069            or lower in self.RESERVED_KEYWORDS
2070            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2071        ):
2072            text = (
2073                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2074            )
2075        return text
2076
2077    def hex_sql(self, expression: exp.Hex) -> str:
2078        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2079        if self.dialect.HEX_LOWERCASE:
2080            text = self.func("LOWER", text)
2081
2082        return text
2083
2084    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2085        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2086        if not self.dialect.HEX_LOWERCASE:
2087            text = self.func("LOWER", text)
2088        return text
2089
2090    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2091        input_format = self.sql(expression, "input_format")
2092        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2093        output_format = self.sql(expression, "output_format")
2094        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2095        return self.sep().join((input_format, output_format))
2096
2097    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2098        string = self.sql(exp.Literal.string(expression.name))
2099        return f"{prefix}{string}"
2100
2101    def partition_sql(self, expression: exp.Partition) -> str:
2102        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2103        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2104
2105    def properties_sql(self, expression: exp.Properties) -> str:
2106        root_properties = []
2107        with_properties = []
2108
2109        for p in expression.expressions:
2110            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2111            if p_loc == exp.Properties.Location.POST_WITH:
2112                with_properties.append(p)
2113            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2114                root_properties.append(p)
2115
2116        root_props_ast = exp.Properties(expressions=root_properties)
2117        root_props_ast.parent = expression.parent
2118
2119        with_props_ast = exp.Properties(expressions=with_properties)
2120        with_props_ast.parent = expression.parent
2121
2122        root_props = self.root_properties(root_props_ast)
2123        with_props = self.with_properties(with_props_ast)
2124
2125        if root_props and with_props and not self.pretty:
2126            with_props = " " + with_props
2127
2128        return root_props + with_props
2129
2130    def root_properties(self, properties: exp.Properties) -> str:
2131        if properties.expressions:
2132            return self.expressions(properties, indent=False, sep=" ")
2133        return ""
2134
2135    def properties(
2136        self,
2137        properties: exp.Properties,
2138        prefix: str = "",
2139        sep: str = ", ",
2140        suffix: str = "",
2141        wrapped: bool = True,
2142    ) -> str:
2143        if properties.expressions:
2144            expressions = self.expressions(properties, sep=sep, indent=False)
2145            if expressions:
2146                expressions = self.wrap(expressions) if wrapped else expressions
2147                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2148        return ""
2149
2150    def with_properties(self, properties: exp.Properties) -> str:
2151        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2152
2153    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2154        properties_locs = defaultdict(list)
2155        for p in properties.expressions:
2156            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2157            if p_loc != exp.Properties.Location.UNSUPPORTED:
2158                properties_locs[p_loc].append(p)
2159            else:
2160                self.unsupported(f"Unsupported property {p.key}")
2161
2162        return properties_locs
2163
2164    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2165        if isinstance(expression.this, exp.Dot):
2166            return self.sql(expression, "this")
2167        return f"'{expression.name}'" if string_key else expression.name
2168
2169    def property_sql(self, expression: exp.Property) -> str:
2170        property_cls = expression.__class__
2171        if property_cls == exp.Property:
2172            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2173
2174        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2175        if not property_name:
2176            self.unsupported(f"Unsupported property {expression.key}")
2177
2178        return f"{property_name}={self.sql(expression, 'this')}"
2179
2180    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2181        return f"UUID {self.sql(expression, 'this')}"
2182
2183    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2184        if self.SUPPORTS_CREATE_TABLE_LIKE:
2185            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2186            options = f" {options}" if options else ""
2187
2188            like = f"LIKE {self.sql(expression, 'this')}{options}"
2189            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2190                like = f"({like})"
2191
2192            return like
2193
2194        if expression.expressions:
2195            self.unsupported("Transpilation of LIKE property options is unsupported")
2196
2197        select = exp.select("*").from_(expression.this).limit(0)
2198        return f"AS {self.sql(select)}"
2199
2200    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2201        no = "NO " if expression.args.get("no") else ""
2202        protection = " PROTECTION" if expression.args.get("protection") else ""
2203        return f"{no}FALLBACK{protection}"
2204
2205    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2206        no = "NO " if expression.args.get("no") else ""
2207        local = expression.args.get("local")
2208        local = f"{local} " if local else ""
2209        dual = "DUAL " if expression.args.get("dual") else ""
2210        before = "BEFORE " if expression.args.get("before") else ""
2211        after = "AFTER " if expression.args.get("after") else ""
2212        return f"{no}{local}{dual}{before}{after}JOURNAL"
2213
2214    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2215        freespace = self.sql(expression, "this")
2216        percent = " PERCENT" if expression.args.get("percent") else ""
2217        return f"FREESPACE={freespace}{percent}"
2218
2219    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2220        if expression.args.get("default"):
2221            property = "DEFAULT"
2222        elif expression.args.get("on"):
2223            property = "ON"
2224        else:
2225            property = "OFF"
2226        return f"CHECKSUM={property}"
2227
2228    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2229        if expression.args.get("no"):
2230            return "NO MERGEBLOCKRATIO"
2231        if expression.args.get("default"):
2232            return "DEFAULT MERGEBLOCKRATIO"
2233
2234        percent = " PERCENT" if expression.args.get("percent") else ""
2235        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2236
2237    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2238        expressions = self.expressions(expression, flat=True)
2239        expressions = f"({expressions})" if expressions else ""
2240        return f"USING {self.sql(expression, 'this')}{expressions}"
2241
2242    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2243        default = expression.args.get("default")
2244        minimum = expression.args.get("minimum")
2245        maximum = expression.args.get("maximum")
2246        if default or minimum or maximum:
2247            if default:
2248                prop = "DEFAULT"
2249            elif minimum:
2250                prop = "MINIMUM"
2251            else:
2252                prop = "MAXIMUM"
2253            return f"{prop} DATABLOCKSIZE"
2254        units = expression.args.get("units")
2255        units = f" {units}" if units else ""
2256        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2257
2258    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2259        autotemp = expression.args.get("autotemp")
2260        always = expression.args.get("always")
2261        default = expression.args.get("default")
2262        manual = expression.args.get("manual")
2263        never = expression.args.get("never")
2264
2265        if autotemp is not None:
2266            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2267        elif always:
2268            prop = "ALWAYS"
2269        elif default:
2270            prop = "DEFAULT"
2271        elif manual:
2272            prop = "MANUAL"
2273        elif never:
2274            prop = "NEVER"
2275        return f"BLOCKCOMPRESSION={prop}"
2276
2277    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2278        no = expression.args.get("no")
2279        no = " NO" if no else ""
2280        concurrent = expression.args.get("concurrent")
2281        concurrent = " CONCURRENT" if concurrent else ""
2282        target = self.sql(expression, "target")
2283        target = f" {target}" if target else ""
2284        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2285
2286    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2287        if isinstance(expression.this, list):
2288            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2289        if expression.this:
2290            modulus = self.sql(expression, "this")
2291            remainder = self.sql(expression, "expression")
2292            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2293
2294        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2295        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2296        return f"FROM ({from_expressions}) TO ({to_expressions})"
2297
2298    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2299        this = self.sql(expression, "this")
2300
2301        for_values_or_default = expression.expression
2302        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2303            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2304        else:
2305            for_values_or_default = " DEFAULT"
2306
2307        return f"PARTITION OF {this}{for_values_or_default}"
2308
2309    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2310        kind = expression.args.get("kind")
2311        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2312        for_or_in = expression.args.get("for_or_in")
2313        for_or_in = f" {for_or_in}" if for_or_in else ""
2314        lock_type = expression.args.get("lock_type")
2315        override = " OVERRIDE" if expression.args.get("override") else ""
2316        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2317
2318    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2319        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2320        statistics = expression.args.get("statistics")
2321        statistics_sql = ""
2322        if statistics is not None:
2323            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2324        return f"{data_sql}{statistics_sql}"
2325
2326    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2327        this = self.sql(expression, "this")
2328        this = f"HISTORY_TABLE={this}" if this else ""
2329        data_consistency: str | None = self.sql(expression, "data_consistency")
2330        data_consistency = (
2331            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2332        )
2333        retention_period: str | None = self.sql(expression, "retention_period")
2334        retention_period = (
2335            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2336        )
2337
2338        if this:
2339            on_sql = self.func("ON", this, data_consistency, retention_period)
2340        else:
2341            on_sql = "ON" if expression.args.get("on") else "OFF"
2342
2343        sql = f"SYSTEM_VERSIONING={on_sql}"
2344
2345        return f"WITH({sql})" if expression.args.get("with_") else sql
2346
2347    def insert_sql(self, expression: exp.Insert) -> str:
2348        hint = self.sql(expression, "hint")
2349        overwrite = expression.args.get("overwrite")
2350
2351        if isinstance(expression.this, exp.Directory):
2352            this = " OVERWRITE" if overwrite else " INTO"
2353        else:
2354            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2355
2356        stored = self.sql(expression, "stored")
2357        stored = f" {stored}" if stored else ""
2358        alternative = expression.args.get("alternative")
2359        alternative = f" OR {alternative}" if alternative else ""
2360        ignore = " IGNORE" if expression.args.get("ignore") else ""
2361        is_function = expression.args.get("is_function")
2362        if is_function:
2363            this = f"{this} FUNCTION"
2364        this = f"{this} {self.sql(expression, 'this')}"
2365
2366        exists = " IF EXISTS" if expression.args.get("exists") else ""
2367        where = self.sql(expression, "where")
2368        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2369        using = self.expressions(expression, key="using", flat=True)
2370        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2371        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2372        on_conflict = self.sql(expression, "conflict")
2373        on_conflict = f" {on_conflict}" if on_conflict else ""
2374        by_name = " BY NAME" if expression.args.get("by_name") else ""
2375        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2376        returning = self.sql(expression, "returning")
2377
2378        if self.RETURNING_END:
2379            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2380        else:
2381            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2382
2383        partition_by = self.sql(expression, "partition")
2384        partition_by = f" {partition_by}" if partition_by else ""
2385        settings = self.sql(expression, "settings")
2386        settings = f" {settings}" if settings else ""
2387
2388        source = self.sql(expression, "source")
2389        source = f"TABLE {source}" if source else ""
2390
2391        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2392        return self.prepend_ctes(expression, sql)
2393
2394    def introducer_sql(self, expression: exp.Introducer) -> str:
2395        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2396
2397    def kill_sql(self, expression: exp.Kill) -> str:
2398        kind = self.sql(expression, "kind")
2399        kind = f" {kind}" if kind else ""
2400        this = self.sql(expression, "this")
2401        this = f" {this}" if this else ""
2402        return f"KILL{kind}{this}"
2403
2404    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2405        return expression.name
2406
2407    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2408        return expression.name
2409
2410    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2411        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2412
2413        constraint = self.sql(expression, "constraint")
2414        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2415
2416        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2417        if conflict_keys:
2418            conflict_keys = f"({conflict_keys})"
2419
2420        index_predicate = self.sql(expression, "index_predicate")
2421        conflict_keys = f"{conflict_keys}{index_predicate} "
2422
2423        action = self.sql(expression, "action")
2424
2425        expressions = self.expressions(expression, flat=True)
2426        if expressions:
2427            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2428            expressions = f" {set_keyword}{expressions}"
2429
2430        where = self.sql(expression, "where")
2431        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2432
2433    def returning_sql(self, expression: exp.Returning) -> str:
2434        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2435
2436    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2437        fields = self.sql(expression, "fields")
2438        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2439        escaped = self.sql(expression, "escaped")
2440        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2441        items = self.sql(expression, "collection_items")
2442        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2443        keys = self.sql(expression, "map_keys")
2444        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2445        lines = self.sql(expression, "lines")
2446        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2447        null = self.sql(expression, "null")
2448        null = f" NULL DEFINED AS {null}" if null else ""
2449        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2450
2451    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2452        return f"WITH ({self.expressions(expression, flat=True)})"
2453
2454    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2455        this = f"{self.sql(expression, 'this')} INDEX"
2456        target = self.sql(expression, "target")
2457        target = f" FOR {target}" if target else ""
2458        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2459
2460    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2461        this = self.sql(expression, "this")
2462        kind = self.sql(expression, "kind")
2463        expr = self.sql(expression, "expression")
2464        return f"{this} ({kind} => {expr})"
2465
2466    def table_parts(self, expression: exp.Table) -> str:
2467        return ".".join(
2468            self.sql(part)
2469            for part in (
2470                expression.args.get("catalog"),
2471                expression.args.get("db"),
2472                expression.args.get("this"),
2473            )
2474            if part is not None
2475        )
2476
2477    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2478        table = self.table_parts(expression)
2479        only = "ONLY " if expression.args.get("only") else ""
2480        partition = self.sql(expression, "partition")
2481        partition = f" {partition}" if partition else ""
2482        version = self.sql(expression, "version")
2483        version = f" {version}" if version else ""
2484        alias = self.sql(expression, "alias")
2485        alias = f"{sep}{alias}" if alias else ""
2486
2487        sample = self.sql(expression, "sample")
2488        post_alias = ""
2489        pre_alias = ""
2490
2491        if self.dialect.ALIAS_POST_TABLESAMPLE:
2492            pre_alias = sample
2493        else:
2494            post_alias = sample
2495
2496        if self.dialect.ALIAS_POST_VERSION:
2497            pre_alias = f"{pre_alias}{version}"
2498        else:
2499            post_alias = f"{post_alias}{version}"
2500
2501        hints = self.expressions(expression, key="hints", sep=" ")
2502        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2503        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2504        joins = self.indent(
2505            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2506        )
2507        laterals = self.expressions(expression, key="laterals", sep="")
2508
2509        file_format = self.sql(expression, "format")
2510        pattern = self.sql(expression, "pattern")
2511        if file_format:
2512            pattern = f", PATTERN => {pattern}" if pattern else ""
2513            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2514        elif pattern:
2515            file_format = f" (PATTERN => {pattern})"
2516
2517        ordinality = expression.args.get("ordinality") or ""
2518        if ordinality:
2519            ordinality = f" WITH ORDINALITY{alias}"
2520            alias = ""
2521
2522        when = self.sql(expression, "when")
2523        if when:
2524            if self.HISTORICAL_DATA_POST_ALIAS:
2525                alias = f"{alias} {when}"
2526            else:
2527                table = f"{table} {when}"
2528
2529        changes = self.sql(expression, "changes")
2530        changes = f" {changes}" if changes else ""
2531
2532        rows_from = self.expressions(expression, key="rows_from")
2533        if rows_from:
2534            table = f"ROWS FROM {self.wrap(rows_from)}"
2535
2536        indexed = expression.args.get("indexed")
2537        if indexed is not None:
2538            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2539        else:
2540            indexed = ""
2541
2542        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2543
2544    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2545        table = self.func("TABLE", expression.this)
2546        alias = self.sql(expression, "alias")
2547        alias = f" AS {alias}" if alias else ""
2548        sample = self.sql(expression, "sample")
2549        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2550        joins = self.indent(
2551            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2552        )
2553        return f"{table}{alias}{pivots}{sample}{joins}"
2554
2555    def tablesample_sql(
2556        self,
2557        expression: exp.TableSample,
2558        tablesample_keyword: str | None = None,
2559    ) -> str:
2560        method = self.sql(expression, "method")
2561        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2562        numerator = self.sql(expression, "bucket_numerator")
2563        denominator = self.sql(expression, "bucket_denominator")
2564        field = self.sql(expression, "bucket_field")
2565        field = f" ON {field}" if field else ""
2566        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2567        seed = self.sql(expression, "seed")
2568        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2569
2570        size = self.sql(expression, "size")
2571        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2572            size = f"{size} ROWS"
2573
2574        percent = self.sql(expression, "percent")
2575        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2576            percent = f"{percent} PERCENT"
2577
2578        expr = f"{bucket}{percent}{size}"
2579        if self.TABLESAMPLE_REQUIRES_PARENS:
2580            expr = f"({expr})"
2581
2582        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2583
2584    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2585        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2586        # the stored column name differs from the target dialect's natural output.
2587        columns = expression.args.get("columns")
2588        if not columns or len(expression.fields) != 1:
2589            return None
2590
2591        args = expression.args
2592        parser_cls = self.dialect.parser_class
2593
2594        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2595        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2596        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2597
2598        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2599        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2600        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2601
2602        if (
2603            src_identify_pivot_strings == tgt_identify_pivot_strings
2604            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2605            and src_pivot_column_naming == tgt_pivot_column_naming
2606        ):
2607            return None
2608
2609        in_exprs = expression.fields[0].expressions
2610        step = len(columns) // len(in_exprs)
2611
2612        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2613        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2614        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2615        first_stored = columns[0].name
2616
2617        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2618        if not first_stored.startswith(first_base):
2619            return None
2620
2621        suffix = first_stored[len(first_base) :]
2622
2623        # Whether the target dialect would append an agg-name suffix for this pivot.
2624        # Spark single-agg uniquely drops the agg alias entirely.
2625        target_has_suffix = (
2626            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2627        ) and any(a.alias for a in expression.expressions)
2628        source_has_suffix = suffix != ""
2629
2630        new_exprs: list[exp.Expression] = []
2631        modified = False
2632        for val_idx, e in enumerate(in_exprs):
2633            if isinstance(e, exp.PivotAlias):
2634                new_exprs.append(e)
2635                continue
2636
2637            i = val_idx * step
2638            stored_full = columns[i].name
2639            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2640            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2641
2642            # Source had a suffix, but target won't apply one
2643            if source_has_suffix and not target_has_suffix:
2644                new_exprs.append(
2645                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2646                )
2647                modified = True
2648            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2649            elif stored_value != target_value:
2650                new_exprs.append(
2651                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2652                )
2653                modified = True
2654            else:
2655                new_exprs.append(e)
2656
2657        return new_exprs if modified else None
2658
2659    def pivot_sql(self, expression: exp.Pivot) -> str:
2660        expressions = self.expressions(expression, flat=True)
2661        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2662
2663        group = self.sql(expression, "group")
2664
2665        if expression.this:
2666            this = self.sql(expression, "this")
2667            if not expressions:
2668                sql = f"UNPIVOT {this}"
2669            else:
2670                on = f"{self.seg('ON')} {expressions}"
2671                into = self.sql(expression, "into")
2672                into = f"{self.seg('INTO')} {into}" if into else ""
2673                using = self.expressions(expression, key="using", flat=True)
2674                using = f"{self.seg('USING')} {using}" if using else ""
2675                sql = f"{direction} {this}{on}{into}{using}{group}"
2676            return self.prepend_ctes(expression, sql)
2677
2678        if not expression.unpivot:
2679            # Wrap IN-list values with explicit aliases where the target dialect would differ
2680            new_field_exprs = self._pivot_in_value_aliases(expression)
2681            if new_field_exprs is not None:
2682                expression.fields[0].set("expressions", new_field_exprs)
2683
2684        alias = self.sql(expression, "alias")
2685        if alias:
2686            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2687
2688        fields = self.expressions(
2689            expression,
2690            "fields",
2691            sep=" ",
2692            dynamic=True,
2693            new_line=True,
2694            skip_first=True,
2695            skip_last=True,
2696        )
2697
2698        include_nulls = expression.args.get("include_nulls")
2699        if include_nulls is not None:
2700            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2701        else:
2702            nulls = ""
2703
2704        default_on_null = self.sql(expression, "default_on_null")
2705        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2706        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2707        return self.prepend_ctes(expression, sql)
2708
2709    def version_sql(self, expression: exp.Version) -> str:
2710        this = f"FOR {expression.name}"
2711        kind = expression.text("kind")
2712        expr = self.sql(expression, "expression")
2713        return f"{this} {kind} {expr}"
2714
2715    def tuple_sql(self, expression: exp.Tuple) -> str:
2716        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2717
2718    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2719        """
2720        Returns (join_sql, from_sql) for UPDATE statements.
2721        - join_sql: placed after UPDATE table, before SET
2722        - from_sql: placed after SET clause (standard position)
2723        Dialects like MySQL need to convert FROM to JOIN syntax.
2724        """
2725        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2726            return ("", self.sql(expression, "from_"))
2727
2728        # Qualify unqualified columns in SET clause with the target table
2729        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2730        target_table = expression.this
2731        if isinstance(target_table, exp.Table):
2732            target_name = exp.to_identifier(target_table.alias_or_name)
2733            for eq in expression.expressions:
2734                col = eq.this
2735                if isinstance(col, exp.Column) and not col.table:
2736                    col.set("table", target_name)
2737
2738        table = from_expr.this
2739        if nested_joins := table.args.get("joins", []):
2740            table.set("joins", None)
2741
2742        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2743        for nested in nested_joins:
2744            if not nested.args.get("on") and not nested.args.get("using"):
2745                nested.set("on", exp.true())
2746            join_sql += self.sql(nested)
2747
2748        return (join_sql, "")
2749
2750    def update_sql(self, expression: exp.Update) -> str:
2751        hint = self.sql(expression, "hint")
2752        this = self.sql(expression, "this")
2753        join_sql, from_sql = self._update_from_joins_sql(expression)
2754        set_sql = self.expressions(expression, flat=True)
2755        where_sql = self.sql(expression, "where")
2756        returning = self.sql(expression, "returning")
2757        order = self.sql(expression, "order")
2758        limit = self.sql(expression, "limit")
2759        if self.RETURNING_END:
2760            expression_sql = f"{from_sql}{where_sql}{returning}"
2761        else:
2762            expression_sql = f"{returning}{from_sql}{where_sql}"
2763        options = self.expressions(expression, key="options")
2764        options = f" OPTION({options})" if options else ""
2765        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2766        return self.prepend_ctes(expression, sql)
2767
2768    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2769        values_as_table = values_as_table and self.VALUES_AS_TABLE
2770
2771        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2772        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2773            args = self.expressions(expression)
2774            alias = self.sql(expression, "alias")
2775            values = f"VALUES{self.seg('')}{args}"
2776            values = (
2777                f"({values})"
2778                if self.WRAP_DERIVED_VALUES
2779                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2780                else values
2781            )
2782            values = self.query_modifiers(expression, values)
2783            return f"{values} AS {alias}" if alias else values
2784
2785        # Converts `VALUES...` expression into a series of select unions.
2786        alias_node = expression.args.get("alias")
2787        column_names = alias_node and alias_node.columns
2788
2789        selects: list[exp.Query] = []
2790
2791        for i, tup in enumerate(expression.expressions):
2792            row = tup.expressions
2793
2794            if i == 0 and column_names:
2795                row = [
2796                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2797                ]
2798
2799            selects.append(exp.Select(expressions=row))
2800
2801        if self.pretty:
2802            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2803            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2804            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2805            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2806            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2807
2808        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2809        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2810        return f"({unions}){alias}"
2811
2812    def var_sql(self, expression: exp.Var) -> str:
2813        return self.sql(expression, "this")
2814
2815    @unsupported_args("expressions")
2816    def into_sql(self, expression: exp.Into) -> str:
2817        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2818        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2819        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2820
2821    def from_sql(self, expression: exp.From) -> str:
2822        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2823
2824    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2825        grouping_sets = self.expressions(expression, indent=False)
2826        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2827
2828    def rollup_sql(self, expression: exp.Rollup) -> str:
2829        expressions = self.expressions(expression, indent=False)
2830        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2831
2832    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2833        this = self.sql(expression, "this")
2834
2835        columns = self.expressions(expression, flat=True)
2836
2837        from_sql = self.sql(expression, "from_index")
2838        from_sql = f" FROM {from_sql}" if from_sql else ""
2839
2840        properties = expression.args.get("properties")
2841        properties_sql = (
2842            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2843        )
2844
2845        return f"{this}({columns}){from_sql}{properties_sql}"
2846
2847    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2848        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2849
2850    def cube_sql(self, expression: exp.Cube) -> str:
2851        expressions = self.expressions(expression, indent=False)
2852        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2853
2854    def group_sql(self, expression: exp.Group) -> str:
2855        group_by_all = expression.args.get("all")
2856        if group_by_all is True:
2857            modifier = " ALL"
2858        elif group_by_all is False:
2859            modifier = " DISTINCT"
2860        else:
2861            modifier = ""
2862
2863        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2864
2865        grouping_sets = self.expressions(expression, key="grouping_sets")
2866        cube = self.expressions(expression, key="cube")
2867        rollup = self.expressions(expression, key="rollup")
2868
2869        groupings = csv(
2870            self.seg(grouping_sets) if grouping_sets else "",
2871            self.seg(cube) if cube else "",
2872            self.seg(rollup) if rollup else "",
2873            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2874            sep=self.GROUPINGS_SEP,
2875        )
2876
2877        if (
2878            expression.expressions
2879            and groupings
2880            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2881        ):
2882            add_separator = True
2883
2884            if grouping_sets:
2885                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2886                    add_separator = False
2887                else:
2888                    self.unsupported(
2889                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2890                    )
2891
2892            if add_separator:
2893                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2894
2895        return f"{group_by}{groupings}"
2896
2897    def having_sql(self, expression: exp.Having) -> str:
2898        this = self.indent(self.sql(expression, "this"))
2899        return f"{self.seg('HAVING')}{self.sep()}{this}"
2900
2901    def connect_sql(self, expression: exp.Connect) -> str:
2902        start = self.sql(expression, "start")
2903        start = self.seg(f"START WITH {start}") if start else ""
2904        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2905        connect = self.sql(expression, "connect")
2906        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2907        return start + connect
2908
2909    def prior_sql(self, expression: exp.Prior) -> str:
2910        return f"PRIOR {self.sql(expression, 'this')}"
2911
2912    def join_sql(self, expression: exp.Join) -> str:
2913        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2914            side = None
2915        else:
2916            side = expression.side
2917
2918        op_sql = " ".join(
2919            op
2920            for op in (
2921                expression.method,
2922                "GLOBAL" if expression.args.get("global_") else None,
2923                side,
2924                expression.kind,
2925                expression.hint if self.JOIN_HINTS else None,
2926                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2927            )
2928            if op
2929        )
2930        match_cond = self.sql(expression, "match_condition")
2931        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2932        on_sql = self.sql(expression, "on")
2933        using = expression.args.get("using")
2934
2935        if not on_sql and using:
2936            on_sql = csv(*(self.sql(column) for column in using))
2937
2938        this = expression.this
2939        this_sql = self.sql(this)
2940
2941        exprs = self.expressions(expression)
2942        if exprs:
2943            this_sql = f"{this_sql},{self.seg(exprs)}"
2944
2945        if on_sql:
2946            on_sql = self.indent(on_sql, skip_first=True)
2947            space = self.seg(" " * self.pad) if self.pretty else " "
2948            if using:
2949                on_sql = f"{space}USING ({on_sql})"
2950            else:
2951                on_sql = f"{space}ON {on_sql}"
2952        elif not op_sql:
2953            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2954                return f" {this_sql}"
2955
2956            return f", {this_sql}"
2957
2958        if op_sql != "STRAIGHT_JOIN":
2959            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2960
2961        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2962        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2963
2964    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2965        args = self.expressions(expression, flat=True)
2966        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2967        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2968
2969    def lateral_op(self, expression: exp.Lateral) -> str:
2970        cross_apply = expression.args.get("cross_apply")
2971
2972        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2973        if cross_apply is True:
2974            op = "INNER JOIN "
2975        elif cross_apply is False:
2976            op = "LEFT JOIN "
2977        else:
2978            op = ""
2979
2980        return f"{op}LATERAL"
2981
2982    def lateral_sql(self, expression: exp.Lateral) -> str:
2983        this = self.sql(expression, "this")
2984
2985        if expression.args.get("view"):
2986            alias = expression.args["alias"]
2987            columns = self.expressions(alias, key="columns", flat=True)
2988            table = f" {alias.name}" if alias.name else ""
2989            columns = f" AS {columns}" if columns else ""
2990            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2991            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2992
2993        table_alias = expression.args.get("alias")
2994        offset = expression.this.args.get("offset")
2995
2996        if (
2997            self.UNNEST_WITH_ORDINALITY
2998            and table_alias
2999            and isinstance(expression.this, exp.Unnest)
3000            and isinstance(offset, exp.Identifier)
3001        ):
3002            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3003            table_alias = table_alias.copy()
3004            table_alias.append("columns", offset.copy())
3005
3006        alias = self.sql(table_alias)
3007        alias = f" AS {alias}" if alias else ""
3008
3009        ordinality = expression.args.get("ordinality") or ""
3010        if ordinality:
3011            ordinality = f" WITH ORDINALITY{alias}"
3012            alias = ""
3013
3014        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3015
3016    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3017        this = self.sql(expression, "this")
3018
3019        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3020            return this
3021
3022        args = [
3023            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3024            for e in (expression.args.get(k) for k in ("offset", "expression"))
3025            if e
3026        ]
3027
3028        args_sql = ", ".join(self.sql(e) for e in args)
3029        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3030        expressions = self.expressions(expression, flat=True)
3031        limit_options = self.sql(expression, "limit_options")
3032        expressions = f" BY {expressions}" if expressions else ""
3033
3034        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3035
3036    def offset_sql(self, expression: exp.Offset) -> str:
3037        this = self.sql(expression, "this")
3038        value = expression.expression
3039        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3040        expressions = self.expressions(expression, flat=True)
3041        expressions = f" BY {expressions}" if expressions else ""
3042        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3043
3044    def setitem_sql(self, expression: exp.SetItem) -> str:
3045        kind = self.sql(expression, "kind")
3046        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3047            kind = ""
3048        else:
3049            kind = f"{kind} " if kind else ""
3050        this = self.sql(expression, "this")
3051        expressions = self.expressions(expression)
3052        collate = self.sql(expression, "collate")
3053        collate = f" COLLATE {collate}" if collate else ""
3054        global_ = "GLOBAL " if expression.args.get("global_") else ""
3055        return f"{global_}{kind}{this}{expressions}{collate}"
3056
3057    def set_sql(self, expression: exp.Set) -> str:
3058        expressions = f" {self.expressions(expression, flat=True)}"
3059        tag = " TAG" if expression.args.get("tag") else ""
3060        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3061
3062    def queryband_sql(self, expression: exp.QueryBand) -> str:
3063        this = self.sql(expression, "this")
3064        update = " UPDATE" if expression.args.get("update") else ""
3065        scope = self.sql(expression, "scope")
3066        scope = f" FOR {scope}" if scope else ""
3067
3068        return f"QUERY_BAND = {this}{update}{scope}"
3069
3070    def pragma_sql(self, expression: exp.Pragma) -> str:
3071        return f"PRAGMA {self.sql(expression, 'this')}"
3072
3073    def lock_sql(self, expression: exp.Lock) -> str:
3074        if not self.LOCKING_READS_SUPPORTED:
3075            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3076            return ""
3077
3078        update = expression.args["update"]
3079        key = expression.args.get("key")
3080        if update:
3081            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3082        else:
3083            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3084        expressions = self.expressions(expression, flat=True)
3085        expressions = f" OF {expressions}" if expressions else ""
3086        wait = expression.args.get("wait")
3087
3088        if wait is not None:
3089            if isinstance(wait, exp.Literal):
3090                wait = f" WAIT {self.sql(wait)}"
3091            else:
3092                wait = " NOWAIT" if wait else " SKIP LOCKED"
3093
3094        return f"{lock_type}{expressions}{wait or ''}"
3095
3096    def literal_sql(self, expression: exp.Literal) -> str:
3097        text = expression.this or ""
3098        if expression.is_string:
3099            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3100        return text
3101
3102    def escape_str(
3103        self,
3104        text: str,
3105        escape_backslash: bool = True,
3106        delimiter: str | None = None,
3107        escaped_delimiter: str | None = None,
3108        is_byte_string: bool = False,
3109        is_bytes: bool = False,
3110    ) -> str:
3111        if is_byte_string:
3112            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3113        else:
3114            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3115
3116        if supports_escape_sequences:
3117            text = "".join(
3118                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3119                for ch in text
3120            )
3121
3122            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3123                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3124                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3125
3126        delimiter = delimiter or self.dialect.QUOTE_END
3127        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3128
3129        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3130
3131    def loaddata_sql(self, expression: exp.LoadData) -> str:
3132        is_overwrite = expression.args.get("overwrite")
3133        overwrite = " OVERWRITE" if is_overwrite else ""
3134        this = self.sql(expression, "this")
3135
3136        files = expression.args.get("files")
3137        if files:
3138            files_sql = self.expressions(files, flat=True)
3139            files_sql = f"FILES{self.wrap(files_sql)}"
3140            if is_overwrite:
3141                this = f" {this}"
3142            elif expression.args.get("temp"):
3143                this = f" INTO TEMP TABLE {this}"
3144            else:
3145                this = f" INTO TABLE {this}"
3146            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3147
3148        local = " LOCAL" if expression.args.get("local") else ""
3149        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3150        this = f" INTO TABLE {this}"
3151        partition = self.sql(expression, "partition")
3152        partition = f" {partition}" if partition else ""
3153        input_format = self.sql(expression, "input_format")
3154        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3155        serde = self.sql(expression, "serde")
3156        serde = f" SERDE {serde}" if serde else ""
3157        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3158
3159    def null_sql(self, *_) -> str:
3160        return "NULL"
3161
3162    def boolean_sql(self, expression: exp.Boolean) -> str:
3163        return "TRUE" if expression.this else "FALSE"
3164
3165    def booland_sql(self, expression: exp.Booland) -> str:
3166        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3167
3168    def boolor_sql(self, expression: exp.Boolor) -> str:
3169        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3170
3171    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3172        this = self.sql(expression, "this")
3173        this = f"{this} " if this else this
3174        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3175        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3176
3177    def withfill_sql(self, expression: exp.WithFill) -> str:
3178        from_sql = self.sql(expression, "from_")
3179        from_sql = f" FROM {from_sql}" if from_sql else ""
3180        to_sql = self.sql(expression, "to")
3181        to_sql = f" TO {to_sql}" if to_sql else ""
3182        step_sql = self.sql(expression, "step")
3183        step_sql = f" STEP {step_sql}" if step_sql else ""
3184        interpolated_values = [
3185            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3186            if isinstance(e, exp.Alias)
3187            else self.sql(e, "this")
3188            for e in expression.args.get("interpolate") or []
3189        ]
3190        interpolate = (
3191            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3192        )
3193        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3194
3195    def cluster_sql(self, expression: exp.Cluster) -> str:
3196        return self.op_expressions("CLUSTER BY", expression)
3197
3198    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3199        if expression.this:
3200            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3201            return ""
3202        expressions = self.expressions(expression, flat=True)
3203        return f"CLUSTER BY ({expressions})"
3204
3205    def distribute_sql(self, expression: exp.Distribute) -> str:
3206        return self.op_expressions("DISTRIBUTE BY", expression)
3207
3208    def sort_sql(self, expression: exp.Sort) -> str:
3209        return self.op_expressions("SORT BY", expression)
3210
3211    def _resolve_ordered_for_null_ordering_simulation(
3212        self, expression: exp.Ordered
3213    ) -> exp.Expr | None:
3214        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3215
3216        Returns the underlying expression of the uniquely-matching projection
3217        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3218        simulation, since the CASE is evaluated in FROM-clause scope rather
3219        than alias scope (MySQL error 1052). Returns None if no safe
3220        substitution applies, leaving the original behaviour unchanged.
3221        """
3222        this = expression.this
3223        if not (isinstance(this, exp.Column) and not this.table):
3224            return None
3225
3226        # Only a query's own ORDER BY can reference its projection aliases
3227        order = expression.parent
3228        ancestor = order.parent if isinstance(order, exp.Order) else None
3229        if not isinstance(ancestor, exp.Select):
3230            return None
3231
3232        column_name = this.name
3233        matched: list[exp.Expr] = [
3234            p.this if isinstance(p, exp.Alias) else p
3235            for p in ancestor.selects
3236            if p.output_name == column_name
3237        ]
3238        match = matched[0] if len(matched) == 1 else None
3239
3240        # Skip the substitution when it would be identical to the existing
3241        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3242        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3243            return None
3244
3245        return match
3246
3247    def ordered_sql(self, expression: exp.Ordered) -> str:
3248        desc = expression.args.get("desc")
3249        asc = not desc
3250
3251        nulls_first = expression.args.get("nulls_first")
3252        nulls_last = not nulls_first
3253        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3254        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3255        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3256
3257        this = self.sql(expression, "this")
3258
3259        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3260        nulls_sort_change = ""
3261        if nulls_first and (
3262            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3263        ):
3264            nulls_sort_change = " NULLS FIRST"
3265        elif (
3266            nulls_last
3267            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3268            and not nulls_are_last
3269        ):
3270            nulls_sort_change = " NULLS LAST"
3271
3272        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3273        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3274            window = expression.find_ancestor(exp.Window, exp.Select)
3275
3276            if isinstance(window, exp.Window):
3277                window_this = window.this
3278                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3279                    window_this = window_this.this
3280                spec = window.args.get("spec")
3281            else:
3282                window_this = None
3283                spec = None
3284
3285            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3286            # without a spec or with a ROWS spec, but not with RANGE
3287            if not (
3288                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3289                and (not spec or spec.text("kind").upper() == "ROWS")
3290            ):
3291                if window_this and spec:
3292                    self.unsupported(
3293                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3294                    )
3295                    nulls_sort_change = ""
3296                elif self.NULL_ORDERING_SUPPORTED is False and (
3297                    (asc and nulls_sort_change == " NULLS LAST")
3298                    or (desc and nulls_sort_change == " NULLS FIRST")
3299                ):
3300                    # BigQuery does not allow these ordering/nulls combinations when used under
3301                    # an aggregation func or under a window containing one
3302                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3303
3304                    if isinstance(ancestor, exp.Window):
3305                        ancestor = ancestor.this
3306                    if isinstance(ancestor, exp.AggFunc):
3307                        self.unsupported(
3308                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3309                        )
3310                        nulls_sort_change = ""
3311                elif self.NULL_ORDERING_SUPPORTED is None:
3312                    if expression.this.is_int:
3313                        self.unsupported(
3314                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3315                        )
3316                    elif not isinstance(expression.this, exp.Rand):
3317                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3318                        target = self.sql(resolved) if resolved is not None else this
3319                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3320                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3321                    nulls_sort_change = ""
3322
3323        with_fill = self.sql(expression, "with_fill")
3324        with_fill = f" {with_fill}" if with_fill else ""
3325
3326        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3327
3328    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3329        window_frame = self.sql(expression, "window_frame")
3330        window_frame = f"{window_frame} " if window_frame else ""
3331
3332        this = self.sql(expression, "this")
3333
3334        return f"{window_frame}{this}"
3335
3336    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3337        partition = self.partition_by_sql(expression)
3338        order = self.sql(expression, "order")
3339        measures = self.expressions(expression, key="measures")
3340        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3341        rows = self.sql(expression, "rows")
3342        rows = self.seg(rows) if rows else ""
3343        after = self.sql(expression, "after")
3344        after = self.seg(after) if after else ""
3345        pattern = self.sql(expression, "pattern")
3346        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3347        definition_sqls = [
3348            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3349            for definition in expression.args.get("define", [])
3350        ]
3351        definitions = self.expressions(sqls=definition_sqls)
3352        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3353        body = "".join(
3354            (
3355                partition,
3356                order,
3357                measures,
3358                rows,
3359                after,
3360                pattern,
3361                define,
3362            )
3363        )
3364        alias = self.sql(expression, "alias")
3365        alias = f" {alias}" if alias else ""
3366        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3367
3368    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3369        limit = expression.args.get("limit")
3370
3371        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3372            count = limit.args.get("count")
3373            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3374            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3375            limit = exp.Limit(
3376                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3377            )
3378        elif (
3379            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3380        ):
3381            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3382
3383        return csv(
3384            *sqls,
3385            *[self.sql(join) for join in expression.args.get("joins") or []],
3386            self.sql(expression, "match"),
3387            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3388            self.sql(expression, "prewhere"),
3389            self.sql(expression, "where"),
3390            self.sql(expression, "connect"),
3391            self.sql(expression, "group"),
3392            self.sql(expression, "having"),
3393            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3394            self.sql(expression, "order"),
3395            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3396            *self.after_limit_modifiers(expression),
3397            self.sql(expression, "for_"),
3398            self.options_modifier(expression),
3399            sep="",
3400        )
3401
3402    def options_modifier(self, expression: exp.Expr) -> str:
3403        options = self.expressions(expression, key="options")
3404        return f" {options}" if options else ""
3405
3406    def forclause_sql(self, expression: exp.ForClause) -> str:
3407        kind = expression.args["kind"]
3408        if kind == "BROWSE":
3409            return f"{self.sep()}FOR BROWSE"
3410        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3411        # the target dialect doesn't support QueryOption, so we drop the clause.
3412        options = self.expressions(expression, key="expressions")
3413        if not options:
3414            return ""
3415        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3416
3417    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3418        self.unsupported("Unsupported query option.")
3419        return ""
3420
3421    def offset_limit_modifiers(
3422        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3423    ) -> list[str]:
3424        return [
3425            self.sql(expression, "offset") if fetch else self.sql(limit),
3426            self.sql(limit) if fetch else self.sql(expression, "offset"),
3427        ]
3428
3429    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3430        locks = self.expressions(expression, key="locks", sep=" ")
3431        locks = f" {locks}" if locks else ""
3432        return [locks, self.sql(expression, "sample")]
3433
3434    def select_sql(self, expression: exp.Select) -> str:
3435        into = expression.args.get("into")
3436        if not self.SUPPORTS_SELECT_INTO and into:
3437            into.pop()
3438
3439        hint = self.sql(expression, "hint")
3440        distinct = self.sql(expression, "distinct")
3441        distinct = f" {distinct}" if distinct else ""
3442        kind = self.sql(expression, "kind")
3443
3444        limit = expression.args.get("limit")
3445        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3446            top = self.limit_sql(limit, top=True)
3447            limit.pop()
3448        else:
3449            top = ""
3450
3451        expressions = self.expressions(expression)
3452
3453        if kind:
3454            if kind in self.SELECT_KINDS:
3455                kind = f" AS {kind}"
3456            else:
3457                if kind == "STRUCT":
3458                    expressions = self.expressions(
3459                        sqls=[
3460                            self.sql(
3461                                exp.Struct(
3462                                    expressions=[
3463                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3464                                        if isinstance(e, exp.Alias)
3465                                        else e
3466                                        for e in expression.expressions
3467                                    ]
3468                                )
3469                            )
3470                        ]
3471                    )
3472                kind = ""
3473
3474        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3475        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3476
3477        exclude = expression.args.get("exclude")
3478
3479        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3480            exclude_sql = self.expressions(sqls=exclude, flat=True)
3481            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3482
3483        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3484        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3485        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3486        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3487        sql = self.query_modifiers(
3488            expression,
3489            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3490            self.sql(expression, "into", comment=False),
3491            self.sql(expression, "from_", comment=False),
3492        )
3493
3494        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3495        if expression.args.get("with_"):
3496            sql = self.maybe_comment(sql, expression)
3497            expression.pop_comments()
3498
3499        sql = self.prepend_ctes(expression, sql)
3500
3501        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3502            expression.set("exclude", None)
3503            subquery = expression.subquery(copy=False)
3504            star = exp.Star(except_=exclude)
3505            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3506
3507        if not self.SUPPORTS_SELECT_INTO and into:
3508            if into.args.get("temporary"):
3509                table_kind = " TEMPORARY"
3510            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3511                table_kind = " UNLOGGED"
3512            else:
3513                table_kind = ""
3514            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3515
3516        return sql
3517
3518    def schema_sql(self, expression: exp.Schema) -> str:
3519        this = self.sql(expression, "this")
3520        sql = self.schema_columns_sql(expression)
3521        return f"{this} {sql}" if this and sql else this or sql
3522
3523    def schema_columns_sql(self, expression: exp.Expr) -> str:
3524        if expression.expressions:
3525            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3526        return ""
3527
3528    def star_sql(self, expression: exp.Star) -> str:
3529        except_ = self.expressions(expression, key="except_", flat=True)
3530        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3531        replace = self.expressions(expression, key="replace", flat=True)
3532        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3533        rename = self.expressions(expression, key="rename", flat=True)
3534        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3535        ilike = self.sql(expression, "ilike")
3536        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3537        return f"*{ilike}{except_}{replace}{rename}"
3538
3539    def parameter_sql(self, expression: exp.Parameter) -> str:
3540        this = self.sql(expression, "this")
3541        return f"{self.PARAMETER_TOKEN}{this}"
3542
3543    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3544        this = self.sql(expression, "this")
3545        kind = expression.text("kind")
3546        if kind:
3547            kind = f"{kind}."
3548        return f"@@{kind}{this}"
3549
3550    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3551        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3552
3553    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3554        alias = self.sql(expression, "alias")
3555        alias = f"{sep}{alias}" if alias else ""
3556        sample = self.sql(expression, "sample")
3557        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3558            alias = f"{sample}{alias}"
3559
3560            # Set to None so it's not generated again by self.query_modifiers()
3561            expression.set("sample", None)
3562
3563        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3564        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3565        return self.prepend_ctes(expression, sql)
3566
3567    def qualify_sql(self, expression: exp.Qualify) -> str:
3568        this = self.indent(self.sql(expression, "this"))
3569        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3570
3571    def unnest_sql(self, expression: exp.Unnest) -> str:
3572        args = self.expressions(expression, flat=True)
3573
3574        alias = expression.args.get("alias")
3575        offset = expression.args.get("offset")
3576
3577        if self.UNNEST_WITH_ORDINALITY:
3578            if alias and isinstance(offset, exp.Expr):
3579                alias.append("columns", offset)
3580                expression.set("offset", None)
3581
3582        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3583            columns = alias.columns
3584            alias = self.sql(columns[0]) if columns else ""
3585        else:
3586            alias = self.sql(alias)
3587
3588        alias = f" AS {alias}" if alias else alias
3589        if self.UNNEST_WITH_ORDINALITY:
3590            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3591        else:
3592            if isinstance(offset, exp.Expr):
3593                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3594            elif offset:
3595                suffix = f"{alias} WITH OFFSET"
3596            else:
3597                suffix = alias
3598
3599        return f"UNNEST({args}){suffix}"
3600
3601    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3602        return ""
3603
3604    def where_sql(self, expression: exp.Where) -> str:
3605        this = self.indent(self.sql(expression, "this"))
3606        return f"{self.seg('WHERE')}{self.sep()}{this}"
3607
3608    def window_sql(self, expression: exp.Window) -> str:
3609        this = self.sql(expression, "this")
3610        partition = self.partition_by_sql(expression)
3611        order = expression.args.get("order")
3612        order = self.order_sql(order, flat=True) if order else ""
3613        spec = self.sql(expression, "spec")
3614        alias = self.sql(expression, "alias")
3615        over = self.sql(expression, "over") or "OVER"
3616
3617        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3618
3619        first = expression.args.get("first")
3620        if first is None:
3621            first = ""
3622        else:
3623            first = "FIRST" if first else "LAST"
3624
3625        if not partition and not order and not spec and alias:
3626            return f"{this} {alias}"
3627
3628        args = self.format_args(
3629            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3630        )
3631        return f"{this} ({args})"
3632
3633    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3634        partition = self.expressions(expression, key="partition_by", flat=True)
3635        return f"PARTITION BY {partition}" if partition else ""
3636
3637    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3638        kind = self.sql(expression, "kind")
3639        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3640        end = (
3641            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3642            or "CURRENT ROW"
3643        )
3644
3645        window_spec = f"{kind} BETWEEN {start} AND {end}"
3646
3647        exclude = self.sql(expression, "exclude")
3648        if exclude:
3649            if self.SUPPORTS_WINDOW_EXCLUDE:
3650                window_spec += f" EXCLUDE {exclude}"
3651            else:
3652                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3653
3654        return window_spec
3655
3656    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3657        this = self.sql(expression, "this")
3658        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3659        return f"{this} WITHIN GROUP ({expression_sql})"
3660
3661    def between_sql(self, expression: exp.Between) -> str:
3662        this = self.sql(expression, "this")
3663        low = self.sql(expression, "low")
3664        high = self.sql(expression, "high")
3665        symmetric = expression.args.get("symmetric")
3666
3667        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3668            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3669
3670        flag = (
3671            " SYMMETRIC"
3672            if symmetric
3673            else " ASYMMETRIC"
3674            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3675            else ""  # silently drop ASYMMETRIC – semantics identical
3676        )
3677        return f"{this} BETWEEN{flag} {low} AND {high}"
3678
3679    def bracket_offset_expressions(
3680        self, expression: exp.Bracket, index_offset: int | None = None
3681    ) -> list[exp.Expr]:
3682        if expression.args.get("json_access"):
3683            return expression.expressions
3684
3685        return apply_index_offset(
3686            expression.this,
3687            expression.expressions,
3688            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3689            dialect=self.dialect,
3690        )
3691
3692    def bracket_sql(self, expression: exp.Bracket) -> str:
3693        expressions = self.bracket_offset_expressions(expression)
3694        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3695        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3696
3697    def all_sql(self, expression: exp.All) -> str:
3698        this = self.sql(expression, "this")
3699        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3700            this = self.wrap(this)
3701        return f"ALL {this}"
3702
3703    def any_sql(self, expression: exp.Any) -> str:
3704        this = self.sql(expression, "this")
3705        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3706            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3707                this = self.wrap(this)
3708            return f"ANY{this}"
3709        return f"ANY {this}"
3710
3711    def exists_sql(self, expression: exp.Exists) -> str:
3712        return f"EXISTS{self.wrap(expression)}"
3713
3714    def case_sql(self, expression: exp.Case) -> str:
3715        this = self.sql(expression, "this")
3716        statements = [f"CASE {this}" if this else "CASE"]
3717
3718        for e in expression.args["ifs"]:
3719            statements.append(f"WHEN {self.sql(e, 'this')}")
3720            statements.append(f"THEN {self.sql(e, 'true')}")
3721
3722        default = self.sql(expression, "default")
3723
3724        if default:
3725            statements.append(f"ELSE {default}")
3726
3727        statements.append("END")
3728
3729        if self.pretty and self.too_wide(statements):
3730            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3731
3732        return " ".join(statements)
3733
3734    def constraint_sql(self, expression: exp.Constraint) -> str:
3735        this = self.sql(expression, "this")
3736        expressions = self.expressions(expression, flat=True)
3737        return f"CONSTRAINT {this} {expressions}"
3738
3739    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3740        order = expression.args.get("order")
3741        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3742        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3743
3744    def extract_sql(self, expression: exp.Extract) -> str:
3745        import sqlglot.dialects.dialect
3746
3747        this = (
3748            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3749            if self.NORMALIZE_EXTRACT_DATE_PARTS
3750            else expression.this
3751        )
3752        if self.EXTRACT_ALLOWS_QUOTES:
3753            this_sql = self.sql(this)
3754        elif isinstance(this, exp.WeekStart):
3755            this_sql = self.weekstart_name(this)
3756        else:
3757            this_sql = this.name
3758        expression_sql = self.sql(expression, "expression")
3759
3760        return f"EXTRACT({this_sql} FROM {expression_sql})"
3761
3762    def trim_sql(self, expression: exp.Trim) -> str:
3763        trim_type = self.sql(expression, "position")
3764
3765        if trim_type == "LEADING":
3766            func_name = "LTRIM"
3767        elif trim_type == "TRAILING":
3768            func_name = "RTRIM"
3769        else:
3770            func_name = "TRIM"
3771
3772        return self.func(func_name, expression.this, expression.expression)
3773
3774    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3775        args = expression.expressions
3776        if isinstance(expression, exp.ConcatWs):
3777            args = args[1:]  # Skip the delimiter
3778
3779        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3780            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3781
3782        concat_coalesce = (
3783            self.dialect.CONCAT_WS_COALESCE
3784            if isinstance(expression, exp.ConcatWs)
3785            else self.dialect.CONCAT_COALESCE
3786        )
3787
3788        if not concat_coalesce and expression.args.get("coalesce"):
3789
3790            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3791                if not e.type:
3792                    import sqlglot.optimizer.annotate_types
3793
3794                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3795
3796                if e.is_string or e.is_type(exp.DType.ARRAY):
3797                    return e
3798
3799                return exp.func("coalesce", e, exp.Literal.string(""))
3800
3801            args = [_wrap_with_coalesce(e) for e in args]
3802
3803        return args
3804
3805    def concat_sql(self, expression: exp.Concat) -> str:
3806        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3807            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3808            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3809            # instead of coalescing them to empty string.
3810            import sqlglot.dialects.dialect
3811
3812            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3813
3814        expressions = self.convert_concat_args(expression)
3815
3816        # Some dialects don't allow a single-argument CONCAT call
3817        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3818            return self.sql(expressions[0])
3819
3820        return self.func("CONCAT", *expressions)
3821
3822    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3823        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3824            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3825            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3826            all_args = expression.expressions
3827            expression.set("coalesce", True)
3828            return self.sql(
3829                exp.case()
3830                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3831                .else_(expression)
3832            )
3833
3834        return self.func(
3835            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3836        )
3837
3838    def check_sql(self, expression: exp.Check) -> str:
3839        this = self.sql(expression, key="this")
3840        return f"CHECK ({this})"
3841
3842    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3843        expressions = self.expressions(expression, flat=True)
3844        expressions = f" ({expressions})" if expressions else ""
3845        reference = self.sql(expression, "reference")
3846        reference = f" {reference}" if reference else ""
3847        delete = self.sql(expression, "delete")
3848        delete = f" ON DELETE {delete}" if delete else ""
3849        update = self.sql(expression, "update")
3850        update = f" ON UPDATE {update}" if update else ""
3851        options = self.expressions(expression, key="options", flat=True, sep=" ")
3852        options = f" {options}" if options else ""
3853        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3854
3855    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3856        this = self.sql(expression, "this")
3857        this = f" {this}" if this else ""
3858        expressions = self.expressions(expression, flat=True)
3859        include = self.sql(expression, "include")
3860        options = self.expressions(expression, key="options", flat=True, sep=" ")
3861        options = f" {options}" if options else ""
3862        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3863
3864    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3865        self.unsupported("TIMESERIES primary key columns are not supported")
3866        return self.sql(expression, "this")
3867
3868    def if_sql(self, expression: exp.If) -> str:
3869        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3870
3871    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3872        if self.MATCH_AGAINST_TABLE_PREFIX:
3873            expressions = []
3874            for expr in expression.expressions:
3875                if isinstance(expr, exp.Table):
3876                    expressions.append(f"TABLE {self.sql(expr)}")
3877                else:
3878                    expressions.append(expr)
3879        else:
3880            expressions = expression.expressions
3881
3882        modifier = expression.args.get("modifier")
3883        modifier = f" {modifier}" if modifier else ""
3884        return (
3885            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3886        )
3887
3888    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3889        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3890
3891    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3892        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3893
3894        if self.QUOTE_JSON_PATH:
3895            path = self.escape_str(path)
3896            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3897
3898        return path
3899
3900    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3901        if isinstance(expression, exp.JSONPathPart):
3902            transform = self.TRANSFORMS.get(expression.__class__)
3903            if not callable(transform):
3904                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3905                return ""
3906
3907            return transform(self, expression)
3908
3909        if isinstance(expression, int):
3910            return str(expression)
3911
3912        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3913            escaped = expression.replace("'", "\\'")
3914            escaped = f"'{escaped}'"
3915        else:
3916            escaped = expression.replace('"', '\\"')
3917            escaped = f'"{escaped}"'
3918
3919        return escaped
3920
3921    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3922        return f"{self.sql(expression, 'this')} FORMAT JSON"
3923
3924    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3925        # Output the Teradata column FORMAT override.
3926        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3927        this = self.sql(expression, "this")
3928        fmt = self.sql(expression, "format")
3929        return f"{this} (FORMAT {fmt})"
3930
3931    def _jsonobject_sql(
3932        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3933    ) -> str:
3934        null_handling = expression.args.get("null_handling")
3935        null_handling = f" {null_handling}" if null_handling else ""
3936
3937        unique_keys = expression.args.get("unique_keys")
3938        if unique_keys is not None:
3939            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3940        else:
3941            unique_keys = ""
3942
3943        return_type = self.sql(expression, "return_type")
3944        return_type = f" RETURNING {return_type}" if return_type else ""
3945        encoding = self.sql(expression, "encoding")
3946        encoding = f" ENCODING {encoding}" if encoding else ""
3947
3948        if not name:
3949            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3950
3951        return self.func(
3952            name,
3953            *expression.expressions,
3954            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3955        )
3956
3957    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3958        null_handling = expression.args.get("null_handling")
3959        null_handling = f" {null_handling}" if null_handling else ""
3960        return_type = self.sql(expression, "return_type")
3961        return_type = f" RETURNING {return_type}" if return_type else ""
3962        strict = " STRICT" if expression.args.get("strict") else ""
3963        return self.func(
3964            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3965        )
3966
3967    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3968        this = self.sql(expression, "this")
3969        order = self.sql(expression, "order")
3970        null_handling = expression.args.get("null_handling")
3971        null_handling = f" {null_handling}" if null_handling else ""
3972        return_type = self.sql(expression, "return_type")
3973        return_type = f" RETURNING {return_type}" if return_type else ""
3974        strict = " STRICT" if expression.args.get("strict") else ""
3975        return self.func(
3976            "JSON_ARRAYAGG",
3977            this,
3978            suffix=f"{order}{null_handling}{return_type}{strict})",
3979        )
3980
3981    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3982        path = self.sql(expression, "path")
3983        path = f" PATH {path}" if path else ""
3984        nested_schema = self.sql(expression, "nested_schema")
3985
3986        if nested_schema:
3987            return f"NESTED{path} {nested_schema}"
3988
3989        this = self.sql(expression, "this")
3990        kind = self.sql(expression, "kind")
3991        kind = f" {kind}" if kind else ""
3992        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3993
3994        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3995        return f"{this}{kind}{format_json}{path}{ordinality}"
3996
3997    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3998        return self.func("COLUMNS", *expression.expressions)
3999
4000    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4001        this = self.sql(expression, "this")
4002        path = self.sql(expression, "path")
4003        path = f", {path}" if path else ""
4004        error_handling = expression.args.get("error_handling")
4005        error_handling = f" {error_handling}" if error_handling else ""
4006        empty_handling = expression.args.get("empty_handling")
4007        empty_handling = f" {empty_handling}" if empty_handling else ""
4008        schema = self.sql(expression, "schema")
4009        return self.func(
4010            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4011        )
4012
4013    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4014        this = self.sql(expression, "this")
4015        kind = self.sql(expression, "kind")
4016        path = self.sql(expression, "path")
4017        path = f" {path}" if path else ""
4018        as_json = " AS JSON" if expression.args.get("as_json") else ""
4019        return f"{this} {kind}{path}{as_json}"
4020
4021    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4022        this = self.sql(expression, "this")
4023        path = self.sql(expression, "path")
4024        path = f", {path}" if path else ""
4025        expressions = self.expressions(expression)
4026        with_ = (
4027            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4028            if expressions
4029            else ""
4030        )
4031        return f"OPENJSON({this}{path}){with_}"
4032
4033    def in_sql(self, expression: exp.In) -> str:
4034        query = expression.args.get("query")
4035        unnest = expression.args.get("unnest")
4036        field = expression.args.get("field")
4037        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4038
4039        if query:
4040            in_sql = self.sql(query)
4041        elif unnest:
4042            in_sql = self.in_unnest_op(unnest)
4043        elif field:
4044            in_sql = self.sql(field)
4045        else:
4046            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4047
4048        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4049
4050    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4051        return f"(SELECT {self.sql(unnest)})"
4052
4053    def interval_sql(self, expression: exp.Interval) -> str:
4054        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4055            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4056            exp.AutoRefreshProperty,
4057        )
4058        interval_keyword = "INTERVAL" if include_keyword else ""
4059        unit_expression = expression.args.get("unit")
4060        unit = self.sql(unit_expression) if unit_expression else ""
4061        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4062            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4063        unit = f" {unit}" if unit else ""
4064
4065        if self.SINGLE_STRING_INTERVAL:
4066            this = expression.this.name if expression.this else ""
4067            if this:
4068                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4069                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4070                    return f"{interval_keyword}'{this}'{unit}"
4071                return f"{interval_keyword}'{this}{unit}'"
4072            return f"{interval_keyword}{unit}"
4073
4074        this = self.sql(expression, "this")
4075        if this:
4076            if not include_keyword and expression.this.is_string:
4077                this = expression.this.name
4078            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4079                this = f"({this})"
4080            if include_keyword:
4081                this = f" {this}"
4082
4083        return f"{interval_keyword}{this}{unit}"
4084
4085    def return_sql(self, expression: exp.Return) -> str:
4086        return f"RETURN {self.sql(expression, 'this')}"
4087
4088    def reference_sql(self, expression: exp.Reference) -> str:
4089        this = self.sql(expression, "this")
4090        expressions = self.expressions(expression, flat=True)
4091        expressions = f"({expressions})" if expressions else ""
4092        options = self.expressions(expression, key="options", flat=True, sep=" ")
4093        options = f" {options}" if options else ""
4094        return f"REFERENCES {this}{expressions}{options}"
4095
4096    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4097        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4098        parent = expression.parent
4099        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4100
4101        return self.func(
4102            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4103        )
4104
4105    def paren_sql(self, expression: exp.Paren) -> str:
4106        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4107        return f"({sql}{self.seg(')', sep='')}"
4108
4109    def neg_sql(self, expression: exp.Neg) -> str:
4110        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4111        this_sql = self.sql(expression, "this")
4112        sep = " " if this_sql[0] == "-" else ""
4113        return f"-{sep}{this_sql}"
4114
4115    def not_sql(self, expression: exp.Not) -> str:
4116        return f"NOT {self.sql(expression, 'this')}"
4117
4118    def alias_sql(self, expression: exp.Alias) -> str:
4119        alias = self.sql(expression, "alias")
4120        alias = f" AS {alias}" if alias else ""
4121        return f"{self.sql(expression, 'this')}{alias}"
4122
4123    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4124        alias = expression.args["alias"]
4125
4126        parent = expression.parent
4127        pivot = parent and parent.parent
4128
4129        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4130            identifier_alias = isinstance(alias, exp.Identifier)
4131            literal_alias = isinstance(alias, exp.Literal)
4132
4133            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4134                alias.replace(exp.Literal.string(alias.output_name))
4135            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4136                alias.replace(exp.to_identifier(alias.output_name))
4137
4138        return self.alias_sql(expression)
4139
4140    def aliases_sql(self, expression: exp.Aliases) -> str:
4141        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4142
4143    def atindex_sql(self, expression: exp.AtIndex) -> str:
4144        this = self.sql(expression, "this")
4145        index = self.sql(expression, "expression")
4146        return f"{this} AT {index}"
4147
4148    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4149        this = self.sql(expression, "this")
4150        zone = self.sql(expression, "zone")
4151        return f"{this} AT TIME ZONE {zone}"
4152
4153    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4154        this = self.sql(expression, "this")
4155        zone = self.sql(expression, "zone")
4156        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4157
4158    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4159        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4160
4161    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4162        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4163
4164    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4165        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4166
4167    def add_sql(self, expression: exp.Add) -> str:
4168        return self.binary(expression, "+")
4169
4170    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4171        return self.connector_sql(expression, "AND", stack)
4172
4173    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4174        return self.connector_sql(expression, "OR", stack)
4175
4176    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4177        return self.connector_sql(expression, "XOR", stack)
4178
4179    def connector_sql(
4180        self,
4181        expression: exp.Connector,
4182        op: str,
4183        stack: list[str | exp.Expr] | None = None,
4184    ) -> str:
4185        if stack is not None:
4186            stack.append(expression.right)
4187            if expression.comments and self.comments:
4188                op = self.maybe_comment(op, comments=expression.comments)
4189
4190            stack.extend((op, expression.left))
4191            return op
4192
4193        stack = [expression]
4194        sqls: list[str] = []
4195        ops = set()
4196
4197        while stack:
4198            node = stack.pop()
4199            if isinstance(node, exp.Connector):
4200                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4201            else:
4202                sql = self.sql(node)
4203                if sqls and sqls[-1] in ops:
4204                    sqls[-1] += f" {sql}"
4205                else:
4206                    sqls.append(sql)
4207
4208        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4209        return sep.join(sqls)
4210
4211    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4212        return self.binary(expression, "&")
4213
4214    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4215        return self.binary(expression, "<<")
4216
4217    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4218        return f"~{self.sql(expression, 'this')}"
4219
4220    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4221        return self.binary(expression, "|")
4222
4223    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4224        return self.binary(expression, ">>")
4225
4226    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4227        return self.binary(expression, "^")
4228
4229    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4230        format_sql = self.sql(expression, "format")
4231        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4232        to_sql = self.sql(expression, "to")
4233        to_sql = f" {to_sql}" if to_sql else ""
4234        action = self.sql(expression, "action")
4235        action = f" {action}" if action else ""
4236        default = self.sql(expression, "default")
4237        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4238        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4239
4240    # Base implementation that excludes safe, zone, and target_type metadata args
4241    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4242        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4243
4244    # Base implementation that excludes the safe and default_year metadata args
4245    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4246        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4247
4248    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4249        return self.func(
4250            "PARSE_DATETIME",
4251            expression.this,
4252            expression.args.get("format"),
4253            expression.args.get("zone"),
4254        )
4255
4256    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4257        zone = self.sql(expression, "this")
4258        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4259
4260    def collate_sql(self, expression: exp.Collate) -> str:
4261        if self.COLLATE_IS_FUNC:
4262            return self.function_fallback_sql(expression)
4263        return self.binary(expression, "COLLATE")
4264
4265    def command_sql(self, expression: exp.Command) -> str:
4266        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4267
4268    def comment_sql(self, expression: exp.Comment) -> str:
4269        this = self.sql(expression, "this")
4270        kind = expression.args["kind"]
4271        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4272        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4273        expression_sql = self.sql(expression, "expression")
4274        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4275
4276    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4277        this = self.sql(expression, "this")
4278        delete = " DELETE" if expression.args.get("delete") else ""
4279        recompress = self.sql(expression, "recompress")
4280        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4281        to_disk = self.sql(expression, "to_disk")
4282        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4283        to_volume = self.sql(expression, "to_volume")
4284        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4285        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4286
4287    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4288        where = self.sql(expression, "where")
4289        group = self.sql(expression, "group")
4290        aggregates = self.expressions(expression, key="aggregates")
4291        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4292
4293        if not (where or group or aggregates) and len(expression.expressions) == 1:
4294            return f"TTL {self.expressions(expression, flat=True)}"
4295
4296        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4297
4298    def transaction_sql(self, expression: exp.Transaction) -> str:
4299        modes = self.expressions(expression, key="modes")
4300        modes = f" {modes}" if modes else ""
4301        return f"BEGIN{modes}"
4302
4303    def commit_sql(self, expression: exp.Commit) -> str:
4304        chain = expression.args.get("chain")
4305        if chain is not None:
4306            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4307
4308        return f"COMMIT{chain or ''}"
4309
4310    def rollback_sql(self, expression: exp.Rollback) -> str:
4311        savepoint = expression.args.get("savepoint")
4312        savepoint = f" TO {savepoint}" if savepoint else ""
4313        return f"ROLLBACK{savepoint}"
4314
4315    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4316        this = self.sql(expression, "this")
4317
4318        exists = ""
4319        if expression.args.get("exists"):
4320            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4321                exists = " IF EXISTS"
4322            else:
4323                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4324
4325        dtype = self.sql(expression, "dtype")
4326        if dtype:
4327            collate = self.sql(expression, "collate")
4328            collate = f" COLLATE {collate}" if collate else ""
4329            using = self.sql(expression, "using")
4330            using = f" USING {using}" if using else ""
4331            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4332            null_constraint = self._alter_column_null_constraint_sql(expression)
4333
4334            return (
4335                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4336                f"{collate}{using}{null_constraint}"
4337            )
4338
4339        default = self.sql(expression, "default")
4340        if default:
4341            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4342
4343        comment = self.sql(expression, "comment")
4344        if comment:
4345            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4346
4347        visible = expression.args.get("visible")
4348        if visible:
4349            return f"ALTER COLUMN{exists} {this} SET {visible}"
4350
4351        allow_null = expression.args.get("allow_null")
4352        drop = expression.args.get("drop")
4353
4354        if not drop and not allow_null:
4355            self.unsupported("Unsupported ALTER COLUMN syntax")
4356
4357        if allow_null is not None:
4358            keyword = "DROP" if drop else "SET"
4359            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4360
4361        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4362
4363    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4364        allow_null = expression.args.get("allow_null")
4365        if allow_null is None:
4366            return ""
4367
4368        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4369            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4370            return ""
4371
4372        return " NULL" if allow_null else " NOT NULL"
4373
4374    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4375        this = self.sql(expression, "this")
4376        rename_from = self.sql(expression, "rename_from")
4377        if rename_from:
4378            if not self.SUPPORTS_CHANGE_COLUMN:
4379                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4380            return f"CHANGE COLUMN {rename_from} {this}"
4381        if not self.SUPPORTS_MODIFY_COLUMN:
4382            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4383        return f"MODIFY COLUMN {this}"
4384
4385    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4386        this = self.sql(expression, "this")
4387
4388        visible = expression.args.get("visible")
4389        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4390
4391        return f"ALTER INDEX {this} {visible_sql}"
4392
4393    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4394        this = self.sql(expression, "this")
4395        if not isinstance(expression.this, exp.Var):
4396            this = f"KEY DISTKEY {this}"
4397        return f"ALTER DISTSTYLE {this}"
4398
4399    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4400        compound = " COMPOUND" if expression.args.get("compound") else ""
4401        this = self.sql(expression, "this")
4402        expressions = self.expressions(expression, flat=True)
4403        expressions = f"({expressions})" if expressions else ""
4404        return f"ALTER{compound} SORTKEY {this or expressions}"
4405
4406    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4407        if not self.RENAME_TABLE_WITH_DB:
4408            # Remove db from tables
4409            expression = expression.transform(
4410                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4411            ).assert_is(exp.AlterRename)
4412        this = self.sql(expression, "this")
4413        to_kw = " TO" if include_to else ""
4414        return f"RENAME{to_kw} {this}"
4415
4416    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4417        exists = " IF EXISTS" if expression.args.get("exists") else ""
4418        old_column = self.sql(expression, "this")
4419        new_column = self.sql(expression, "to")
4420        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4421
4422    def alterset_sql(self, expression: exp.AlterSet) -> str:
4423        exprs = self.expressions(expression, flat=True)
4424        if self.ALTER_SET_WRAPPED:
4425            exprs = f"({exprs})"
4426
4427        return f"SET {exprs}"
4428
4429    def alter_sql(self, expression: exp.Alter) -> str:
4430        actions = expression.actions
4431
4432        if (
4433            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4434            and actions
4435            and isinstance(actions[0], exp.ColumnDef)
4436        ):
4437            actions_sql = self.expressions(expression, key="actions", flat=True)
4438            actions_sql = f"ADD {actions_sql}"
4439        else:
4440            actions_list = []
4441            for action in actions:
4442                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4443                    action_sql = self.add_column_sql(action)
4444                else:
4445                    action_sql = self.sql(action)
4446                    if isinstance(action, exp.Query):
4447                        action_sql = f"AS {action_sql}"
4448
4449                actions_list.append(action_sql)
4450
4451            actions_sql = self.format_args(*actions_list).lstrip("\n")
4452
4453        iceberg = (
4454            "ICEBERG "
4455            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4456            else ""
4457        )
4458        exists = " IF EXISTS" if expression.args.get("exists") else ""
4459        on_cluster = self.sql(expression, "cluster")
4460        on_cluster = f" {on_cluster}" if on_cluster else ""
4461        only = " ONLY" if expression.args.get("only") else ""
4462        options = expression.args.get("options") or []
4463        options_sql = self.expressions(
4464            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4465        )
4466        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4467        partitions_sql = self.expressions(
4468            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4469            sep=" ",
4470            flat=True,
4471        )
4472        if partitions_sql and (actions_sql or options_sql):
4473            partitions_sql = f" {partitions_sql}"
4474        kind = self.sql(expression, "kind")
4475        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4476        check = " WITH CHECK" if expression.args.get("check") else ""
4477        cascade = (
4478            " CASCADE"
4479            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4480            else ""
4481        )
4482        this = self.sql(expression, "this")
4483        this = f" {this}" if this else ""
4484
4485        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4486
4487    def altersession_sql(self, expression: exp.AlterSession) -> str:
4488        items_sql = self.expressions(expression, flat=True)
4489        keyword = "UNSET" if expression.args.get("unset") else "SET"
4490        return f"{keyword} {items_sql}"
4491
4492    def add_column_sql(self, expression: exp.Expr) -> str:
4493        sql = self.sql(expression)
4494        if isinstance(expression, exp.Schema):
4495            column_text = " COLUMNS"
4496        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4497            column_text = " COLUMN"
4498        else:
4499            column_text = ""
4500
4501        return f"ADD{column_text} {sql}"
4502
4503    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4504        expressions = self.expressions(expression)
4505        exists = " IF EXISTS " if expression.args.get("exists") else " "
4506        return f"DROP{exists}{expressions}"
4507
4508    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4509        return "DROP PRIMARY KEY"
4510
4511    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4512        return f"ADD {self.expressions(expression, indent=False)}"
4513
4514    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4515        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4516        location = self.sql(expression, "location")
4517        location = f" {location}" if location else ""
4518        return f"ADD {exists}{self.sql(expression.this)}{location}"
4519
4520    def distinct_sql(self, expression: exp.Distinct) -> str:
4521        this = self.expressions(expression, flat=True)
4522
4523        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4524            case = exp.case()
4525            for arg in expression.expressions:
4526                case = case.when(arg.is_(exp.null()), exp.null())
4527            this = self.sql(case.else_(f"({this})"))
4528
4529        this = f" {this}" if this else ""
4530
4531        on = self.sql(expression, "on")
4532        on = f" ON {on}" if on else ""
4533        return f"DISTINCT{this}{on}"
4534
4535    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4536        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4537
4538    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4539        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4540
4541    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4542        this_sql = self.sql(expression, "this")
4543        expression_sql = self.sql(expression, "expression")
4544        kind = "MAX" if expression.args.get("max") else "MIN"
4545        return f"{this_sql} HAVING {kind} {expression_sql}"
4546
4547    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4548        return self._truncated_div_sql(expression.this, expression.expression)
4549
4550    def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4551        div: exp.Expr = exp.Div(this=this, expression=divisor)
4552        if self.CAST_TO_INT_ROUNDS:
4553            div = exp.Trunc(this=div)
4554
4555        return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4556
4557    def dpipe_sql(self, expression: exp.DPipe) -> str:
4558        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4559            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4560        return self.binary(expression, "||")
4561
4562    def div_sql(self, expression: exp.Div) -> str:
4563        l, r = expression.left, expression.right
4564
4565        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4566            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4567
4568        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4569            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4570                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4571
4572        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4573            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4574                # Native integer division depends on the target's operand types, which may differ
4575                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4576                # integer literals or casts
4577                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4578                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4579                ):
4580                    return self.sql(exp.IntDiv(this=l, expression=r))
4581                return self._truncated_div_sql(l, r)
4582
4583        return self.binary(expression, "/")
4584
4585    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4586        n = exp._wrap(expression.this, exp.Binary)
4587        d = exp._wrap(expression.expression, exp.Binary)
4588        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4589
4590    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4591        return self.binary(expression, "OVERLAPS")
4592
4593    def distance_sql(self, expression: exp.Distance) -> str:
4594        return self.binary(expression, "<->")
4595
4596    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4597        return self.binary(expression, "<<->>")
4598
4599    def dot_sql(self, expression: exp.Dot) -> str:
4600        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4601
4602    def eq_sql(self, expression: exp.EQ) -> str:
4603        return self.binary(expression, "=")
4604
4605    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4606        return self.binary(expression, ":=")
4607
4608    def escape_sql(self, expression: exp.Escape) -> str:
4609        this = expression.this
4610        if (
4611            isinstance(this, (exp.Like, exp.ILike))
4612            and isinstance(this.expression, (exp.All, exp.Any))
4613            and not self.SUPPORTS_LIKE_QUANTIFIERS
4614        ):
4615            return self._like_sql(this, escape=expression)
4616        return self.binary(expression, "ESCAPE")
4617
4618    def glob_sql(self, expression: exp.Glob) -> str:
4619        return self.binary(expression, "GLOB")
4620
4621    def gt_sql(self, expression: exp.GT) -> str:
4622        return self.binary(expression, ">")
4623
4624    def gte_sql(self, expression: exp.GTE) -> str:
4625        return self.binary(expression, ">=")
4626
4627    def is_sql(self, expression: exp.Is) -> str:
4628        negate = expression.args.get("negate")
4629        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4630            positive = bool(expression.expression.this) != bool(negate)
4631            return self.sql(expression.this if positive else exp.not_(expression.this))
4632        return self.binary(expression, "IS NOT" if negate else "IS")
4633
4634    def _like_sql(
4635        self,
4636        expression: exp.Like | exp.ILike,
4637        escape: exp.Escape | None = None,
4638    ) -> str:
4639        this = expression.this
4640        rhs = expression.expression
4641
4642        if isinstance(expression, exp.Like):
4643            exp_class: type[exp.Like | exp.ILike] = exp.Like
4644            op = "LIKE"
4645        else:
4646            exp_class = exp.ILike
4647            op = "ILIKE"
4648
4649        if expression.args.get("negate"):
4650            op = f"NOT {op}"
4651
4652        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4653            exprs = rhs.this.unnest()
4654
4655            if isinstance(exprs, exp.Tuple):
4656                exprs = exprs.expressions
4657            else:
4658                exprs = [exprs]
4659
4660            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4661
4662            def _make_like(expr: exp.Expression) -> exp.Expression:
4663                like: exp.Expression = exp_class(
4664                    this=this, expression=expr, negate=expression.args.get("negate")
4665                )
4666                if escape:
4667                    like = exp.Escape(this=like, expression=escape.expression.copy())
4668                return like
4669
4670            like_expr: exp.Expr = _make_like(exprs[0])
4671            for expr in exprs[1:]:
4672                like_expr = connective(like_expr, _make_like(expr), copy=False)
4673
4674            parent = escape.parent if escape else expression.parent
4675            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4676                parent, exp.Condition
4677            ):
4678                like_expr = exp.paren(like_expr, copy=False)
4679
4680            return self.sql(like_expr)
4681
4682        return self.binary(expression, op)
4683
4684    def like_sql(self, expression: exp.Like) -> str:
4685        return self._like_sql(expression)
4686
4687    def ilike_sql(self, expression: exp.ILike) -> str:
4688        return self._like_sql(expression)
4689
4690    def match_sql(self, expression: exp.Match) -> str:
4691        return self.binary(expression, "MATCH")
4692
4693    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4694        return self.binary(expression, "SIMILAR TO")
4695
4696    def lt_sql(self, expression: exp.LT) -> str:
4697        return self.binary(expression, "<")
4698
4699    def lte_sql(self, expression: exp.LTE) -> str:
4700        return self.binary(expression, "<=")
4701
4702    def mod_sql(self, expression: exp.Mod) -> str:
4703        this = self.sql(expression, "this")
4704        expr = self.sql(expression, "expression")
4705        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4706
4707        parent = expression.parent
4708        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4709            return f"({sql})"
4710
4711        return sql
4712
4713    def mul_sql(self, expression: exp.Mul) -> str:
4714        return self.binary(expression, "*")
4715
4716    def neq_sql(self, expression: exp.NEQ) -> str:
4717        return self.binary(expression, "<>")
4718
4719    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4720        return self.binary(expression, "IS NOT DISTINCT FROM")
4721
4722    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4723        return self.binary(expression, "IS DISTINCT FROM")
4724
4725    def sub_sql(self, expression: exp.Sub) -> str:
4726        return self.binary(expression, "-")
4727
4728    def trycast_sql(self, expression: exp.TryCast) -> str:
4729        return self.cast_sql(expression, safe_prefix="TRY_")
4730
4731    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4732        return self.cast_sql(expression)
4733
4734    def try_sql(self, expression: exp.Try) -> str:
4735        if not self.TRY_SUPPORTED:
4736            self.unsupported("Unsupported TRY function")
4737            return self.sql(expression, "this")
4738
4739        return self.func("TRY", expression.this)
4740
4741    def log_sql(self, expression: exp.Log) -> str:
4742        this = expression.this
4743        expr = expression.expression
4744
4745        if self.dialect.LOG_BASE_FIRST is False:
4746            this, expr = expr, this
4747        elif self.dialect.LOG_BASE_FIRST is None and expr:
4748            if this.name in ("2", "10"):
4749                return self.func(f"LOG{this.name}", expr)
4750
4751            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4752
4753        return self.func("LOG", this, expr)
4754
4755    def use_sql(self, expression: exp.Use) -> str:
4756        kind = self.sql(expression, "kind")
4757        kind = f" {kind}" if kind else ""
4758        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4759        this = f" {this}" if this else ""
4760        return f"USE{kind}{this}"
4761
4762    def binary(self, expression: exp.Binary, op: str) -> str:
4763        sqls: list[str] = []
4764        stack: list[None | str | exp.Expr] = [expression]
4765        binary_type = type(expression)
4766
4767        while stack:
4768            node = stack.pop()
4769
4770            if type(node) is binary_type:
4771                op_func = node.args.get("operator")
4772                if op_func:
4773                    op = f"OPERATOR({self.sql(op_func)})"
4774
4775                stack.append(node.args.get("expression"))
4776                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4777                stack.append(node.args.get("this"))
4778            else:
4779                sqls.append(self.sql(node))
4780
4781        return "".join(sqls)
4782
4783    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4784        to_clause = self.sql(expression, "to")
4785        if to_clause:
4786            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4787
4788        return self.function_fallback_sql(expression)
4789
4790    def function_fallback_sql(self, expression: exp.Func) -> str:
4791        args = []
4792
4793        for key in expression.arg_types:
4794            arg_value = expression.args.get(key)
4795
4796            if isinstance(arg_value, list):
4797                for value in arg_value:
4798                    args.append(value)
4799            elif arg_value is not None:
4800                args.append(arg_value)
4801
4802        return self.func(self.function_name(expression), *args)
4803
4804    def function_name(self, expression: exp.Func) -> str:
4805        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4806        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4807
4808    def func(
4809        self,
4810        name: str,
4811        *args: t.Any,
4812        prefix: str = "(",
4813        suffix: str = ")",
4814        normalize: bool = True,
4815    ) -> str:
4816        name = self.normalize_func(name) if normalize else name
4817        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4818
4819    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4820        arg_sqls = tuple(
4821            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4822        )
4823        if self.pretty and self.too_wide(arg_sqls):
4824            return self.indent(
4825                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4826            )
4827        return sep.join(arg_sqls)
4828
4829    def too_wide(self, args: t.Iterable) -> bool:
4830        return sum(len(arg) for arg in args) > self.max_text_width
4831
4832    def format_time(
4833        self,
4834        expression: exp.Expr,
4835        inverse_time_mapping: dict[str, str] | None = None,
4836        inverse_time_trie: dict | None = None,
4837    ) -> str | None:
4838        return format_time(
4839            self.sql(expression, "format"),
4840            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4841            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4842        )
4843
4844    def expressions(
4845        self,
4846        expression: exp.Expr | None = None,
4847        key: str | None = None,
4848        sqls: t.Collection[str | exp.Expr] | None = None,
4849        flat: bool = False,
4850        indent: bool = True,
4851        skip_first: bool = False,
4852        skip_last: bool = False,
4853        sep: str = ", ",
4854        prefix: str = "",
4855        dynamic: bool = False,
4856        new_line: bool = False,
4857    ) -> str:
4858        expressions = expression.args.get(key or "expressions") if expression else sqls
4859
4860        if not expressions:
4861            return ""
4862
4863        if flat:
4864            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4865
4866        num_sqls = len(expressions)
4867        result_sqls = []
4868
4869        for i, e in enumerate(expressions):
4870            sql = self.sql(e, comment=False)
4871            if not sql:
4872                continue
4873
4874            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4875
4876            if self.pretty:
4877                if self.leading_comma:
4878                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4879                else:
4880                    result_sqls.append(
4881                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4882                    )
4883            else:
4884                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4885
4886        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4887            if new_line:
4888                result_sqls.insert(0, "")
4889                result_sqls.append("")
4890            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4891        else:
4892            result_sql = "".join(result_sqls)
4893
4894        return (
4895            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4896            if indent
4897            else result_sql
4898        )
4899
4900    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4901        flat = flat or isinstance(expression.parent, exp.Properties)
4902        expressions_sql = self.expressions(expression, flat=flat)
4903        if flat:
4904            return f"{op} {expressions_sql}"
4905        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4906
4907    def naked_property(self, expression: exp.Property) -> str:
4908        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4909        if not property_name:
4910            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4911        return f"{property_name} {self.sql(expression, 'this')}"
4912
4913    def tag_sql(self, expression: exp.Tag) -> str:
4914        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4915
4916    def token_sql(self, token_type: TokenType) -> str:
4917        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4918
4919    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4920        this = self.sql(expression, "this")
4921        expressions = self.no_identify(self.expressions, expression)
4922        expressions = (
4923            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4924        )
4925        return f"{this}{expressions}" if expressions.strip() != "" else this
4926
4927    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4928        return self.expressions(expression, flat=True)
4929
4930    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4931        params = self.no_identify(self.expressions, expression, flat=True)
4932        body = self.sql(expression, "this")
4933        prefix = "TABLE " if expression.args.get("is_table") else ""
4934        return f"({params}) AS {prefix}{body}"
4935
4936    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4937        this = self.sql(expression, "this")
4938        expressions = self.expressions(expression, flat=True)
4939        return f"{this}({expressions})"
4940
4941    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4942        return self.binary(expression, "=>")
4943
4944    def when_sql(self, expression: exp.When) -> str:
4945        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4946        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4947        condition = self.sql(expression, "condition")
4948        condition = f" AND {condition}" if condition else ""
4949
4950        then_expression = expression.args.get("then")
4951        if isinstance(then_expression, exp.Insert):
4952            this = self.sql(then_expression, "this")
4953            this = f"INSERT {this}" if this else "INSERT"
4954            then = self.sql(then_expression, "expression")
4955            then = f"{this} VALUES {then}" if then else this
4956        elif isinstance(then_expression, exp.Update):
4957            if isinstance(then_expression.args.get("expressions"), exp.Star):
4958                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4959            else:
4960                expressions_sql = self.expressions(then_expression)
4961                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4962        else:
4963            then = self.sql(then_expression)
4964
4965        if isinstance(then_expression, (exp.Insert, exp.Update)):
4966            where = self.sql(then_expression, "where")
4967            if where and not self.SUPPORTS_MERGE_WHERE:
4968                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4969                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4970                where = ""
4971            then = f"{then}{where}"
4972        return f"WHEN {matched}{source}{condition} THEN {then}"
4973
4974    def whens_sql(self, expression: exp.Whens) -> str:
4975        return self.expressions(expression, sep=" ", indent=False)
4976
4977    def merge_sql(self, expression: exp.Merge) -> str:
4978        table = expression.this
4979        table_alias = ""
4980
4981        hints = table.args.get("hints")
4982        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4983            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4984            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4985
4986        this = self.sql(table)
4987        using = f"USING {self.sql(expression, 'using')}"
4988        whens = self.sql(expression, "whens")
4989
4990        on = self.sql(expression, "on")
4991        on = f"ON {on}" if on else ""
4992
4993        if not on:
4994            on = self.expressions(expression, key="using_cond")
4995            on = f"USING ({on})" if on else ""
4996
4997        returning = self.sql(expression, "returning")
4998        if returning:
4999            whens = f"{whens}{returning}"
5000
5001        sep = self.sep()
5002
5003        return self.prepend_ctes(
5004            expression,
5005            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5006        )
5007
5008    @unsupported_args("format")
5009    def tochar_sql(self, expression: exp.ToChar) -> str:
5010        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
5011
5012    @unsupported_args("default")
5013    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5014        if not self.SUPPORTS_TO_NUMBER:
5015            self.unsupported("Unsupported TO_NUMBER function")
5016            return self._tonumber_cast_sql(expression)
5017
5018        fmt = expression.args.get("format")
5019        if not fmt:
5020            self.unsupported("Conversion format is required for TO_NUMBER")
5021            return self._tonumber_cast_sql(expression)
5022
5023        return self.func("TO_NUMBER", expression.this, fmt)
5024
5025    def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
5026        if expression.args.get("safe"):
5027            return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
5028        return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
5029
5030    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5031        this = self.sql(expression, "this")
5032        kind = self.sql(expression, "kind")
5033        settings_sql = self.expressions(expression, key="settings", sep=" ")
5034        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5035        return f"{this}({kind}{args})"
5036
5037    def dictrange_sql(self, expression: exp.DictRange) -> str:
5038        this = self.sql(expression, "this")
5039        max = self.sql(expression, "max")
5040        min = self.sql(expression, "min")
5041        return f"{this}(MIN {min} MAX {max})"
5042
5043    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5044        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
5045
5046    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5047        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
5048
5049    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
5050    def uniquekeyproperty_sql(
5051        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5052    ) -> str:
5053        return f"{prefix} ({self.expressions(expression, flat=True)})"
5054
5055    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
5056    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5057        expressions = self.expressions(expression, flat=True)
5058        expressions = f" {self.wrap(expressions)}" if expressions else ""
5059        buckets = self.sql(expression, "buckets")
5060        kind = self.sql(expression, "kind")
5061        buckets = f" BUCKETS {buckets}" if buckets else ""
5062        order = self.sql(expression, "order")
5063        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
5064
5065    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5066        return ""
5067
5068    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5069        expressions = self.expressions(expression, key="expressions", flat=True)
5070        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5071        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5072        buckets = self.sql(expression, "buckets")
5073        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5074
5075    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5076        this = self.sql(expression, "this")
5077        having = self.sql(expression, "having")
5078
5079        if having:
5080            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5081
5082        return self.func("ANY_VALUE", this)
5083
5084    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5085        transform = self.func("TRANSFORM", *expression.expressions)
5086        row_format_before = self.sql(expression, "row_format_before")
5087        row_format_before = f" {row_format_before}" if row_format_before else ""
5088        record_writer = self.sql(expression, "record_writer")
5089        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5090        using = f" USING {self.sql(expression, 'command_script')}"
5091        schema = self.sql(expression, "schema")
5092        schema = f" AS {schema}" if schema else ""
5093        row_format_after = self.sql(expression, "row_format_after")
5094        row_format_after = f" {row_format_after}" if row_format_after else ""
5095        record_reader = self.sql(expression, "record_reader")
5096        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5097        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5098
5099    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5100        key_block_size = self.sql(expression, "key_block_size")
5101        if key_block_size:
5102            return f"KEY_BLOCK_SIZE = {key_block_size}"
5103
5104        using = self.sql(expression, "using")
5105        if using:
5106            return f"USING {using}"
5107
5108        parser = self.sql(expression, "parser")
5109        if parser:
5110            return f"WITH PARSER {parser}"
5111
5112        comment = self.sql(expression, "comment")
5113        if comment:
5114            return f"COMMENT {comment}"
5115
5116        visible = expression.args.get("visible")
5117        if visible is not None:
5118            return "VISIBLE" if visible else "INVISIBLE"
5119
5120        engine_attr = self.sql(expression, "engine_attr")
5121        if engine_attr:
5122            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5123
5124        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5125        if secondary_engine_attr:
5126            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5127
5128        self.unsupported("Unsupported index constraint option.")
5129        return ""
5130
5131    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5132        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5133        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5134
5135    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5136        kind = self.sql(expression, "kind")
5137        kind = f"{kind} INDEX" if kind else "INDEX"
5138        this = self.sql(expression, "this")
5139        this = f" {this}" if this else ""
5140        index_type = self.sql(expression, "index_type")
5141        index_type = f" USING {index_type}" if index_type else ""
5142        expressions = self.expressions(expression, flat=True)
5143        expressions = f" ({expressions})" if expressions else ""
5144        options = self.expressions(expression, key="options", sep=" ")
5145        options = f" {options}" if options else ""
5146        return f"{kind}{this}{index_type}{expressions}{options}"
5147
5148    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5149        if self.NVL2_SUPPORTED:
5150            return self.function_fallback_sql(expression)
5151
5152        case = exp.Case().when(
5153            expression.this.is_(exp.null()).not_(copy=False),
5154            expression.args["true"],
5155            copy=False,
5156        )
5157        else_cond = expression.args.get("false")
5158        if else_cond:
5159            case.else_(else_cond, copy=False)
5160
5161        return self.sql(case)
5162
5163    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5164        if expression.args.get("from_first") is False:
5165            self.unsupported("NTH_VALUE FROM LAST is not supported")
5166
5167        return self.function_fallback_sql(expression)
5168
5169    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5170        this = self.sql(expression, "this")
5171        expr = self.sql(expression, "expression")
5172        position = self.sql(expression, "position")
5173        position = f", {position}" if position else ""
5174        iterator = self.sql(expression, "iterator")
5175        condition = self.sql(expression, "condition")
5176        condition = f" IF {condition}" if condition else ""
5177        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5178
5179    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5180        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5181
5182    def opclass_sql(self, expression: exp.Opclass) -> str:
5183        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5184
5185    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5186        model = self.sql(expression, "this")
5187        model = f"MODEL {model}"
5188        expr = expression.expression
5189        if expr:
5190            expr_sql = self.sql(expression, "expression")
5191            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5192        else:
5193            expr_sql = None
5194
5195        parameters = self.sql(expression, "params_struct") or None
5196
5197        return self.func(name, model, expr_sql, parameters)
5198
5199    def predict_sql(self, expression: exp.Predict) -> str:
5200        return self._ml_sql(expression, "PREDICT")
5201
5202    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5203        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5204        return self._ml_sql(expression, name)
5205
5206    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5207        return self._ml_sql(expression, "GENERATE_TEXT")
5208
5209    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5210        return self._ml_sql(expression, "GENERATE_TABLE")
5211
5212    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5213        return self._ml_sql(expression, "GENERATE_BOOL")
5214
5215    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5216        return self._ml_sql(expression, "GENERATE_INT")
5217
5218    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5219        return self._ml_sql(expression, "GENERATE_DOUBLE")
5220
5221    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5222        return self._ml_sql(expression, "TRANSLATE")
5223
5224    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5225        return self._ml_sql(expression, "FORECAST")
5226
5227    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5228        this_sql = self.sql(expression, "this")
5229        if isinstance(expression.this, exp.Table):
5230            this_sql = f"TABLE {this_sql}"
5231
5232        return self.func(
5233            "FORECAST",
5234            this_sql,
5235            expression.args.get("data_col"),
5236            expression.args.get("timestamp_col"),
5237            expression.args.get("model"),
5238            expression.args.get("id_cols"),
5239            expression.args.get("horizon"),
5240            expression.args.get("forecast_end_timestamp"),
5241            expression.args.get("confidence_level"),
5242            expression.args.get("output_historical_time_series"),
5243            expression.args.get("context_window"),
5244        )
5245
5246    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5247        this_sql = self.sql(expression, "this")
5248        if isinstance(expression.this, exp.Table):
5249            this_sql = f"TABLE {this_sql}"
5250
5251        return self.func(
5252            "FEATURES_AT_TIME",
5253            this_sql,
5254            expression.args.get("time"),
5255            expression.args.get("num_rows"),
5256            expression.args.get("ignore_feature_nulls"),
5257        )
5258
5259    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5260        this_sql = self.sql(expression, "this")
5261        if isinstance(expression.this, exp.Table):
5262            this_sql = f"TABLE {this_sql}"
5263
5264        query_table = self.sql(expression, "query_table")
5265        if isinstance(expression.args["query_table"], exp.Table):
5266            query_table = f"TABLE {query_table}"
5267
5268        return self.func(
5269            "VECTOR_SEARCH",
5270            this_sql,
5271            expression.args.get("column_to_search"),
5272            query_table,
5273            expression.args.get("query_column_to_search"),
5274            expression.args.get("top_k"),
5275            expression.args.get("distance_type"),
5276            expression.args.get("options"),
5277        )
5278
5279    def forin_sql(self, expression: exp.ForIn) -> str:
5280        this = self.sql(expression, "this")
5281        expression_sql = self.sql(expression, "expression")
5282        return f"FOR {this} DO {expression_sql}"
5283
5284    def refresh_sql(self, expression: exp.Refresh) -> str:
5285        this = self.sql(expression, "this")
5286        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5287        return f"REFRESH {kind}{this}"
5288
5289    def toarray_sql(self, expression: exp.ToArray) -> str:
5290        arg = expression.this
5291        if not arg.type:
5292            import sqlglot.optimizer.annotate_types
5293
5294            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5295
5296        if arg.is_type(exp.DType.ARRAY):
5297            return self.sql(arg)
5298
5299        cond_for_null = arg.is_(exp.null())
5300        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5301
5302    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5303        this = expression.this
5304        time_format = self.format_time(expression)
5305
5306        if time_format:
5307            return self.sql(
5308                exp.cast(
5309                    exp.StrToTime(this=this, format=expression.args["format"]),
5310                    exp.DType.TIME,
5311                )
5312            )
5313
5314        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5315            return self.sql(this)
5316
5317        return self.sql(exp.cast(this, exp.DType.TIME))
5318
5319    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5320        this = expression.this
5321        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5322            return self.sql(this)
5323
5324        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5325
5326    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5327        this = expression.this
5328        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5329            return self.sql(this)
5330
5331        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5332
5333    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5334        this = expression.this
5335        time_format = self.format_time(expression)
5336        safe = expression.args.get("safe")
5337        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5338            return self.sql(
5339                exp.cast(
5340                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5341                    exp.DType.DATE,
5342                )
5343            )
5344
5345        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5346            return self.sql(this)
5347
5348        if safe:
5349            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5350
5351        return self.sql(exp.cast(this, exp.DType.DATE))
5352
5353    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5354        return self.sql(
5355            exp.func(
5356                "DATEDIFF",
5357                expression.this,
5358                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5359                "day",
5360            )
5361        )
5362
5363    def lastday_sql(self, expression: exp.LastDay) -> str:
5364        if self.LAST_DAY_SUPPORTS_DATE_PART:
5365            return self.function_fallback_sql(expression)
5366
5367        unit = expression.args.get("unit")
5368        if unit and unit.name.upper() != "MONTH":
5369            self.unsupported("Date parts are not supported in LAST_DAY.")
5370
5371        return self.func("LAST_DAY", expression.this)
5372
5373    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5374        import sqlglot.dialects.dialect
5375
5376        return self.func(
5377            "DATE_ADD",
5378            expression.this,
5379            expression.expression,
5380            sqlglot.dialects.dialect.unit_to_str(expression),
5381        )
5382
5383    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5384        this = expression.this
5385        position = expression.args["position"]
5386        offset = index_offset - (expression.args.get("offset") or 0)
5387
5388        if offset:
5389            if position.is_int:
5390                value = position.to_py()
5391                if value >= 0:
5392                    position = exp.Literal.number(value + offset)
5393                elif offset < 0 and value == -1:
5394                    # 1-based -1 appends, which a 0-based position can only express as the size
5395                    position = exp.ArraySize(this=this.copy())
5396                else:
5397                    # Negative positions count from the end, so they shift in the opposite
5398                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5399                    position = exp.Literal.number(value - offset)
5400            else:
5401                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5402
5403        return self.func("ARRAY_INSERT", this, position, expression.expression)
5404
5405    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5406        if self.CAN_IMPLEMENT_ARRAY_ANY:
5407            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5408            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5409            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5410            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5411
5412        import sqlglot.dialects.dialect
5413
5414        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5415        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5416            self.unsupported("ARRAY_ANY is unsupported")
5417
5418        return self.function_fallback_sql(expression)
5419
5420    def struct_sql(self, expression: exp.Struct) -> str:
5421        expression.set(
5422            "expressions",
5423            [
5424                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5425                if isinstance(e, exp.PropertyEQ)
5426                else e
5427                for e in expression.expressions
5428            ],
5429        )
5430
5431        return self.function_fallback_sql(expression)
5432
5433    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5434        low = self.sql(expression, "this")
5435        high = self.sql(expression, "expression")
5436
5437        return f"{low} TO {high}"
5438
5439    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5440        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5441        tables = f" {self.expressions(expression)}"
5442
5443        exists = " IF EXISTS" if expression.args.get("exists") else ""
5444
5445        on_cluster = self.sql(expression, "cluster")
5446        on_cluster = f" {on_cluster}" if on_cluster else ""
5447
5448        identity = self.sql(expression, "identity")
5449        identity = f" {identity} IDENTITY" if identity else ""
5450
5451        option = self.sql(expression, "option")
5452        option = f" {option}" if option else ""
5453
5454        partition = self.sql(expression, "partition")
5455        partition = f" {partition}" if partition else ""
5456
5457        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5458
5459    # This transpiles T-SQL's CONVERT function
5460    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5461    def convert_sql(self, expression: exp.Convert) -> str:
5462        to = expression.this
5463        value = expression.expression
5464        style = expression.args.get("style")
5465        safe = expression.args.get("safe")
5466        strict = expression.args.get("strict")
5467
5468        if not to or not value:
5469            return ""
5470
5471        # Retrieve length of datatype and override to default if not specified
5472        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5473            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5474
5475        transformed: exp.Expr | None = None
5476        cast = exp.Cast if strict else exp.TryCast
5477
5478        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5479        if isinstance(style, exp.Literal) and style.is_int:
5480            import sqlglot.dialects.tsql
5481
5482            style_value = style.name
5483            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5484            if not converted_style:
5485                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5486
5487            fmt = exp.Literal.string(converted_style)
5488
5489            if to.this == exp.DType.DATE:
5490                transformed = exp.StrToDate(this=value, format=fmt)
5491            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5492                transformed = exp.StrToTime(this=value, format=fmt)
5493            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5494                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5495            elif to.this == exp.DType.TEXT:
5496                transformed = exp.TimeToStr(this=value, format=fmt)
5497
5498        if not transformed:
5499            transformed = cast(this=value, to=to, safe=safe)
5500
5501        return self.sql(transformed)
5502
5503    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5504        this = expression.this
5505        if isinstance(this, exp.JSONPathWildcard):
5506            this = self.json_path_part(this)
5507            return f".{this}" if this else ""
5508
5509        quoted = expression.args.get("quoted")
5510        if not (
5511            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5512        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5513            return f".{this}"
5514
5515        this = self.json_path_part(this)
5516
5517        return (
5518            f"[{this}]"
5519            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5520            else f".{this}"
5521        )
5522
5523    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5524        this = self.json_path_part(expression.this)
5525        return f"[{this}]" if this else ""
5526
5527    def _simplify_unless_literal(self, expression: E) -> E:
5528        if not isinstance(expression, exp.Literal):
5529            import sqlglot.optimizer.simplify
5530
5531            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5532
5533        return expression
5534
5535    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5536        this = expression.this
5537        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5538            self.unsupported(
5539                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5540            )
5541            return self.sql(this)
5542
5543        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5544            if self.IGNORE_NULLS_BEFORE_ORDER:
5545                from sqlglot.optimizer.scope import find_all_in_scope
5546
5547                # The first modifier here will be the one closest to the AggFunc's arg
5548                mods = sorted(
5549                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5550                    key=lambda x: (
5551                        0
5552                        if isinstance(x, exp.HavingMax)
5553                        else (1 if isinstance(x, exp.Order) else 2)
5554                    ),
5555                )
5556
5557                if mods:
5558                    mod = mods[0]
5559                    this = expression.__class__(this=mod.this.copy())
5560                    this.meta["inline"] = True
5561                    mod.this.replace(this)
5562                    return self.sql(expression.this)
5563
5564            agg_func = expression.find(exp.AggFunc)
5565
5566            if agg_func:
5567                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5568                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5569
5570        return f"{self.sql(expression, 'this')} {text}"
5571
5572    def _replace_line_breaks(self, string: str) -> str:
5573        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5574        if self.pretty:
5575            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5576        return string
5577
5578    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5579        option = self.sql(expression, "this")
5580
5581        if expression.expressions:
5582            upper = option.upper()
5583
5584            # Snowflake FILE_FORMAT options are separated by whitespace
5585            sep = " " if upper == "FILE_FORMAT" else ", "
5586
5587            # Databricks copy/format options do not set their list of values with EQ
5588            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5589            values = self.expressions(expression, flat=True, sep=sep)
5590            return f"{option}{op}({values})"
5591
5592        value = self.sql(expression, "expression")
5593
5594        if not value:
5595            return option
5596
5597        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5598
5599        return f"{option}{op}{value}"
5600
5601    def credentials_sql(self, expression: exp.Credentials) -> str:
5602        cred_expr = expression.args.get("credentials")
5603        if isinstance(cred_expr, exp.Literal):
5604            # Redshift case: CREDENTIALS <string>
5605            credentials = self.sql(expression, "credentials")
5606            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5607        else:
5608            # Snowflake case: CREDENTIALS = (...)
5609            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5610            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5611
5612        storage = self.sql(expression, "storage")
5613        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5614
5615        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5616        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5617
5618        iam_role = self.sql(expression, "iam_role")
5619        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5620
5621        region = self.sql(expression, "region")
5622        region = f" REGION {region}" if region else ""
5623
5624        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5625
5626    def copy_sql(self, expression: exp.Copy) -> str:
5627        this = self.sql(expression, "this")
5628        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5629
5630        credentials = self.sql(expression, "credentials")
5631        credentials = self.seg(credentials) if credentials else ""
5632        files = self.expressions(expression, key="files", flat=True)
5633        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5634
5635        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5636        params = self.expressions(
5637            expression,
5638            key="params",
5639            sep=sep,
5640            new_line=True,
5641            skip_last=True,
5642            skip_first=True,
5643            indent=self.COPY_PARAMS_ARE_WRAPPED,
5644        )
5645
5646        if params:
5647            if self.COPY_PARAMS_ARE_WRAPPED:
5648                params = f" WITH ({params})"
5649            elif not self.pretty and (files or credentials):
5650                params = f" {params}"
5651
5652        return f"COPY{this}{kind} {files}{credentials}{params}"
5653
5654    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5655        return ""
5656
5657    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5658        on_sql = "ON" if expression.args.get("on") else "OFF"
5659        filter_col: str | None = self.sql(expression, "filter_column")
5660        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5661        retention_period: str | None = self.sql(expression, "retention_period")
5662        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5663
5664        if filter_col or retention_period:
5665            on_sql = self.func("ON", filter_col, retention_period)
5666
5667        return f"DATA_DELETION={on_sql}"
5668
5669    def maskingpolicycolumnconstraint_sql(
5670        self, expression: exp.MaskingPolicyColumnConstraint
5671    ) -> str:
5672        this = self.sql(expression, "this")
5673        expressions = self.expressions(expression, flat=True)
5674        expressions = f" USING ({expressions})" if expressions else ""
5675        return f"MASKING POLICY {this}{expressions}"
5676
5677    def gapfill_sql(self, expression: exp.GapFill) -> str:
5678        this = self.sql(expression, "this")
5679        this = f"TABLE {this}"
5680        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5681
5682    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5683        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5684
5685    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5686        this = self.sql(expression, "this")
5687        expr = expression.expression
5688
5689        if isinstance(expr, exp.Func):
5690            # T-SQL's CLR functions are case sensitive
5691            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5692        else:
5693            expr = self.sql(expression, "expression")
5694
5695        return self.scope_resolution(expr, this)
5696
5697    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5698        if self.PARSE_JSON_NAME is None:
5699            return self.sql(expression.this)
5700
5701        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5702
5703    def rand_sql(self, expression: exp.Rand) -> str:
5704        lower = self.sql(expression, "lower")
5705        upper = self.sql(expression, "upper")
5706
5707        if lower and upper:
5708            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5709        return self.func("RAND", expression.this)
5710
5711    def changes_sql(self, expression: exp.Changes) -> str:
5712        information = self.sql(expression, "information")
5713        information = f"INFORMATION => {information}"
5714        at_before = self.sql(expression, "at_before")
5715        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5716        end = self.sql(expression, "end")
5717        end = f"{self.seg('')}{end}" if end else ""
5718
5719        return f"CHANGES ({information}){at_before}{end}"
5720
5721    def pad_sql(self, expression: exp.Pad) -> str:
5722        prefix = "L" if expression.args.get("is_left") else "R"
5723
5724        fill_pattern = self.sql(expression, "fill_pattern") or None
5725        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5726            fill_pattern = "' '"
5727
5728        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5729
5730    def summarize_sql(self, expression: exp.Summarize) -> str:
5731        table = " TABLE" if expression.args.get("table") else ""
5732        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5733
5734    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5735        generate_series = exp.GenerateSeries(**expression.args)
5736
5737        parent = expression.parent
5738        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5739            parent = parent.parent
5740
5741        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5742            return self.sql(exp.Unnest(expressions=[generate_series]))
5743
5744        if isinstance(parent, exp.Select):
5745            self.unsupported("GenerateSeries projection unnesting is not supported.")
5746
5747        return self.sql(generate_series)
5748
5749    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5750        if self.SUPPORTS_CONVERT_TIMEZONE:
5751            return self.function_fallback_sql(expression)
5752
5753        source_tz = expression.args.get("source_tz")
5754        target_tz = expression.args.get("target_tz")
5755        timestamp = expression.args.get("timestamp")
5756
5757        if source_tz and timestamp:
5758            timestamp = exp.AtTimeZone(
5759                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5760            )
5761
5762        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5763
5764        return self.sql(expr)
5765
5766    def json_sql(self, expression: exp.JSON) -> str:
5767        this = self.sql(expression, "this")
5768        this = f" {this}" if this else ""
5769
5770        _with = expression.args.get("with_")
5771
5772        if _with is None:
5773            with_sql = ""
5774        elif not _with:
5775            with_sql = " WITHOUT"
5776        else:
5777            with_sql = " WITH"
5778
5779        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5780
5781        return f"JSON{this}{with_sql}{unique_sql}"
5782
5783    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5784        path = self.sql(expression, "path")
5785        returning = self.sql(expression, "returning")
5786        returning = f" RETURNING {returning}" if returning else ""
5787
5788        on_condition = self.sql(expression, "on_condition")
5789        on_condition = f" {on_condition}" if on_condition else ""
5790
5791        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5792
5793    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5794        regexp = " REGEXP" if expression.args.get("regexp") else ""
5795        return f"SKIP{regexp} {self.sql(expression.expression)}"
5796
5797    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5798        else_ = "ELSE " if expression.args.get("else_") else ""
5799        condition = self.sql(expression, "expression")
5800        condition = f"WHEN {condition} THEN " if condition else else_
5801        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5802        return f"{condition}{insert}"
5803
5804    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5805        kind = self.sql(expression, "kind")
5806        source = self.sql(expression, "source")
5807
5808        if kind:
5809            expressions = self.seg(self.expressions(expression, sep=" "))
5810            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5811        else:
5812            # If kind isn't present, we have Hive's FROM-first form:
5813            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5814            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5815            sql = f"FROM {source}{self.seg(inserts)}"
5816
5817        return self.prepend_ctes(expression, sql)
5818
5819    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5820        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5821        empty = expression.args.get("empty")
5822        empty = (
5823            f"DEFAULT {empty} ON EMPTY"
5824            if isinstance(empty, exp.Expr)
5825            else self.sql(expression, "empty")
5826        )
5827
5828        error = expression.args.get("error")
5829        error = (
5830            f"DEFAULT {error} ON ERROR"
5831            if isinstance(error, exp.Expr)
5832            else self.sql(expression, "error")
5833        )
5834
5835        if error and empty:
5836            error = (
5837                f"{empty} {error}"
5838                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5839                else f"{error} {empty}"
5840            )
5841            empty = ""
5842
5843        null = self.sql(expression, "null")
5844
5845        return f"{empty}{error}{null}"
5846
5847    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5848        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5849        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5850
5851    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5852        this = self.sql(expression, "this")
5853        path = self.sql(expression, "path")
5854
5855        passing = self.expressions(expression, "passing")
5856        passing = f" PASSING {passing}" if passing else ""
5857
5858        on_condition = self.sql(expression, "on_condition")
5859        on_condition = f" {on_condition}" if on_condition else ""
5860
5861        path = f"{path}{passing}{on_condition}"
5862
5863        return self.func("JSON_EXISTS", this, path)
5864
5865    def _add_arrayagg_null_filter(
5866        self,
5867        array_agg_sql: str,
5868        array_agg_expr: exp.ArrayAgg,
5869        column_expr: exp.Expr,
5870    ) -> str:
5871        """
5872        Add NULL filter to ARRAY_AGG if dialect requires it.
5873
5874        Args:
5875            array_agg_sql: The generated ARRAY_AGG SQL string
5876            array_agg_expr: The ArrayAgg expression node
5877            column_expr: The column/expression to filter (before ORDER BY wrapping)
5878
5879        Returns:
5880            SQL string with FILTER clause added if needed
5881        """
5882        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5883        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5884        if not (
5885            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5886        ):
5887            return array_agg_sql
5888
5889        parent = array_agg_expr.parent
5890        if isinstance(parent, exp.Filter):
5891            parent_cond = parent.expression.this
5892            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5893        elif column_expr.find(exp.Column):
5894            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5895            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5896            this_sql = (
5897                self.expressions(column_expr)
5898                if isinstance(column_expr, exp.Distinct)
5899                else self.sql(column_expr)
5900            )
5901            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5902
5903        return array_agg_sql
5904
5905    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5906        array_agg = self.function_fallback_sql(expression)
5907        column_expr = expression.this
5908        if isinstance(column_expr, exp.Order):
5909            column_expr = column_expr.this
5910
5911        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5912
5913    def slice_sql(self, expression: exp.Slice) -> str:
5914        step = self.sql(expression, "step")
5915        end = self.sql(expression.expression)
5916        begin = self.sql(expression.this)
5917
5918        sql = f"{end}:{step}" if step else end
5919        return f"{begin}:{sql}" if sql else f"{begin}:"
5920
5921    def apply_sql(self, expression: exp.Apply) -> str:
5922        this = self.sql(expression, "this")
5923        expr = self.sql(expression, "expression")
5924
5925        return f"{this} APPLY({expr})"
5926
5927    def _grant_or_revoke_sql(
5928        self,
5929        expression: exp.Grant | exp.Revoke,
5930        keyword: str,
5931        preposition: str,
5932        grant_option_prefix: str = "",
5933        grant_option_suffix: str = "",
5934    ) -> str:
5935        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5936
5937        kind = self.sql(expression, "kind")
5938        kind = f" {kind}" if kind else ""
5939
5940        securable = self.sql(expression, "securable")
5941        securable = f" {securable}" if securable else ""
5942
5943        principals = self.expressions(expression, key="principals", flat=True)
5944
5945        if not expression.args.get("grant_option"):
5946            grant_option_prefix = grant_option_suffix = ""
5947
5948        # cascade for revoke only
5949        cascade = self.sql(expression, "cascade")
5950        cascade = f" {cascade}" if cascade else ""
5951
5952        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5953
5954    def grant_sql(self, expression: exp.Grant) -> str:
5955        return self._grant_or_revoke_sql(
5956            expression,
5957            keyword="GRANT",
5958            preposition="TO",
5959            grant_option_suffix=" WITH GRANT OPTION",
5960        )
5961
5962    def revoke_sql(self, expression: exp.Revoke) -> str:
5963        return self._grant_or_revoke_sql(
5964            expression,
5965            keyword="REVOKE",
5966            preposition="FROM",
5967            grant_option_prefix="GRANT OPTION FOR ",
5968        )
5969
5970    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5971        this = self.sql(expression, "this")
5972        columns = self.expressions(expression, flat=True)
5973        columns = f"({columns})" if columns else ""
5974
5975        return f"{this}{columns}"
5976
5977    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5978        this = self.sql(expression, "this")
5979
5980        kind = self.sql(expression, "kind")
5981        kind = f"{kind} " if kind else ""
5982
5983        return f"{kind}{this}"
5984
5985    def columns_sql(self, expression: exp.Columns) -> str:
5986        func = self.function_fallback_sql(expression)
5987        if expression.args.get("unpack"):
5988            func = f"*{func}"
5989
5990        return func
5991
5992    def overlay_sql(self, expression: exp.Overlay) -> str:
5993        this = self.sql(expression, "this")
5994        expr = self.sql(expression, "expression")
5995        from_sql = self.sql(expression, "from_")
5996        for_sql = self.sql(expression, "for_")
5997        for_sql = f" FOR {for_sql}" if for_sql else ""
5998
5999        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
6000
6001    @unsupported_args("format")
6002    def todouble_sql(self, expression: exp.ToDouble) -> str:
6003        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6004        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
6005
6006    def string_sql(self, expression: exp.String) -> str:
6007        this = expression.this
6008        zone = expression.args.get("zone")
6009
6010        if zone:
6011            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6012            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6013            # set for source_tz to transpile the time conversion before the STRING cast
6014            this = exp.ConvertTimezone(
6015                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6016            )
6017
6018        return self.sql(exp.cast(this, exp.DType.VARCHAR))
6019
6020    def median_sql(self, expression: exp.Median) -> str:
6021        if not self.SUPPORTS_MEDIAN:
6022            return self.sql(
6023                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6024            )
6025
6026        return self.function_fallback_sql(expression)
6027
6028    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6029        filler = self.sql(expression, "this")
6030        filler = f" {filler}" if filler else ""
6031        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6032        return f"TRUNCATE{filler} {with_count}"
6033
6034    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6035        if self.SUPPORTS_UNIX_SECONDS:
6036            return self.function_fallback_sql(expression)
6037
6038        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6039
6040        return self.sql(
6041            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6042        )
6043
6044    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6045        dim = expression.expression
6046
6047        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6048        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6049            if not (dim.is_int and dim.name == "1"):
6050                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6051            dim = None
6052
6053        # If dimension is required but not specified, default initialize it
6054        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6055            dim = exp.Literal.number(1)
6056
6057        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6058
6059    def attach_sql(self, expression: exp.Attach) -> str:
6060        this = self.sql(expression, "this")
6061        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6062        expressions = self.expressions(expression)
6063        expressions = f" ({expressions})" if expressions else ""
6064
6065        return f"ATTACH{exists_sql} {this}{expressions}"
6066
6067    def detach_sql(self, expression: exp.Detach) -> str:
6068        kind = self.sql(expression, "kind")
6069        kind = f" {kind}" if kind else ""
6070        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6071        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6072        exists = " IF EXISTS" if expression.args.get("exists") else ""
6073        if exists:
6074            kind = kind or " DATABASE"
6075
6076        this = self.sql(expression, "this")
6077        this = f" {this}" if this else ""
6078        cluster = self.sql(expression, "cluster")
6079        cluster = f" {cluster}" if cluster else ""
6080        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6081        sync = " SYNC" if expression.args.get("sync") else ""
6082        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
6083
6084    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6085        this = self.sql(expression, "this")
6086        value = self.sql(expression, "expression")
6087        value = f" {value}" if value else ""
6088        return f"{this}{value}"
6089
6090    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6091        return (
6092            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6093        )
6094
6095    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6096        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6097        encode = f"{encode} {self.sql(expression, 'this')}"
6098
6099        properties = expression.args.get("properties")
6100        if properties:
6101            encode = f"{encode} {self.properties(properties)}"
6102
6103        return encode
6104
6105    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6106        this = self.sql(expression, "this")
6107        include = f"INCLUDE {this}"
6108
6109        column_def = self.sql(expression, "column_def")
6110        if column_def:
6111            include = f"{include} {column_def}"
6112
6113        alias = self.sql(expression, "alias")
6114        if alias:
6115            include = f"{include} AS {alias}"
6116
6117        return include
6118
6119    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6120        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6121        name = f"{prefix} {self.sql(expression, 'this')}"
6122        return self.func("XMLELEMENT", name, *expression.expressions)
6123
6124    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6125        this = self.sql(expression, "this")
6126        expr = self.sql(expression, "expression")
6127        expr = f"({expr})" if expr else ""
6128        return f"{this}{expr}"
6129
6130    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6131        partitions = self.expressions(expression, "partition_expressions")
6132        create = self.expressions(expression, "create_expressions")
6133        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6134
6135    def partitionbyrangepropertydynamic_sql(
6136        self, expression: exp.PartitionByRangePropertyDynamic
6137    ) -> str:
6138        start = self.sql(expression, "start")
6139        end = self.sql(expression, "end")
6140
6141        every = expression.args["every"]
6142        if isinstance(every, exp.Interval) and every.this.is_string:
6143            every.this.replace(exp.Literal.number(every.name))
6144
6145        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6146
6147    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6148        name = self.sql(expression, "this")
6149        values = self.expressions(expression, flat=True)
6150
6151        return f"NAME {name} VALUE {values}"
6152
6153    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6154        kind = self.sql(expression, "kind")
6155        sample = self.sql(expression, "sample")
6156        return f"SAMPLE {sample} {kind}"
6157
6158    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6159        kind = self.sql(expression, "kind")
6160        option = self.sql(expression, "option")
6161        option = f" {option}" if option else ""
6162        this = self.sql(expression, "this")
6163        this = f" {this}" if this else ""
6164        columns = self.expressions(expression)
6165        columns = f" {columns}" if columns else ""
6166        return f"{kind}{option} STATISTICS{this}{columns}"
6167
6168    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6169        this = self.sql(expression, "this")
6170        columns = self.expressions(expression)
6171        inner_expression = self.sql(expression, "expression")
6172        inner_expression = f" {inner_expression}" if inner_expression else ""
6173        update_options = self.sql(expression, "update_options")
6174        update_options = f" {update_options} UPDATE" if update_options else ""
6175        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6176
6177    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6178        kind = self.sql(expression, "kind")
6179        kind = f" {kind}" if kind else ""
6180        return f"DELETE{kind} STATISTICS"
6181
6182    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6183        inner_expression = self.sql(expression, "expression")
6184        return f"LIST CHAINED ROWS{inner_expression}"
6185
6186    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6187        kind = self.sql(expression, "kind")
6188        this = self.sql(expression, "this")
6189        this = f" {this}" if this else ""
6190        inner_expression = self.sql(expression, "expression")
6191        return f"VALIDATE {kind}{this}{inner_expression}"
6192
6193    def analyze_sql(self, expression: exp.Analyze) -> str:
6194        options = self.expressions(expression, key="options", sep=" ")
6195        options = f" {options}" if options else ""
6196        kind = self.sql(expression, "kind")
6197        kind = f" {kind}" if kind else ""
6198        tables = self.expressions(expression, key="tables", flat=True)
6199        tables = f" {tables}" if tables else ""
6200        mode = self.sql(expression, "mode")
6201        mode = f" {mode}" if mode else ""
6202        properties = self.sql(expression, "properties")
6203        properties = f" {properties}" if properties else ""
6204        partition = self.sql(expression, "partition")
6205        partition = f" {partition}" if partition else ""
6206        inner_expression = self.sql(expression, "expression")
6207        inner_expression = f" {inner_expression}" if inner_expression else ""
6208        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6209
6210    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6211        this = self.sql(expression, "this")
6212        namespaces = self.expressions(expression, key="namespaces")
6213        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6214        passing = self.expressions(expression, key="passing")
6215        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6216        columns = self.expressions(expression, key="columns")
6217        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6218        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6219        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6220
6221    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6222        this = self.sql(expression, "this")
6223        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6224
6225    def export_sql(self, expression: exp.Export) -> str:
6226        this = self.sql(expression, "this")
6227        connection = self.sql(expression, "connection")
6228        connection = f"WITH CONNECTION {connection} " if connection else ""
6229        options = self.sql(expression, "options")
6230        return f"EXPORT DATA {connection}{options} AS {this}"
6231
6232    def declare_sql(self, expression: exp.Declare) -> str:
6233        replace = "OR REPLACE " if expression.args.get("replace") else ""
6234        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6235
6236    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6237        variables = self.expressions(expression, "this")
6238        default = self.sql(expression, "default")
6239        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6240
6241        kind = self.sql(expression, "kind")
6242        if isinstance(expression.args.get("kind"), exp.Schema):
6243            kind = f"TABLE {kind}"
6244
6245        kind = f" {kind}" if kind else ""
6246
6247        return f"{variables}{kind}{default}"
6248
6249    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6250        kind = self.sql(expression, "kind")
6251        this = self.sql(expression, "this")
6252        set = self.sql(expression, "expression")
6253        to = self.sql(expression, "to")
6254        to = f" TO {to}" if to else ""
6255        default = self.sql(expression, "default")
6256        default = f" DEFAULT {default}" if default else ""
6257        using = self.sql(expression, "using")
6258        using = f" USING {using}" if using else ""
6259
6260        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6261
6262        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6263
6264    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6265        params = self.expressions(expression, key="params", flat=True)
6266        return self.func(expression.name, *expression.expressions) + f"({params})"
6267
6268    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6269        return self.func(expression.name, *expression.expressions)
6270
6271    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6272        return self.anonymousaggfunc_sql(expression)
6273
6274    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6275        return self.parameterizedagg_sql(expression)
6276
6277    def show_sql(self, expression: exp.Show) -> str:
6278        self.unsupported("Unsupported SHOW statement")
6279        return ""
6280
6281    def install_sql(self, expression: exp.Install) -> str:
6282        self.unsupported("Unsupported INSTALL statement")
6283        return ""
6284
6285    def liststage_sql(self, expression: exp.ListStage) -> str:
6286        pattern = self.sql(expression, "pattern")
6287        pattern = f" PATTERN = {pattern}" if pattern else ""
6288        return f"LIST {self.sql(expression, 'this')}{pattern}"
6289
6290    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6291        # Snowflake GET/PUT statements:
6292        #   PUT <file> <internalStage> <properties>
6293        #   GET <internalStage> <file> <properties>
6294        props = expression.args.get("properties")
6295        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6296        this = self.sql(expression, "this")
6297        target = self.sql(expression, "target")
6298
6299        if isinstance(expression, exp.Put):
6300            return f"PUT {this} {target}{props_sql}"
6301        else:
6302            return f"GET {target} {this}{props_sql}"
6303
6304    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6305        this = self.sql(expression, "this")
6306        expr = self.sql(expression, "expression")
6307        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6308        return f"TRANSLATE({this} USING {expr}{with_error})"
6309
6310    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6311        if self.SUPPORTS_DECODE_CASE:
6312            return self.func("DECODE", *expression.expressions)
6313
6314        decode_expr, *expressions = expression.expressions
6315
6316        ifs = []
6317        for search, result in zip(expressions[::2], expressions[1::2]):
6318            if isinstance(search, exp.Literal):
6319                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6320            elif isinstance(search, exp.Null):
6321                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6322            else:
6323                if isinstance(search, exp.Binary):
6324                    search = exp.paren(search)
6325
6326                cond = exp.or_(
6327                    decode_expr.eq(search),
6328                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6329                    copy=False,
6330                )
6331                ifs.append(exp.If(this=cond, true=result))
6332
6333        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6334        return self.sql(case)
6335
6336    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6337        this = self.sql(expression, "this")
6338        this = self.seg(this, sep="")
6339        dimensions = self.expressions(
6340            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6341        )
6342        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6343        metrics = self.expressions(
6344            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6345        )
6346        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6347        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6348        facts = self.seg(f"FACTS {facts}") if facts else ""
6349        where = self.sql(expression, "where")
6350        where = self.seg(f"WHERE {where}") if where else ""
6351        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6352        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6353
6354    def getextract_sql(self, expression: exp.GetExtract) -> str:
6355        this = expression.this
6356        expr = expression.expression
6357
6358        if not this.type or not expression.type:
6359            import sqlglot.optimizer.annotate_types
6360
6361            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6362
6363        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6364            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6365
6366        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6367
6368    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6369        return self.sql(
6370            exp.DateAdd(
6371                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6372                expression=expression.this,
6373                unit=exp.var("DAY"),
6374            )
6375        )
6376
6377    def space_sql(self: Generator, expression: exp.Space) -> str:
6378        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6379
6380    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6381        return f"BUILD {self.sql(expression, 'this')}"
6382
6383    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6384        method = self.sql(expression, "method")
6385        kind = expression.args.get("kind")
6386        if not kind:
6387            return f"REFRESH {method}"
6388
6389        every = self.sql(expression, "every")
6390        unit = self.sql(expression, "unit")
6391        every = f" EVERY {every} {unit}" if every else ""
6392        starts = self.sql(expression, "starts")
6393        starts = f" STARTS {starts}" if starts else ""
6394
6395        return f"REFRESH {method} ON {kind}{every}{starts}"
6396
6397    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6398        self.unsupported("The model!attribute syntax is not supported")
6399        return ""
6400
6401    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6402        return self.func("DIRECTORY", expression.this)
6403
6404    def uuid_sql(self, expression: exp.Uuid) -> str:
6405        is_string = expression.args.get("is_string", False)
6406        uuid_func_sql = self.func("UUID")
6407
6408        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6409            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6410
6411        return uuid_func_sql
6412
6413    def initcap_sql(self, expression: exp.Initcap) -> str:
6414        delimiters = expression.expression
6415
6416        if delimiters:
6417            # do not generate delimiters arg if we are round-tripping from default delimiters
6418            if (
6419                delimiters.is_string
6420                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6421            ):
6422                delimiters = None
6423            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6424                self.unsupported("INITCAP does not support custom delimiters")
6425                delimiters = None
6426
6427        return self.func("INITCAP", expression.this, delimiters)
6428
6429    def localtime_sql(self, expression: exp.Localtime) -> str:
6430        this = expression.this
6431        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6432
6433    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6434        this = expression.this
6435        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6436
6437    def weekstart_name(self, expression: exp.WeekStart) -> str:
6438        import sqlglot.dialects.dialect
6439
6440        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6441        this = expression.this.name.upper()
6442
6443        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6444        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6445
6446        if dow_from_week_start_day != dow_from_week_offset:
6447            self.unsupported(
6448                f"WEEK({this}) is not supported; falling back to the default week start day"
6449            )
6450
6451        return "WEEK"
6452
6453    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6454        name = self.weekstart_name(expression)
6455
6456        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6457        if isinstance(expression.parent, exp.DateTrunc):
6458            return self.sql(exp.Literal.string(name))
6459
6460        return name
6461
6462    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6463        this = self.expressions(expression)
6464        charset = self.sql(expression, "charset")
6465        using = f" USING {charset}" if charset else ""
6466        return self.func(name, this + using)
6467
6468    def weightstring_sql(self, expression: exp.WeightString) -> str:
6469        to = self.sql(expression, "to")
6470        to = f" AS {to}" if to else ""
6471        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6472
6473    def block_sql(self, expression: exp.Block) -> str:
6474        expressions = self.expressions(expression, sep="; ", flat=True)
6475        begin = "BEGIN " if expression.args.get("begin") else ""
6476        return f"{begin}{expressions}" if expressions else ""
6477
6478    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6479        self.unsupported("Unsupported Inline UDFs syntax")
6480        return ""
6481
6482    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6483        self.unsupported("Unsupported Stored Procedure syntax")
6484        return ""
6485
6486    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6487        self.unsupported("Unsupported If block syntax")
6488        return ""
6489
6490    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6491        self.unsupported("Unsupported Case statement syntax")
6492        return ""
6493
6494    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6495        self.unsupported("Unsupported While block syntax")
6496        return ""
6497
6498    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6499        self.unsupported("Unsupported Loop block syntax")
6500        return ""
6501
6502    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6503        self.unsupported("Unsupported Repeat block syntax")
6504        return ""
6505
6506    def leave_sql(self, expression: exp.Leave) -> str:
6507        self.unsupported("Unsupported Leave syntax")
6508        return ""
6509
6510    def iterate_sql(self, expression: exp.Iterate) -> str:
6511        self.unsupported("Unsupported Iterate syntax")
6512        return ""
6513
6514    def execute_sql(self, expression: exp.Execute) -> str:
6515        self.unsupported("Unsupported Execute syntax")
6516        return ""
6517
6518    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6519        self.unsupported("Unsupported Execute syntax")
6520        return ""
6521
6522    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6523        props = self.expressions(expression, sep=" ")
6524        return f"MODIFY {props}"
6525
6526    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6527        kind = expression.args.get("kind")
6528        return f"USING {kind} {self.sql(expression, 'this')}"
6529
6530    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6531        this = self.sql(expression, "this")
6532        to = self.sql(expression, "to")
6533        return f"RENAME INDEX {this} TO {to}"
logger = <Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE = re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
def unsupported_args( *args: str | tuple[str, str]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
35def unsupported_args(
36    *args: str | tuple[str, str],
37) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
38    """
39    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
40    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
41    """
42    diagnostic_by_arg: dict[str, str | None] = {}
43    for arg in args:
44        if isinstance(arg, str):
45            diagnostic_by_arg[arg] = None
46        else:
47            diagnostic_by_arg[arg[0]] = arg[1]
48
49    def decorator(func: GeneratorMethod) -> GeneratorMethod:
50        @wraps(func)
51        def _func(generator: G, expression: E) -> str:
52            expression_name = expression.__class__.__name__
53            dialect_name = generator.dialect.__class__.__name__
54
55            for arg_name, diagnostic in diagnostic_by_arg.items():
56                if expression.args.get(arg_name):
57                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
58                        arg_name, expression_name, dialect_name
59                    )
60                    generator.unsupported(diagnostic)
61
62            return func(generator, expression)
63
64        return _func
65
66    return decorator

Decorator that can be used to mark certain args of an Expr subclass as unsupported. It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).

AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, typing.Any] = {'windows': <function <lambda>>, 'qualify': <function <lambda>>}
class Generator:
 100class Generator:
 101    """
 102    Generator converts a given syntax tree to the corresponding SQL string.
 103
 104    Args:
 105        pretty: Whether to format the produced SQL string.
 106            Default: False.
 107        identify: Determines when an identifier should be quoted. Possible values are:
 108            False (default): Never quote, except in cases where it's mandatory by the dialect.
 109            True: Always quote except for specials cases.
 110            'safe': Only quote identifiers that are case insensitive.
 111        normalize: Whether to normalize identifiers to lowercase.
 112            Default: False.
 113        pad: The pad size in a formatted string. For example, this affects the indentation of
 114            a projection in a query, relative to its nesting level.
 115            Default: 2.
 116        indent: The indentation size in a formatted string. For example, this affects the
 117            indentation of subqueries and filters under a `WHERE` clause.
 118            Default: 2.
 119        normalize_functions: How to normalize function names. Possible values are:
 120            "upper" or True (default): Convert names to uppercase.
 121            "lower": Convert names to lowercase.
 122            False: Disables function name normalization.
 123        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 124            Default ErrorLevel.WARN.
 125        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 126            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 127            Default: 3
 128        leading_comma: Whether the comma is leading or trailing in select expressions.
 129            This is only relevant when generating in pretty mode.
 130            Default: False
 131        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 132            The default is on the smaller end because the length only represents a segment and not the true
 133            line length.
 134            Default: 80
 135        comments: Whether to preserve comments in the output SQL code.
 136            Default: True
 137    """
 138
 139    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 140        **JSON_PATH_PART_TRANSFORMS,
 141        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 142        exp.AllowedValuesProperty: lambda self, e: (
 143            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 144        ),
 145        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 146        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 147        exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
 148        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 149        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 150        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 151        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 152        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 153        exp.BinaryColumnConstraint: lambda *_: "BINARY",
 154        exp.CaseSpecificColumnConstraint: lambda _, e: (
 155            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 156        ),
 157        exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
 158        exp.Ceil: lambda self, e: self.ceil_floor(e),
 159        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 160        exp.CharacterSetProperty: lambda self, e: (
 161            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 162        ),
 163        exp.ClusteredColumnConstraint: lambda self, e: (
 164            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 165        ),
 166        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 167        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 168        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 169        exp.ConvertToCharset: lambda self, e: self.func(
 170            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 171        ),
 172        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 173        exp.CredentialsProperty: lambda self, e: (
 174            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 175        ),
 176        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 177        exp.SessionUser: lambda *_: "SESSION_USER",
 178        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 179        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 180        exp.ApiProperty: lambda *_: "API",
 181        exp.ApplicationProperty: lambda *_: "APPLICATION",
 182        exp.CatalogProperty: lambda *_: "CATALOG",
 183        exp.ComputeProperty: lambda *_: "COMPUTE",
 184        exp.DatabaseProperty: lambda *_: "DATABASE",
 185        exp.DynamicProperty: lambda *_: "DYNAMIC",
 186        exp.EmptyProperty: lambda *_: "EMPTY",
 187        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 188        exp.EndStatement: lambda *_: "END",
 189        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 190        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 191        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 192        exp.EphemeralColumnConstraint: lambda self, e: (
 193            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 194        ),
 195        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 196        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 197        exp.Except: lambda self, e: self.set_operations(e),
 198        exp.ExternalProperty: lambda *_: "EXTERNAL",
 199        exp.Floor: lambda self, e: self.ceil_floor(e),
 200        exp.Get: lambda self, e: self.get_put_sql(e),
 201        exp.GlobalProperty: lambda *_: "GLOBAL",
 202        exp.HeapProperty: lambda *_: "HEAP",
 203        exp.HybridProperty: lambda *_: "HYBRID",
 204        exp.IcebergProperty: lambda *_: "ICEBERG",
 205        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 206        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 207        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 208        exp.Intersect: lambda self, e: self.set_operations(e),
 209        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 210        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 211        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 212        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 213        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 214        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 215        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 216        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 217        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 218        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 219        exp.LocationProperty: lambda self, e: self.naked_property(e),
 220        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 221        exp.MaskingProperty: lambda *_: "MASKING",
 222        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 223        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 224        exp.NetworkProperty: lambda *_: "NETWORK",
 225        exp.NonClusteredColumnConstraint: lambda self, e: (
 226            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 227        ),
 228        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 229        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 230        exp.OnCommitProperty: lambda _, e: (
 231            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 232        ),
 233        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 234        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 235        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 236        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 237        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 238        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 239        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 240        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 241        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 242        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 243        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 244        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 245            f"PROJECTION POLICY {self.sql(e, 'this')}"
 246        ),
 247        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 248        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 249        exp.Put: lambda self, e: self.get_put_sql(e),
 250        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 251            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 252        ),
 253        exp.ReturnsProperty: lambda self, e: (
 254            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 255        ),
 256        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 257        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 258        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 259        exp.SecureProperty: lambda *_: "SECURE",
 260        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 261        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 262        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 263        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 264        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 265        exp.SqlReadWriteProperty: lambda _, e: e.name,
 266        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 267        exp.StabilityProperty: lambda _, e: e.name,
 268        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 269        exp.StreamingTableProperty: lambda *_: "STREAMING",
 270        exp.StrictProperty: lambda *_: "STRICT",
 271        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 272        exp.TableColumn: lambda self, e: self.sql(e.this),
 273        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 274        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 275        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 276        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 277        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 278        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 279        exp.TransientProperty: lambda *_: "TRANSIENT",
 280        exp.VirtualProperty: lambda *_: "VIRTUAL",
 281        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 282        exp.Union: lambda self, e: self.set_operations(e),
 283        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 284        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 285        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 286        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 287        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 288        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 289        exp.UtcTimestamp: lambda self, e: self.sql(
 290            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 291        ),
 292        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 293        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 294        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 295        exp.VolatileProperty: lambda *_: "VOLATILE",
 296        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 297        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 298        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 299        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 300        exp.ForceProperty: lambda *_: "FORCE",
 301    }
 302
 303    # Whether null ordering is supported in order by
 304    # True: Full Support, None: No support, False: No support for certain cases
 305    # such as window specifications, aggregate functions etc
 306    NULL_ORDERING_SUPPORTED: bool | None = True
 307
 308    # Window functions that support NULLS FIRST/LAST
 309    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 310
 311    # Whether ignore nulls is inside the agg or outside.
 312    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 313    IGNORE_NULLS_IN_FUNC = False
 314
 315    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 316    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 317    IGNORE_NULLS_BEFORE_ORDER = True
 318
 319    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 320    LOCKING_READS_SUPPORTED = False
 321
 322    # Whether the EXCEPT and INTERSECT operations can return duplicates
 323    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 324
 325    # Wrap derived values in parens, usually standard but spark doesn't support it
 326    WRAP_DERIVED_VALUES = True
 327
 328    # Whether create function uses an AS before the RETURN
 329    CREATE_FUNCTION_RETURN_AS = True
 330
 331    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 332    MATCHED_BY_SOURCE = True
 333
 334    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 335    SUPPORTS_MERGE_WHERE = False
 336
 337    # Whether the INTERVAL expression works only with values like '1 day'
 338    SINGLE_STRING_INTERVAL = False
 339
 340    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 341    INTERVAL_ALLOWS_PLURAL_FORM = True
 342
 343    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 344    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 345    AUTO_REFRESH_BARE_INTERVALS = False
 346
 347    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 348    LIMIT_FETCH = "ALL"
 349
 350    # Whether limit and fetch allows expresions or just limits
 351    LIMIT_ONLY_LITERALS = False
 352
 353    # Whether a table is allowed to be renamed with a db
 354    RENAME_TABLE_WITH_DB = True
 355
 356    # The separator for grouping sets and rollups
 357    GROUPINGS_SEP = ","
 358
 359    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 360    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 361
 362    # The string used for creating an index on a table
 363    INDEX_ON = "ON"
 364
 365    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 366    INOUT_SEPARATOR = " "
 367
 368    # Whether join hints should be generated
 369    JOIN_HINTS = True
 370
 371    # Whether directed joins are supported
 372    DIRECTED_JOINS = False
 373
 374    # Whether table hints should be generated
 375    TABLE_HINTS = True
 376
 377    # Whether query hints should be generated
 378    QUERY_HINTS = True
 379
 380    # What kind of separator to use for query hints
 381    QUERY_HINT_SEP = ", "
 382
 383    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 384    IS_BOOL_ALLOWED = True
 385
 386    # Whether casting a fractional number to an integer rounds it instead of truncating it
 387    CAST_TO_INT_ROUNDS = False
 388
 389    # Whether the native integer division operator returns NULL for a zero divisor instead of raising
 390    SAFE_INT_DIVISION = False
 391
 392    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 393    DUPLICATE_KEY_UPDATE_WITH_SET = True
 394
 395    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 396    LIMIT_IS_TOP = False
 397
 398    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 399    RETURNING_END = True
 400
 401    # Whether to generate an unquoted value for EXTRACT's date part argument
 402    EXTRACT_ALLOWS_QUOTES = True
 403
 404    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 405    TZ_TO_WITH_TIME_ZONE = False
 406
 407    # Whether the NVL2 function is supported
 408    NVL2_SUPPORTED = True
 409
 410    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 411    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 412
 413    # Whether VALUES statements can be used as derived tables.
 414    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 415    # SELECT * VALUES into SELECT UNION
 416    VALUES_AS_TABLE = True
 417
 418    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 419    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 420
 421    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 422    UNNEST_WITH_ORDINALITY = True
 423
 424    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 425    SEMI_ANTI_JOIN_WITH_SIDE = True
 426
 427    # Whether to include the type of a computed column in the CREATE DDL
 428    COMPUTED_COLUMN_WITH_TYPE = True
 429
 430    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 431    SUPPORTS_TABLE_COPY = True
 432
 433    # Whether parentheses are required around the table sample's expression
 434    TABLESAMPLE_REQUIRES_PARENS = True
 435
 436    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 437    TABLESAMPLE_SIZE_IS_ROWS = True
 438
 439    # The keyword(s) to use when generating a sample clause
 440    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 441
 442    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 443    TABLESAMPLE_WITH_METHOD = True
 444
 445    # The keyword to use when specifying the seed of a sample clause
 446    TABLESAMPLE_SEED_KEYWORD = "SEED"
 447
 448    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 449    HISTORICAL_DATA_POST_ALIAS = False
 450
 451    # Whether COLLATE is a function instead of a binary operator
 452    COLLATE_IS_FUNC = False
 453
 454    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 455    DATA_TYPE_SPECIFIERS_ALLOWED = False
 456
 457    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 458    ENSURE_BOOLS = False
 459
 460    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 461    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 462
 463    # Whether CONCAT requires >1 arguments
 464    SUPPORTS_SINGLE_ARG_CONCAT = True
 465
 466    # Whether LAST_DAY function supports a date part argument
 467    LAST_DAY_SUPPORTS_DATE_PART = True
 468
 469    # Whether named columns are allowed in table aliases
 470    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 471
 472    # Whether named columns are allowed in CTE definitions
 473    SUPPORTS_NAMED_CTE_COLUMNS = True
 474
 475    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 476    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 477
 478    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 479    PIVOT_ALIAS_WITH_AS = True
 480
 481    # What delimiter to use for separating JSON key/value pairs
 482    JSON_KEY_VALUE_PAIR_SEP = ":"
 483
 484    # INSERT OVERWRITE TABLE x override
 485    INSERT_OVERWRITE = " OVERWRITE TABLE"
 486
 487    # Whether the SELECT .. INTO syntax is used instead of CTAS
 488    SUPPORTS_SELECT_INTO = False
 489
 490    # Whether UNLOGGED tables can be created
 491    SUPPORTS_UNLOGGED_TABLES = False
 492
 493    # Whether the CREATE TABLE LIKE statement is supported
 494    SUPPORTS_CREATE_TABLE_LIKE = True
 495
 496    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 497    SUPPORTS_MODIFY_COLUMN = False
 498
 499    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 500    SUPPORTS_CHANGE_COLUMN = False
 501
 502    # Whether ALTER COLUMN can set a column's nullability together with its type
 503    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 504
 505    # Whether ALTER COLUMN IF EXISTS is supported
 506    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 507
 508    # Whether the LikeProperty needs to be specified inside of the schema clause
 509    LIKE_PROPERTY_INSIDE_SCHEMA = False
 510
 511    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 512    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 513    MULTI_ARG_DISTINCT = True
 514
 515    # Whether the JSON extraction operators expect a value of type JSON
 516    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 517
 518    # Whether bracketed keys like ["foo"] are supported in JSON paths
 519    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 520
 521    # Whether to escape keys using single quotes in JSON paths
 522    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 523
 524    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 525    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 526    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 527    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 528    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 529
 530    # The JSONPathPart expressions supported by this dialect
 531    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 532
 533    # Whether any(f(x) for x in array) can be implemented by this dialect
 534    CAN_IMPLEMENT_ARRAY_ANY = False
 535
 536    # Whether the function TO_NUMBER is supported
 537    SUPPORTS_TO_NUMBER = True
 538
 539    # Whether EXCLUDE in window specification is supported
 540    SUPPORTS_WINDOW_EXCLUDE = False
 541
 542    # Whether or not set op modifiers apply to the outer set op or select.
 543    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 544    # True means limit 1 happens after the set op, False means it it happens on y.
 545    SET_OP_MODIFIERS = True
 546
 547    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 548    SET_OP_LIMITS = False
 549
 550    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 551    SET_OP_PARENTHESIZED_OPERANDS = True
 552
 553    # Whether parameters from COPY statement are wrapped in parentheses
 554    COPY_PARAMS_ARE_WRAPPED = True
 555
 556    # Whether values of params are set with "=" token or empty space
 557    COPY_PARAMS_EQ_REQUIRED = False
 558
 559    # Whether COPY statement has INTO keyword
 560    COPY_HAS_INTO_KEYWORD = True
 561
 562    # Whether the conditional TRY(expression) function is supported
 563    TRY_SUPPORTED = True
 564
 565    # Whether the UESCAPE syntax in unicode strings is supported
 566    SUPPORTS_UESCAPE = True
 567
 568    # Function used to replace escaped unicode codes in unicode strings
 569    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 570
 571    # The keyword to use when generating a star projection with excluded columns
 572    STAR_EXCEPT = "EXCEPT"
 573
 574    # The HEX function name
 575    HEX_FUNC = "HEX"
 576
 577    # Function that constructs a binary value from a hex-encoded byte string.
 578    BYTE_STRING_FUNCTION = ""
 579
 580    # The keywords to use when prefixing & separating WITH based properties
 581    WITH_PROPERTIES_PREFIX = "WITH"
 582
 583    # Whether to quote the generated expression of exp.JsonPath
 584    QUOTE_JSON_PATH = True
 585
 586    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 587    PAD_FILL_PATTERN_IS_REQUIRED = False
 588
 589    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 590    SUPPORTS_EXPLODING_PROJECTIONS = True
 591
 592    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 593    ARRAY_CONCAT_IS_VAR_LEN = True
 594
 595    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 596    SUPPORTS_CONVERT_TIMEZONE = False
 597
 598    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 599    SUPPORTS_MEDIAN = True
 600
 601    # Whether UNIX_SECONDS(timestamp) is supported
 602    SUPPORTS_UNIX_SECONDS = False
 603
 604    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 605    ALTER_SET_WRAPPED = False
 606
 607    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 608    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 609    # TODO: The normalization should be done by default once we've tested it across all dialects.
 610    NORMALIZE_EXTRACT_DATE_PARTS = False
 611
 612    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 613    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 614
 615    # The function name of the exp.ArraySize expression
 616    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 617
 618    # The syntax to use when altering the type of a column
 619    ALTER_SET_TYPE = "SET DATA TYPE"
 620
 621    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 622    # None -> Doesn't support it at all
 623    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 624    # True (Postgres) -> Explicitly requires it
 625    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 626
 627    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 628    SUPPORTS_DECODE_CASE = True
 629
 630    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 631    SUPPORTS_BETWEEN_FLAGS = False
 632
 633    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 634    SUPPORTS_LIKE_QUANTIFIERS = True
 635
 636    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 637    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 638
 639    # Whether to include the VARIABLE keyword for SET assignments
 640    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 641
 642    # The keyword to use for default value assignment in DECLARE statements
 643    DECLARE_DEFAULT_ASSIGNMENT = "="
 644
 645    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 646    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 647    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 648    UPDATE_STATEMENT_SUPPORTS_FROM = True
 649
 650    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 651    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 652
 653    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 654    # - Snowflake: DROP ICEBERG TABLE a.b;
 655    # - DuckDB:    DROP TABLE a.b;
 656    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 657
 658    TYPE_MAPPING: t.ClassVar = {
 659        exp.DType.DATETIME2: "TIMESTAMP",
 660        exp.DType.NCHAR: "CHAR",
 661        exp.DType.NVARCHAR: "VARCHAR",
 662        exp.DType.MEDIUMTEXT: "TEXT",
 663        exp.DType.LONGTEXT: "TEXT",
 664        exp.DType.TINYTEXT: "TEXT",
 665        exp.DType.BLOB: "VARBINARY",
 666        exp.DType.MEDIUMBLOB: "BLOB",
 667        exp.DType.LONGBLOB: "BLOB",
 668        exp.DType.TINYBLOB: "BLOB",
 669        exp.DType.INET: "INET",
 670        exp.DType.ROWVERSION: "VARBINARY",
 671        exp.DType.SMALLDATETIME: "TIMESTAMP",
 672    }
 673
 674    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 675
 676    # mapping of DType to its default parameters, bounds
 677    TYPE_PARAM_SETTINGS: t.ClassVar[
 678        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 679    ] = {}
 680
 681    TIME_PART_SINGULARS: t.ClassVar = {
 682        "MICROSECONDS": "MICROSECOND",
 683        "SECONDS": "SECOND",
 684        "MINUTES": "MINUTE",
 685        "HOURS": "HOUR",
 686        "DAYS": "DAY",
 687        "WEEKS": "WEEK",
 688        "MONTHS": "MONTH",
 689        "QUARTERS": "QUARTER",
 690        "YEARS": "YEAR",
 691    }
 692
 693    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 694        "cluster": lambda self, e: self.sql(e, "cluster"),
 695        "distribute": lambda self, e: self.sql(e, "distribute"),
 696        "sort": lambda self, e: self.sql(e, "sort"),
 697        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 698    }
 699
 700    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 701
 702    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 703
 704    PARAMETER_TOKEN = "@"
 705    NAMED_PLACEHOLDER_TOKEN = ":"
 706
 707    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 708
 709    PROPERTIES_LOCATION: t.ClassVar = {
 710        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 711        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 712        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 713        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 714        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 718        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 719        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 720        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 722        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 723        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 724        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 726        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 731        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 732        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 734        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 735        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 736        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 737        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 741        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 747        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 748        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 749        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 750        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 751        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 752        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 753        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 754        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 755        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 758        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 759        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 760        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 761        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 762        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 764        exp.LogProperty: exp.Properties.Location.POST_NAME,
 765        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 766        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 767        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 768        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 770        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 771        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 772        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 773        exp.Order: exp.Properties.Location.POST_SCHEMA,
 774        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 776        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 777        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 778        exp.Property: exp.Properties.Location.POST_WITH,
 779        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 780        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 781        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 782        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 783        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 784        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 785        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 786        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 787        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 788        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 789        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 790        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 791        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 792        exp.Set: exp.Properties.Location.POST_SCHEMA,
 793        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 794        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 795        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 796        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 797        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 798        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 799        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 800        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 801        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 802        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 803        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 804        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 805        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 806        exp.Tags: exp.Properties.Location.POST_WITH,
 807        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 808        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 809        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 810        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 811        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 812        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 813        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 814        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 815        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 816        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 817        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 818        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 819        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 820        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 821        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 822        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 823        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 824    }
 825
 826    # Keywords that can't be used as unquoted identifier names
 827    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 828
 829    # Exprs whose comments are separated from them for better formatting
 830    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 831        exp.Command,
 832        exp.Create,
 833        exp.Describe,
 834        exp.Delete,
 835        exp.Drop,
 836        exp.From,
 837        exp.Insert,
 838        exp.Join,
 839        exp.MultitableInserts,
 840        exp.Order,
 841        exp.Group,
 842        exp.Having,
 843        exp.Select,
 844        exp.SetOperation,
 845        exp.Update,
 846        exp.Where,
 847        exp.With,
 848    )
 849
 850    # Exprs that should not have their comments generated in maybe_comment
 851    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 852        exp.Binary,
 853        exp.SetOperation,
 854    )
 855
 856    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 857    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 858        exp.Column,
 859        exp.Literal,
 860        exp.Neg,
 861        exp.Paren,
 862    )
 863
 864    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 865        exp.DType.NVARCHAR,
 866        exp.DType.VARCHAR,
 867        exp.DType.CHAR,
 868        exp.DType.NCHAR,
 869    }
 870
 871    # Exprs that need to have all CTEs under them bubbled up to them
 872    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 873
 874    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 875
 876    MOD_OPERATOR = "%"
 877
 878    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 879    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 880        exp.Mul,
 881        exp.Div,
 882        exp.IntDiv,
 883        exp.Mod,
 884    )
 885
 886    # ALTER options that follow the others without a comma, e.g. MySQL's
 887    # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
 888    ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 889
 890    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 891
 892    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 893
 894    __slots__ = (
 895        "pretty",
 896        "identify",
 897        "normalize",
 898        "pad",
 899        "_indent",
 900        "normalize_functions",
 901        "unsupported_level",
 902        "max_unsupported",
 903        "leading_comma",
 904        "max_text_width",
 905        "comments",
 906        "dialect",
 907        "unsupported_messages",
 908        "_escaped_quote_end",
 909        "_escaped_byte_quote_end",
 910        "_escaped_identifier_end",
 911        "_identifier_escaped_sequences",
 912        "_next_name",
 913        "_identifier_start",
 914        "_identifier_end",
 915        "_quote_json_path_key_using_brackets",
 916        "_dispatch",
 917    )
 918
 919    def __init__(
 920        self,
 921        pretty: bool | int | None = None,
 922        identify: str | bool = False,
 923        normalize: bool = False,
 924        pad: int = 2,
 925        indent: int = 2,
 926        normalize_functions: str | bool | None = None,
 927        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 928        max_unsupported: int = 3,
 929        leading_comma: bool = False,
 930        max_text_width: int = 80,
 931        comments: bool = True,
 932        dialect: DialectType = None,
 933    ):
 934        import sqlglot
 935        import sqlglot.dialects.dialect
 936
 937        self.pretty = pretty if pretty is not None else sqlglot.pretty
 938        self.identify = identify
 939        self.normalize = normalize
 940        self.pad = pad
 941        self._indent = indent
 942        self.unsupported_level = unsupported_level
 943        self.max_unsupported = max_unsupported
 944        self.leading_comma = leading_comma
 945        self.max_text_width = max_text_width
 946        self.comments = comments
 947        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 948
 949        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 950        self.normalize_functions = (
 951            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 952        )
 953
 954        self.unsupported_messages: list[str] = []
 955        self._escaped_quote_end: str = (
 956            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 957        )
 958        self._escaped_byte_quote_end: str = (
 959            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 960            if self.dialect.BYTE_END
 961            else ""
 962        )
 963        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 964        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
 965
 966        self._next_name = name_sequence("_t")
 967
 968        self._identifier_start = self.dialect.IDENTIFIER_START
 969        self._identifier_end = self.dialect.IDENTIFIER_END
 970
 971        self._quote_json_path_key_using_brackets = True
 972
 973        cls = type(self)
 974        dispatch = _DISPATCH_CACHE.get(cls)
 975        if dispatch is None:
 976            dispatch = _build_dispatch(cls)
 977            _DISPATCH_CACHE[cls] = dispatch
 978        self._dispatch = dispatch
 979
 980    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 981        """
 982        Generates the SQL string corresponding to the given syntax tree.
 983
 984        Args:
 985            expression: The syntax tree.
 986            copy: Whether to copy the expression. The generator performs mutations so
 987                it is safer to copy.
 988
 989        Returns:
 990            The SQL string corresponding to `expression`.
 991        """
 992        if copy:
 993            expression = expression.copy()
 994
 995        expression = self.preprocess(expression)
 996
 997        self.unsupported_messages = []
 998        sql = self.sql(expression).strip()
 999
1000        if self.pretty:
1001            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1002
1003        if self.unsupported_level == ErrorLevel.IGNORE:
1004            return sql
1005
1006        if self.unsupported_level == ErrorLevel.WARN:
1007            for msg in self.unsupported_messages:
1008                logger.warning(msg)
1009        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1010            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1011
1012        return sql
1013
1014    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1015        """Apply generic preprocessing transformations to a given expression."""
1016        expression = self._move_ctes_to_top_level(expression)
1017
1018        if self.ENSURE_BOOLS:
1019            import sqlglot.transforms
1020
1021            expression = sqlglot.transforms.ensure_bools(expression)
1022
1023        return expression
1024
1025    def _move_ctes_to_top_level(self, expression: E) -> E:
1026        if (
1027            not expression.parent
1028            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1029            and any(node.parent is not expression for node in expression.find_all(exp.With))
1030        ):
1031            import sqlglot.transforms
1032
1033            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1034        return expression
1035
1036    def unsupported(self, message: str) -> None:
1037        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1038            raise UnsupportedError(message)
1039        self.unsupported_messages.append(message)
1040
1041    def sep(self, sep: str = " ") -> str:
1042        return f"{sep.strip()}\n" if self.pretty else sep
1043
1044    def seg(self, sql: str, sep: str = " ") -> str:
1045        return f"{self.sep(sep)}{sql}"
1046
1047    def sanitize_comment(self, comment: str) -> str:
1048        comment = " " + comment if comment[0].strip() else comment
1049        comment = comment + " " if comment[-1].strip() else comment
1050
1051        # Escape block comment markers to prevent premature closure or unintended nesting.
1052        # This is necessary because single-line comments (--) are converted to block comments
1053        # (/* */) on output, and any */ in the original text would close the comment early.
1054        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1055
1056        return comment
1057
1058    def maybe_comment(
1059        self,
1060        sql: str,
1061        expression: exp.Expr | None = None,
1062        comments: list[str] | None = None,
1063        separated: bool = False,
1064    ) -> str:
1065        comments = (
1066            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1067            if self.comments
1068            else None
1069        )
1070
1071        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1072            return sql
1073
1074        comments_list = [
1075            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1076            for comment in comments
1077            if comment
1078        ]
1079
1080        if not comments_list:
1081            return sql
1082
1083        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1084            comments_sql = self.sep().join(comments_list)
1085            return (
1086                f"{self.sep()}{comments_sql}{sql}"
1087                if not sql or sql[0].isspace()
1088                else f"{comments_sql}{self.sep()}{sql}"
1089            )
1090
1091        return f"{sql} {' '.join(comments_list)}"
1092
1093    def wrap(self, expression: exp.Expr | str) -> str:
1094        this_sql = (
1095            self.sql(expression)
1096            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1097            else self.sql(expression, "this")
1098        )
1099        if not this_sql:
1100            return "()"
1101
1102        this_sql = self.indent(this_sql, level=1, pad=0)
1103        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1104
1105    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1106        original = self.identify
1107        self.identify = False
1108        result = func(*args, **kwargs)
1109        self.identify = original
1110        return result
1111
1112    def normalize_func(self, name: str) -> str:
1113        if self.normalize_functions == "upper" or self.normalize_functions is True:
1114            return name.upper()
1115        if self.normalize_functions == "lower":
1116            return name.lower()
1117        return name
1118
1119    def indent(
1120        self,
1121        sql: str,
1122        level: int = 0,
1123        pad: int | None = None,
1124        skip_first: bool = False,
1125        skip_last: bool = False,
1126    ) -> str:
1127        if not self.pretty or not sql:
1128            return sql
1129
1130        pad = self.pad if pad is None else pad
1131        lines = sql.split("\n")
1132
1133        return "\n".join(
1134            (
1135                line
1136                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1137                else f"{' ' * (level * self._indent + pad)}{line}"
1138            )
1139            for i, line in enumerate(lines)
1140        )
1141
1142    def sql(
1143        self,
1144        expression: str | exp.Expr | None,
1145        key: str | None = None,
1146        comment: bool = True,
1147    ) -> str:
1148        if not expression:
1149            return ""
1150
1151        if isinstance(expression, str):
1152            return expression
1153
1154        if key:
1155            value = expression.args.get(key)
1156            if value:
1157                return self.sql(value)
1158            return ""
1159
1160        handler = self._dispatch.get(expression.__class__)
1161
1162        if handler:
1163            sql = handler(self, expression)
1164        elif isinstance(expression, exp.Func):
1165            sql = self.function_fallback_sql(expression)
1166        elif isinstance(expression, exp.Property):
1167            sql = self.property_sql(expression)
1168        else:
1169            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1170
1171        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1172
1173    def uncache_sql(self, expression: exp.Uncache) -> str:
1174        table = self.sql(expression, "this")
1175        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1176        return f"UNCACHE TABLE{exists_sql} {table}"
1177
1178    def cache_sql(self, expression: exp.Cache) -> str:
1179        lazy = " LAZY" if expression.args.get("lazy") else ""
1180        table = self.sql(expression, "this")
1181        options = expression.args.get("options")
1182        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1183        sql = self.sql(expression, "expression")
1184        sql = f" AS{self.sep()}{sql}" if sql else ""
1185        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1186        return self.prepend_ctes(expression, sql)
1187
1188    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1189        default = "DEFAULT " if expression.args.get("default") else ""
1190        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1191
1192    def column_parts(self, expression: exp.Column) -> str:
1193        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1194            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1195            return self.sql(expression, "this")
1196
1197        return ".".join(
1198            self.sql(part)
1199            for part in (
1200                expression.args.get("catalog"),
1201                expression.args.get("db"),
1202                expression.args.get("table"),
1203                expression.args.get("this"),
1204            )
1205            if part
1206        )
1207
1208    def column_sql(self, expression: exp.Column) -> str:
1209        join_mark = " (+)" if expression.args.get("join_mark") else ""
1210
1211        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1212            join_mark = ""
1213            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1214
1215        return f"{self.column_parts(expression)}{join_mark}"
1216
1217    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1218        return self.column_sql(expression)
1219
1220    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1221        this = self.sql(expression, "this")
1222        this = f" {this}" if this else ""
1223        position = self.sql(expression, "position")
1224        return f"{position}{this}"
1225
1226    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1227        column = self.sql(expression, "this")
1228        kind = self.sql(expression, "kind")
1229        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1230        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1231        kind = f"{sep}{kind}" if kind else ""
1232        constraints = f" {constraints}" if constraints else ""
1233        position = self.sql(expression, "position")
1234        position = f" {position}" if position else ""
1235
1236        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1237            kind = ""
1238
1239        return f"{exists}{column}{kind}{constraints}{position}"
1240
1241    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1242        this = self.sql(expression, "this")
1243        kind_sql = self.sql(expression, "kind").strip()
1244        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1245
1246    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1247        this = self.sql(expression, "this")
1248        if expression.args.get("not_null"):
1249            persisted = " PERSISTED NOT NULL"
1250        elif expression.args.get("persisted"):
1251            persisted = " PERSISTED"
1252        else:
1253            persisted = ""
1254
1255        return f"AS {this}{persisted}"
1256
1257    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1258        return self.token_sql(TokenType.AUTO_INCREMENT)
1259
1260    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1261        if isinstance(expression.this, list):
1262            this = self.wrap(self.expressions(expression, key="this", flat=True))
1263        else:
1264            this = self.sql(expression, "this")
1265
1266        return f"COMPRESS {this}"
1267
1268    def generatedasidentitycolumnconstraint_sql(
1269        self, expression: exp.GeneratedAsIdentityColumnConstraint
1270    ) -> str:
1271        this = ""
1272        if expression.this is not None:
1273            on_null = " ON NULL" if expression.args.get("on_null") else ""
1274            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1275
1276        start = expression.args.get("start")
1277        start = f"START WITH {start}" if start else ""
1278        increment = expression.args.get("increment")
1279        increment = f" INCREMENT BY {increment}" if increment else ""
1280        minvalue = expression.args.get("minvalue")
1281        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1282        maxvalue = expression.args.get("maxvalue")
1283        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1284        cycle = expression.args.get("cycle")
1285        cycle_sql = ""
1286
1287        if cycle is not None:
1288            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1289            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1290
1291        sequence_opts = ""
1292        if start or increment or cycle_sql:
1293            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1294            sequence_opts = f" ({sequence_opts.strip()})"
1295
1296        expr = self.sql(expression, "expression")
1297        expr = f"({expr})" if expr else "IDENTITY"
1298
1299        return f"GENERATED{this} AS {expr}{sequence_opts}"
1300
1301    def generatedasrowcolumnconstraint_sql(
1302        self, expression: exp.GeneratedAsRowColumnConstraint
1303    ) -> str:
1304        start = "START" if expression.args.get("start") else "END"
1305        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1306        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1307
1308    def periodforsystemtimeconstraint_sql(
1309        self, expression: exp.PeriodForSystemTimeConstraint
1310    ) -> str:
1311        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1312
1313    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1314        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1315
1316    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1317        desc = expression.args.get("desc")
1318        if desc is not None:
1319            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1320        options = self.expressions(expression, key="options", flat=True, sep=" ")
1321        options = f" {options}" if options else ""
1322        return f"PRIMARY KEY{options}"
1323
1324    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1325        this = self.sql(expression, "this")
1326        this = f" {this}" if this else ""
1327        index_type = expression.args.get("index_type")
1328        index_type = f" USING {index_type}" if index_type else ""
1329        on_conflict = self.sql(expression, "on_conflict")
1330        on_conflict = f" {on_conflict}" if on_conflict else ""
1331        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1332        options = self.expressions(expression, key="options", flat=True, sep=" ")
1333        options = f" {options}" if options else ""
1334        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1335
1336    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1337        input_ = expression.args.get("input_")
1338        output = expression.args.get("output")
1339        variadic = expression.args.get("variadic")
1340
1341        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1342        if variadic:
1343            return "VARIADIC"
1344
1345        if input_ and output:
1346            return f"IN{self.INOUT_SEPARATOR}OUT"
1347        if input_:
1348            return "IN"
1349        if output:
1350            return "OUT"
1351
1352        return ""
1353
1354    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1355        return self.sql(expression, "this")
1356
1357    def create_sql(self, expression: exp.Create) -> str:
1358        kind = self.sql(expression, "kind")
1359        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1360
1361        properties = expression.args.get("properties")
1362
1363        if (
1364            kind == "TRIGGER"
1365            and properties
1366            and properties.expressions
1367            and isinstance(properties.expressions[0], exp.TriggerProperties)
1368            and properties.expressions[0].args.get("constraint")
1369        ):
1370            kind = f"CONSTRAINT {kind}"
1371
1372        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1373
1374        this = self.createable_sql(expression, properties_locs)
1375
1376        properties_sql = ""
1377        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1378            exp.Properties.Location.POST_WITH
1379        ):
1380            props_ast = exp.Properties(
1381                expressions=[
1382                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1383                    *properties_locs[exp.Properties.Location.POST_WITH],
1384                ]
1385            )
1386            props_ast.parent = expression
1387            properties_sql = self.sql(props_ast)
1388
1389            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1390                properties_sql = self.sep() + properties_sql
1391            elif not self.pretty:
1392                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1393                properties_sql = f" {properties_sql}"
1394
1395        begin = " BEGIN" if expression.args.get("begin") else ""
1396
1397        expression_sql = self.sql(expression, "expression")
1398        if expression_sql:
1399            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1400
1401            if not isinstance(expression.expression, exp.MacroOverloads) and (
1402                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1403            ):
1404                postalias_props_sql = ""
1405                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1406                    postalias_props_sql = self.properties(
1407                        exp.Properties(
1408                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1409                        ),
1410                        wrapped=False,
1411                    )
1412                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1413                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1414
1415        postindex_props_sql = ""
1416        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1417            postindex_props_sql = self.properties(
1418                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1419                wrapped=False,
1420                prefix=" ",
1421            )
1422
1423        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1424        indexes = f" {indexes}" if indexes else ""
1425        index_sql = indexes + postindex_props_sql
1426
1427        replace = " OR REPLACE" if expression.args.get("replace") else ""
1428        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1429        unique = " UNIQUE" if expression.args.get("unique") else ""
1430
1431        clustered = expression.args.get("clustered")
1432        if clustered is None:
1433            clustered_sql = ""
1434        elif clustered:
1435            clustered_sql = " CLUSTERED COLUMNSTORE"
1436        else:
1437            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1438
1439        postcreate_props_sql = ""
1440        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1441            postcreate_props_sql = self.properties(
1442                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1443                sep=" ",
1444                prefix=" ",
1445                wrapped=False,
1446            )
1447
1448        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1449
1450        postexpression_props_sql = ""
1451        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1452            postexpression_props_sql = self.properties(
1453                exp.Properties(
1454                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1455                ),
1456                sep=" ",
1457                prefix=" ",
1458                wrapped=False,
1459            )
1460
1461        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1462        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1463        no_schema_binding = (
1464            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1465        )
1466
1467        clone = self.sql(expression, "clone")
1468        clone = f" {clone}" if clone else ""
1469
1470        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1471            properties_expression = f"{expression_sql}{properties_sql}"
1472        else:
1473            properties_expression = f"{properties_sql}{expression_sql}"
1474
1475        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1476        return self.prepend_ctes(expression, expression_sql)
1477
1478    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1479        start = self.sql(expression, "start")
1480        start = f"START WITH {start}" if start else ""
1481        increment = self.sql(expression, "increment")
1482        increment = f" INCREMENT BY {increment}" if increment else ""
1483        minvalue = self.sql(expression, "minvalue")
1484        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1485        maxvalue = self.sql(expression, "maxvalue")
1486        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1487        owned = self.sql(expression, "owned")
1488        owned = f" OWNED BY {owned}" if owned else ""
1489
1490        cache = expression.args.get("cache")
1491        if cache is None:
1492            cache_str = ""
1493        elif cache is True:
1494            cache_str = " CACHE"
1495        else:
1496            cache_str = f" CACHE {cache}"
1497
1498        options = self.expressions(expression, key="options", flat=True, sep=" ")
1499        options = f" {options}" if options else ""
1500
1501        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1502
1503    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1504        timing = expression.args.get("timing", "")
1505        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1506        timing_events = f"{timing} {events}".strip() if timing or events else ""
1507
1508        parts = [timing_events, "ON", self.sql(expression, "table")]
1509
1510        if referenced_table := expression.args.get("referenced_table"):
1511            parts.extend(["FROM", self.sql(referenced_table)])
1512
1513        if deferrable := expression.args.get("deferrable"):
1514            parts.append(deferrable)
1515
1516        if initially := expression.args.get("initially"):
1517            parts.append(f"INITIALLY {initially}")
1518
1519        if referencing := expression.args.get("referencing"):
1520            parts.append(self.sql(referencing))
1521
1522        if for_each := expression.args.get("for_each"):
1523            parts.append(f"FOR EACH {for_each}")
1524
1525        if when := expression.args.get("when"):
1526            parts.append(f"WHEN ({self.sql(when)})")
1527
1528        parts.append(self.sql(expression, "execute"))
1529
1530        return self.sep().join(parts)
1531
1532    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1533        parts = []
1534
1535        if old_alias := expression.args.get("old"):
1536            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1537
1538        if new_alias := expression.args.get("new"):
1539            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1540
1541        return f"REFERENCING {' '.join(parts)}"
1542
1543    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1544        columns = expression.args.get("columns")
1545        if columns:
1546            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1547
1548        return self.sql(expression, "this")
1549
1550    def clone_sql(self, expression: exp.Clone) -> str:
1551        this = self.sql(expression, "this")
1552        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1553        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1554        return f"{shallow}{keyword} {this}"
1555
1556    def describe_sql(self, expression: exp.Describe) -> str:
1557        style = expression.args.get("style")
1558        style = f" {style}" if style else ""
1559        partition = self.sql(expression, "partition")
1560        partition = f" {partition}" if partition else ""
1561        format = self.sql(expression, "format")
1562        format = f" {format}" if format else ""
1563        as_json = " AS JSON" if expression.args.get("as_json") else ""
1564
1565        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1566
1567    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1568        tag = self.sql(expression, "tag")
1569        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1570
1571    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1572        with_ = self.sql(expression, "with_")
1573        if with_:
1574            sql = f"{with_}{self.sep()}{sql}"
1575        return sql
1576
1577    def with_sql(self, expression: exp.With) -> str:
1578        udfs = self.expressions(expression, key="udfs", flat=True)
1579        udfs = f"WITH {udfs}" if udfs else ""
1580
1581        sql = self.expressions(expression, flat=True)
1582
1583        recursive = (
1584            "RECURSIVE "
1585            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1586            else ""
1587        )
1588        search = self.sql(expression, "search")
1589        search = f" {search}" if search else ""
1590        cycle = self.sql(expression, "cycle")
1591        cycle = f" {cycle}" if cycle else ""
1592
1593        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1594        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1595
1596    def cte_sql(self, expression: exp.CTE) -> str:
1597        alias = expression.args.get("alias")
1598        if alias:
1599            alias.add_comments(expression.pop_comments())
1600
1601        alias_sql = self.sql(expression, "alias")
1602
1603        materialized = expression.args.get("materialized")
1604        if materialized is False:
1605            materialized = "NOT MATERIALIZED "
1606        elif materialized:
1607            materialized = "MATERIALIZED "
1608
1609        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1610        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1611
1612        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1613
1614    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1615        alias = self.sql(expression, "this")
1616        columns = self.expressions(expression, key="columns", flat=True)
1617        columns = f"({columns})" if columns else ""
1618
1619        if (
1620            columns
1621            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1622            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1623        ):
1624            columns = ""
1625            self.unsupported("Named columns are not supported in table alias.")
1626
1627        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1628            alias = self._next_name()
1629
1630        return f"{alias}{columns}"
1631
1632    def bitstring_sql(self, expression: exp.BitString) -> str:
1633        this = self.sql(expression, "this")
1634        if self.dialect.BIT_START:
1635            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1636        return f"{int(this, 2)}"
1637
1638    def hexstring_sql(
1639        self, expression: exp.HexString, binary_function_repr: str | None = None
1640    ) -> str:
1641        this = self.sql(expression, "this")
1642        is_integer_type = expression.args.get("is_integer")
1643
1644        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1645            not self.dialect.HEX_START and not binary_function_repr
1646        ):
1647            # Integer representation will be returned if:
1648            # - The read dialect treats the hex value as integer literal but not the write
1649            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1650            return f"{int(this, 16)}"
1651
1652        if not is_integer_type:
1653            # Read dialect treats the hex value as BINARY/BLOB
1654            if binary_function_repr:
1655                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1656                return self.func(binary_function_repr, exp.Literal.string(this))
1657            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1658                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1659                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1660
1661        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1662
1663    def bytestring_sql(self, expression: exp.ByteString) -> str:
1664        this = self.sql(expression, "this")
1665        is_bytes = bool(expression.args.get("is_bytes"))
1666        if is_bytes and self.BYTE_STRING_FUNCTION:
1667            # Hex digits can be quoted directly without escaping.
1668            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1669
1670        if self.dialect.BYTE_START:
1671            escaped_byte_string = self.escape_str(
1672                this,
1673                escape_backslash=bool(
1674                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1675                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1676                ),
1677                delimiter=self.dialect.BYTE_END,
1678                escaped_delimiter=self._escaped_byte_quote_end,
1679                is_byte_string=True,
1680                is_bytes=is_bytes,
1681            )
1682            delimited_byte_string = (
1683                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1684            )
1685            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1686                return self.sql(
1687                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1688                )
1689            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1690                return self.sql(
1691                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1692                )
1693
1694            return delimited_byte_string
1695
1696        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1697            return self.sql(exp.Literal.string(this))
1698
1699        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1700        return ""
1701
1702    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1703        this = self.sql(expression, "this")
1704        escape = expression.args.get("escape")
1705        unicode_start = self.dialect.UNICODE_START
1706
1707        if unicode_start:
1708            escape_substitute = r"\\\1"
1709            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1710        else:
1711            escape_substitute = r"\\u\1"
1712            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1713
1714        if escape:
1715            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1716            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1717        else:
1718            escape_pattern = ESCAPED_UNICODE_RE
1719            escape_sql = ""
1720
1721        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1722            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1723
1724        if unicode_start:
1725            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1726            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1727            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1728        else:
1729            this = self.escape_str(this, escape_backslash=False)
1730
1731        return f"{left_quote}{this}{right_quote}{escape_sql}"
1732
1733    def rawstring_sql(self, expression: exp.RawString) -> str:
1734        string = expression.this
1735        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1736            string = string.replace("\\", "\\\\")
1737
1738        string = self.escape_str(string, escape_backslash=False)
1739        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1740
1741    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1742        this = self.sql(expression, "this")
1743        specifier = self.sql(expression, "expression")
1744        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1745        return f"{this}{specifier}"
1746
1747    def datatype_param_bound_limiter(
1748        self,
1749        expression: exp.DataType,
1750        type_value: exp.DType,
1751        defaults: tuple[int, ...],
1752        bounds: tuple[int | None, ...],
1753    ) -> exp.DataType:
1754        params = expression.expressions
1755
1756        if not params:
1757            if defaults:
1758                expression.set(
1759                    "expressions",
1760                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1761                )
1762            return expression
1763
1764        if not bounds:
1765            return expression
1766
1767        for i, param in enumerate(params):
1768            bound = bounds[i] if i < len(bounds) else None
1769            if bound is None:
1770                continue
1771
1772            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1773            value = (
1774                param_value.to_py()
1775                if isinstance(param_value, exp.Literal) and param_value.is_number
1776                else None
1777            )
1778            if isinstance(value, (int, Decimal)) and value > bound:
1779                self.unsupported(
1780                    f"{type_value.value} parameter {param_value.name} exceeds "
1781                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1782                )
1783                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1784
1785        return expression
1786
1787    def datatype_sql(self, expression: exp.DataType) -> str:
1788        nested = ""
1789        values = ""
1790
1791        expr_nested = expression.args.get("nested")
1792        type_value = expression.this
1793
1794        if (
1795            not expr_nested
1796            and isinstance(type_value, exp.DType)
1797            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1798        ):
1799            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1800
1801        interior = (
1802            self.expressions(
1803                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1804            )
1805            if expr_nested and self.pretty
1806            else self.expressions(expression, flat=True)
1807        )
1808
1809        if type_value in self.UNSUPPORTED_TYPES:
1810            self.unsupported(
1811                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1812            )
1813
1814        type_sql: t.Any = ""
1815        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1816            type_sql = self.sql(expression, "kind")
1817        elif type_value == exp.DType.CHARACTER_SET:
1818            char = f"CHAR({interior})" if interior else "CHAR"
1819            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1820        else:
1821            type_sql = (
1822                self.TYPE_MAPPING.get(type_value, type_value.value)
1823                if isinstance(type_value, exp.DType)
1824                else type_value
1825            )
1826
1827        if interior:
1828            if expr_nested:
1829                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1830                if expression.args.get("values") is not None:
1831                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1832                    values = self.expressions(expression, key="values", flat=True)
1833                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1834            elif type_value == exp.DType.INTERVAL:
1835                nested = f" {interior}"
1836            else:
1837                nested = f"({interior})"
1838
1839        type_sql = f"{type_sql}{nested}{values}"
1840        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1841            exp.DType.TIMETZ,
1842            exp.DType.TIMESTAMPTZ,
1843        ):
1844            type_sql = f"{type_sql} WITH TIME ZONE"
1845
1846        collate = self.sql(expression, "collate")
1847        if collate:
1848            type_sql = f"{type_sql} COLLATE {collate}"
1849
1850        return type_sql
1851
1852    def directory_sql(self, expression: exp.Directory) -> str:
1853        local = "LOCAL " if expression.args.get("local") else ""
1854        row_format = self.sql(expression, "row_format")
1855        row_format = f" {row_format}" if row_format else ""
1856        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1857
1858    def delete_sql(self, expression: exp.Delete) -> str:
1859        hint = self.sql(expression, "hint")
1860        this = self.sql(expression, "this")
1861        this = f" FROM {this}" if this else ""
1862        using = self.expressions(expression, key="using")
1863        using = f" USING {using}" if using else ""
1864        cluster = self.sql(expression, "cluster")
1865        cluster = f" {cluster}" if cluster else ""
1866        where = self.sql(expression, "where")
1867        returning = self.sql(expression, "returning")
1868        order = self.sql(expression, "order")
1869        limit = self.sql(expression, "limit")
1870        tables = self.expressions(expression, key="tables")
1871        tables = f" {tables}" if tables else ""
1872        if self.RETURNING_END:
1873            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1874        else:
1875            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1876        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1877
1878    def drop_sql(self, expression: exp.Drop) -> str:
1879        tables = self.expressions(expression, key="tables", flat=True)
1880        expressions = self.expressions(expression, flat=True)
1881        expressions = f" ({expressions})" if expressions else ""
1882        kind = expression.args["kind"]
1883        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1884        iceberg = (
1885            " ICEBERG"
1886            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1887            else ""
1888        )
1889        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1890        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1891        on_cluster = self.sql(expression, "cluster")
1892        on_cluster = f" {on_cluster}" if on_cluster else ""
1893        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1894        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1895        cascade = " CASCADE" if expression.args.get("cascade") else ""
1896        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1897        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1898        purge = " PURGE" if expression.args.get("purge") else ""
1899        sync = " SYNC" if expression.args.get("sync") else ""
1900        force = " FORCE" if expression.args.get("force") else ""
1901        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1902
1903    def set_operation(self, expression: exp.SetOperation) -> str:
1904        op_type = type(expression)
1905        op_name = op_type.key.upper()
1906
1907        distinct = expression.args.get("distinct")
1908        if (
1909            distinct is False
1910            and op_type in (exp.Except, exp.Intersect)
1911            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1912        ):
1913            self.unsupported(f"{op_name} ALL is not supported")
1914
1915        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1916
1917        if distinct is None:
1918            distinct = default_distinct
1919            if distinct is None:
1920                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1921
1922        if distinct is default_distinct:
1923            distinct_or_all = ""
1924        else:
1925            distinct_or_all = " DISTINCT" if distinct else " ALL"
1926
1927        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1928        side_kind = f"{side_kind} " if side_kind else ""
1929
1930        by_name = " BY NAME" if expression.args.get("by_name") else ""
1931        on = self.expressions(expression, key="on", flat=True)
1932        on = f" ON ({on})" if on else ""
1933
1934        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1935
1936    def set_operations(self, expression: exp.SetOperation) -> str:
1937        if not self.SET_OP_MODIFIERS:
1938            limit = expression.args.get("limit")
1939            order = expression.args.get("order")
1940            offset = expression.args.get("offset")
1941
1942            if limit or order or offset:
1943                select = self._move_ctes_to_top_level(
1944                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1945                )
1946
1947                for arg in ("limit", "order", "offset"):
1948                    if value := expression.args.get(arg):
1949                        select.set(arg, value.pop())
1950                return self.sql(select)
1951
1952        sqls: list[str] = []
1953        stack: list[str | exp.Expr] = [expression]
1954
1955        while stack:
1956            node = stack.pop()
1957
1958            if isinstance(node, exp.SetOperation):
1959                stack.append(node.expression)
1960                stack.append(
1961                    self.maybe_comment(
1962                        self.set_operation(node), comments=node.comments, separated=True
1963                    )
1964                )
1965                stack.append(node.this)
1966            else:
1967                if (
1968                    not self.SET_OP_LIMITS
1969                    and isinstance(node, exp.Select)
1970                    and node.args.get("limit")
1971                ):
1972                    node = node.subquery(copy=False)
1973                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1974                        node = exp.select("*").from_(node, copy=False)
1975                sqls.append(self.sql(node))
1976
1977        this = self.sep().join(sqls)
1978        this = self.query_modifiers(expression, this)
1979        return self.prepend_ctes(expression, this)
1980
1981    def fetch_sql(self, expression: exp.Fetch) -> str:
1982        direction = expression.args.get("direction")
1983        direction = f" {direction}" if direction else ""
1984        count = self.sql(expression, "count")
1985        count = f" {count}" if count else ""
1986        limit_options = self.sql(expression, "limit_options")
1987        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1988        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1989
1990    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1991        percent = " PERCENT" if expression.args.get("percent") else ""
1992        rows = " ROWS" if expression.args.get("rows") else ""
1993        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1994        if not with_ties and rows:
1995            with_ties = " ONLY"
1996        return f"{percent}{rows}{with_ties}"
1997
1998    def filter_sql(self, expression: exp.Filter) -> str:
1999        this = self.sql(expression, "this")
2000        where = self.sql(expression, "expression").strip()
2001        return f"{this} FILTER({where})"
2002
2003    def hint_sql(self, expression: exp.Hint) -> str:
2004        if not self.QUERY_HINTS:
2005            self.unsupported("Hints are not supported")
2006            return ""
2007
2008        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
2009
2010    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2011        using = self.sql(expression, "using")
2012        using = f" USING {using}" if using else ""
2013        columns = self.expressions(expression, key="columns", flat=True)
2014        columns = f"({columns})" if columns else ""
2015        partition_by = self.expressions(expression, key="partition_by", flat=True)
2016        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2017        where = self.sql(expression, "where")
2018        include = self.expressions(expression, key="include", flat=True)
2019        if include:
2020            include = f" INCLUDE ({include})"
2021        with_storage = self.expressions(expression, key="with_storage", flat=True)
2022        with_storage = f" WITH ({with_storage})" if with_storage else ""
2023        tablespace = self.sql(expression, "tablespace")
2024        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
2025        on = self.sql(expression, "on")
2026        on = f" ON {on}" if on else ""
2027
2028        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2029
2030    def index_sql(self, expression: exp.Index) -> str:
2031        unique = "UNIQUE " if expression.args.get("unique") else ""
2032        primary = "PRIMARY " if expression.args.get("primary") else ""
2033        amp = "AMP " if expression.args.get("amp") else ""
2034        name = self.sql(expression, "this")
2035        name = f"{name} " if name else ""
2036        table = self.sql(expression, "table")
2037        table = f"{self.INDEX_ON} {table}" if table else ""
2038
2039        index = "INDEX " if not table else ""
2040
2041        params = self.sql(expression, "params")
2042        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2043
2044    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2045        this = expression.this
2046        if this and this.is_string:
2047            resolved = maybe_parse(this.name).sql(self.dialect)
2048            if "expressions" in expression.args:
2049                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2050                # We can't safely emit the call to other dialects since name/arg semantics may differ
2051                self.unsupported(
2052                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2053                )
2054            return resolved
2055        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2056        return self.func("IDENTIFIER", this)
2057
2058    def identifier_sql(self, expression: exp.Identifier) -> str:
2059        text = expression.name
2060        lower = text.lower()
2061        quoted = expression.quoted
2062        text = lower if self.normalize and not quoted else text
2063        if self._identifier_escaped_sequences:
2064            for char, escaped in self._identifier_escaped_sequences.items():
2065                text = text.replace(char, escaped)
2066        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2067        if (
2068            quoted
2069            or self.dialect.can_quote(expression, self.identify)
2070            or lower in self.RESERVED_KEYWORDS
2071            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2072        ):
2073            text = (
2074                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2075            )
2076        return text
2077
2078    def hex_sql(self, expression: exp.Hex) -> str:
2079        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2080        if self.dialect.HEX_LOWERCASE:
2081            text = self.func("LOWER", text)
2082
2083        return text
2084
2085    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2086        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2087        if not self.dialect.HEX_LOWERCASE:
2088            text = self.func("LOWER", text)
2089        return text
2090
2091    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2092        input_format = self.sql(expression, "input_format")
2093        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2094        output_format = self.sql(expression, "output_format")
2095        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2096        return self.sep().join((input_format, output_format))
2097
2098    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2099        string = self.sql(exp.Literal.string(expression.name))
2100        return f"{prefix}{string}"
2101
2102    def partition_sql(self, expression: exp.Partition) -> str:
2103        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2104        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2105
2106    def properties_sql(self, expression: exp.Properties) -> str:
2107        root_properties = []
2108        with_properties = []
2109
2110        for p in expression.expressions:
2111            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2112            if p_loc == exp.Properties.Location.POST_WITH:
2113                with_properties.append(p)
2114            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2115                root_properties.append(p)
2116
2117        root_props_ast = exp.Properties(expressions=root_properties)
2118        root_props_ast.parent = expression.parent
2119
2120        with_props_ast = exp.Properties(expressions=with_properties)
2121        with_props_ast.parent = expression.parent
2122
2123        root_props = self.root_properties(root_props_ast)
2124        with_props = self.with_properties(with_props_ast)
2125
2126        if root_props and with_props and not self.pretty:
2127            with_props = " " + with_props
2128
2129        return root_props + with_props
2130
2131    def root_properties(self, properties: exp.Properties) -> str:
2132        if properties.expressions:
2133            return self.expressions(properties, indent=False, sep=" ")
2134        return ""
2135
2136    def properties(
2137        self,
2138        properties: exp.Properties,
2139        prefix: str = "",
2140        sep: str = ", ",
2141        suffix: str = "",
2142        wrapped: bool = True,
2143    ) -> str:
2144        if properties.expressions:
2145            expressions = self.expressions(properties, sep=sep, indent=False)
2146            if expressions:
2147                expressions = self.wrap(expressions) if wrapped else expressions
2148                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2149        return ""
2150
2151    def with_properties(self, properties: exp.Properties) -> str:
2152        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2153
2154    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2155        properties_locs = defaultdict(list)
2156        for p in properties.expressions:
2157            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2158            if p_loc != exp.Properties.Location.UNSUPPORTED:
2159                properties_locs[p_loc].append(p)
2160            else:
2161                self.unsupported(f"Unsupported property {p.key}")
2162
2163        return properties_locs
2164
2165    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2166        if isinstance(expression.this, exp.Dot):
2167            return self.sql(expression, "this")
2168        return f"'{expression.name}'" if string_key else expression.name
2169
2170    def property_sql(self, expression: exp.Property) -> str:
2171        property_cls = expression.__class__
2172        if property_cls == exp.Property:
2173            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2174
2175        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2176        if not property_name:
2177            self.unsupported(f"Unsupported property {expression.key}")
2178
2179        return f"{property_name}={self.sql(expression, 'this')}"
2180
2181    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2182        return f"UUID {self.sql(expression, 'this')}"
2183
2184    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2185        if self.SUPPORTS_CREATE_TABLE_LIKE:
2186            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2187            options = f" {options}" if options else ""
2188
2189            like = f"LIKE {self.sql(expression, 'this')}{options}"
2190            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2191                like = f"({like})"
2192
2193            return like
2194
2195        if expression.expressions:
2196            self.unsupported("Transpilation of LIKE property options is unsupported")
2197
2198        select = exp.select("*").from_(expression.this).limit(0)
2199        return f"AS {self.sql(select)}"
2200
2201    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2202        no = "NO " if expression.args.get("no") else ""
2203        protection = " PROTECTION" if expression.args.get("protection") else ""
2204        return f"{no}FALLBACK{protection}"
2205
2206    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2207        no = "NO " if expression.args.get("no") else ""
2208        local = expression.args.get("local")
2209        local = f"{local} " if local else ""
2210        dual = "DUAL " if expression.args.get("dual") else ""
2211        before = "BEFORE " if expression.args.get("before") else ""
2212        after = "AFTER " if expression.args.get("after") else ""
2213        return f"{no}{local}{dual}{before}{after}JOURNAL"
2214
2215    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2216        freespace = self.sql(expression, "this")
2217        percent = " PERCENT" if expression.args.get("percent") else ""
2218        return f"FREESPACE={freespace}{percent}"
2219
2220    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2221        if expression.args.get("default"):
2222            property = "DEFAULT"
2223        elif expression.args.get("on"):
2224            property = "ON"
2225        else:
2226            property = "OFF"
2227        return f"CHECKSUM={property}"
2228
2229    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2230        if expression.args.get("no"):
2231            return "NO MERGEBLOCKRATIO"
2232        if expression.args.get("default"):
2233            return "DEFAULT MERGEBLOCKRATIO"
2234
2235        percent = " PERCENT" if expression.args.get("percent") else ""
2236        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2237
2238    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2239        expressions = self.expressions(expression, flat=True)
2240        expressions = f"({expressions})" if expressions else ""
2241        return f"USING {self.sql(expression, 'this')}{expressions}"
2242
2243    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2244        default = expression.args.get("default")
2245        minimum = expression.args.get("minimum")
2246        maximum = expression.args.get("maximum")
2247        if default or minimum or maximum:
2248            if default:
2249                prop = "DEFAULT"
2250            elif minimum:
2251                prop = "MINIMUM"
2252            else:
2253                prop = "MAXIMUM"
2254            return f"{prop} DATABLOCKSIZE"
2255        units = expression.args.get("units")
2256        units = f" {units}" if units else ""
2257        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2258
2259    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2260        autotemp = expression.args.get("autotemp")
2261        always = expression.args.get("always")
2262        default = expression.args.get("default")
2263        manual = expression.args.get("manual")
2264        never = expression.args.get("never")
2265
2266        if autotemp is not None:
2267            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2268        elif always:
2269            prop = "ALWAYS"
2270        elif default:
2271            prop = "DEFAULT"
2272        elif manual:
2273            prop = "MANUAL"
2274        elif never:
2275            prop = "NEVER"
2276        return f"BLOCKCOMPRESSION={prop}"
2277
2278    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2279        no = expression.args.get("no")
2280        no = " NO" if no else ""
2281        concurrent = expression.args.get("concurrent")
2282        concurrent = " CONCURRENT" if concurrent else ""
2283        target = self.sql(expression, "target")
2284        target = f" {target}" if target else ""
2285        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2286
2287    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2288        if isinstance(expression.this, list):
2289            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2290        if expression.this:
2291            modulus = self.sql(expression, "this")
2292            remainder = self.sql(expression, "expression")
2293            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2294
2295        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2296        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2297        return f"FROM ({from_expressions}) TO ({to_expressions})"
2298
2299    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2300        this = self.sql(expression, "this")
2301
2302        for_values_or_default = expression.expression
2303        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2304            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2305        else:
2306            for_values_or_default = " DEFAULT"
2307
2308        return f"PARTITION OF {this}{for_values_or_default}"
2309
2310    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2311        kind = expression.args.get("kind")
2312        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2313        for_or_in = expression.args.get("for_or_in")
2314        for_or_in = f" {for_or_in}" if for_or_in else ""
2315        lock_type = expression.args.get("lock_type")
2316        override = " OVERRIDE" if expression.args.get("override") else ""
2317        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2318
2319    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2320        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2321        statistics = expression.args.get("statistics")
2322        statistics_sql = ""
2323        if statistics is not None:
2324            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2325        return f"{data_sql}{statistics_sql}"
2326
2327    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2328        this = self.sql(expression, "this")
2329        this = f"HISTORY_TABLE={this}" if this else ""
2330        data_consistency: str | None = self.sql(expression, "data_consistency")
2331        data_consistency = (
2332            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2333        )
2334        retention_period: str | None = self.sql(expression, "retention_period")
2335        retention_period = (
2336            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2337        )
2338
2339        if this:
2340            on_sql = self.func("ON", this, data_consistency, retention_period)
2341        else:
2342            on_sql = "ON" if expression.args.get("on") else "OFF"
2343
2344        sql = f"SYSTEM_VERSIONING={on_sql}"
2345
2346        return f"WITH({sql})" if expression.args.get("with_") else sql
2347
2348    def insert_sql(self, expression: exp.Insert) -> str:
2349        hint = self.sql(expression, "hint")
2350        overwrite = expression.args.get("overwrite")
2351
2352        if isinstance(expression.this, exp.Directory):
2353            this = " OVERWRITE" if overwrite else " INTO"
2354        else:
2355            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2356
2357        stored = self.sql(expression, "stored")
2358        stored = f" {stored}" if stored else ""
2359        alternative = expression.args.get("alternative")
2360        alternative = f" OR {alternative}" if alternative else ""
2361        ignore = " IGNORE" if expression.args.get("ignore") else ""
2362        is_function = expression.args.get("is_function")
2363        if is_function:
2364            this = f"{this} FUNCTION"
2365        this = f"{this} {self.sql(expression, 'this')}"
2366
2367        exists = " IF EXISTS" if expression.args.get("exists") else ""
2368        where = self.sql(expression, "where")
2369        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2370        using = self.expressions(expression, key="using", flat=True)
2371        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2372        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2373        on_conflict = self.sql(expression, "conflict")
2374        on_conflict = f" {on_conflict}" if on_conflict else ""
2375        by_name = " BY NAME" if expression.args.get("by_name") else ""
2376        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2377        returning = self.sql(expression, "returning")
2378
2379        if self.RETURNING_END:
2380            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2381        else:
2382            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2383
2384        partition_by = self.sql(expression, "partition")
2385        partition_by = f" {partition_by}" if partition_by else ""
2386        settings = self.sql(expression, "settings")
2387        settings = f" {settings}" if settings else ""
2388
2389        source = self.sql(expression, "source")
2390        source = f"TABLE {source}" if source else ""
2391
2392        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2393        return self.prepend_ctes(expression, sql)
2394
2395    def introducer_sql(self, expression: exp.Introducer) -> str:
2396        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2397
2398    def kill_sql(self, expression: exp.Kill) -> str:
2399        kind = self.sql(expression, "kind")
2400        kind = f" {kind}" if kind else ""
2401        this = self.sql(expression, "this")
2402        this = f" {this}" if this else ""
2403        return f"KILL{kind}{this}"
2404
2405    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2406        return expression.name
2407
2408    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2409        return expression.name
2410
2411    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2412        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2413
2414        constraint = self.sql(expression, "constraint")
2415        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2416
2417        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2418        if conflict_keys:
2419            conflict_keys = f"({conflict_keys})"
2420
2421        index_predicate = self.sql(expression, "index_predicate")
2422        conflict_keys = f"{conflict_keys}{index_predicate} "
2423
2424        action = self.sql(expression, "action")
2425
2426        expressions = self.expressions(expression, flat=True)
2427        if expressions:
2428            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2429            expressions = f" {set_keyword}{expressions}"
2430
2431        where = self.sql(expression, "where")
2432        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2433
2434    def returning_sql(self, expression: exp.Returning) -> str:
2435        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2436
2437    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2438        fields = self.sql(expression, "fields")
2439        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2440        escaped = self.sql(expression, "escaped")
2441        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2442        items = self.sql(expression, "collection_items")
2443        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2444        keys = self.sql(expression, "map_keys")
2445        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2446        lines = self.sql(expression, "lines")
2447        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2448        null = self.sql(expression, "null")
2449        null = f" NULL DEFINED AS {null}" if null else ""
2450        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2451
2452    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2453        return f"WITH ({self.expressions(expression, flat=True)})"
2454
2455    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2456        this = f"{self.sql(expression, 'this')} INDEX"
2457        target = self.sql(expression, "target")
2458        target = f" FOR {target}" if target else ""
2459        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2460
2461    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2462        this = self.sql(expression, "this")
2463        kind = self.sql(expression, "kind")
2464        expr = self.sql(expression, "expression")
2465        return f"{this} ({kind} => {expr})"
2466
2467    def table_parts(self, expression: exp.Table) -> str:
2468        return ".".join(
2469            self.sql(part)
2470            for part in (
2471                expression.args.get("catalog"),
2472                expression.args.get("db"),
2473                expression.args.get("this"),
2474            )
2475            if part is not None
2476        )
2477
2478    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2479        table = self.table_parts(expression)
2480        only = "ONLY " if expression.args.get("only") else ""
2481        partition = self.sql(expression, "partition")
2482        partition = f" {partition}" if partition else ""
2483        version = self.sql(expression, "version")
2484        version = f" {version}" if version else ""
2485        alias = self.sql(expression, "alias")
2486        alias = f"{sep}{alias}" if alias else ""
2487
2488        sample = self.sql(expression, "sample")
2489        post_alias = ""
2490        pre_alias = ""
2491
2492        if self.dialect.ALIAS_POST_TABLESAMPLE:
2493            pre_alias = sample
2494        else:
2495            post_alias = sample
2496
2497        if self.dialect.ALIAS_POST_VERSION:
2498            pre_alias = f"{pre_alias}{version}"
2499        else:
2500            post_alias = f"{post_alias}{version}"
2501
2502        hints = self.expressions(expression, key="hints", sep=" ")
2503        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2504        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2505        joins = self.indent(
2506            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2507        )
2508        laterals = self.expressions(expression, key="laterals", sep="")
2509
2510        file_format = self.sql(expression, "format")
2511        pattern = self.sql(expression, "pattern")
2512        if file_format:
2513            pattern = f", PATTERN => {pattern}" if pattern else ""
2514            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2515        elif pattern:
2516            file_format = f" (PATTERN => {pattern})"
2517
2518        ordinality = expression.args.get("ordinality") or ""
2519        if ordinality:
2520            ordinality = f" WITH ORDINALITY{alias}"
2521            alias = ""
2522
2523        when = self.sql(expression, "when")
2524        if when:
2525            if self.HISTORICAL_DATA_POST_ALIAS:
2526                alias = f"{alias} {when}"
2527            else:
2528                table = f"{table} {when}"
2529
2530        changes = self.sql(expression, "changes")
2531        changes = f" {changes}" if changes else ""
2532
2533        rows_from = self.expressions(expression, key="rows_from")
2534        if rows_from:
2535            table = f"ROWS FROM {self.wrap(rows_from)}"
2536
2537        indexed = expression.args.get("indexed")
2538        if indexed is not None:
2539            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2540        else:
2541            indexed = ""
2542
2543        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2544
2545    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2546        table = self.func("TABLE", expression.this)
2547        alias = self.sql(expression, "alias")
2548        alias = f" AS {alias}" if alias else ""
2549        sample = self.sql(expression, "sample")
2550        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2551        joins = self.indent(
2552            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2553        )
2554        return f"{table}{alias}{pivots}{sample}{joins}"
2555
2556    def tablesample_sql(
2557        self,
2558        expression: exp.TableSample,
2559        tablesample_keyword: str | None = None,
2560    ) -> str:
2561        method = self.sql(expression, "method")
2562        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2563        numerator = self.sql(expression, "bucket_numerator")
2564        denominator = self.sql(expression, "bucket_denominator")
2565        field = self.sql(expression, "bucket_field")
2566        field = f" ON {field}" if field else ""
2567        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2568        seed = self.sql(expression, "seed")
2569        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2570
2571        size = self.sql(expression, "size")
2572        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2573            size = f"{size} ROWS"
2574
2575        percent = self.sql(expression, "percent")
2576        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2577            percent = f"{percent} PERCENT"
2578
2579        expr = f"{bucket}{percent}{size}"
2580        if self.TABLESAMPLE_REQUIRES_PARENS:
2581            expr = f"({expr})"
2582
2583        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2584
2585    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2586        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2587        # the stored column name differs from the target dialect's natural output.
2588        columns = expression.args.get("columns")
2589        if not columns or len(expression.fields) != 1:
2590            return None
2591
2592        args = expression.args
2593        parser_cls = self.dialect.parser_class
2594
2595        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2596        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2597        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2598
2599        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2600        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2601        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2602
2603        if (
2604            src_identify_pivot_strings == tgt_identify_pivot_strings
2605            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2606            and src_pivot_column_naming == tgt_pivot_column_naming
2607        ):
2608            return None
2609
2610        in_exprs = expression.fields[0].expressions
2611        step = len(columns) // len(in_exprs)
2612
2613        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2614        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2615        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2616        first_stored = columns[0].name
2617
2618        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2619        if not first_stored.startswith(first_base):
2620            return None
2621
2622        suffix = first_stored[len(first_base) :]
2623
2624        # Whether the target dialect would append an agg-name suffix for this pivot.
2625        # Spark single-agg uniquely drops the agg alias entirely.
2626        target_has_suffix = (
2627            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2628        ) and any(a.alias for a in expression.expressions)
2629        source_has_suffix = suffix != ""
2630
2631        new_exprs: list[exp.Expression] = []
2632        modified = False
2633        for val_idx, e in enumerate(in_exprs):
2634            if isinstance(e, exp.PivotAlias):
2635                new_exprs.append(e)
2636                continue
2637
2638            i = val_idx * step
2639            stored_full = columns[i].name
2640            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2641            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2642
2643            # Source had a suffix, but target won't apply one
2644            if source_has_suffix and not target_has_suffix:
2645                new_exprs.append(
2646                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2647                )
2648                modified = True
2649            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2650            elif stored_value != target_value:
2651                new_exprs.append(
2652                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2653                )
2654                modified = True
2655            else:
2656                new_exprs.append(e)
2657
2658        return new_exprs if modified else None
2659
2660    def pivot_sql(self, expression: exp.Pivot) -> str:
2661        expressions = self.expressions(expression, flat=True)
2662        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2663
2664        group = self.sql(expression, "group")
2665
2666        if expression.this:
2667            this = self.sql(expression, "this")
2668            if not expressions:
2669                sql = f"UNPIVOT {this}"
2670            else:
2671                on = f"{self.seg('ON')} {expressions}"
2672                into = self.sql(expression, "into")
2673                into = f"{self.seg('INTO')} {into}" if into else ""
2674                using = self.expressions(expression, key="using", flat=True)
2675                using = f"{self.seg('USING')} {using}" if using else ""
2676                sql = f"{direction} {this}{on}{into}{using}{group}"
2677            return self.prepend_ctes(expression, sql)
2678
2679        if not expression.unpivot:
2680            # Wrap IN-list values with explicit aliases where the target dialect would differ
2681            new_field_exprs = self._pivot_in_value_aliases(expression)
2682            if new_field_exprs is not None:
2683                expression.fields[0].set("expressions", new_field_exprs)
2684
2685        alias = self.sql(expression, "alias")
2686        if alias:
2687            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2688
2689        fields = self.expressions(
2690            expression,
2691            "fields",
2692            sep=" ",
2693            dynamic=True,
2694            new_line=True,
2695            skip_first=True,
2696            skip_last=True,
2697        )
2698
2699        include_nulls = expression.args.get("include_nulls")
2700        if include_nulls is not None:
2701            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2702        else:
2703            nulls = ""
2704
2705        default_on_null = self.sql(expression, "default_on_null")
2706        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2707        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2708        return self.prepend_ctes(expression, sql)
2709
2710    def version_sql(self, expression: exp.Version) -> str:
2711        this = f"FOR {expression.name}"
2712        kind = expression.text("kind")
2713        expr = self.sql(expression, "expression")
2714        return f"{this} {kind} {expr}"
2715
2716    def tuple_sql(self, expression: exp.Tuple) -> str:
2717        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2718
2719    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2720        """
2721        Returns (join_sql, from_sql) for UPDATE statements.
2722        - join_sql: placed after UPDATE table, before SET
2723        - from_sql: placed after SET clause (standard position)
2724        Dialects like MySQL need to convert FROM to JOIN syntax.
2725        """
2726        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2727            return ("", self.sql(expression, "from_"))
2728
2729        # Qualify unqualified columns in SET clause with the target table
2730        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2731        target_table = expression.this
2732        if isinstance(target_table, exp.Table):
2733            target_name = exp.to_identifier(target_table.alias_or_name)
2734            for eq in expression.expressions:
2735                col = eq.this
2736                if isinstance(col, exp.Column) and not col.table:
2737                    col.set("table", target_name)
2738
2739        table = from_expr.this
2740        if nested_joins := table.args.get("joins", []):
2741            table.set("joins", None)
2742
2743        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2744        for nested in nested_joins:
2745            if not nested.args.get("on") and not nested.args.get("using"):
2746                nested.set("on", exp.true())
2747            join_sql += self.sql(nested)
2748
2749        return (join_sql, "")
2750
2751    def update_sql(self, expression: exp.Update) -> str:
2752        hint = self.sql(expression, "hint")
2753        this = self.sql(expression, "this")
2754        join_sql, from_sql = self._update_from_joins_sql(expression)
2755        set_sql = self.expressions(expression, flat=True)
2756        where_sql = self.sql(expression, "where")
2757        returning = self.sql(expression, "returning")
2758        order = self.sql(expression, "order")
2759        limit = self.sql(expression, "limit")
2760        if self.RETURNING_END:
2761            expression_sql = f"{from_sql}{where_sql}{returning}"
2762        else:
2763            expression_sql = f"{returning}{from_sql}{where_sql}"
2764        options = self.expressions(expression, key="options")
2765        options = f" OPTION({options})" if options else ""
2766        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2767        return self.prepend_ctes(expression, sql)
2768
2769    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2770        values_as_table = values_as_table and self.VALUES_AS_TABLE
2771
2772        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2773        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2774            args = self.expressions(expression)
2775            alias = self.sql(expression, "alias")
2776            values = f"VALUES{self.seg('')}{args}"
2777            values = (
2778                f"({values})"
2779                if self.WRAP_DERIVED_VALUES
2780                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2781                else values
2782            )
2783            values = self.query_modifiers(expression, values)
2784            return f"{values} AS {alias}" if alias else values
2785
2786        # Converts `VALUES...` expression into a series of select unions.
2787        alias_node = expression.args.get("alias")
2788        column_names = alias_node and alias_node.columns
2789
2790        selects: list[exp.Query] = []
2791
2792        for i, tup in enumerate(expression.expressions):
2793            row = tup.expressions
2794
2795            if i == 0 and column_names:
2796                row = [
2797                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2798                ]
2799
2800            selects.append(exp.Select(expressions=row))
2801
2802        if self.pretty:
2803            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2804            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2805            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2806            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2807            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2808
2809        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2810        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2811        return f"({unions}){alias}"
2812
2813    def var_sql(self, expression: exp.Var) -> str:
2814        return self.sql(expression, "this")
2815
2816    @unsupported_args("expressions")
2817    def into_sql(self, expression: exp.Into) -> str:
2818        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2819        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2820        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2821
2822    def from_sql(self, expression: exp.From) -> str:
2823        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2824
2825    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2826        grouping_sets = self.expressions(expression, indent=False)
2827        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2828
2829    def rollup_sql(self, expression: exp.Rollup) -> str:
2830        expressions = self.expressions(expression, indent=False)
2831        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2832
2833    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2834        this = self.sql(expression, "this")
2835
2836        columns = self.expressions(expression, flat=True)
2837
2838        from_sql = self.sql(expression, "from_index")
2839        from_sql = f" FROM {from_sql}" if from_sql else ""
2840
2841        properties = expression.args.get("properties")
2842        properties_sql = (
2843            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2844        )
2845
2846        return f"{this}({columns}){from_sql}{properties_sql}"
2847
2848    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2849        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2850
2851    def cube_sql(self, expression: exp.Cube) -> str:
2852        expressions = self.expressions(expression, indent=False)
2853        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2854
2855    def group_sql(self, expression: exp.Group) -> str:
2856        group_by_all = expression.args.get("all")
2857        if group_by_all is True:
2858            modifier = " ALL"
2859        elif group_by_all is False:
2860            modifier = " DISTINCT"
2861        else:
2862            modifier = ""
2863
2864        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2865
2866        grouping_sets = self.expressions(expression, key="grouping_sets")
2867        cube = self.expressions(expression, key="cube")
2868        rollup = self.expressions(expression, key="rollup")
2869
2870        groupings = csv(
2871            self.seg(grouping_sets) if grouping_sets else "",
2872            self.seg(cube) if cube else "",
2873            self.seg(rollup) if rollup else "",
2874            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2875            sep=self.GROUPINGS_SEP,
2876        )
2877
2878        if (
2879            expression.expressions
2880            and groupings
2881            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2882        ):
2883            add_separator = True
2884
2885            if grouping_sets:
2886                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2887                    add_separator = False
2888                else:
2889                    self.unsupported(
2890                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2891                    )
2892
2893            if add_separator:
2894                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2895
2896        return f"{group_by}{groupings}"
2897
2898    def having_sql(self, expression: exp.Having) -> str:
2899        this = self.indent(self.sql(expression, "this"))
2900        return f"{self.seg('HAVING')}{self.sep()}{this}"
2901
2902    def connect_sql(self, expression: exp.Connect) -> str:
2903        start = self.sql(expression, "start")
2904        start = self.seg(f"START WITH {start}") if start else ""
2905        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2906        connect = self.sql(expression, "connect")
2907        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2908        return start + connect
2909
2910    def prior_sql(self, expression: exp.Prior) -> str:
2911        return f"PRIOR {self.sql(expression, 'this')}"
2912
2913    def join_sql(self, expression: exp.Join) -> str:
2914        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2915            side = None
2916        else:
2917            side = expression.side
2918
2919        op_sql = " ".join(
2920            op
2921            for op in (
2922                expression.method,
2923                "GLOBAL" if expression.args.get("global_") else None,
2924                side,
2925                expression.kind,
2926                expression.hint if self.JOIN_HINTS else None,
2927                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2928            )
2929            if op
2930        )
2931        match_cond = self.sql(expression, "match_condition")
2932        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2933        on_sql = self.sql(expression, "on")
2934        using = expression.args.get("using")
2935
2936        if not on_sql and using:
2937            on_sql = csv(*(self.sql(column) for column in using))
2938
2939        this = expression.this
2940        this_sql = self.sql(this)
2941
2942        exprs = self.expressions(expression)
2943        if exprs:
2944            this_sql = f"{this_sql},{self.seg(exprs)}"
2945
2946        if on_sql:
2947            on_sql = self.indent(on_sql, skip_first=True)
2948            space = self.seg(" " * self.pad) if self.pretty else " "
2949            if using:
2950                on_sql = f"{space}USING ({on_sql})"
2951            else:
2952                on_sql = f"{space}ON {on_sql}"
2953        elif not op_sql:
2954            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2955                return f" {this_sql}"
2956
2957            return f", {this_sql}"
2958
2959        if op_sql != "STRAIGHT_JOIN":
2960            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2961
2962        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2963        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2964
2965    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2966        args = self.expressions(expression, flat=True)
2967        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2968        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2969
2970    def lateral_op(self, expression: exp.Lateral) -> str:
2971        cross_apply = expression.args.get("cross_apply")
2972
2973        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2974        if cross_apply is True:
2975            op = "INNER JOIN "
2976        elif cross_apply is False:
2977            op = "LEFT JOIN "
2978        else:
2979            op = ""
2980
2981        return f"{op}LATERAL"
2982
2983    def lateral_sql(self, expression: exp.Lateral) -> str:
2984        this = self.sql(expression, "this")
2985
2986        if expression.args.get("view"):
2987            alias = expression.args["alias"]
2988            columns = self.expressions(alias, key="columns", flat=True)
2989            table = f" {alias.name}" if alias.name else ""
2990            columns = f" AS {columns}" if columns else ""
2991            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2992            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2993
2994        table_alias = expression.args.get("alias")
2995        offset = expression.this.args.get("offset")
2996
2997        if (
2998            self.UNNEST_WITH_ORDINALITY
2999            and table_alias
3000            and isinstance(expression.this, exp.Unnest)
3001            and isinstance(offset, exp.Identifier)
3002        ):
3003            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3004            table_alias = table_alias.copy()
3005            table_alias.append("columns", offset.copy())
3006
3007        alias = self.sql(table_alias)
3008        alias = f" AS {alias}" if alias else ""
3009
3010        ordinality = expression.args.get("ordinality") or ""
3011        if ordinality:
3012            ordinality = f" WITH ORDINALITY{alias}"
3013            alias = ""
3014
3015        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3016
3017    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3018        this = self.sql(expression, "this")
3019
3020        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3021            return this
3022
3023        args = [
3024            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3025            for e in (expression.args.get(k) for k in ("offset", "expression"))
3026            if e
3027        ]
3028
3029        args_sql = ", ".join(self.sql(e) for e in args)
3030        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3031        expressions = self.expressions(expression, flat=True)
3032        limit_options = self.sql(expression, "limit_options")
3033        expressions = f" BY {expressions}" if expressions else ""
3034
3035        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3036
3037    def offset_sql(self, expression: exp.Offset) -> str:
3038        this = self.sql(expression, "this")
3039        value = expression.expression
3040        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3041        expressions = self.expressions(expression, flat=True)
3042        expressions = f" BY {expressions}" if expressions else ""
3043        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3044
3045    def setitem_sql(self, expression: exp.SetItem) -> str:
3046        kind = self.sql(expression, "kind")
3047        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3048            kind = ""
3049        else:
3050            kind = f"{kind} " if kind else ""
3051        this = self.sql(expression, "this")
3052        expressions = self.expressions(expression)
3053        collate = self.sql(expression, "collate")
3054        collate = f" COLLATE {collate}" if collate else ""
3055        global_ = "GLOBAL " if expression.args.get("global_") else ""
3056        return f"{global_}{kind}{this}{expressions}{collate}"
3057
3058    def set_sql(self, expression: exp.Set) -> str:
3059        expressions = f" {self.expressions(expression, flat=True)}"
3060        tag = " TAG" if expression.args.get("tag") else ""
3061        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3062
3063    def queryband_sql(self, expression: exp.QueryBand) -> str:
3064        this = self.sql(expression, "this")
3065        update = " UPDATE" if expression.args.get("update") else ""
3066        scope = self.sql(expression, "scope")
3067        scope = f" FOR {scope}" if scope else ""
3068
3069        return f"QUERY_BAND = {this}{update}{scope}"
3070
3071    def pragma_sql(self, expression: exp.Pragma) -> str:
3072        return f"PRAGMA {self.sql(expression, 'this')}"
3073
3074    def lock_sql(self, expression: exp.Lock) -> str:
3075        if not self.LOCKING_READS_SUPPORTED:
3076            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3077            return ""
3078
3079        update = expression.args["update"]
3080        key = expression.args.get("key")
3081        if update:
3082            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3083        else:
3084            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3085        expressions = self.expressions(expression, flat=True)
3086        expressions = f" OF {expressions}" if expressions else ""
3087        wait = expression.args.get("wait")
3088
3089        if wait is not None:
3090            if isinstance(wait, exp.Literal):
3091                wait = f" WAIT {self.sql(wait)}"
3092            else:
3093                wait = " NOWAIT" if wait else " SKIP LOCKED"
3094
3095        return f"{lock_type}{expressions}{wait or ''}"
3096
3097    def literal_sql(self, expression: exp.Literal) -> str:
3098        text = expression.this or ""
3099        if expression.is_string:
3100            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3101        return text
3102
3103    def escape_str(
3104        self,
3105        text: str,
3106        escape_backslash: bool = True,
3107        delimiter: str | None = None,
3108        escaped_delimiter: str | None = None,
3109        is_byte_string: bool = False,
3110        is_bytes: bool = False,
3111    ) -> str:
3112        if is_byte_string:
3113            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3114        else:
3115            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3116
3117        if supports_escape_sequences:
3118            text = "".join(
3119                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3120                for ch in text
3121            )
3122
3123            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3124                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3125                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3126
3127        delimiter = delimiter or self.dialect.QUOTE_END
3128        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3129
3130        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3131
3132    def loaddata_sql(self, expression: exp.LoadData) -> str:
3133        is_overwrite = expression.args.get("overwrite")
3134        overwrite = " OVERWRITE" if is_overwrite else ""
3135        this = self.sql(expression, "this")
3136
3137        files = expression.args.get("files")
3138        if files:
3139            files_sql = self.expressions(files, flat=True)
3140            files_sql = f"FILES{self.wrap(files_sql)}"
3141            if is_overwrite:
3142                this = f" {this}"
3143            elif expression.args.get("temp"):
3144                this = f" INTO TEMP TABLE {this}"
3145            else:
3146                this = f" INTO TABLE {this}"
3147            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3148
3149        local = " LOCAL" if expression.args.get("local") else ""
3150        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3151        this = f" INTO TABLE {this}"
3152        partition = self.sql(expression, "partition")
3153        partition = f" {partition}" if partition else ""
3154        input_format = self.sql(expression, "input_format")
3155        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3156        serde = self.sql(expression, "serde")
3157        serde = f" SERDE {serde}" if serde else ""
3158        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3159
3160    def null_sql(self, *_) -> str:
3161        return "NULL"
3162
3163    def boolean_sql(self, expression: exp.Boolean) -> str:
3164        return "TRUE" if expression.this else "FALSE"
3165
3166    def booland_sql(self, expression: exp.Booland) -> str:
3167        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3168
3169    def boolor_sql(self, expression: exp.Boolor) -> str:
3170        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3171
3172    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3173        this = self.sql(expression, "this")
3174        this = f"{this} " if this else this
3175        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3176        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3177
3178    def withfill_sql(self, expression: exp.WithFill) -> str:
3179        from_sql = self.sql(expression, "from_")
3180        from_sql = f" FROM {from_sql}" if from_sql else ""
3181        to_sql = self.sql(expression, "to")
3182        to_sql = f" TO {to_sql}" if to_sql else ""
3183        step_sql = self.sql(expression, "step")
3184        step_sql = f" STEP {step_sql}" if step_sql else ""
3185        interpolated_values = [
3186            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3187            if isinstance(e, exp.Alias)
3188            else self.sql(e, "this")
3189            for e in expression.args.get("interpolate") or []
3190        ]
3191        interpolate = (
3192            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3193        )
3194        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3195
3196    def cluster_sql(self, expression: exp.Cluster) -> str:
3197        return self.op_expressions("CLUSTER BY", expression)
3198
3199    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3200        if expression.this:
3201            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3202            return ""
3203        expressions = self.expressions(expression, flat=True)
3204        return f"CLUSTER BY ({expressions})"
3205
3206    def distribute_sql(self, expression: exp.Distribute) -> str:
3207        return self.op_expressions("DISTRIBUTE BY", expression)
3208
3209    def sort_sql(self, expression: exp.Sort) -> str:
3210        return self.op_expressions("SORT BY", expression)
3211
3212    def _resolve_ordered_for_null_ordering_simulation(
3213        self, expression: exp.Ordered
3214    ) -> exp.Expr | None:
3215        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3216
3217        Returns the underlying expression of the uniquely-matching projection
3218        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3219        simulation, since the CASE is evaluated in FROM-clause scope rather
3220        than alias scope (MySQL error 1052). Returns None if no safe
3221        substitution applies, leaving the original behaviour unchanged.
3222        """
3223        this = expression.this
3224        if not (isinstance(this, exp.Column) and not this.table):
3225            return None
3226
3227        # Only a query's own ORDER BY can reference its projection aliases
3228        order = expression.parent
3229        ancestor = order.parent if isinstance(order, exp.Order) else None
3230        if not isinstance(ancestor, exp.Select):
3231            return None
3232
3233        column_name = this.name
3234        matched: list[exp.Expr] = [
3235            p.this if isinstance(p, exp.Alias) else p
3236            for p in ancestor.selects
3237            if p.output_name == column_name
3238        ]
3239        match = matched[0] if len(matched) == 1 else None
3240
3241        # Skip the substitution when it would be identical to the existing
3242        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3243        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3244            return None
3245
3246        return match
3247
3248    def ordered_sql(self, expression: exp.Ordered) -> str:
3249        desc = expression.args.get("desc")
3250        asc = not desc
3251
3252        nulls_first = expression.args.get("nulls_first")
3253        nulls_last = not nulls_first
3254        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3255        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3256        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3257
3258        this = self.sql(expression, "this")
3259
3260        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3261        nulls_sort_change = ""
3262        if nulls_first and (
3263            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3264        ):
3265            nulls_sort_change = " NULLS FIRST"
3266        elif (
3267            nulls_last
3268            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3269            and not nulls_are_last
3270        ):
3271            nulls_sort_change = " NULLS LAST"
3272
3273        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3274        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3275            window = expression.find_ancestor(exp.Window, exp.Select)
3276
3277            if isinstance(window, exp.Window):
3278                window_this = window.this
3279                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3280                    window_this = window_this.this
3281                spec = window.args.get("spec")
3282            else:
3283                window_this = None
3284                spec = None
3285
3286            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3287            # without a spec or with a ROWS spec, but not with RANGE
3288            if not (
3289                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3290                and (not spec or spec.text("kind").upper() == "ROWS")
3291            ):
3292                if window_this and spec:
3293                    self.unsupported(
3294                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3295                    )
3296                    nulls_sort_change = ""
3297                elif self.NULL_ORDERING_SUPPORTED is False and (
3298                    (asc and nulls_sort_change == " NULLS LAST")
3299                    or (desc and nulls_sort_change == " NULLS FIRST")
3300                ):
3301                    # BigQuery does not allow these ordering/nulls combinations when used under
3302                    # an aggregation func or under a window containing one
3303                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3304
3305                    if isinstance(ancestor, exp.Window):
3306                        ancestor = ancestor.this
3307                    if isinstance(ancestor, exp.AggFunc):
3308                        self.unsupported(
3309                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3310                        )
3311                        nulls_sort_change = ""
3312                elif self.NULL_ORDERING_SUPPORTED is None:
3313                    if expression.this.is_int:
3314                        self.unsupported(
3315                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3316                        )
3317                    elif not isinstance(expression.this, exp.Rand):
3318                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3319                        target = self.sql(resolved) if resolved is not None else this
3320                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3321                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3322                    nulls_sort_change = ""
3323
3324        with_fill = self.sql(expression, "with_fill")
3325        with_fill = f" {with_fill}" if with_fill else ""
3326
3327        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3328
3329    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3330        window_frame = self.sql(expression, "window_frame")
3331        window_frame = f"{window_frame} " if window_frame else ""
3332
3333        this = self.sql(expression, "this")
3334
3335        return f"{window_frame}{this}"
3336
3337    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3338        partition = self.partition_by_sql(expression)
3339        order = self.sql(expression, "order")
3340        measures = self.expressions(expression, key="measures")
3341        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3342        rows = self.sql(expression, "rows")
3343        rows = self.seg(rows) if rows else ""
3344        after = self.sql(expression, "after")
3345        after = self.seg(after) if after else ""
3346        pattern = self.sql(expression, "pattern")
3347        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3348        definition_sqls = [
3349            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3350            for definition in expression.args.get("define", [])
3351        ]
3352        definitions = self.expressions(sqls=definition_sqls)
3353        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3354        body = "".join(
3355            (
3356                partition,
3357                order,
3358                measures,
3359                rows,
3360                after,
3361                pattern,
3362                define,
3363            )
3364        )
3365        alias = self.sql(expression, "alias")
3366        alias = f" {alias}" if alias else ""
3367        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3368
3369    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3370        limit = expression.args.get("limit")
3371
3372        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3373            count = limit.args.get("count")
3374            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3375            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3376            limit = exp.Limit(
3377                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3378            )
3379        elif (
3380            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3381        ):
3382            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3383
3384        return csv(
3385            *sqls,
3386            *[self.sql(join) for join in expression.args.get("joins") or []],
3387            self.sql(expression, "match"),
3388            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3389            self.sql(expression, "prewhere"),
3390            self.sql(expression, "where"),
3391            self.sql(expression, "connect"),
3392            self.sql(expression, "group"),
3393            self.sql(expression, "having"),
3394            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3395            self.sql(expression, "order"),
3396            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3397            *self.after_limit_modifiers(expression),
3398            self.sql(expression, "for_"),
3399            self.options_modifier(expression),
3400            sep="",
3401        )
3402
3403    def options_modifier(self, expression: exp.Expr) -> str:
3404        options = self.expressions(expression, key="options")
3405        return f" {options}" if options else ""
3406
3407    def forclause_sql(self, expression: exp.ForClause) -> str:
3408        kind = expression.args["kind"]
3409        if kind == "BROWSE":
3410            return f"{self.sep()}FOR BROWSE"
3411        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3412        # the target dialect doesn't support QueryOption, so we drop the clause.
3413        options = self.expressions(expression, key="expressions")
3414        if not options:
3415            return ""
3416        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3417
3418    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3419        self.unsupported("Unsupported query option.")
3420        return ""
3421
3422    def offset_limit_modifiers(
3423        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3424    ) -> list[str]:
3425        return [
3426            self.sql(expression, "offset") if fetch else self.sql(limit),
3427            self.sql(limit) if fetch else self.sql(expression, "offset"),
3428        ]
3429
3430    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3431        locks = self.expressions(expression, key="locks", sep=" ")
3432        locks = f" {locks}" if locks else ""
3433        return [locks, self.sql(expression, "sample")]
3434
3435    def select_sql(self, expression: exp.Select) -> str:
3436        into = expression.args.get("into")
3437        if not self.SUPPORTS_SELECT_INTO and into:
3438            into.pop()
3439
3440        hint = self.sql(expression, "hint")
3441        distinct = self.sql(expression, "distinct")
3442        distinct = f" {distinct}" if distinct else ""
3443        kind = self.sql(expression, "kind")
3444
3445        limit = expression.args.get("limit")
3446        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3447            top = self.limit_sql(limit, top=True)
3448            limit.pop()
3449        else:
3450            top = ""
3451
3452        expressions = self.expressions(expression)
3453
3454        if kind:
3455            if kind in self.SELECT_KINDS:
3456                kind = f" AS {kind}"
3457            else:
3458                if kind == "STRUCT":
3459                    expressions = self.expressions(
3460                        sqls=[
3461                            self.sql(
3462                                exp.Struct(
3463                                    expressions=[
3464                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3465                                        if isinstance(e, exp.Alias)
3466                                        else e
3467                                        for e in expression.expressions
3468                                    ]
3469                                )
3470                            )
3471                        ]
3472                    )
3473                kind = ""
3474
3475        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3476        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3477
3478        exclude = expression.args.get("exclude")
3479
3480        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3481            exclude_sql = self.expressions(sqls=exclude, flat=True)
3482            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3483
3484        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3485        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3486        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3487        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3488        sql = self.query_modifiers(
3489            expression,
3490            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3491            self.sql(expression, "into", comment=False),
3492            self.sql(expression, "from_", comment=False),
3493        )
3494
3495        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3496        if expression.args.get("with_"):
3497            sql = self.maybe_comment(sql, expression)
3498            expression.pop_comments()
3499
3500        sql = self.prepend_ctes(expression, sql)
3501
3502        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3503            expression.set("exclude", None)
3504            subquery = expression.subquery(copy=False)
3505            star = exp.Star(except_=exclude)
3506            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3507
3508        if not self.SUPPORTS_SELECT_INTO and into:
3509            if into.args.get("temporary"):
3510                table_kind = " TEMPORARY"
3511            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3512                table_kind = " UNLOGGED"
3513            else:
3514                table_kind = ""
3515            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3516
3517        return sql
3518
3519    def schema_sql(self, expression: exp.Schema) -> str:
3520        this = self.sql(expression, "this")
3521        sql = self.schema_columns_sql(expression)
3522        return f"{this} {sql}" if this and sql else this or sql
3523
3524    def schema_columns_sql(self, expression: exp.Expr) -> str:
3525        if expression.expressions:
3526            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3527        return ""
3528
3529    def star_sql(self, expression: exp.Star) -> str:
3530        except_ = self.expressions(expression, key="except_", flat=True)
3531        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3532        replace = self.expressions(expression, key="replace", flat=True)
3533        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3534        rename = self.expressions(expression, key="rename", flat=True)
3535        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3536        ilike = self.sql(expression, "ilike")
3537        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3538        return f"*{ilike}{except_}{replace}{rename}"
3539
3540    def parameter_sql(self, expression: exp.Parameter) -> str:
3541        this = self.sql(expression, "this")
3542        return f"{self.PARAMETER_TOKEN}{this}"
3543
3544    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3545        this = self.sql(expression, "this")
3546        kind = expression.text("kind")
3547        if kind:
3548            kind = f"{kind}."
3549        return f"@@{kind}{this}"
3550
3551    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3552        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3553
3554    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3555        alias = self.sql(expression, "alias")
3556        alias = f"{sep}{alias}" if alias else ""
3557        sample = self.sql(expression, "sample")
3558        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3559            alias = f"{sample}{alias}"
3560
3561            # Set to None so it's not generated again by self.query_modifiers()
3562            expression.set("sample", None)
3563
3564        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3565        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3566        return self.prepend_ctes(expression, sql)
3567
3568    def qualify_sql(self, expression: exp.Qualify) -> str:
3569        this = self.indent(self.sql(expression, "this"))
3570        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3571
3572    def unnest_sql(self, expression: exp.Unnest) -> str:
3573        args = self.expressions(expression, flat=True)
3574
3575        alias = expression.args.get("alias")
3576        offset = expression.args.get("offset")
3577
3578        if self.UNNEST_WITH_ORDINALITY:
3579            if alias and isinstance(offset, exp.Expr):
3580                alias.append("columns", offset)
3581                expression.set("offset", None)
3582
3583        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3584            columns = alias.columns
3585            alias = self.sql(columns[0]) if columns else ""
3586        else:
3587            alias = self.sql(alias)
3588
3589        alias = f" AS {alias}" if alias else alias
3590        if self.UNNEST_WITH_ORDINALITY:
3591            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3592        else:
3593            if isinstance(offset, exp.Expr):
3594                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3595            elif offset:
3596                suffix = f"{alias} WITH OFFSET"
3597            else:
3598                suffix = alias
3599
3600        return f"UNNEST({args}){suffix}"
3601
3602    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3603        return ""
3604
3605    def where_sql(self, expression: exp.Where) -> str:
3606        this = self.indent(self.sql(expression, "this"))
3607        return f"{self.seg('WHERE')}{self.sep()}{this}"
3608
3609    def window_sql(self, expression: exp.Window) -> str:
3610        this = self.sql(expression, "this")
3611        partition = self.partition_by_sql(expression)
3612        order = expression.args.get("order")
3613        order = self.order_sql(order, flat=True) if order else ""
3614        spec = self.sql(expression, "spec")
3615        alias = self.sql(expression, "alias")
3616        over = self.sql(expression, "over") or "OVER"
3617
3618        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3619
3620        first = expression.args.get("first")
3621        if first is None:
3622            first = ""
3623        else:
3624            first = "FIRST" if first else "LAST"
3625
3626        if not partition and not order and not spec and alias:
3627            return f"{this} {alias}"
3628
3629        args = self.format_args(
3630            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3631        )
3632        return f"{this} ({args})"
3633
3634    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3635        partition = self.expressions(expression, key="partition_by", flat=True)
3636        return f"PARTITION BY {partition}" if partition else ""
3637
3638    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3639        kind = self.sql(expression, "kind")
3640        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3641        end = (
3642            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3643            or "CURRENT ROW"
3644        )
3645
3646        window_spec = f"{kind} BETWEEN {start} AND {end}"
3647
3648        exclude = self.sql(expression, "exclude")
3649        if exclude:
3650            if self.SUPPORTS_WINDOW_EXCLUDE:
3651                window_spec += f" EXCLUDE {exclude}"
3652            else:
3653                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3654
3655        return window_spec
3656
3657    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3658        this = self.sql(expression, "this")
3659        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3660        return f"{this} WITHIN GROUP ({expression_sql})"
3661
3662    def between_sql(self, expression: exp.Between) -> str:
3663        this = self.sql(expression, "this")
3664        low = self.sql(expression, "low")
3665        high = self.sql(expression, "high")
3666        symmetric = expression.args.get("symmetric")
3667
3668        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3669            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3670
3671        flag = (
3672            " SYMMETRIC"
3673            if symmetric
3674            else " ASYMMETRIC"
3675            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3676            else ""  # silently drop ASYMMETRIC – semantics identical
3677        )
3678        return f"{this} BETWEEN{flag} {low} AND {high}"
3679
3680    def bracket_offset_expressions(
3681        self, expression: exp.Bracket, index_offset: int | None = None
3682    ) -> list[exp.Expr]:
3683        if expression.args.get("json_access"):
3684            return expression.expressions
3685
3686        return apply_index_offset(
3687            expression.this,
3688            expression.expressions,
3689            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3690            dialect=self.dialect,
3691        )
3692
3693    def bracket_sql(self, expression: exp.Bracket) -> str:
3694        expressions = self.bracket_offset_expressions(expression)
3695        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3696        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3697
3698    def all_sql(self, expression: exp.All) -> str:
3699        this = self.sql(expression, "this")
3700        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3701            this = self.wrap(this)
3702        return f"ALL {this}"
3703
3704    def any_sql(self, expression: exp.Any) -> str:
3705        this = self.sql(expression, "this")
3706        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3707            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3708                this = self.wrap(this)
3709            return f"ANY{this}"
3710        return f"ANY {this}"
3711
3712    def exists_sql(self, expression: exp.Exists) -> str:
3713        return f"EXISTS{self.wrap(expression)}"
3714
3715    def case_sql(self, expression: exp.Case) -> str:
3716        this = self.sql(expression, "this")
3717        statements = [f"CASE {this}" if this else "CASE"]
3718
3719        for e in expression.args["ifs"]:
3720            statements.append(f"WHEN {self.sql(e, 'this')}")
3721            statements.append(f"THEN {self.sql(e, 'true')}")
3722
3723        default = self.sql(expression, "default")
3724
3725        if default:
3726            statements.append(f"ELSE {default}")
3727
3728        statements.append("END")
3729
3730        if self.pretty and self.too_wide(statements):
3731            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3732
3733        return " ".join(statements)
3734
3735    def constraint_sql(self, expression: exp.Constraint) -> str:
3736        this = self.sql(expression, "this")
3737        expressions = self.expressions(expression, flat=True)
3738        return f"CONSTRAINT {this} {expressions}"
3739
3740    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3741        order = expression.args.get("order")
3742        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3743        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3744
3745    def extract_sql(self, expression: exp.Extract) -> str:
3746        import sqlglot.dialects.dialect
3747
3748        this = (
3749            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3750            if self.NORMALIZE_EXTRACT_DATE_PARTS
3751            else expression.this
3752        )
3753        if self.EXTRACT_ALLOWS_QUOTES:
3754            this_sql = self.sql(this)
3755        elif isinstance(this, exp.WeekStart):
3756            this_sql = self.weekstart_name(this)
3757        else:
3758            this_sql = this.name
3759        expression_sql = self.sql(expression, "expression")
3760
3761        return f"EXTRACT({this_sql} FROM {expression_sql})"
3762
3763    def trim_sql(self, expression: exp.Trim) -> str:
3764        trim_type = self.sql(expression, "position")
3765
3766        if trim_type == "LEADING":
3767            func_name = "LTRIM"
3768        elif trim_type == "TRAILING":
3769            func_name = "RTRIM"
3770        else:
3771            func_name = "TRIM"
3772
3773        return self.func(func_name, expression.this, expression.expression)
3774
3775    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3776        args = expression.expressions
3777        if isinstance(expression, exp.ConcatWs):
3778            args = args[1:]  # Skip the delimiter
3779
3780        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3781            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3782
3783        concat_coalesce = (
3784            self.dialect.CONCAT_WS_COALESCE
3785            if isinstance(expression, exp.ConcatWs)
3786            else self.dialect.CONCAT_COALESCE
3787        )
3788
3789        if not concat_coalesce and expression.args.get("coalesce"):
3790
3791            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3792                if not e.type:
3793                    import sqlglot.optimizer.annotate_types
3794
3795                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3796
3797                if e.is_string or e.is_type(exp.DType.ARRAY):
3798                    return e
3799
3800                return exp.func("coalesce", e, exp.Literal.string(""))
3801
3802            args = [_wrap_with_coalesce(e) for e in args]
3803
3804        return args
3805
3806    def concat_sql(self, expression: exp.Concat) -> str:
3807        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3808            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3809            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3810            # instead of coalescing them to empty string.
3811            import sqlglot.dialects.dialect
3812
3813            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3814
3815        expressions = self.convert_concat_args(expression)
3816
3817        # Some dialects don't allow a single-argument CONCAT call
3818        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3819            return self.sql(expressions[0])
3820
3821        return self.func("CONCAT", *expressions)
3822
3823    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3824        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3825            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3826            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3827            all_args = expression.expressions
3828            expression.set("coalesce", True)
3829            return self.sql(
3830                exp.case()
3831                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3832                .else_(expression)
3833            )
3834
3835        return self.func(
3836            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3837        )
3838
3839    def check_sql(self, expression: exp.Check) -> str:
3840        this = self.sql(expression, key="this")
3841        return f"CHECK ({this})"
3842
3843    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3844        expressions = self.expressions(expression, flat=True)
3845        expressions = f" ({expressions})" if expressions else ""
3846        reference = self.sql(expression, "reference")
3847        reference = f" {reference}" if reference else ""
3848        delete = self.sql(expression, "delete")
3849        delete = f" ON DELETE {delete}" if delete else ""
3850        update = self.sql(expression, "update")
3851        update = f" ON UPDATE {update}" if update else ""
3852        options = self.expressions(expression, key="options", flat=True, sep=" ")
3853        options = f" {options}" if options else ""
3854        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3855
3856    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3857        this = self.sql(expression, "this")
3858        this = f" {this}" if this else ""
3859        expressions = self.expressions(expression, flat=True)
3860        include = self.sql(expression, "include")
3861        options = self.expressions(expression, key="options", flat=True, sep=" ")
3862        options = f" {options}" if options else ""
3863        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3864
3865    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3866        self.unsupported("TIMESERIES primary key columns are not supported")
3867        return self.sql(expression, "this")
3868
3869    def if_sql(self, expression: exp.If) -> str:
3870        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3871
3872    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3873        if self.MATCH_AGAINST_TABLE_PREFIX:
3874            expressions = []
3875            for expr in expression.expressions:
3876                if isinstance(expr, exp.Table):
3877                    expressions.append(f"TABLE {self.sql(expr)}")
3878                else:
3879                    expressions.append(expr)
3880        else:
3881            expressions = expression.expressions
3882
3883        modifier = expression.args.get("modifier")
3884        modifier = f" {modifier}" if modifier else ""
3885        return (
3886            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3887        )
3888
3889    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3890        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3891
3892    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3893        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3894
3895        if self.QUOTE_JSON_PATH:
3896            path = self.escape_str(path)
3897            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3898
3899        return path
3900
3901    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3902        if isinstance(expression, exp.JSONPathPart):
3903            transform = self.TRANSFORMS.get(expression.__class__)
3904            if not callable(transform):
3905                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3906                return ""
3907
3908            return transform(self, expression)
3909
3910        if isinstance(expression, int):
3911            return str(expression)
3912
3913        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3914            escaped = expression.replace("'", "\\'")
3915            escaped = f"'{escaped}'"
3916        else:
3917            escaped = expression.replace('"', '\\"')
3918            escaped = f'"{escaped}"'
3919
3920        return escaped
3921
3922    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3923        return f"{self.sql(expression, 'this')} FORMAT JSON"
3924
3925    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3926        # Output the Teradata column FORMAT override.
3927        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3928        this = self.sql(expression, "this")
3929        fmt = self.sql(expression, "format")
3930        return f"{this} (FORMAT {fmt})"
3931
3932    def _jsonobject_sql(
3933        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3934    ) -> str:
3935        null_handling = expression.args.get("null_handling")
3936        null_handling = f" {null_handling}" if null_handling else ""
3937
3938        unique_keys = expression.args.get("unique_keys")
3939        if unique_keys is not None:
3940            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3941        else:
3942            unique_keys = ""
3943
3944        return_type = self.sql(expression, "return_type")
3945        return_type = f" RETURNING {return_type}" if return_type else ""
3946        encoding = self.sql(expression, "encoding")
3947        encoding = f" ENCODING {encoding}" if encoding else ""
3948
3949        if not name:
3950            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3951
3952        return self.func(
3953            name,
3954            *expression.expressions,
3955            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3956        )
3957
3958    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3959        null_handling = expression.args.get("null_handling")
3960        null_handling = f" {null_handling}" if null_handling else ""
3961        return_type = self.sql(expression, "return_type")
3962        return_type = f" RETURNING {return_type}" if return_type else ""
3963        strict = " STRICT" if expression.args.get("strict") else ""
3964        return self.func(
3965            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3966        )
3967
3968    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3969        this = self.sql(expression, "this")
3970        order = self.sql(expression, "order")
3971        null_handling = expression.args.get("null_handling")
3972        null_handling = f" {null_handling}" if null_handling else ""
3973        return_type = self.sql(expression, "return_type")
3974        return_type = f" RETURNING {return_type}" if return_type else ""
3975        strict = " STRICT" if expression.args.get("strict") else ""
3976        return self.func(
3977            "JSON_ARRAYAGG",
3978            this,
3979            suffix=f"{order}{null_handling}{return_type}{strict})",
3980        )
3981
3982    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3983        path = self.sql(expression, "path")
3984        path = f" PATH {path}" if path else ""
3985        nested_schema = self.sql(expression, "nested_schema")
3986
3987        if nested_schema:
3988            return f"NESTED{path} {nested_schema}"
3989
3990        this = self.sql(expression, "this")
3991        kind = self.sql(expression, "kind")
3992        kind = f" {kind}" if kind else ""
3993        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3994
3995        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3996        return f"{this}{kind}{format_json}{path}{ordinality}"
3997
3998    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3999        return self.func("COLUMNS", *expression.expressions)
4000
4001    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4002        this = self.sql(expression, "this")
4003        path = self.sql(expression, "path")
4004        path = f", {path}" if path else ""
4005        error_handling = expression.args.get("error_handling")
4006        error_handling = f" {error_handling}" if error_handling else ""
4007        empty_handling = expression.args.get("empty_handling")
4008        empty_handling = f" {empty_handling}" if empty_handling else ""
4009        schema = self.sql(expression, "schema")
4010        return self.func(
4011            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4012        )
4013
4014    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4015        this = self.sql(expression, "this")
4016        kind = self.sql(expression, "kind")
4017        path = self.sql(expression, "path")
4018        path = f" {path}" if path else ""
4019        as_json = " AS JSON" if expression.args.get("as_json") else ""
4020        return f"{this} {kind}{path}{as_json}"
4021
4022    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4023        this = self.sql(expression, "this")
4024        path = self.sql(expression, "path")
4025        path = f", {path}" if path else ""
4026        expressions = self.expressions(expression)
4027        with_ = (
4028            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4029            if expressions
4030            else ""
4031        )
4032        return f"OPENJSON({this}{path}){with_}"
4033
4034    def in_sql(self, expression: exp.In) -> str:
4035        query = expression.args.get("query")
4036        unnest = expression.args.get("unnest")
4037        field = expression.args.get("field")
4038        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4039
4040        if query:
4041            in_sql = self.sql(query)
4042        elif unnest:
4043            in_sql = self.in_unnest_op(unnest)
4044        elif field:
4045            in_sql = self.sql(field)
4046        else:
4047            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4048
4049        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4050
4051    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4052        return f"(SELECT {self.sql(unnest)})"
4053
4054    def interval_sql(self, expression: exp.Interval) -> str:
4055        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4056            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4057            exp.AutoRefreshProperty,
4058        )
4059        interval_keyword = "INTERVAL" if include_keyword else ""
4060        unit_expression = expression.args.get("unit")
4061        unit = self.sql(unit_expression) if unit_expression else ""
4062        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4063            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4064        unit = f" {unit}" if unit else ""
4065
4066        if self.SINGLE_STRING_INTERVAL:
4067            this = expression.this.name if expression.this else ""
4068            if this:
4069                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4070                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4071                    return f"{interval_keyword}'{this}'{unit}"
4072                return f"{interval_keyword}'{this}{unit}'"
4073            return f"{interval_keyword}{unit}"
4074
4075        this = self.sql(expression, "this")
4076        if this:
4077            if not include_keyword and expression.this.is_string:
4078                this = expression.this.name
4079            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4080                this = f"({this})"
4081            if include_keyword:
4082                this = f" {this}"
4083
4084        return f"{interval_keyword}{this}{unit}"
4085
4086    def return_sql(self, expression: exp.Return) -> str:
4087        return f"RETURN {self.sql(expression, 'this')}"
4088
4089    def reference_sql(self, expression: exp.Reference) -> str:
4090        this = self.sql(expression, "this")
4091        expressions = self.expressions(expression, flat=True)
4092        expressions = f"({expressions})" if expressions else ""
4093        options = self.expressions(expression, key="options", flat=True, sep=" ")
4094        options = f" {options}" if options else ""
4095        return f"REFERENCES {this}{expressions}{options}"
4096
4097    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4098        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4099        parent = expression.parent
4100        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4101
4102        return self.func(
4103            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4104        )
4105
4106    def paren_sql(self, expression: exp.Paren) -> str:
4107        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4108        return f"({sql}{self.seg(')', sep='')}"
4109
4110    def neg_sql(self, expression: exp.Neg) -> str:
4111        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4112        this_sql = self.sql(expression, "this")
4113        sep = " " if this_sql[0] == "-" else ""
4114        return f"-{sep}{this_sql}"
4115
4116    def not_sql(self, expression: exp.Not) -> str:
4117        return f"NOT {self.sql(expression, 'this')}"
4118
4119    def alias_sql(self, expression: exp.Alias) -> str:
4120        alias = self.sql(expression, "alias")
4121        alias = f" AS {alias}" if alias else ""
4122        return f"{self.sql(expression, 'this')}{alias}"
4123
4124    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4125        alias = expression.args["alias"]
4126
4127        parent = expression.parent
4128        pivot = parent and parent.parent
4129
4130        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4131            identifier_alias = isinstance(alias, exp.Identifier)
4132            literal_alias = isinstance(alias, exp.Literal)
4133
4134            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4135                alias.replace(exp.Literal.string(alias.output_name))
4136            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4137                alias.replace(exp.to_identifier(alias.output_name))
4138
4139        return self.alias_sql(expression)
4140
4141    def aliases_sql(self, expression: exp.Aliases) -> str:
4142        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4143
4144    def atindex_sql(self, expression: exp.AtIndex) -> str:
4145        this = self.sql(expression, "this")
4146        index = self.sql(expression, "expression")
4147        return f"{this} AT {index}"
4148
4149    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4150        this = self.sql(expression, "this")
4151        zone = self.sql(expression, "zone")
4152        return f"{this} AT TIME ZONE {zone}"
4153
4154    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4155        this = self.sql(expression, "this")
4156        zone = self.sql(expression, "zone")
4157        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4158
4159    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4160        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4161
4162    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4163        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4164
4165    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4166        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4167
4168    def add_sql(self, expression: exp.Add) -> str:
4169        return self.binary(expression, "+")
4170
4171    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4172        return self.connector_sql(expression, "AND", stack)
4173
4174    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4175        return self.connector_sql(expression, "OR", stack)
4176
4177    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4178        return self.connector_sql(expression, "XOR", stack)
4179
4180    def connector_sql(
4181        self,
4182        expression: exp.Connector,
4183        op: str,
4184        stack: list[str | exp.Expr] | None = None,
4185    ) -> str:
4186        if stack is not None:
4187            stack.append(expression.right)
4188            if expression.comments and self.comments:
4189                op = self.maybe_comment(op, comments=expression.comments)
4190
4191            stack.extend((op, expression.left))
4192            return op
4193
4194        stack = [expression]
4195        sqls: list[str] = []
4196        ops = set()
4197
4198        while stack:
4199            node = stack.pop()
4200            if isinstance(node, exp.Connector):
4201                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4202            else:
4203                sql = self.sql(node)
4204                if sqls and sqls[-1] in ops:
4205                    sqls[-1] += f" {sql}"
4206                else:
4207                    sqls.append(sql)
4208
4209        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4210        return sep.join(sqls)
4211
4212    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4213        return self.binary(expression, "&")
4214
4215    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4216        return self.binary(expression, "<<")
4217
4218    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4219        return f"~{self.sql(expression, 'this')}"
4220
4221    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4222        return self.binary(expression, "|")
4223
4224    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4225        return self.binary(expression, ">>")
4226
4227    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4228        return self.binary(expression, "^")
4229
4230    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4231        format_sql = self.sql(expression, "format")
4232        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4233        to_sql = self.sql(expression, "to")
4234        to_sql = f" {to_sql}" if to_sql else ""
4235        action = self.sql(expression, "action")
4236        action = f" {action}" if action else ""
4237        default = self.sql(expression, "default")
4238        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4239        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4240
4241    # Base implementation that excludes safe, zone, and target_type metadata args
4242    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4243        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4244
4245    # Base implementation that excludes the safe and default_year metadata args
4246    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4247        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4248
4249    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4250        return self.func(
4251            "PARSE_DATETIME",
4252            expression.this,
4253            expression.args.get("format"),
4254            expression.args.get("zone"),
4255        )
4256
4257    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4258        zone = self.sql(expression, "this")
4259        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4260
4261    def collate_sql(self, expression: exp.Collate) -> str:
4262        if self.COLLATE_IS_FUNC:
4263            return self.function_fallback_sql(expression)
4264        return self.binary(expression, "COLLATE")
4265
4266    def command_sql(self, expression: exp.Command) -> str:
4267        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4268
4269    def comment_sql(self, expression: exp.Comment) -> str:
4270        this = self.sql(expression, "this")
4271        kind = expression.args["kind"]
4272        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4273        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4274        expression_sql = self.sql(expression, "expression")
4275        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4276
4277    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4278        this = self.sql(expression, "this")
4279        delete = " DELETE" if expression.args.get("delete") else ""
4280        recompress = self.sql(expression, "recompress")
4281        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4282        to_disk = self.sql(expression, "to_disk")
4283        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4284        to_volume = self.sql(expression, "to_volume")
4285        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4286        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4287
4288    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4289        where = self.sql(expression, "where")
4290        group = self.sql(expression, "group")
4291        aggregates = self.expressions(expression, key="aggregates")
4292        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4293
4294        if not (where or group or aggregates) and len(expression.expressions) == 1:
4295            return f"TTL {self.expressions(expression, flat=True)}"
4296
4297        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4298
4299    def transaction_sql(self, expression: exp.Transaction) -> str:
4300        modes = self.expressions(expression, key="modes")
4301        modes = f" {modes}" if modes else ""
4302        return f"BEGIN{modes}"
4303
4304    def commit_sql(self, expression: exp.Commit) -> str:
4305        chain = expression.args.get("chain")
4306        if chain is not None:
4307            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4308
4309        return f"COMMIT{chain or ''}"
4310
4311    def rollback_sql(self, expression: exp.Rollback) -> str:
4312        savepoint = expression.args.get("savepoint")
4313        savepoint = f" TO {savepoint}" if savepoint else ""
4314        return f"ROLLBACK{savepoint}"
4315
4316    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4317        this = self.sql(expression, "this")
4318
4319        exists = ""
4320        if expression.args.get("exists"):
4321            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4322                exists = " IF EXISTS"
4323            else:
4324                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4325
4326        dtype = self.sql(expression, "dtype")
4327        if dtype:
4328            collate = self.sql(expression, "collate")
4329            collate = f" COLLATE {collate}" if collate else ""
4330            using = self.sql(expression, "using")
4331            using = f" USING {using}" if using else ""
4332            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4333            null_constraint = self._alter_column_null_constraint_sql(expression)
4334
4335            return (
4336                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4337                f"{collate}{using}{null_constraint}"
4338            )
4339
4340        default = self.sql(expression, "default")
4341        if default:
4342            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4343
4344        comment = self.sql(expression, "comment")
4345        if comment:
4346            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4347
4348        visible = expression.args.get("visible")
4349        if visible:
4350            return f"ALTER COLUMN{exists} {this} SET {visible}"
4351
4352        allow_null = expression.args.get("allow_null")
4353        drop = expression.args.get("drop")
4354
4355        if not drop and not allow_null:
4356            self.unsupported("Unsupported ALTER COLUMN syntax")
4357
4358        if allow_null is not None:
4359            keyword = "DROP" if drop else "SET"
4360            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4361
4362        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4363
4364    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4365        allow_null = expression.args.get("allow_null")
4366        if allow_null is None:
4367            return ""
4368
4369        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4370            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4371            return ""
4372
4373        return " NULL" if allow_null else " NOT NULL"
4374
4375    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4376        this = self.sql(expression, "this")
4377        rename_from = self.sql(expression, "rename_from")
4378        if rename_from:
4379            if not self.SUPPORTS_CHANGE_COLUMN:
4380                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4381            return f"CHANGE COLUMN {rename_from} {this}"
4382        if not self.SUPPORTS_MODIFY_COLUMN:
4383            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4384        return f"MODIFY COLUMN {this}"
4385
4386    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4387        this = self.sql(expression, "this")
4388
4389        visible = expression.args.get("visible")
4390        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4391
4392        return f"ALTER INDEX {this} {visible_sql}"
4393
4394    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4395        this = self.sql(expression, "this")
4396        if not isinstance(expression.this, exp.Var):
4397            this = f"KEY DISTKEY {this}"
4398        return f"ALTER DISTSTYLE {this}"
4399
4400    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4401        compound = " COMPOUND" if expression.args.get("compound") else ""
4402        this = self.sql(expression, "this")
4403        expressions = self.expressions(expression, flat=True)
4404        expressions = f"({expressions})" if expressions else ""
4405        return f"ALTER{compound} SORTKEY {this or expressions}"
4406
4407    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4408        if not self.RENAME_TABLE_WITH_DB:
4409            # Remove db from tables
4410            expression = expression.transform(
4411                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4412            ).assert_is(exp.AlterRename)
4413        this = self.sql(expression, "this")
4414        to_kw = " TO" if include_to else ""
4415        return f"RENAME{to_kw} {this}"
4416
4417    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4418        exists = " IF EXISTS" if expression.args.get("exists") else ""
4419        old_column = self.sql(expression, "this")
4420        new_column = self.sql(expression, "to")
4421        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4422
4423    def alterset_sql(self, expression: exp.AlterSet) -> str:
4424        exprs = self.expressions(expression, flat=True)
4425        if self.ALTER_SET_WRAPPED:
4426            exprs = f"({exprs})"
4427
4428        return f"SET {exprs}"
4429
4430    def alter_sql(self, expression: exp.Alter) -> str:
4431        actions = expression.actions
4432
4433        if (
4434            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4435            and actions
4436            and isinstance(actions[0], exp.ColumnDef)
4437        ):
4438            actions_sql = self.expressions(expression, key="actions", flat=True)
4439            actions_sql = f"ADD {actions_sql}"
4440        else:
4441            actions_list = []
4442            for action in actions:
4443                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4444                    action_sql = self.add_column_sql(action)
4445                else:
4446                    action_sql = self.sql(action)
4447                    if isinstance(action, exp.Query):
4448                        action_sql = f"AS {action_sql}"
4449
4450                actions_list.append(action_sql)
4451
4452            actions_sql = self.format_args(*actions_list).lstrip("\n")
4453
4454        iceberg = (
4455            "ICEBERG "
4456            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4457            else ""
4458        )
4459        exists = " IF EXISTS" if expression.args.get("exists") else ""
4460        on_cluster = self.sql(expression, "cluster")
4461        on_cluster = f" {on_cluster}" if on_cluster else ""
4462        only = " ONLY" if expression.args.get("only") else ""
4463        options = expression.args.get("options") or []
4464        options_sql = self.expressions(
4465            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4466        )
4467        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4468        partitions_sql = self.expressions(
4469            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4470            sep=" ",
4471            flat=True,
4472        )
4473        if partitions_sql and (actions_sql or options_sql):
4474            partitions_sql = f" {partitions_sql}"
4475        kind = self.sql(expression, "kind")
4476        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4477        check = " WITH CHECK" if expression.args.get("check") else ""
4478        cascade = (
4479            " CASCADE"
4480            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4481            else ""
4482        )
4483        this = self.sql(expression, "this")
4484        this = f" {this}" if this else ""
4485
4486        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4487
4488    def altersession_sql(self, expression: exp.AlterSession) -> str:
4489        items_sql = self.expressions(expression, flat=True)
4490        keyword = "UNSET" if expression.args.get("unset") else "SET"
4491        return f"{keyword} {items_sql}"
4492
4493    def add_column_sql(self, expression: exp.Expr) -> str:
4494        sql = self.sql(expression)
4495        if isinstance(expression, exp.Schema):
4496            column_text = " COLUMNS"
4497        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4498            column_text = " COLUMN"
4499        else:
4500            column_text = ""
4501
4502        return f"ADD{column_text} {sql}"
4503
4504    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4505        expressions = self.expressions(expression)
4506        exists = " IF EXISTS " if expression.args.get("exists") else " "
4507        return f"DROP{exists}{expressions}"
4508
4509    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4510        return "DROP PRIMARY KEY"
4511
4512    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4513        return f"ADD {self.expressions(expression, indent=False)}"
4514
4515    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4516        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4517        location = self.sql(expression, "location")
4518        location = f" {location}" if location else ""
4519        return f"ADD {exists}{self.sql(expression.this)}{location}"
4520
4521    def distinct_sql(self, expression: exp.Distinct) -> str:
4522        this = self.expressions(expression, flat=True)
4523
4524        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4525            case = exp.case()
4526            for arg in expression.expressions:
4527                case = case.when(arg.is_(exp.null()), exp.null())
4528            this = self.sql(case.else_(f"({this})"))
4529
4530        this = f" {this}" if this else ""
4531
4532        on = self.sql(expression, "on")
4533        on = f" ON {on}" if on else ""
4534        return f"DISTINCT{this}{on}"
4535
4536    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4537        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4538
4539    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4540        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4541
4542    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4543        this_sql = self.sql(expression, "this")
4544        expression_sql = self.sql(expression, "expression")
4545        kind = "MAX" if expression.args.get("max") else "MIN"
4546        return f"{this_sql} HAVING {kind} {expression_sql}"
4547
4548    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4549        return self._truncated_div_sql(expression.this, expression.expression)
4550
4551    def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4552        div: exp.Expr = exp.Div(this=this, expression=divisor)
4553        if self.CAST_TO_INT_ROUNDS:
4554            div = exp.Trunc(this=div)
4555
4556        return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4557
4558    def dpipe_sql(self, expression: exp.DPipe) -> str:
4559        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4560            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4561        return self.binary(expression, "||")
4562
4563    def div_sql(self, expression: exp.Div) -> str:
4564        l, r = expression.left, expression.right
4565
4566        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4567            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4568
4569        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4570            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4571                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4572
4573        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4574            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4575                # Native integer division depends on the target's operand types, which may differ
4576                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4577                # integer literals or casts
4578                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4579                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4580                ):
4581                    return self.sql(exp.IntDiv(this=l, expression=r))
4582                return self._truncated_div_sql(l, r)
4583
4584        return self.binary(expression, "/")
4585
4586    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4587        n = exp._wrap(expression.this, exp.Binary)
4588        d = exp._wrap(expression.expression, exp.Binary)
4589        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4590
4591    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4592        return self.binary(expression, "OVERLAPS")
4593
4594    def distance_sql(self, expression: exp.Distance) -> str:
4595        return self.binary(expression, "<->")
4596
4597    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4598        return self.binary(expression, "<<->>")
4599
4600    def dot_sql(self, expression: exp.Dot) -> str:
4601        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4602
4603    def eq_sql(self, expression: exp.EQ) -> str:
4604        return self.binary(expression, "=")
4605
4606    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4607        return self.binary(expression, ":=")
4608
4609    def escape_sql(self, expression: exp.Escape) -> str:
4610        this = expression.this
4611        if (
4612            isinstance(this, (exp.Like, exp.ILike))
4613            and isinstance(this.expression, (exp.All, exp.Any))
4614            and not self.SUPPORTS_LIKE_QUANTIFIERS
4615        ):
4616            return self._like_sql(this, escape=expression)
4617        return self.binary(expression, "ESCAPE")
4618
4619    def glob_sql(self, expression: exp.Glob) -> str:
4620        return self.binary(expression, "GLOB")
4621
4622    def gt_sql(self, expression: exp.GT) -> str:
4623        return self.binary(expression, ">")
4624
4625    def gte_sql(self, expression: exp.GTE) -> str:
4626        return self.binary(expression, ">=")
4627
4628    def is_sql(self, expression: exp.Is) -> str:
4629        negate = expression.args.get("negate")
4630        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4631            positive = bool(expression.expression.this) != bool(negate)
4632            return self.sql(expression.this if positive else exp.not_(expression.this))
4633        return self.binary(expression, "IS NOT" if negate else "IS")
4634
4635    def _like_sql(
4636        self,
4637        expression: exp.Like | exp.ILike,
4638        escape: exp.Escape | None = None,
4639    ) -> str:
4640        this = expression.this
4641        rhs = expression.expression
4642
4643        if isinstance(expression, exp.Like):
4644            exp_class: type[exp.Like | exp.ILike] = exp.Like
4645            op = "LIKE"
4646        else:
4647            exp_class = exp.ILike
4648            op = "ILIKE"
4649
4650        if expression.args.get("negate"):
4651            op = f"NOT {op}"
4652
4653        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4654            exprs = rhs.this.unnest()
4655
4656            if isinstance(exprs, exp.Tuple):
4657                exprs = exprs.expressions
4658            else:
4659                exprs = [exprs]
4660
4661            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4662
4663            def _make_like(expr: exp.Expression) -> exp.Expression:
4664                like: exp.Expression = exp_class(
4665                    this=this, expression=expr, negate=expression.args.get("negate")
4666                )
4667                if escape:
4668                    like = exp.Escape(this=like, expression=escape.expression.copy())
4669                return like
4670
4671            like_expr: exp.Expr = _make_like(exprs[0])
4672            for expr in exprs[1:]:
4673                like_expr = connective(like_expr, _make_like(expr), copy=False)
4674
4675            parent = escape.parent if escape else expression.parent
4676            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4677                parent, exp.Condition
4678            ):
4679                like_expr = exp.paren(like_expr, copy=False)
4680
4681            return self.sql(like_expr)
4682
4683        return self.binary(expression, op)
4684
4685    def like_sql(self, expression: exp.Like) -> str:
4686        return self._like_sql(expression)
4687
4688    def ilike_sql(self, expression: exp.ILike) -> str:
4689        return self._like_sql(expression)
4690
4691    def match_sql(self, expression: exp.Match) -> str:
4692        return self.binary(expression, "MATCH")
4693
4694    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4695        return self.binary(expression, "SIMILAR TO")
4696
4697    def lt_sql(self, expression: exp.LT) -> str:
4698        return self.binary(expression, "<")
4699
4700    def lte_sql(self, expression: exp.LTE) -> str:
4701        return self.binary(expression, "<=")
4702
4703    def mod_sql(self, expression: exp.Mod) -> str:
4704        this = self.sql(expression, "this")
4705        expr = self.sql(expression, "expression")
4706        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4707
4708        parent = expression.parent
4709        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4710            return f"({sql})"
4711
4712        return sql
4713
4714    def mul_sql(self, expression: exp.Mul) -> str:
4715        return self.binary(expression, "*")
4716
4717    def neq_sql(self, expression: exp.NEQ) -> str:
4718        return self.binary(expression, "<>")
4719
4720    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4721        return self.binary(expression, "IS NOT DISTINCT FROM")
4722
4723    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4724        return self.binary(expression, "IS DISTINCT FROM")
4725
4726    def sub_sql(self, expression: exp.Sub) -> str:
4727        return self.binary(expression, "-")
4728
4729    def trycast_sql(self, expression: exp.TryCast) -> str:
4730        return self.cast_sql(expression, safe_prefix="TRY_")
4731
4732    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4733        return self.cast_sql(expression)
4734
4735    def try_sql(self, expression: exp.Try) -> str:
4736        if not self.TRY_SUPPORTED:
4737            self.unsupported("Unsupported TRY function")
4738            return self.sql(expression, "this")
4739
4740        return self.func("TRY", expression.this)
4741
4742    def log_sql(self, expression: exp.Log) -> str:
4743        this = expression.this
4744        expr = expression.expression
4745
4746        if self.dialect.LOG_BASE_FIRST is False:
4747            this, expr = expr, this
4748        elif self.dialect.LOG_BASE_FIRST is None and expr:
4749            if this.name in ("2", "10"):
4750                return self.func(f"LOG{this.name}", expr)
4751
4752            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4753
4754        return self.func("LOG", this, expr)
4755
4756    def use_sql(self, expression: exp.Use) -> str:
4757        kind = self.sql(expression, "kind")
4758        kind = f" {kind}" if kind else ""
4759        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4760        this = f" {this}" if this else ""
4761        return f"USE{kind}{this}"
4762
4763    def binary(self, expression: exp.Binary, op: str) -> str:
4764        sqls: list[str] = []
4765        stack: list[None | str | exp.Expr] = [expression]
4766        binary_type = type(expression)
4767
4768        while stack:
4769            node = stack.pop()
4770
4771            if type(node) is binary_type:
4772                op_func = node.args.get("operator")
4773                if op_func:
4774                    op = f"OPERATOR({self.sql(op_func)})"
4775
4776                stack.append(node.args.get("expression"))
4777                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4778                stack.append(node.args.get("this"))
4779            else:
4780                sqls.append(self.sql(node))
4781
4782        return "".join(sqls)
4783
4784    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4785        to_clause = self.sql(expression, "to")
4786        if to_clause:
4787            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4788
4789        return self.function_fallback_sql(expression)
4790
4791    def function_fallback_sql(self, expression: exp.Func) -> str:
4792        args = []
4793
4794        for key in expression.arg_types:
4795            arg_value = expression.args.get(key)
4796
4797            if isinstance(arg_value, list):
4798                for value in arg_value:
4799                    args.append(value)
4800            elif arg_value is not None:
4801                args.append(arg_value)
4802
4803        return self.func(self.function_name(expression), *args)
4804
4805    def function_name(self, expression: exp.Func) -> str:
4806        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4807        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4808
4809    def func(
4810        self,
4811        name: str,
4812        *args: t.Any,
4813        prefix: str = "(",
4814        suffix: str = ")",
4815        normalize: bool = True,
4816    ) -> str:
4817        name = self.normalize_func(name) if normalize else name
4818        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4819
4820    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4821        arg_sqls = tuple(
4822            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4823        )
4824        if self.pretty and self.too_wide(arg_sqls):
4825            return self.indent(
4826                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4827            )
4828        return sep.join(arg_sqls)
4829
4830    def too_wide(self, args: t.Iterable) -> bool:
4831        return sum(len(arg) for arg in args) > self.max_text_width
4832
4833    def format_time(
4834        self,
4835        expression: exp.Expr,
4836        inverse_time_mapping: dict[str, str] | None = None,
4837        inverse_time_trie: dict | None = None,
4838    ) -> str | None:
4839        return format_time(
4840            self.sql(expression, "format"),
4841            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4842            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4843        )
4844
4845    def expressions(
4846        self,
4847        expression: exp.Expr | None = None,
4848        key: str | None = None,
4849        sqls: t.Collection[str | exp.Expr] | None = None,
4850        flat: bool = False,
4851        indent: bool = True,
4852        skip_first: bool = False,
4853        skip_last: bool = False,
4854        sep: str = ", ",
4855        prefix: str = "",
4856        dynamic: bool = False,
4857        new_line: bool = False,
4858    ) -> str:
4859        expressions = expression.args.get(key or "expressions") if expression else sqls
4860
4861        if not expressions:
4862            return ""
4863
4864        if flat:
4865            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4866
4867        num_sqls = len(expressions)
4868        result_sqls = []
4869
4870        for i, e in enumerate(expressions):
4871            sql = self.sql(e, comment=False)
4872            if not sql:
4873                continue
4874
4875            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4876
4877            if self.pretty:
4878                if self.leading_comma:
4879                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4880                else:
4881                    result_sqls.append(
4882                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4883                    )
4884            else:
4885                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4886
4887        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4888            if new_line:
4889                result_sqls.insert(0, "")
4890                result_sqls.append("")
4891            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4892        else:
4893            result_sql = "".join(result_sqls)
4894
4895        return (
4896            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4897            if indent
4898            else result_sql
4899        )
4900
4901    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4902        flat = flat or isinstance(expression.parent, exp.Properties)
4903        expressions_sql = self.expressions(expression, flat=flat)
4904        if flat:
4905            return f"{op} {expressions_sql}"
4906        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4907
4908    def naked_property(self, expression: exp.Property) -> str:
4909        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4910        if not property_name:
4911            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4912        return f"{property_name} {self.sql(expression, 'this')}"
4913
4914    def tag_sql(self, expression: exp.Tag) -> str:
4915        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4916
4917    def token_sql(self, token_type: TokenType) -> str:
4918        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4919
4920    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4921        this = self.sql(expression, "this")
4922        expressions = self.no_identify(self.expressions, expression)
4923        expressions = (
4924            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4925        )
4926        return f"{this}{expressions}" if expressions.strip() != "" else this
4927
4928    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4929        return self.expressions(expression, flat=True)
4930
4931    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4932        params = self.no_identify(self.expressions, expression, flat=True)
4933        body = self.sql(expression, "this")
4934        prefix = "TABLE " if expression.args.get("is_table") else ""
4935        return f"({params}) AS {prefix}{body}"
4936
4937    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4938        this = self.sql(expression, "this")
4939        expressions = self.expressions(expression, flat=True)
4940        return f"{this}({expressions})"
4941
4942    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4943        return self.binary(expression, "=>")
4944
4945    def when_sql(self, expression: exp.When) -> str:
4946        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4947        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4948        condition = self.sql(expression, "condition")
4949        condition = f" AND {condition}" if condition else ""
4950
4951        then_expression = expression.args.get("then")
4952        if isinstance(then_expression, exp.Insert):
4953            this = self.sql(then_expression, "this")
4954            this = f"INSERT {this}" if this else "INSERT"
4955            then = self.sql(then_expression, "expression")
4956            then = f"{this} VALUES {then}" if then else this
4957        elif isinstance(then_expression, exp.Update):
4958            if isinstance(then_expression.args.get("expressions"), exp.Star):
4959                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4960            else:
4961                expressions_sql = self.expressions(then_expression)
4962                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4963        else:
4964            then = self.sql(then_expression)
4965
4966        if isinstance(then_expression, (exp.Insert, exp.Update)):
4967            where = self.sql(then_expression, "where")
4968            if where and not self.SUPPORTS_MERGE_WHERE:
4969                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4970                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4971                where = ""
4972            then = f"{then}{where}"
4973        return f"WHEN {matched}{source}{condition} THEN {then}"
4974
4975    def whens_sql(self, expression: exp.Whens) -> str:
4976        return self.expressions(expression, sep=" ", indent=False)
4977
4978    def merge_sql(self, expression: exp.Merge) -> str:
4979        table = expression.this
4980        table_alias = ""
4981
4982        hints = table.args.get("hints")
4983        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4984            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4985            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4986
4987        this = self.sql(table)
4988        using = f"USING {self.sql(expression, 'using')}"
4989        whens = self.sql(expression, "whens")
4990
4991        on = self.sql(expression, "on")
4992        on = f"ON {on}" if on else ""
4993
4994        if not on:
4995            on = self.expressions(expression, key="using_cond")
4996            on = f"USING ({on})" if on else ""
4997
4998        returning = self.sql(expression, "returning")
4999        if returning:
5000            whens = f"{whens}{returning}"
5001
5002        sep = self.sep()
5003
5004        return self.prepend_ctes(
5005            expression,
5006            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5007        )
5008
5009    @unsupported_args("format")
5010    def tochar_sql(self, expression: exp.ToChar) -> str:
5011        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
5012
5013    @unsupported_args("default")
5014    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5015        if not self.SUPPORTS_TO_NUMBER:
5016            self.unsupported("Unsupported TO_NUMBER function")
5017            return self._tonumber_cast_sql(expression)
5018
5019        fmt = expression.args.get("format")
5020        if not fmt:
5021            self.unsupported("Conversion format is required for TO_NUMBER")
5022            return self._tonumber_cast_sql(expression)
5023
5024        return self.func("TO_NUMBER", expression.this, fmt)
5025
5026    def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
5027        if expression.args.get("safe"):
5028            return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
5029        return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
5030
5031    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5032        this = self.sql(expression, "this")
5033        kind = self.sql(expression, "kind")
5034        settings_sql = self.expressions(expression, key="settings", sep=" ")
5035        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5036        return f"{this}({kind}{args})"
5037
5038    def dictrange_sql(self, expression: exp.DictRange) -> str:
5039        this = self.sql(expression, "this")
5040        max = self.sql(expression, "max")
5041        min = self.sql(expression, "min")
5042        return f"{this}(MIN {min} MAX {max})"
5043
5044    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5045        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
5046
5047    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5048        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
5049
5050    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
5051    def uniquekeyproperty_sql(
5052        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5053    ) -> str:
5054        return f"{prefix} ({self.expressions(expression, flat=True)})"
5055
5056    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
5057    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5058        expressions = self.expressions(expression, flat=True)
5059        expressions = f" {self.wrap(expressions)}" if expressions else ""
5060        buckets = self.sql(expression, "buckets")
5061        kind = self.sql(expression, "kind")
5062        buckets = f" BUCKETS {buckets}" if buckets else ""
5063        order = self.sql(expression, "order")
5064        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
5065
5066    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5067        return ""
5068
5069    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5070        expressions = self.expressions(expression, key="expressions", flat=True)
5071        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5072        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5073        buckets = self.sql(expression, "buckets")
5074        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5075
5076    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5077        this = self.sql(expression, "this")
5078        having = self.sql(expression, "having")
5079
5080        if having:
5081            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5082
5083        return self.func("ANY_VALUE", this)
5084
5085    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5086        transform = self.func("TRANSFORM", *expression.expressions)
5087        row_format_before = self.sql(expression, "row_format_before")
5088        row_format_before = f" {row_format_before}" if row_format_before else ""
5089        record_writer = self.sql(expression, "record_writer")
5090        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5091        using = f" USING {self.sql(expression, 'command_script')}"
5092        schema = self.sql(expression, "schema")
5093        schema = f" AS {schema}" if schema else ""
5094        row_format_after = self.sql(expression, "row_format_after")
5095        row_format_after = f" {row_format_after}" if row_format_after else ""
5096        record_reader = self.sql(expression, "record_reader")
5097        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5098        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5099
5100    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5101        key_block_size = self.sql(expression, "key_block_size")
5102        if key_block_size:
5103            return f"KEY_BLOCK_SIZE = {key_block_size}"
5104
5105        using = self.sql(expression, "using")
5106        if using:
5107            return f"USING {using}"
5108
5109        parser = self.sql(expression, "parser")
5110        if parser:
5111            return f"WITH PARSER {parser}"
5112
5113        comment = self.sql(expression, "comment")
5114        if comment:
5115            return f"COMMENT {comment}"
5116
5117        visible = expression.args.get("visible")
5118        if visible is not None:
5119            return "VISIBLE" if visible else "INVISIBLE"
5120
5121        engine_attr = self.sql(expression, "engine_attr")
5122        if engine_attr:
5123            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5124
5125        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5126        if secondary_engine_attr:
5127            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5128
5129        self.unsupported("Unsupported index constraint option.")
5130        return ""
5131
5132    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5133        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5134        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5135
5136    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5137        kind = self.sql(expression, "kind")
5138        kind = f"{kind} INDEX" if kind else "INDEX"
5139        this = self.sql(expression, "this")
5140        this = f" {this}" if this else ""
5141        index_type = self.sql(expression, "index_type")
5142        index_type = f" USING {index_type}" if index_type else ""
5143        expressions = self.expressions(expression, flat=True)
5144        expressions = f" ({expressions})" if expressions else ""
5145        options = self.expressions(expression, key="options", sep=" ")
5146        options = f" {options}" if options else ""
5147        return f"{kind}{this}{index_type}{expressions}{options}"
5148
5149    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5150        if self.NVL2_SUPPORTED:
5151            return self.function_fallback_sql(expression)
5152
5153        case = exp.Case().when(
5154            expression.this.is_(exp.null()).not_(copy=False),
5155            expression.args["true"],
5156            copy=False,
5157        )
5158        else_cond = expression.args.get("false")
5159        if else_cond:
5160            case.else_(else_cond, copy=False)
5161
5162        return self.sql(case)
5163
5164    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5165        if expression.args.get("from_first") is False:
5166            self.unsupported("NTH_VALUE FROM LAST is not supported")
5167
5168        return self.function_fallback_sql(expression)
5169
5170    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5171        this = self.sql(expression, "this")
5172        expr = self.sql(expression, "expression")
5173        position = self.sql(expression, "position")
5174        position = f", {position}" if position else ""
5175        iterator = self.sql(expression, "iterator")
5176        condition = self.sql(expression, "condition")
5177        condition = f" IF {condition}" if condition else ""
5178        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5179
5180    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5181        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5182
5183    def opclass_sql(self, expression: exp.Opclass) -> str:
5184        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5185
5186    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5187        model = self.sql(expression, "this")
5188        model = f"MODEL {model}"
5189        expr = expression.expression
5190        if expr:
5191            expr_sql = self.sql(expression, "expression")
5192            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5193        else:
5194            expr_sql = None
5195
5196        parameters = self.sql(expression, "params_struct") or None
5197
5198        return self.func(name, model, expr_sql, parameters)
5199
5200    def predict_sql(self, expression: exp.Predict) -> str:
5201        return self._ml_sql(expression, "PREDICT")
5202
5203    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5204        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5205        return self._ml_sql(expression, name)
5206
5207    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5208        return self._ml_sql(expression, "GENERATE_TEXT")
5209
5210    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5211        return self._ml_sql(expression, "GENERATE_TABLE")
5212
5213    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5214        return self._ml_sql(expression, "GENERATE_BOOL")
5215
5216    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5217        return self._ml_sql(expression, "GENERATE_INT")
5218
5219    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5220        return self._ml_sql(expression, "GENERATE_DOUBLE")
5221
5222    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5223        return self._ml_sql(expression, "TRANSLATE")
5224
5225    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5226        return self._ml_sql(expression, "FORECAST")
5227
5228    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5229        this_sql = self.sql(expression, "this")
5230        if isinstance(expression.this, exp.Table):
5231            this_sql = f"TABLE {this_sql}"
5232
5233        return self.func(
5234            "FORECAST",
5235            this_sql,
5236            expression.args.get("data_col"),
5237            expression.args.get("timestamp_col"),
5238            expression.args.get("model"),
5239            expression.args.get("id_cols"),
5240            expression.args.get("horizon"),
5241            expression.args.get("forecast_end_timestamp"),
5242            expression.args.get("confidence_level"),
5243            expression.args.get("output_historical_time_series"),
5244            expression.args.get("context_window"),
5245        )
5246
5247    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5248        this_sql = self.sql(expression, "this")
5249        if isinstance(expression.this, exp.Table):
5250            this_sql = f"TABLE {this_sql}"
5251
5252        return self.func(
5253            "FEATURES_AT_TIME",
5254            this_sql,
5255            expression.args.get("time"),
5256            expression.args.get("num_rows"),
5257            expression.args.get("ignore_feature_nulls"),
5258        )
5259
5260    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5261        this_sql = self.sql(expression, "this")
5262        if isinstance(expression.this, exp.Table):
5263            this_sql = f"TABLE {this_sql}"
5264
5265        query_table = self.sql(expression, "query_table")
5266        if isinstance(expression.args["query_table"], exp.Table):
5267            query_table = f"TABLE {query_table}"
5268
5269        return self.func(
5270            "VECTOR_SEARCH",
5271            this_sql,
5272            expression.args.get("column_to_search"),
5273            query_table,
5274            expression.args.get("query_column_to_search"),
5275            expression.args.get("top_k"),
5276            expression.args.get("distance_type"),
5277            expression.args.get("options"),
5278        )
5279
5280    def forin_sql(self, expression: exp.ForIn) -> str:
5281        this = self.sql(expression, "this")
5282        expression_sql = self.sql(expression, "expression")
5283        return f"FOR {this} DO {expression_sql}"
5284
5285    def refresh_sql(self, expression: exp.Refresh) -> str:
5286        this = self.sql(expression, "this")
5287        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5288        return f"REFRESH {kind}{this}"
5289
5290    def toarray_sql(self, expression: exp.ToArray) -> str:
5291        arg = expression.this
5292        if not arg.type:
5293            import sqlglot.optimizer.annotate_types
5294
5295            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5296
5297        if arg.is_type(exp.DType.ARRAY):
5298            return self.sql(arg)
5299
5300        cond_for_null = arg.is_(exp.null())
5301        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5302
5303    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5304        this = expression.this
5305        time_format = self.format_time(expression)
5306
5307        if time_format:
5308            return self.sql(
5309                exp.cast(
5310                    exp.StrToTime(this=this, format=expression.args["format"]),
5311                    exp.DType.TIME,
5312                )
5313            )
5314
5315        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5316            return self.sql(this)
5317
5318        return self.sql(exp.cast(this, exp.DType.TIME))
5319
5320    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5321        this = expression.this
5322        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5323            return self.sql(this)
5324
5325        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5326
5327    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5328        this = expression.this
5329        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5330            return self.sql(this)
5331
5332        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5333
5334    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5335        this = expression.this
5336        time_format = self.format_time(expression)
5337        safe = expression.args.get("safe")
5338        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5339            return self.sql(
5340                exp.cast(
5341                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5342                    exp.DType.DATE,
5343                )
5344            )
5345
5346        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5347            return self.sql(this)
5348
5349        if safe:
5350            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5351
5352        return self.sql(exp.cast(this, exp.DType.DATE))
5353
5354    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5355        return self.sql(
5356            exp.func(
5357                "DATEDIFF",
5358                expression.this,
5359                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5360                "day",
5361            )
5362        )
5363
5364    def lastday_sql(self, expression: exp.LastDay) -> str:
5365        if self.LAST_DAY_SUPPORTS_DATE_PART:
5366            return self.function_fallback_sql(expression)
5367
5368        unit = expression.args.get("unit")
5369        if unit and unit.name.upper() != "MONTH":
5370            self.unsupported("Date parts are not supported in LAST_DAY.")
5371
5372        return self.func("LAST_DAY", expression.this)
5373
5374    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5375        import sqlglot.dialects.dialect
5376
5377        return self.func(
5378            "DATE_ADD",
5379            expression.this,
5380            expression.expression,
5381            sqlglot.dialects.dialect.unit_to_str(expression),
5382        )
5383
5384    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5385        this = expression.this
5386        position = expression.args["position"]
5387        offset = index_offset - (expression.args.get("offset") or 0)
5388
5389        if offset:
5390            if position.is_int:
5391                value = position.to_py()
5392                if value >= 0:
5393                    position = exp.Literal.number(value + offset)
5394                elif offset < 0 and value == -1:
5395                    # 1-based -1 appends, which a 0-based position can only express as the size
5396                    position = exp.ArraySize(this=this.copy())
5397                else:
5398                    # Negative positions count from the end, so they shift in the opposite
5399                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5400                    position = exp.Literal.number(value - offset)
5401            else:
5402                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5403
5404        return self.func("ARRAY_INSERT", this, position, expression.expression)
5405
5406    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5407        if self.CAN_IMPLEMENT_ARRAY_ANY:
5408            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5409            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5410            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5411            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5412
5413        import sqlglot.dialects.dialect
5414
5415        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5416        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5417            self.unsupported("ARRAY_ANY is unsupported")
5418
5419        return self.function_fallback_sql(expression)
5420
5421    def struct_sql(self, expression: exp.Struct) -> str:
5422        expression.set(
5423            "expressions",
5424            [
5425                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5426                if isinstance(e, exp.PropertyEQ)
5427                else e
5428                for e in expression.expressions
5429            ],
5430        )
5431
5432        return self.function_fallback_sql(expression)
5433
5434    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5435        low = self.sql(expression, "this")
5436        high = self.sql(expression, "expression")
5437
5438        return f"{low} TO {high}"
5439
5440    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5441        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5442        tables = f" {self.expressions(expression)}"
5443
5444        exists = " IF EXISTS" if expression.args.get("exists") else ""
5445
5446        on_cluster = self.sql(expression, "cluster")
5447        on_cluster = f" {on_cluster}" if on_cluster else ""
5448
5449        identity = self.sql(expression, "identity")
5450        identity = f" {identity} IDENTITY" if identity else ""
5451
5452        option = self.sql(expression, "option")
5453        option = f" {option}" if option else ""
5454
5455        partition = self.sql(expression, "partition")
5456        partition = f" {partition}" if partition else ""
5457
5458        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5459
5460    # This transpiles T-SQL's CONVERT function
5461    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5462    def convert_sql(self, expression: exp.Convert) -> str:
5463        to = expression.this
5464        value = expression.expression
5465        style = expression.args.get("style")
5466        safe = expression.args.get("safe")
5467        strict = expression.args.get("strict")
5468
5469        if not to or not value:
5470            return ""
5471
5472        # Retrieve length of datatype and override to default if not specified
5473        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5474            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5475
5476        transformed: exp.Expr | None = None
5477        cast = exp.Cast if strict else exp.TryCast
5478
5479        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5480        if isinstance(style, exp.Literal) and style.is_int:
5481            import sqlglot.dialects.tsql
5482
5483            style_value = style.name
5484            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5485            if not converted_style:
5486                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5487
5488            fmt = exp.Literal.string(converted_style)
5489
5490            if to.this == exp.DType.DATE:
5491                transformed = exp.StrToDate(this=value, format=fmt)
5492            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5493                transformed = exp.StrToTime(this=value, format=fmt)
5494            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5495                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5496            elif to.this == exp.DType.TEXT:
5497                transformed = exp.TimeToStr(this=value, format=fmt)
5498
5499        if not transformed:
5500            transformed = cast(this=value, to=to, safe=safe)
5501
5502        return self.sql(transformed)
5503
5504    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5505        this = expression.this
5506        if isinstance(this, exp.JSONPathWildcard):
5507            this = self.json_path_part(this)
5508            return f".{this}" if this else ""
5509
5510        quoted = expression.args.get("quoted")
5511        if not (
5512            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5513        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5514            return f".{this}"
5515
5516        this = self.json_path_part(this)
5517
5518        return (
5519            f"[{this}]"
5520            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5521            else f".{this}"
5522        )
5523
5524    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5525        this = self.json_path_part(expression.this)
5526        return f"[{this}]" if this else ""
5527
5528    def _simplify_unless_literal(self, expression: E) -> E:
5529        if not isinstance(expression, exp.Literal):
5530            import sqlglot.optimizer.simplify
5531
5532            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5533
5534        return expression
5535
5536    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5537        this = expression.this
5538        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5539            self.unsupported(
5540                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5541            )
5542            return self.sql(this)
5543
5544        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5545            if self.IGNORE_NULLS_BEFORE_ORDER:
5546                from sqlglot.optimizer.scope import find_all_in_scope
5547
5548                # The first modifier here will be the one closest to the AggFunc's arg
5549                mods = sorted(
5550                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5551                    key=lambda x: (
5552                        0
5553                        if isinstance(x, exp.HavingMax)
5554                        else (1 if isinstance(x, exp.Order) else 2)
5555                    ),
5556                )
5557
5558                if mods:
5559                    mod = mods[0]
5560                    this = expression.__class__(this=mod.this.copy())
5561                    this.meta["inline"] = True
5562                    mod.this.replace(this)
5563                    return self.sql(expression.this)
5564
5565            agg_func = expression.find(exp.AggFunc)
5566
5567            if agg_func:
5568                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5569                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5570
5571        return f"{self.sql(expression, 'this')} {text}"
5572
5573    def _replace_line_breaks(self, string: str) -> str:
5574        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5575        if self.pretty:
5576            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5577        return string
5578
5579    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5580        option = self.sql(expression, "this")
5581
5582        if expression.expressions:
5583            upper = option.upper()
5584
5585            # Snowflake FILE_FORMAT options are separated by whitespace
5586            sep = " " if upper == "FILE_FORMAT" else ", "
5587
5588            # Databricks copy/format options do not set their list of values with EQ
5589            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5590            values = self.expressions(expression, flat=True, sep=sep)
5591            return f"{option}{op}({values})"
5592
5593        value = self.sql(expression, "expression")
5594
5595        if not value:
5596            return option
5597
5598        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5599
5600        return f"{option}{op}{value}"
5601
5602    def credentials_sql(self, expression: exp.Credentials) -> str:
5603        cred_expr = expression.args.get("credentials")
5604        if isinstance(cred_expr, exp.Literal):
5605            # Redshift case: CREDENTIALS <string>
5606            credentials = self.sql(expression, "credentials")
5607            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5608        else:
5609            # Snowflake case: CREDENTIALS = (...)
5610            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5611            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5612
5613        storage = self.sql(expression, "storage")
5614        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5615
5616        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5617        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5618
5619        iam_role = self.sql(expression, "iam_role")
5620        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5621
5622        region = self.sql(expression, "region")
5623        region = f" REGION {region}" if region else ""
5624
5625        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5626
5627    def copy_sql(self, expression: exp.Copy) -> str:
5628        this = self.sql(expression, "this")
5629        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5630
5631        credentials = self.sql(expression, "credentials")
5632        credentials = self.seg(credentials) if credentials else ""
5633        files = self.expressions(expression, key="files", flat=True)
5634        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5635
5636        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5637        params = self.expressions(
5638            expression,
5639            key="params",
5640            sep=sep,
5641            new_line=True,
5642            skip_last=True,
5643            skip_first=True,
5644            indent=self.COPY_PARAMS_ARE_WRAPPED,
5645        )
5646
5647        if params:
5648            if self.COPY_PARAMS_ARE_WRAPPED:
5649                params = f" WITH ({params})"
5650            elif not self.pretty and (files or credentials):
5651                params = f" {params}"
5652
5653        return f"COPY{this}{kind} {files}{credentials}{params}"
5654
5655    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5656        return ""
5657
5658    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5659        on_sql = "ON" if expression.args.get("on") else "OFF"
5660        filter_col: str | None = self.sql(expression, "filter_column")
5661        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5662        retention_period: str | None = self.sql(expression, "retention_period")
5663        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5664
5665        if filter_col or retention_period:
5666            on_sql = self.func("ON", filter_col, retention_period)
5667
5668        return f"DATA_DELETION={on_sql}"
5669
5670    def maskingpolicycolumnconstraint_sql(
5671        self, expression: exp.MaskingPolicyColumnConstraint
5672    ) -> str:
5673        this = self.sql(expression, "this")
5674        expressions = self.expressions(expression, flat=True)
5675        expressions = f" USING ({expressions})" if expressions else ""
5676        return f"MASKING POLICY {this}{expressions}"
5677
5678    def gapfill_sql(self, expression: exp.GapFill) -> str:
5679        this = self.sql(expression, "this")
5680        this = f"TABLE {this}"
5681        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5682
5683    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5684        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5685
5686    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5687        this = self.sql(expression, "this")
5688        expr = expression.expression
5689
5690        if isinstance(expr, exp.Func):
5691            # T-SQL's CLR functions are case sensitive
5692            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5693        else:
5694            expr = self.sql(expression, "expression")
5695
5696        return self.scope_resolution(expr, this)
5697
5698    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5699        if self.PARSE_JSON_NAME is None:
5700            return self.sql(expression.this)
5701
5702        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5703
5704    def rand_sql(self, expression: exp.Rand) -> str:
5705        lower = self.sql(expression, "lower")
5706        upper = self.sql(expression, "upper")
5707
5708        if lower and upper:
5709            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5710        return self.func("RAND", expression.this)
5711
5712    def changes_sql(self, expression: exp.Changes) -> str:
5713        information = self.sql(expression, "information")
5714        information = f"INFORMATION => {information}"
5715        at_before = self.sql(expression, "at_before")
5716        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5717        end = self.sql(expression, "end")
5718        end = f"{self.seg('')}{end}" if end else ""
5719
5720        return f"CHANGES ({information}){at_before}{end}"
5721
5722    def pad_sql(self, expression: exp.Pad) -> str:
5723        prefix = "L" if expression.args.get("is_left") else "R"
5724
5725        fill_pattern = self.sql(expression, "fill_pattern") or None
5726        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5727            fill_pattern = "' '"
5728
5729        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5730
5731    def summarize_sql(self, expression: exp.Summarize) -> str:
5732        table = " TABLE" if expression.args.get("table") else ""
5733        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5734
5735    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5736        generate_series = exp.GenerateSeries(**expression.args)
5737
5738        parent = expression.parent
5739        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5740            parent = parent.parent
5741
5742        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5743            return self.sql(exp.Unnest(expressions=[generate_series]))
5744
5745        if isinstance(parent, exp.Select):
5746            self.unsupported("GenerateSeries projection unnesting is not supported.")
5747
5748        return self.sql(generate_series)
5749
5750    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5751        if self.SUPPORTS_CONVERT_TIMEZONE:
5752            return self.function_fallback_sql(expression)
5753
5754        source_tz = expression.args.get("source_tz")
5755        target_tz = expression.args.get("target_tz")
5756        timestamp = expression.args.get("timestamp")
5757
5758        if source_tz and timestamp:
5759            timestamp = exp.AtTimeZone(
5760                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5761            )
5762
5763        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5764
5765        return self.sql(expr)
5766
5767    def json_sql(self, expression: exp.JSON) -> str:
5768        this = self.sql(expression, "this")
5769        this = f" {this}" if this else ""
5770
5771        _with = expression.args.get("with_")
5772
5773        if _with is None:
5774            with_sql = ""
5775        elif not _with:
5776            with_sql = " WITHOUT"
5777        else:
5778            with_sql = " WITH"
5779
5780        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5781
5782        return f"JSON{this}{with_sql}{unique_sql}"
5783
5784    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5785        path = self.sql(expression, "path")
5786        returning = self.sql(expression, "returning")
5787        returning = f" RETURNING {returning}" if returning else ""
5788
5789        on_condition = self.sql(expression, "on_condition")
5790        on_condition = f" {on_condition}" if on_condition else ""
5791
5792        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5793
5794    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5795        regexp = " REGEXP" if expression.args.get("regexp") else ""
5796        return f"SKIP{regexp} {self.sql(expression.expression)}"
5797
5798    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5799        else_ = "ELSE " if expression.args.get("else_") else ""
5800        condition = self.sql(expression, "expression")
5801        condition = f"WHEN {condition} THEN " if condition else else_
5802        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5803        return f"{condition}{insert}"
5804
5805    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5806        kind = self.sql(expression, "kind")
5807        source = self.sql(expression, "source")
5808
5809        if kind:
5810            expressions = self.seg(self.expressions(expression, sep=" "))
5811            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5812        else:
5813            # If kind isn't present, we have Hive's FROM-first form:
5814            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5815            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5816            sql = f"FROM {source}{self.seg(inserts)}"
5817
5818        return self.prepend_ctes(expression, sql)
5819
5820    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5821        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5822        empty = expression.args.get("empty")
5823        empty = (
5824            f"DEFAULT {empty} ON EMPTY"
5825            if isinstance(empty, exp.Expr)
5826            else self.sql(expression, "empty")
5827        )
5828
5829        error = expression.args.get("error")
5830        error = (
5831            f"DEFAULT {error} ON ERROR"
5832            if isinstance(error, exp.Expr)
5833            else self.sql(expression, "error")
5834        )
5835
5836        if error and empty:
5837            error = (
5838                f"{empty} {error}"
5839                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5840                else f"{error} {empty}"
5841            )
5842            empty = ""
5843
5844        null = self.sql(expression, "null")
5845
5846        return f"{empty}{error}{null}"
5847
5848    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5849        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5850        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5851
5852    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5853        this = self.sql(expression, "this")
5854        path = self.sql(expression, "path")
5855
5856        passing = self.expressions(expression, "passing")
5857        passing = f" PASSING {passing}" if passing else ""
5858
5859        on_condition = self.sql(expression, "on_condition")
5860        on_condition = f" {on_condition}" if on_condition else ""
5861
5862        path = f"{path}{passing}{on_condition}"
5863
5864        return self.func("JSON_EXISTS", this, path)
5865
5866    def _add_arrayagg_null_filter(
5867        self,
5868        array_agg_sql: str,
5869        array_agg_expr: exp.ArrayAgg,
5870        column_expr: exp.Expr,
5871    ) -> str:
5872        """
5873        Add NULL filter to ARRAY_AGG if dialect requires it.
5874
5875        Args:
5876            array_agg_sql: The generated ARRAY_AGG SQL string
5877            array_agg_expr: The ArrayAgg expression node
5878            column_expr: The column/expression to filter (before ORDER BY wrapping)
5879
5880        Returns:
5881            SQL string with FILTER clause added if needed
5882        """
5883        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5884        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5885        if not (
5886            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5887        ):
5888            return array_agg_sql
5889
5890        parent = array_agg_expr.parent
5891        if isinstance(parent, exp.Filter):
5892            parent_cond = parent.expression.this
5893            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5894        elif column_expr.find(exp.Column):
5895            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5896            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5897            this_sql = (
5898                self.expressions(column_expr)
5899                if isinstance(column_expr, exp.Distinct)
5900                else self.sql(column_expr)
5901            )
5902            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5903
5904        return array_agg_sql
5905
5906    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5907        array_agg = self.function_fallback_sql(expression)
5908        column_expr = expression.this
5909        if isinstance(column_expr, exp.Order):
5910            column_expr = column_expr.this
5911
5912        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5913
5914    def slice_sql(self, expression: exp.Slice) -> str:
5915        step = self.sql(expression, "step")
5916        end = self.sql(expression.expression)
5917        begin = self.sql(expression.this)
5918
5919        sql = f"{end}:{step}" if step else end
5920        return f"{begin}:{sql}" if sql else f"{begin}:"
5921
5922    def apply_sql(self, expression: exp.Apply) -> str:
5923        this = self.sql(expression, "this")
5924        expr = self.sql(expression, "expression")
5925
5926        return f"{this} APPLY({expr})"
5927
5928    def _grant_or_revoke_sql(
5929        self,
5930        expression: exp.Grant | exp.Revoke,
5931        keyword: str,
5932        preposition: str,
5933        grant_option_prefix: str = "",
5934        grant_option_suffix: str = "",
5935    ) -> str:
5936        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5937
5938        kind = self.sql(expression, "kind")
5939        kind = f" {kind}" if kind else ""
5940
5941        securable = self.sql(expression, "securable")
5942        securable = f" {securable}" if securable else ""
5943
5944        principals = self.expressions(expression, key="principals", flat=True)
5945
5946        if not expression.args.get("grant_option"):
5947            grant_option_prefix = grant_option_suffix = ""
5948
5949        # cascade for revoke only
5950        cascade = self.sql(expression, "cascade")
5951        cascade = f" {cascade}" if cascade else ""
5952
5953        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5954
5955    def grant_sql(self, expression: exp.Grant) -> str:
5956        return self._grant_or_revoke_sql(
5957            expression,
5958            keyword="GRANT",
5959            preposition="TO",
5960            grant_option_suffix=" WITH GRANT OPTION",
5961        )
5962
5963    def revoke_sql(self, expression: exp.Revoke) -> str:
5964        return self._grant_or_revoke_sql(
5965            expression,
5966            keyword="REVOKE",
5967            preposition="FROM",
5968            grant_option_prefix="GRANT OPTION FOR ",
5969        )
5970
5971    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5972        this = self.sql(expression, "this")
5973        columns = self.expressions(expression, flat=True)
5974        columns = f"({columns})" if columns else ""
5975
5976        return f"{this}{columns}"
5977
5978    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5979        this = self.sql(expression, "this")
5980
5981        kind = self.sql(expression, "kind")
5982        kind = f"{kind} " if kind else ""
5983
5984        return f"{kind}{this}"
5985
5986    def columns_sql(self, expression: exp.Columns) -> str:
5987        func = self.function_fallback_sql(expression)
5988        if expression.args.get("unpack"):
5989            func = f"*{func}"
5990
5991        return func
5992
5993    def overlay_sql(self, expression: exp.Overlay) -> str:
5994        this = self.sql(expression, "this")
5995        expr = self.sql(expression, "expression")
5996        from_sql = self.sql(expression, "from_")
5997        for_sql = self.sql(expression, "for_")
5998        for_sql = f" FOR {for_sql}" if for_sql else ""
5999
6000        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
6001
6002    @unsupported_args("format")
6003    def todouble_sql(self, expression: exp.ToDouble) -> str:
6004        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6005        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
6006
6007    def string_sql(self, expression: exp.String) -> str:
6008        this = expression.this
6009        zone = expression.args.get("zone")
6010
6011        if zone:
6012            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6013            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6014            # set for source_tz to transpile the time conversion before the STRING cast
6015            this = exp.ConvertTimezone(
6016                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6017            )
6018
6019        return self.sql(exp.cast(this, exp.DType.VARCHAR))
6020
6021    def median_sql(self, expression: exp.Median) -> str:
6022        if not self.SUPPORTS_MEDIAN:
6023            return self.sql(
6024                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6025            )
6026
6027        return self.function_fallback_sql(expression)
6028
6029    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6030        filler = self.sql(expression, "this")
6031        filler = f" {filler}" if filler else ""
6032        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6033        return f"TRUNCATE{filler} {with_count}"
6034
6035    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6036        if self.SUPPORTS_UNIX_SECONDS:
6037            return self.function_fallback_sql(expression)
6038
6039        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6040
6041        return self.sql(
6042            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6043        )
6044
6045    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6046        dim = expression.expression
6047
6048        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6049        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6050            if not (dim.is_int and dim.name == "1"):
6051                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6052            dim = None
6053
6054        # If dimension is required but not specified, default initialize it
6055        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6056            dim = exp.Literal.number(1)
6057
6058        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6059
6060    def attach_sql(self, expression: exp.Attach) -> str:
6061        this = self.sql(expression, "this")
6062        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6063        expressions = self.expressions(expression)
6064        expressions = f" ({expressions})" if expressions else ""
6065
6066        return f"ATTACH{exists_sql} {this}{expressions}"
6067
6068    def detach_sql(self, expression: exp.Detach) -> str:
6069        kind = self.sql(expression, "kind")
6070        kind = f" {kind}" if kind else ""
6071        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6072        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6073        exists = " IF EXISTS" if expression.args.get("exists") else ""
6074        if exists:
6075            kind = kind or " DATABASE"
6076
6077        this = self.sql(expression, "this")
6078        this = f" {this}" if this else ""
6079        cluster = self.sql(expression, "cluster")
6080        cluster = f" {cluster}" if cluster else ""
6081        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6082        sync = " SYNC" if expression.args.get("sync") else ""
6083        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
6084
6085    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6086        this = self.sql(expression, "this")
6087        value = self.sql(expression, "expression")
6088        value = f" {value}" if value else ""
6089        return f"{this}{value}"
6090
6091    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6092        return (
6093            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6094        )
6095
6096    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6097        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6098        encode = f"{encode} {self.sql(expression, 'this')}"
6099
6100        properties = expression.args.get("properties")
6101        if properties:
6102            encode = f"{encode} {self.properties(properties)}"
6103
6104        return encode
6105
6106    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6107        this = self.sql(expression, "this")
6108        include = f"INCLUDE {this}"
6109
6110        column_def = self.sql(expression, "column_def")
6111        if column_def:
6112            include = f"{include} {column_def}"
6113
6114        alias = self.sql(expression, "alias")
6115        if alias:
6116            include = f"{include} AS {alias}"
6117
6118        return include
6119
6120    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6121        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6122        name = f"{prefix} {self.sql(expression, 'this')}"
6123        return self.func("XMLELEMENT", name, *expression.expressions)
6124
6125    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6126        this = self.sql(expression, "this")
6127        expr = self.sql(expression, "expression")
6128        expr = f"({expr})" if expr else ""
6129        return f"{this}{expr}"
6130
6131    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6132        partitions = self.expressions(expression, "partition_expressions")
6133        create = self.expressions(expression, "create_expressions")
6134        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6135
6136    def partitionbyrangepropertydynamic_sql(
6137        self, expression: exp.PartitionByRangePropertyDynamic
6138    ) -> str:
6139        start = self.sql(expression, "start")
6140        end = self.sql(expression, "end")
6141
6142        every = expression.args["every"]
6143        if isinstance(every, exp.Interval) and every.this.is_string:
6144            every.this.replace(exp.Literal.number(every.name))
6145
6146        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6147
6148    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6149        name = self.sql(expression, "this")
6150        values = self.expressions(expression, flat=True)
6151
6152        return f"NAME {name} VALUE {values}"
6153
6154    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6155        kind = self.sql(expression, "kind")
6156        sample = self.sql(expression, "sample")
6157        return f"SAMPLE {sample} {kind}"
6158
6159    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6160        kind = self.sql(expression, "kind")
6161        option = self.sql(expression, "option")
6162        option = f" {option}" if option else ""
6163        this = self.sql(expression, "this")
6164        this = f" {this}" if this else ""
6165        columns = self.expressions(expression)
6166        columns = f" {columns}" if columns else ""
6167        return f"{kind}{option} STATISTICS{this}{columns}"
6168
6169    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6170        this = self.sql(expression, "this")
6171        columns = self.expressions(expression)
6172        inner_expression = self.sql(expression, "expression")
6173        inner_expression = f" {inner_expression}" if inner_expression else ""
6174        update_options = self.sql(expression, "update_options")
6175        update_options = f" {update_options} UPDATE" if update_options else ""
6176        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6177
6178    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6179        kind = self.sql(expression, "kind")
6180        kind = f" {kind}" if kind else ""
6181        return f"DELETE{kind} STATISTICS"
6182
6183    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6184        inner_expression = self.sql(expression, "expression")
6185        return f"LIST CHAINED ROWS{inner_expression}"
6186
6187    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6188        kind = self.sql(expression, "kind")
6189        this = self.sql(expression, "this")
6190        this = f" {this}" if this else ""
6191        inner_expression = self.sql(expression, "expression")
6192        return f"VALIDATE {kind}{this}{inner_expression}"
6193
6194    def analyze_sql(self, expression: exp.Analyze) -> str:
6195        options = self.expressions(expression, key="options", sep=" ")
6196        options = f" {options}" if options else ""
6197        kind = self.sql(expression, "kind")
6198        kind = f" {kind}" if kind else ""
6199        tables = self.expressions(expression, key="tables", flat=True)
6200        tables = f" {tables}" if tables else ""
6201        mode = self.sql(expression, "mode")
6202        mode = f" {mode}" if mode else ""
6203        properties = self.sql(expression, "properties")
6204        properties = f" {properties}" if properties else ""
6205        partition = self.sql(expression, "partition")
6206        partition = f" {partition}" if partition else ""
6207        inner_expression = self.sql(expression, "expression")
6208        inner_expression = f" {inner_expression}" if inner_expression else ""
6209        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6210
6211    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6212        this = self.sql(expression, "this")
6213        namespaces = self.expressions(expression, key="namespaces")
6214        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6215        passing = self.expressions(expression, key="passing")
6216        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6217        columns = self.expressions(expression, key="columns")
6218        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6219        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6220        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6221
6222    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6223        this = self.sql(expression, "this")
6224        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6225
6226    def export_sql(self, expression: exp.Export) -> str:
6227        this = self.sql(expression, "this")
6228        connection = self.sql(expression, "connection")
6229        connection = f"WITH CONNECTION {connection} " if connection else ""
6230        options = self.sql(expression, "options")
6231        return f"EXPORT DATA {connection}{options} AS {this}"
6232
6233    def declare_sql(self, expression: exp.Declare) -> str:
6234        replace = "OR REPLACE " if expression.args.get("replace") else ""
6235        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6236
6237    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6238        variables = self.expressions(expression, "this")
6239        default = self.sql(expression, "default")
6240        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6241
6242        kind = self.sql(expression, "kind")
6243        if isinstance(expression.args.get("kind"), exp.Schema):
6244            kind = f"TABLE {kind}"
6245
6246        kind = f" {kind}" if kind else ""
6247
6248        return f"{variables}{kind}{default}"
6249
6250    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6251        kind = self.sql(expression, "kind")
6252        this = self.sql(expression, "this")
6253        set = self.sql(expression, "expression")
6254        to = self.sql(expression, "to")
6255        to = f" TO {to}" if to else ""
6256        default = self.sql(expression, "default")
6257        default = f" DEFAULT {default}" if default else ""
6258        using = self.sql(expression, "using")
6259        using = f" USING {using}" if using else ""
6260
6261        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6262
6263        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6264
6265    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6266        params = self.expressions(expression, key="params", flat=True)
6267        return self.func(expression.name, *expression.expressions) + f"({params})"
6268
6269    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6270        return self.func(expression.name, *expression.expressions)
6271
6272    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6273        return self.anonymousaggfunc_sql(expression)
6274
6275    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6276        return self.parameterizedagg_sql(expression)
6277
6278    def show_sql(self, expression: exp.Show) -> str:
6279        self.unsupported("Unsupported SHOW statement")
6280        return ""
6281
6282    def install_sql(self, expression: exp.Install) -> str:
6283        self.unsupported("Unsupported INSTALL statement")
6284        return ""
6285
6286    def liststage_sql(self, expression: exp.ListStage) -> str:
6287        pattern = self.sql(expression, "pattern")
6288        pattern = f" PATTERN = {pattern}" if pattern else ""
6289        return f"LIST {self.sql(expression, 'this')}{pattern}"
6290
6291    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6292        # Snowflake GET/PUT statements:
6293        #   PUT <file> <internalStage> <properties>
6294        #   GET <internalStage> <file> <properties>
6295        props = expression.args.get("properties")
6296        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6297        this = self.sql(expression, "this")
6298        target = self.sql(expression, "target")
6299
6300        if isinstance(expression, exp.Put):
6301            return f"PUT {this} {target}{props_sql}"
6302        else:
6303            return f"GET {target} {this}{props_sql}"
6304
6305    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6306        this = self.sql(expression, "this")
6307        expr = self.sql(expression, "expression")
6308        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6309        return f"TRANSLATE({this} USING {expr}{with_error})"
6310
6311    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6312        if self.SUPPORTS_DECODE_CASE:
6313            return self.func("DECODE", *expression.expressions)
6314
6315        decode_expr, *expressions = expression.expressions
6316
6317        ifs = []
6318        for search, result in zip(expressions[::2], expressions[1::2]):
6319            if isinstance(search, exp.Literal):
6320                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6321            elif isinstance(search, exp.Null):
6322                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6323            else:
6324                if isinstance(search, exp.Binary):
6325                    search = exp.paren(search)
6326
6327                cond = exp.or_(
6328                    decode_expr.eq(search),
6329                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6330                    copy=False,
6331                )
6332                ifs.append(exp.If(this=cond, true=result))
6333
6334        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6335        return self.sql(case)
6336
6337    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6338        this = self.sql(expression, "this")
6339        this = self.seg(this, sep="")
6340        dimensions = self.expressions(
6341            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6342        )
6343        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6344        metrics = self.expressions(
6345            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6346        )
6347        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6348        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6349        facts = self.seg(f"FACTS {facts}") if facts else ""
6350        where = self.sql(expression, "where")
6351        where = self.seg(f"WHERE {where}") if where else ""
6352        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6353        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6354
6355    def getextract_sql(self, expression: exp.GetExtract) -> str:
6356        this = expression.this
6357        expr = expression.expression
6358
6359        if not this.type or not expression.type:
6360            import sqlglot.optimizer.annotate_types
6361
6362            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6363
6364        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6365            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6366
6367        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6368
6369    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6370        return self.sql(
6371            exp.DateAdd(
6372                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6373                expression=expression.this,
6374                unit=exp.var("DAY"),
6375            )
6376        )
6377
6378    def space_sql(self: Generator, expression: exp.Space) -> str:
6379        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6380
6381    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6382        return f"BUILD {self.sql(expression, 'this')}"
6383
6384    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6385        method = self.sql(expression, "method")
6386        kind = expression.args.get("kind")
6387        if not kind:
6388            return f"REFRESH {method}"
6389
6390        every = self.sql(expression, "every")
6391        unit = self.sql(expression, "unit")
6392        every = f" EVERY {every} {unit}" if every else ""
6393        starts = self.sql(expression, "starts")
6394        starts = f" STARTS {starts}" if starts else ""
6395
6396        return f"REFRESH {method} ON {kind}{every}{starts}"
6397
6398    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6399        self.unsupported("The model!attribute syntax is not supported")
6400        return ""
6401
6402    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6403        return self.func("DIRECTORY", expression.this)
6404
6405    def uuid_sql(self, expression: exp.Uuid) -> str:
6406        is_string = expression.args.get("is_string", False)
6407        uuid_func_sql = self.func("UUID")
6408
6409        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6410            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6411
6412        return uuid_func_sql
6413
6414    def initcap_sql(self, expression: exp.Initcap) -> str:
6415        delimiters = expression.expression
6416
6417        if delimiters:
6418            # do not generate delimiters arg if we are round-tripping from default delimiters
6419            if (
6420                delimiters.is_string
6421                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6422            ):
6423                delimiters = None
6424            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6425                self.unsupported("INITCAP does not support custom delimiters")
6426                delimiters = None
6427
6428        return self.func("INITCAP", expression.this, delimiters)
6429
6430    def localtime_sql(self, expression: exp.Localtime) -> str:
6431        this = expression.this
6432        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6433
6434    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6435        this = expression.this
6436        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6437
6438    def weekstart_name(self, expression: exp.WeekStart) -> str:
6439        import sqlglot.dialects.dialect
6440
6441        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6442        this = expression.this.name.upper()
6443
6444        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6445        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6446
6447        if dow_from_week_start_day != dow_from_week_offset:
6448            self.unsupported(
6449                f"WEEK({this}) is not supported; falling back to the default week start day"
6450            )
6451
6452        return "WEEK"
6453
6454    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6455        name = self.weekstart_name(expression)
6456
6457        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6458        if isinstance(expression.parent, exp.DateTrunc):
6459            return self.sql(exp.Literal.string(name))
6460
6461        return name
6462
6463    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6464        this = self.expressions(expression)
6465        charset = self.sql(expression, "charset")
6466        using = f" USING {charset}" if charset else ""
6467        return self.func(name, this + using)
6468
6469    def weightstring_sql(self, expression: exp.WeightString) -> str:
6470        to = self.sql(expression, "to")
6471        to = f" AS {to}" if to else ""
6472        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6473
6474    def block_sql(self, expression: exp.Block) -> str:
6475        expressions = self.expressions(expression, sep="; ", flat=True)
6476        begin = "BEGIN " if expression.args.get("begin") else ""
6477        return f"{begin}{expressions}" if expressions else ""
6478
6479    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6480        self.unsupported("Unsupported Inline UDFs syntax")
6481        return ""
6482
6483    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6484        self.unsupported("Unsupported Stored Procedure syntax")
6485        return ""
6486
6487    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6488        self.unsupported("Unsupported If block syntax")
6489        return ""
6490
6491    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6492        self.unsupported("Unsupported Case statement syntax")
6493        return ""
6494
6495    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6496        self.unsupported("Unsupported While block syntax")
6497        return ""
6498
6499    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6500        self.unsupported("Unsupported Loop block syntax")
6501        return ""
6502
6503    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6504        self.unsupported("Unsupported Repeat block syntax")
6505        return ""
6506
6507    def leave_sql(self, expression: exp.Leave) -> str:
6508        self.unsupported("Unsupported Leave syntax")
6509        return ""
6510
6511    def iterate_sql(self, expression: exp.Iterate) -> str:
6512        self.unsupported("Unsupported Iterate syntax")
6513        return ""
6514
6515    def execute_sql(self, expression: exp.Execute) -> str:
6516        self.unsupported("Unsupported Execute syntax")
6517        return ""
6518
6519    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6520        self.unsupported("Unsupported Execute syntax")
6521        return ""
6522
6523    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6524        props = self.expressions(expression, sep=" ")
6525        return f"MODIFY {props}"
6526
6527    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6528        kind = expression.args.get("kind")
6529        return f"USING {kind} {self.sql(expression, 'this')}"
6530
6531    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6532        this = self.sql(expression, "this")
6533        to = self.sql(expression, "to")
6534        return f"RENAME INDEX {this} TO {to}"

Generator converts a given syntax tree to the corresponding SQL string.

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: bool | int | None = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: str | bool | None = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, type[sqlglot.dialects.Dialect], NoneType] = None)
919    def __init__(
920        self,
921        pretty: bool | int | None = None,
922        identify: str | bool = False,
923        normalize: bool = False,
924        pad: int = 2,
925        indent: int = 2,
926        normalize_functions: str | bool | None = None,
927        unsupported_level: ErrorLevel = ErrorLevel.WARN,
928        max_unsupported: int = 3,
929        leading_comma: bool = False,
930        max_text_width: int = 80,
931        comments: bool = True,
932        dialect: DialectType = None,
933    ):
934        import sqlglot
935        import sqlglot.dialects.dialect
936
937        self.pretty = pretty if pretty is not None else sqlglot.pretty
938        self.identify = identify
939        self.normalize = normalize
940        self.pad = pad
941        self._indent = indent
942        self.unsupported_level = unsupported_level
943        self.max_unsupported = max_unsupported
944        self.leading_comma = leading_comma
945        self.max_text_width = max_text_width
946        self.comments = comments
947        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
948
949        # This is both a Dialect property and a Generator argument, so we prioritize the latter
950        self.normalize_functions = (
951            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
952        )
953
954        self.unsupported_messages: list[str] = []
955        self._escaped_quote_end: str = (
956            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
957        )
958        self._escaped_byte_quote_end: str = (
959            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
960            if self.dialect.BYTE_END
961            else ""
962        )
963        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
964        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
965
966        self._next_name = name_sequence("_t")
967
968        self._identifier_start = self.dialect.IDENTIFIER_START
969        self._identifier_end = self.dialect.IDENTIFIER_END
970
971        self._quote_json_path_key_using_brackets = True
972
973        cls = type(self)
974        dispatch = _DISPATCH_CACHE.get(cls)
975        if dispatch is None:
976            dispatch = _build_dispatch(cls)
977            _DISPATCH_CACHE[cls] = dispatch
978        self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] = {<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsTopKey'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
NULL_ORDERING_SUPPORTED: bool | None = True
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] = ()
IGNORE_NULLS_IN_FUNC = False
IGNORE_NULLS_BEFORE_ORDER = True
LOCKING_READS_SUPPORTED = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
WRAP_DERIVED_VALUES = True
CREATE_FUNCTION_RETURN_AS = True
MATCHED_BY_SOURCE = True
SUPPORTS_MERGE_WHERE = False
SINGLE_STRING_INTERVAL = False
INTERVAL_ALLOWS_PLURAL_FORM = True
AUTO_REFRESH_BARE_INTERVALS = False
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
INDEX_ON = 'ON'
INOUT_SEPARATOR = ' '
JOIN_HINTS = True
DIRECTED_JOINS = False
TABLE_HINTS = True
QUERY_HINTS = True
QUERY_HINT_SEP = ', '
IS_BOOL_ALLOWED = True
CAST_TO_INT_ROUNDS = False
SAFE_INT_DIVISION = False
DUPLICATE_KEY_UPDATE_WITH_SET = True
LIMIT_IS_TOP = False
RETURNING_END = True
EXTRACT_ALLOWS_QUOTES = True
TZ_TO_WITH_TIME_ZONE = False
NVL2_SUPPORTED = True
SELECT_KINDS: tuple[str, ...] = ('STRUCT', 'VALUE')
VALUES_AS_TABLE = True
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
UNNEST_WITH_ORDINALITY = True
SEMI_ANTI_JOIN_WITH_SIDE = True
COMPUTED_COLUMN_WITH_TYPE = True
SUPPORTS_TABLE_COPY = True
TABLESAMPLE_REQUIRES_PARENS = True
TABLESAMPLE_SIZE_IS_ROWS = True
TABLESAMPLE_KEYWORDS = 'TABLESAMPLE'
TABLESAMPLE_WITH_METHOD = True
TABLESAMPLE_SEED_KEYWORD = 'SEED'
HISTORICAL_DATA_POST_ALIAS = False
COLLATE_IS_FUNC = False
DATA_TYPE_SPECIFIERS_ALLOWED = False
ENSURE_BOOLS = False
CTE_RECURSIVE_KEYWORD_REQUIRED = True
SUPPORTS_SINGLE_ARG_CONCAT = True
LAST_DAY_SUPPORTS_DATE_PART = True
SUPPORTS_TABLE_ALIAS_COLUMNS = True
SUPPORTS_NAMED_CTE_COLUMNS = True
UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
PIVOT_ALIAS_WITH_AS = True
JSON_KEY_VALUE_PAIR_SEP = ':'
INSERT_OVERWRITE = ' OVERWRITE TABLE'
SUPPORTS_SELECT_INTO = False
SUPPORTS_UNLOGGED_TABLES = False
SUPPORTS_CREATE_TABLE_LIKE = True
SUPPORTS_MODIFY_COLUMN = False
SUPPORTS_CHANGE_COLUMN = False
SUPPORTS_ALTER_COLUMN_NULLABILITY = False
SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
CAN_IMPLEMENT_ARRAY_ANY = False
SUPPORTS_TO_NUMBER = True
SUPPORTS_WINDOW_EXCLUDE = False
SET_OP_MODIFIERS = True
SET_OP_LIMITS = False
SET_OP_PARENTHESIZED_OPERANDS = True
COPY_PARAMS_ARE_WRAPPED = True
COPY_PARAMS_EQ_REQUIRED = False
COPY_HAS_INTO_KEYWORD = True
TRY_SUPPORTED = True
SUPPORTS_UESCAPE = True
UNICODE_SUBSTITUTE: ClassVar[Any] = None
STAR_EXCEPT = 'EXCEPT'
HEX_FUNC = 'HEX'
BYTE_STRING_FUNCTION = ''
WITH_PROPERTIES_PREFIX = 'WITH'
QUOTE_JSON_PATH = True
PAD_FILL_PATTERN_IS_REQUIRED = False
SUPPORTS_EXPLODING_PROJECTIONS = True
ARRAY_CONCAT_IS_VAR_LEN = True
SUPPORTS_CONVERT_TIMEZONE = False
SUPPORTS_MEDIAN = True
SUPPORTS_UNIX_SECONDS = False
ALTER_SET_WRAPPED = False
NORMALIZE_EXTRACT_DATE_PARTS = False
PARSE_JSON_NAME: str | None = 'PARSE_JSON'
ARRAY_SIZE_NAME: str = 'ARRAY_LENGTH'
ALTER_SET_TYPE = 'SET DATA TYPE'
ARRAY_SIZE_DIM_REQUIRED: bool | None = None
SUPPORTS_DECODE_CASE = True
SUPPORTS_BETWEEN_FLAGS = False
SUPPORTS_LIKE_QUANTIFIERS = True
MATCH_AGAINST_TABLE_PREFIX: str | None = None
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
DECLARE_DEFAULT_ASSIGNMENT = '='
UPDATE_STATEMENT_SUPPORTS_FROM = True
STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
TYPE_MAPPING: ClassVar = {<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
UNSUPPORTED_TYPES: ClassVar[set[sqlglot.expressions.datatypes.DType]] = set()
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] = {}
TIME_PART_SINGULARS: ClassVar = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
TOKEN_MAPPING: ClassVar[dict[sqlglot.tokenizer_core.TokenType, str]] = {}
STRUCT_DELIMITER: ClassVar = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: ClassVar[set[str]] = set()
PROPERTIES_LOCATION: ClassVar = {<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: ClassVar[set[str]] = set()
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar = {<DType.CHAR: 'CHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.NCHAR: 'NCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: ClassVar[set[type[sqlglot.expressions.core.Expr]]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
MOD_OPERATOR = '%'
MOD_PAREN_PARENT_TYPES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Mul'>, <class 'sqlglot.expressions.core.Div'>, <class 'sqlglot.expressions.core.IntDiv'>, <class 'sqlglot.expressions.core.Mod'>)
ALTER_PARTITION_OPTIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
SAFE_JSON_PATH_KEY_RE: ClassVar = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: list[str]
def generate( self, expression: sqlglot.expressions.core.Expr, copy: bool = True) -> str:
 980    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 981        """
 982        Generates the SQL string corresponding to the given syntax tree.
 983
 984        Args:
 985            expression: The syntax tree.
 986            copy: Whether to copy the expression. The generator performs mutations so
 987                it is safer to copy.
 988
 989        Returns:
 990            The SQL string corresponding to `expression`.
 991        """
 992        if copy:
 993            expression = expression.copy()
 994
 995        expression = self.preprocess(expression)
 996
 997        self.unsupported_messages = []
 998        sql = self.sql(expression).strip()
 999
1000        if self.pretty:
1001            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1002
1003        if self.unsupported_level == ErrorLevel.IGNORE:
1004            return sql
1005
1006        if self.unsupported_level == ErrorLevel.WARN:
1007            for msg in self.unsupported_messages:
1008                logger.warning(msg)
1009        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1010            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1011
1012        return sql

Generates the SQL string corresponding to the given syntax tree.

Arguments:
  • expression: The syntax tree.
  • copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:

The SQL string corresponding to expression.

def preprocess( self, expression: sqlglot.expressions.core.Expr) -> sqlglot.expressions.core.Expr:
1014    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1015        """Apply generic preprocessing transformations to a given expression."""
1016        expression = self._move_ctes_to_top_level(expression)
1017
1018        if self.ENSURE_BOOLS:
1019            import sqlglot.transforms
1020
1021            expression = sqlglot.transforms.ensure_bools(expression)
1022
1023        return expression

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
1036    def unsupported(self, message: str) -> None:
1037        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1038            raise UnsupportedError(message)
1039        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
1041    def sep(self, sep: str = " ") -> str:
1042        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
1044    def seg(self, sql: str, sep: str = " ") -> str:
1045        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
1047    def sanitize_comment(self, comment: str) -> str:
1048        comment = " " + comment if comment[0].strip() else comment
1049        comment = comment + " " if comment[-1].strip() else comment
1050
1051        # Escape block comment markers to prevent premature closure or unintended nesting.
1052        # This is necessary because single-line comments (--) are converted to block comments
1053        # (/* */) on output, and any */ in the original text would close the comment early.
1054        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1055
1056        return comment
def maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1058    def maybe_comment(
1059        self,
1060        sql: str,
1061        expression: exp.Expr | None = None,
1062        comments: list[str] | None = None,
1063        separated: bool = False,
1064    ) -> str:
1065        comments = (
1066            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1067            if self.comments
1068            else None
1069        )
1070
1071        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1072            return sql
1073
1074        comments_list = [
1075            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1076            for comment in comments
1077            if comment
1078        ]
1079
1080        if not comments_list:
1081            return sql
1082
1083        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1084            comments_sql = self.sep().join(comments_list)
1085            return (
1086                f"{self.sep()}{comments_sql}{sql}"
1087                if not sql or sql[0].isspace()
1088                else f"{comments_sql}{self.sep()}{sql}"
1089            )
1090
1091        return f"{sql} {' '.join(comments_list)}"
def wrap(self, expression: sqlglot.expressions.core.Expr | str) -> str:
1093    def wrap(self, expression: exp.Expr | str) -> str:
1094        this_sql = (
1095            self.sql(expression)
1096            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1097            else self.sql(expression, "this")
1098        )
1099        if not this_sql:
1100            return "()"
1101
1102        this_sql = self.indent(this_sql, level=1, pad=0)
1103        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
1105    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1106        original = self.identify
1107        self.identify = False
1108        result = func(*args, **kwargs)
1109        self.identify = original
1110        return result
def normalize_func(self, name: str) -> str:
1112    def normalize_func(self, name: str) -> str:
1113        if self.normalize_functions == "upper" or self.normalize_functions is True:
1114            return name.upper()
1115        if self.normalize_functions == "lower":
1116            return name.lower()
1117        return name
def indent( self, sql: str, level: int = 0, pad: int | None = None, skip_first: bool = False, skip_last: bool = False) -> str:
1119    def indent(
1120        self,
1121        sql: str,
1122        level: int = 0,
1123        pad: int | None = None,
1124        skip_first: bool = False,
1125        skip_last: bool = False,
1126    ) -> str:
1127        if not self.pretty or not sql:
1128            return sql
1129
1130        pad = self.pad if pad is None else pad
1131        lines = sql.split("\n")
1132
1133        return "\n".join(
1134            (
1135                line
1136                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1137                else f"{' ' * (level * self._indent + pad)}{line}"
1138            )
1139            for i, line in enumerate(lines)
1140        )
def sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1142    def sql(
1143        self,
1144        expression: str | exp.Expr | None,
1145        key: str | None = None,
1146        comment: bool = True,
1147    ) -> str:
1148        if not expression:
1149            return ""
1150
1151        if isinstance(expression, str):
1152            return expression
1153
1154        if key:
1155            value = expression.args.get(key)
1156            if value:
1157                return self.sql(value)
1158            return ""
1159
1160        handler = self._dispatch.get(expression.__class__)
1161
1162        if handler:
1163            sql = handler(self, expression)
1164        elif isinstance(expression, exp.Func):
1165            sql = self.function_fallback_sql(expression)
1166        elif isinstance(expression, exp.Property):
1167            sql = self.property_sql(expression)
1168        else:
1169            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1170
1171        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.core.Uncache) -> str:
1173    def uncache_sql(self, expression: exp.Uncache) -> str:
1174        table = self.sql(expression, "this")
1175        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1176        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.core.Cache) -> str:
1178    def cache_sql(self, expression: exp.Cache) -> str:
1179        lazy = " LAZY" if expression.args.get("lazy") else ""
1180        table = self.sql(expression, "this")
1181        options = expression.args.get("options")
1182        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1183        sql = self.sql(expression, "expression")
1184        sql = f" AS{self.sep()}{sql}" if sql else ""
1185        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1186        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.ddl.CharacterSet) -> str:
1188    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1189        default = "DEFAULT " if expression.args.get("default") else ""
1190        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.core.Column) -> str:
1192    def column_parts(self, expression: exp.Column) -> str:
1193        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1194            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1195            return self.sql(expression, "this")
1196
1197        return ".".join(
1198            self.sql(part)
1199            for part in (
1200                expression.args.get("catalog"),
1201                expression.args.get("db"),
1202                expression.args.get("table"),
1203                expression.args.get("this"),
1204            )
1205            if part
1206        )
def column_sql(self, expression: sqlglot.expressions.core.Column) -> str:
1208    def column_sql(self, expression: exp.Column) -> str:
1209        join_mark = " (+)" if expression.args.get("join_mark") else ""
1210
1211        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1212            join_mark = ""
1213            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1214
1215        return f"{self.column_parts(expression)}{join_mark}"
def pseudocolumn_sql(self, expression: sqlglot.expressions.core.Pseudocolumn) -> str:
1217    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1218        return self.column_sql(expression)
def columnposition_sql(self, expression: sqlglot.expressions.query.ColumnPosition) -> str:
1220    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1221        this = self.sql(expression, "this")
1222        this = f" {this}" if this else ""
1223        position = self.sql(expression, "position")
1224        return f"{position}{this}"
def columndef_sql( self, expression: sqlglot.expressions.query.ColumnDef, sep: str = ' ') -> str:
1226    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1227        column = self.sql(expression, "this")
1228        kind = self.sql(expression, "kind")
1229        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1230        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1231        kind = f"{sep}{kind}" if kind else ""
1232        constraints = f" {constraints}" if constraints else ""
1233        position = self.sql(expression, "position")
1234        position = f" {position}" if position else ""
1235
1236        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1237            kind = ""
1238
1239        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql( self, expression: sqlglot.expressions.constraints.ColumnConstraint) -> str:
1241    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1242        this = self.sql(expression, "this")
1243        kind_sql = self.sql(expression, "kind").strip()
1244        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.ComputedColumnConstraint) -> str:
1246    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1247        this = self.sql(expression, "this")
1248        if expression.args.get("not_null"):
1249            persisted = " PERSISTED NOT NULL"
1250        elif expression.args.get("persisted"):
1251            persisted = " PERSISTED"
1252        else:
1253            persisted = ""
1254
1255        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql( self, _: sqlglot.expressions.constraints.AutoIncrementColumnConstraint) -> str:
1257    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1258        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CompressColumnConstraint) -> str:
1260    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1261        if isinstance(expression.this, list):
1262            this = self.wrap(self.expressions(expression, key="this", flat=True))
1263        else:
1264            this = self.sql(expression, "this")
1265
1266        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
1268    def generatedasidentitycolumnconstraint_sql(
1269        self, expression: exp.GeneratedAsIdentityColumnConstraint
1270    ) -> str:
1271        this = ""
1272        if expression.this is not None:
1273            on_null = " ON NULL" if expression.args.get("on_null") else ""
1274            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1275
1276        start = expression.args.get("start")
1277        start = f"START WITH {start}" if start else ""
1278        increment = expression.args.get("increment")
1279        increment = f" INCREMENT BY {increment}" if increment else ""
1280        minvalue = expression.args.get("minvalue")
1281        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1282        maxvalue = expression.args.get("maxvalue")
1283        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1284        cycle = expression.args.get("cycle")
1285        cycle_sql = ""
1286
1287        if cycle is not None:
1288            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1289            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1290
1291        sequence_opts = ""
1292        if start or increment or cycle_sql:
1293            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1294            sequence_opts = f" ({sequence_opts.strip()})"
1295
1296        expr = self.sql(expression, "expression")
1297        expr = f"({expr})" if expr else "IDENTITY"
1298
1299        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1301    def generatedasrowcolumnconstraint_sql(
1302        self, expression: exp.GeneratedAsRowColumnConstraint
1303    ) -> str:
1304        start = "START" if expression.args.get("start") else "END"
1305        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1306        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.constraints.PeriodForSystemTimeConstraint) -> str:
1308    def periodforsystemtimeconstraint_sql(
1309        self, expression: exp.PeriodForSystemTimeConstraint
1310    ) -> str:
1311        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.NotNullColumnConstraint) -> str:
1313    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1314        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.PrimaryKeyColumnConstraint) -> str:
1316    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1317        desc = expression.args.get("desc")
1318        if desc is not None:
1319            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1320        options = self.expressions(expression, key="options", flat=True, sep=" ")
1321        options = f" {options}" if options else ""
1322        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1324    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1325        this = self.sql(expression, "this")
1326        this = f" {this}" if this else ""
1327        index_type = expression.args.get("index_type")
1328        index_type = f" USING {index_type}" if index_type else ""
1329        on_conflict = self.sql(expression, "on_conflict")
1330        on_conflict = f" {on_conflict}" if on_conflict else ""
1331        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1332        options = self.expressions(expression, key="options", flat=True, sep=" ")
1333        options = f" {options}" if options else ""
1334        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1336    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1337        input_ = expression.args.get("input_")
1338        output = expression.args.get("output")
1339        variadic = expression.args.get("variadic")
1340
1341        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1342        if variadic:
1343            return "VARIADIC"
1344
1345        if input_ and output:
1346            return f"IN{self.INOUT_SEPARATOR}OUT"
1347        if input_:
1348            return "IN"
1349        if output:
1350            return "OUT"
1351
1352        return ""
def createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1354    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1355        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
1357    def create_sql(self, expression: exp.Create) -> str:
1358        kind = self.sql(expression, "kind")
1359        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1360
1361        properties = expression.args.get("properties")
1362
1363        if (
1364            kind == "TRIGGER"
1365            and properties
1366            and properties.expressions
1367            and isinstance(properties.expressions[0], exp.TriggerProperties)
1368            and properties.expressions[0].args.get("constraint")
1369        ):
1370            kind = f"CONSTRAINT {kind}"
1371
1372        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1373
1374        this = self.createable_sql(expression, properties_locs)
1375
1376        properties_sql = ""
1377        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1378            exp.Properties.Location.POST_WITH
1379        ):
1380            props_ast = exp.Properties(
1381                expressions=[
1382                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1383                    *properties_locs[exp.Properties.Location.POST_WITH],
1384                ]
1385            )
1386            props_ast.parent = expression
1387            properties_sql = self.sql(props_ast)
1388
1389            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1390                properties_sql = self.sep() + properties_sql
1391            elif not self.pretty:
1392                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1393                properties_sql = f" {properties_sql}"
1394
1395        begin = " BEGIN" if expression.args.get("begin") else ""
1396
1397        expression_sql = self.sql(expression, "expression")
1398        if expression_sql:
1399            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1400
1401            if not isinstance(expression.expression, exp.MacroOverloads) and (
1402                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1403            ):
1404                postalias_props_sql = ""
1405                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1406                    postalias_props_sql = self.properties(
1407                        exp.Properties(
1408                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1409                        ),
1410                        wrapped=False,
1411                    )
1412                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1413                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1414
1415        postindex_props_sql = ""
1416        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1417            postindex_props_sql = self.properties(
1418                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1419                wrapped=False,
1420                prefix=" ",
1421            )
1422
1423        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1424        indexes = f" {indexes}" if indexes else ""
1425        index_sql = indexes + postindex_props_sql
1426
1427        replace = " OR REPLACE" if expression.args.get("replace") else ""
1428        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1429        unique = " UNIQUE" if expression.args.get("unique") else ""
1430
1431        clustered = expression.args.get("clustered")
1432        if clustered is None:
1433            clustered_sql = ""
1434        elif clustered:
1435            clustered_sql = " CLUSTERED COLUMNSTORE"
1436        else:
1437            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1438
1439        postcreate_props_sql = ""
1440        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1441            postcreate_props_sql = self.properties(
1442                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1443                sep=" ",
1444                prefix=" ",
1445                wrapped=False,
1446            )
1447
1448        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1449
1450        postexpression_props_sql = ""
1451        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1452            postexpression_props_sql = self.properties(
1453                exp.Properties(
1454                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1455                ),
1456                sep=" ",
1457                prefix=" ",
1458                wrapped=False,
1459            )
1460
1461        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1462        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1463        no_schema_binding = (
1464            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1465        )
1466
1467        clone = self.sql(expression, "clone")
1468        clone = f" {clone}" if clone else ""
1469
1470        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1471            properties_expression = f"{expression_sql}{properties_sql}"
1472        else:
1473            properties_expression = f"{properties_sql}{expression_sql}"
1474
1475        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1476        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.ddl.SequenceProperties) -> str:
1478    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1479        start = self.sql(expression, "start")
1480        start = f"START WITH {start}" if start else ""
1481        increment = self.sql(expression, "increment")
1482        increment = f" INCREMENT BY {increment}" if increment else ""
1483        minvalue = self.sql(expression, "minvalue")
1484        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1485        maxvalue = self.sql(expression, "maxvalue")
1486        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1487        owned = self.sql(expression, "owned")
1488        owned = f" OWNED BY {owned}" if owned else ""
1489
1490        cache = expression.args.get("cache")
1491        if cache is None:
1492            cache_str = ""
1493        elif cache is True:
1494            cache_str = " CACHE"
1495        else:
1496            cache_str = f" CACHE {cache}"
1497
1498        options = self.expressions(expression, key="options", flat=True, sep=" ")
1499        options = f" {options}" if options else ""
1500
1501        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def triggerproperties_sql(self, expression: sqlglot.expressions.ddl.TriggerProperties) -> str:
1503    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1504        timing = expression.args.get("timing", "")
1505        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1506        timing_events = f"{timing} {events}".strip() if timing or events else ""
1507
1508        parts = [timing_events, "ON", self.sql(expression, "table")]
1509
1510        if referenced_table := expression.args.get("referenced_table"):
1511            parts.extend(["FROM", self.sql(referenced_table)])
1512
1513        if deferrable := expression.args.get("deferrable"):
1514            parts.append(deferrable)
1515
1516        if initially := expression.args.get("initially"):
1517            parts.append(f"INITIALLY {initially}")
1518
1519        if referencing := expression.args.get("referencing"):
1520            parts.append(self.sql(referencing))
1521
1522        if for_each := expression.args.get("for_each"):
1523            parts.append(f"FOR EACH {for_each}")
1524
1525        if when := expression.args.get("when"):
1526            parts.append(f"WHEN ({self.sql(when)})")
1527
1528        parts.append(self.sql(expression, "execute"))
1529
1530        return self.sep().join(parts)
def triggerreferencing_sql(self, expression: sqlglot.expressions.ddl.TriggerReferencing) -> str:
1532    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1533        parts = []
1534
1535        if old_alias := expression.args.get("old"):
1536            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1537
1538        if new_alias := expression.args.get("new"):
1539            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1540
1541        return f"REFERENCING {' '.join(parts)}"
def triggerevent_sql(self, expression: sqlglot.expressions.ddl.TriggerEvent) -> str:
1543    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1544        columns = expression.args.get("columns")
1545        if columns:
1546            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1547
1548        return self.sql(expression, "this")
def clone_sql(self, expression: sqlglot.expressions.ddl.Clone) -> str:
1550    def clone_sql(self, expression: exp.Clone) -> str:
1551        this = self.sql(expression, "this")
1552        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1553        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1554        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.ddl.Describe) -> str:
1556    def describe_sql(self, expression: exp.Describe) -> str:
1557        style = expression.args.get("style")
1558        style = f" {style}" if style else ""
1559        partition = self.sql(expression, "partition")
1560        partition = f" {partition}" if partition else ""
1561        format = self.sql(expression, "format")
1562        format = f" {format}" if format else ""
1563        as_json = " AS JSON" if expression.args.get("as_json") else ""
1564
1565        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
def heredoc_sql(self, expression: sqlglot.expressions.ddl.Heredoc) -> str:
1567    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1568        tag = self.sql(expression, "tag")
1569        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.core.Expr, sql: str) -> str:
1571    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1572        with_ = self.sql(expression, "with_")
1573        if with_:
1574            sql = f"{with_}{self.sep()}{sql}"
1575        return sql
def with_sql(self, expression: sqlglot.expressions.query.With) -> str:
1577    def with_sql(self, expression: exp.With) -> str:
1578        udfs = self.expressions(expression, key="udfs", flat=True)
1579        udfs = f"WITH {udfs}" if udfs else ""
1580
1581        sql = self.expressions(expression, flat=True)
1582
1583        recursive = (
1584            "RECURSIVE "
1585            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1586            else ""
1587        )
1588        search = self.sql(expression, "search")
1589        search = f" {search}" if search else ""
1590        cycle = self.sql(expression, "cycle")
1591        cycle = f" {cycle}" if cycle else ""
1592
1593        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1594        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
def cte_sql(self, expression: sqlglot.expressions.query.CTE) -> str:
1596    def cte_sql(self, expression: exp.CTE) -> str:
1597        alias = expression.args.get("alias")
1598        if alias:
1599            alias.add_comments(expression.pop_comments())
1600
1601        alias_sql = self.sql(expression, "alias")
1602
1603        materialized = expression.args.get("materialized")
1604        if materialized is False:
1605            materialized = "NOT MATERIALIZED "
1606        elif materialized:
1607            materialized = "MATERIALIZED "
1608
1609        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1610        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1611
1612        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.query.TableAlias) -> str:
1614    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1615        alias = self.sql(expression, "this")
1616        columns = self.expressions(expression, key="columns", flat=True)
1617        columns = f"({columns})" if columns else ""
1618
1619        if (
1620            columns
1621            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1622            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1623        ):
1624            columns = ""
1625            self.unsupported("Named columns are not supported in table alias.")
1626
1627        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1628            alias = self._next_name()
1629
1630        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.query.BitString) -> str:
1632    def bitstring_sql(self, expression: exp.BitString) -> str:
1633        this = self.sql(expression, "this")
1634        if self.dialect.BIT_START:
1635            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1636        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1638    def hexstring_sql(
1639        self, expression: exp.HexString, binary_function_repr: str | None = None
1640    ) -> str:
1641        this = self.sql(expression, "this")
1642        is_integer_type = expression.args.get("is_integer")
1643
1644        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1645            not self.dialect.HEX_START and not binary_function_repr
1646        ):
1647            # Integer representation will be returned if:
1648            # - The read dialect treats the hex value as integer literal but not the write
1649            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1650            return f"{int(this, 16)}"
1651
1652        if not is_integer_type:
1653            # Read dialect treats the hex value as BINARY/BLOB
1654            if binary_function_repr:
1655                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1656                return self.func(binary_function_repr, exp.Literal.string(this))
1657            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1658                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1659                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1660
1661        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.query.ByteString) -> str:
1663    def bytestring_sql(self, expression: exp.ByteString) -> str:
1664        this = self.sql(expression, "this")
1665        is_bytes = bool(expression.args.get("is_bytes"))
1666        if is_bytes and self.BYTE_STRING_FUNCTION:
1667            # Hex digits can be quoted directly without escaping.
1668            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1669
1670        if self.dialect.BYTE_START:
1671            escaped_byte_string = self.escape_str(
1672                this,
1673                escape_backslash=bool(
1674                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1675                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1676                ),
1677                delimiter=self.dialect.BYTE_END,
1678                escaped_delimiter=self._escaped_byte_quote_end,
1679                is_byte_string=True,
1680                is_bytes=is_bytes,
1681            )
1682            delimited_byte_string = (
1683                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1684            )
1685            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1686                return self.sql(
1687                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1688                )
1689            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1690                return self.sql(
1691                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1692                )
1693
1694            return delimited_byte_string
1695
1696        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1697            return self.sql(exp.Literal.string(this))
1698
1699        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1700        return ""
def unicodestring_sql(self, expression: sqlglot.expressions.query.UnicodeString) -> str:
1702    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1703        this = self.sql(expression, "this")
1704        escape = expression.args.get("escape")
1705        unicode_start = self.dialect.UNICODE_START
1706
1707        if unicode_start:
1708            escape_substitute = r"\\\1"
1709            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1710        else:
1711            escape_substitute = r"\\u\1"
1712            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1713
1714        if escape:
1715            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1716            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1717        else:
1718            escape_pattern = ESCAPED_UNICODE_RE
1719            escape_sql = ""
1720
1721        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1722            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1723
1724        if unicode_start:
1725            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1726            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1727            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1728        else:
1729            this = self.escape_str(this, escape_backslash=False)
1730
1731        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.query.RawString) -> str:
1733    def rawstring_sql(self, expression: exp.RawString) -> str:
1734        string = expression.this
1735        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1736            string = string.replace("\\", "\\\\")
1737
1738        string = self.escape_str(string, escape_backslash=False)
1739        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.datatypes.DataTypeParam) -> str:
1741    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1742        this = self.sql(expression, "this")
1743        specifier = self.sql(expression, "expression")
1744        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1745        return f"{this}{specifier}"
def datatype_param_bound_limiter( self, expression: sqlglot.expressions.datatypes.DataType, type_value: sqlglot.expressions.datatypes.DType, defaults: tuple[int, ...], bounds: tuple[int | None, ...]) -> sqlglot.expressions.datatypes.DataType:
1747    def datatype_param_bound_limiter(
1748        self,
1749        expression: exp.DataType,
1750        type_value: exp.DType,
1751        defaults: tuple[int, ...],
1752        bounds: tuple[int | None, ...],
1753    ) -> exp.DataType:
1754        params = expression.expressions
1755
1756        if not params:
1757            if defaults:
1758                expression.set(
1759                    "expressions",
1760                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1761                )
1762            return expression
1763
1764        if not bounds:
1765            return expression
1766
1767        for i, param in enumerate(params):
1768            bound = bounds[i] if i < len(bounds) else None
1769            if bound is None:
1770                continue
1771
1772            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1773            value = (
1774                param_value.to_py()
1775                if isinstance(param_value, exp.Literal) and param_value.is_number
1776                else None
1777            )
1778            if isinstance(value, (int, Decimal)) and value > bound:
1779                self.unsupported(
1780                    f"{type_value.value} parameter {param_value.name} exceeds "
1781                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1782                )
1783                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1784
1785        return expression
def datatype_sql(self, expression: sqlglot.expressions.datatypes.DataType) -> str:
1787    def datatype_sql(self, expression: exp.DataType) -> str:
1788        nested = ""
1789        values = ""
1790
1791        expr_nested = expression.args.get("nested")
1792        type_value = expression.this
1793
1794        if (
1795            not expr_nested
1796            and isinstance(type_value, exp.DType)
1797            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1798        ):
1799            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1800
1801        interior = (
1802            self.expressions(
1803                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1804            )
1805            if expr_nested and self.pretty
1806            else self.expressions(expression, flat=True)
1807        )
1808
1809        if type_value in self.UNSUPPORTED_TYPES:
1810            self.unsupported(
1811                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1812            )
1813
1814        type_sql: t.Any = ""
1815        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1816            type_sql = self.sql(expression, "kind")
1817        elif type_value == exp.DType.CHARACTER_SET:
1818            char = f"CHAR({interior})" if interior else "CHAR"
1819            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1820        else:
1821            type_sql = (
1822                self.TYPE_MAPPING.get(type_value, type_value.value)
1823                if isinstance(type_value, exp.DType)
1824                else type_value
1825            )
1826
1827        if interior:
1828            if expr_nested:
1829                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1830                if expression.args.get("values") is not None:
1831                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1832                    values = self.expressions(expression, key="values", flat=True)
1833                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1834            elif type_value == exp.DType.INTERVAL:
1835                nested = f" {interior}"
1836            else:
1837                nested = f"({interior})"
1838
1839        type_sql = f"{type_sql}{nested}{values}"
1840        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1841            exp.DType.TIMETZ,
1842            exp.DType.TIMESTAMPTZ,
1843        ):
1844            type_sql = f"{type_sql} WITH TIME ZONE"
1845
1846        collate = self.sql(expression, "collate")
1847        if collate:
1848            type_sql = f"{type_sql} COLLATE {collate}"
1849
1850        return type_sql
def directory_sql(self, expression: sqlglot.expressions.dml.Directory) -> str:
1852    def directory_sql(self, expression: exp.Directory) -> str:
1853        local = "LOCAL " if expression.args.get("local") else ""
1854        row_format = self.sql(expression, "row_format")
1855        row_format = f" {row_format}" if row_format else ""
1856        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.dml.Delete) -> str:
1858    def delete_sql(self, expression: exp.Delete) -> str:
1859        hint = self.sql(expression, "hint")
1860        this = self.sql(expression, "this")
1861        this = f" FROM {this}" if this else ""
1862        using = self.expressions(expression, key="using")
1863        using = f" USING {using}" if using else ""
1864        cluster = self.sql(expression, "cluster")
1865        cluster = f" {cluster}" if cluster else ""
1866        where = self.sql(expression, "where")
1867        returning = self.sql(expression, "returning")
1868        order = self.sql(expression, "order")
1869        limit = self.sql(expression, "limit")
1870        tables = self.expressions(expression, key="tables")
1871        tables = f" {tables}" if tables else ""
1872        if self.RETURNING_END:
1873            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1874        else:
1875            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1876        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.ddl.Drop) -> str:
1878    def drop_sql(self, expression: exp.Drop) -> str:
1879        tables = self.expressions(expression, key="tables", flat=True)
1880        expressions = self.expressions(expression, flat=True)
1881        expressions = f" ({expressions})" if expressions else ""
1882        kind = expression.args["kind"]
1883        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1884        iceberg = (
1885            " ICEBERG"
1886            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1887            else ""
1888        )
1889        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1890        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1891        on_cluster = self.sql(expression, "cluster")
1892        on_cluster = f" {on_cluster}" if on_cluster else ""
1893        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1894        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1895        cascade = " CASCADE" if expression.args.get("cascade") else ""
1896        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1897        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1898        purge = " PURGE" if expression.args.get("purge") else ""
1899        sync = " SYNC" if expression.args.get("sync") else ""
1900        force = " FORCE" if expression.args.get("force") else ""
1901        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
def set_operation(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1903    def set_operation(self, expression: exp.SetOperation) -> str:
1904        op_type = type(expression)
1905        op_name = op_type.key.upper()
1906
1907        distinct = expression.args.get("distinct")
1908        if (
1909            distinct is False
1910            and op_type in (exp.Except, exp.Intersect)
1911            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1912        ):
1913            self.unsupported(f"{op_name} ALL is not supported")
1914
1915        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1916
1917        if distinct is None:
1918            distinct = default_distinct
1919            if distinct is None:
1920                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1921
1922        if distinct is default_distinct:
1923            distinct_or_all = ""
1924        else:
1925            distinct_or_all = " DISTINCT" if distinct else " ALL"
1926
1927        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1928        side_kind = f"{side_kind} " if side_kind else ""
1929
1930        by_name = " BY NAME" if expression.args.get("by_name") else ""
1931        on = self.expressions(expression, key="on", flat=True)
1932        on = f" ON ({on})" if on else ""
1933
1934        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1936    def set_operations(self, expression: exp.SetOperation) -> str:
1937        if not self.SET_OP_MODIFIERS:
1938            limit = expression.args.get("limit")
1939            order = expression.args.get("order")
1940            offset = expression.args.get("offset")
1941
1942            if limit or order or offset:
1943                select = self._move_ctes_to_top_level(
1944                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1945                )
1946
1947                for arg in ("limit", "order", "offset"):
1948                    if value := expression.args.get(arg):
1949                        select.set(arg, value.pop())
1950                return self.sql(select)
1951
1952        sqls: list[str] = []
1953        stack: list[str | exp.Expr] = [expression]
1954
1955        while stack:
1956            node = stack.pop()
1957
1958            if isinstance(node, exp.SetOperation):
1959                stack.append(node.expression)
1960                stack.append(
1961                    self.maybe_comment(
1962                        self.set_operation(node), comments=node.comments, separated=True
1963                    )
1964                )
1965                stack.append(node.this)
1966            else:
1967                if (
1968                    not self.SET_OP_LIMITS
1969                    and isinstance(node, exp.Select)
1970                    and node.args.get("limit")
1971                ):
1972                    node = node.subquery(copy=False)
1973                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1974                        node = exp.select("*").from_(node, copy=False)
1975                sqls.append(self.sql(node))
1976
1977        this = self.sep().join(sqls)
1978        this = self.query_modifiers(expression, this)
1979        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.query.Fetch) -> str:
1981    def fetch_sql(self, expression: exp.Fetch) -> str:
1982        direction = expression.args.get("direction")
1983        direction = f" {direction}" if direction else ""
1984        count = self.sql(expression, "count")
1985        count = f" {count}" if count else ""
1986        limit_options = self.sql(expression, "limit_options")
1987        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1988        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.query.LimitOptions) -> str:
1990    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1991        percent = " PERCENT" if expression.args.get("percent") else ""
1992        rows = " ROWS" if expression.args.get("rows") else ""
1993        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1994        if not with_ties and rows:
1995            with_ties = " ONLY"
1996        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.core.Filter) -> str:
1998    def filter_sql(self, expression: exp.Filter) -> str:
1999        this = self.sql(expression, "this")
2000        where = self.sql(expression, "expression").strip()
2001        return f"{this} FILTER({where})"
def hint_sql(self, expression: sqlglot.expressions.core.Hint) -> str:
2003    def hint_sql(self, expression: exp.Hint) -> str:
2004        if not self.QUERY_HINTS:
2005            self.unsupported("Hints are not supported")
2006            return ""
2007
2008        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.constraints.IndexParameters) -> str:
2010    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2011        using = self.sql(expression, "using")
2012        using = f" USING {using}" if using else ""
2013        columns = self.expressions(expression, key="columns", flat=True)
2014        columns = f"({columns})" if columns else ""
2015        partition_by = self.expressions(expression, key="partition_by", flat=True)
2016        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2017        where = self.sql(expression, "where")
2018        include = self.expressions(expression, key="include", flat=True)
2019        if include:
2020            include = f" INCLUDE ({include})"
2021        with_storage = self.expressions(expression, key="with_storage", flat=True)
2022        with_storage = f" WITH ({with_storage})" if with_storage else ""
2023        tablespace = self.sql(expression, "tablespace")
2024        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
2025        on = self.sql(expression, "on")
2026        on = f" ON {on}" if on else ""
2027
2028        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.query.Index) -> str:
2030    def index_sql(self, expression: exp.Index) -> str:
2031        unique = "UNIQUE " if expression.args.get("unique") else ""
2032        primary = "PRIMARY " if expression.args.get("primary") else ""
2033        amp = "AMP " if expression.args.get("amp") else ""
2034        name = self.sql(expression, "this")
2035        name = f"{name} " if name else ""
2036        table = self.sql(expression, "table")
2037        table = f"{self.INDEX_ON} {table}" if table else ""
2038
2039        index = "INDEX " if not table else ""
2040
2041        params = self.sql(expression, "params")
2042        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def dynamicidentifier_sql(self, expression: sqlglot.expressions.core.DynamicIdentifier) -> str:
2044    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2045        this = expression.this
2046        if this and this.is_string:
2047            resolved = maybe_parse(this.name).sql(self.dialect)
2048            if "expressions" in expression.args:
2049                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2050                # We can't safely emit the call to other dialects since name/arg semantics may differ
2051                self.unsupported(
2052                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2053                )
2054            return resolved
2055        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2056        return self.func("IDENTIFIER", this)
def identifier_sql(self, expression: sqlglot.expressions.core.Identifier) -> str:
2058    def identifier_sql(self, expression: exp.Identifier) -> str:
2059        text = expression.name
2060        lower = text.lower()
2061        quoted = expression.quoted
2062        text = lower if self.normalize and not quoted else text
2063        if self._identifier_escaped_sequences:
2064            for char, escaped in self._identifier_escaped_sequences.items():
2065                text = text.replace(char, escaped)
2066        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2067        if (
2068            quoted
2069            or self.dialect.can_quote(expression, self.identify)
2070            or lower in self.RESERVED_KEYWORDS
2071            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2072        ):
2073            text = (
2074                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2075            )
2076        return text
def hex_sql(self, expression: sqlglot.expressions.string.Hex) -> str:
2078    def hex_sql(self, expression: exp.Hex) -> str:
2079        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2080        if self.dialect.HEX_LOWERCASE:
2081            text = self.func("LOWER", text)
2082
2083        return text
def lowerhex_sql(self, expression: sqlglot.expressions.string.LowerHex) -> str:
2085    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2086        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2087        if not self.dialect.HEX_LOWERCASE:
2088            text = self.func("LOWER", text)
2089        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.query.InputOutputFormat) -> str:
2091    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2092        input_format = self.sql(expression, "input_format")
2093        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2094        output_format = self.sql(expression, "output_format")
2095        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2096        return self.sep().join((input_format, output_format))
def national_sql( self, expression: sqlglot.expressions.query.National, prefix: str = 'N') -> str:
2098    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2099        string = self.sql(exp.Literal.string(expression.name))
2100        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.query.Partition) -> str:
2102    def partition_sql(self, expression: exp.Partition) -> str:
2103        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2104        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.properties.Properties) -> str:
2106    def properties_sql(self, expression: exp.Properties) -> str:
2107        root_properties = []
2108        with_properties = []
2109
2110        for p in expression.expressions:
2111            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2112            if p_loc == exp.Properties.Location.POST_WITH:
2113                with_properties.append(p)
2114            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2115                root_properties.append(p)
2116
2117        root_props_ast = exp.Properties(expressions=root_properties)
2118        root_props_ast.parent = expression.parent
2119
2120        with_props_ast = exp.Properties(expressions=with_properties)
2121        with_props_ast.parent = expression.parent
2122
2123        root_props = self.root_properties(root_props_ast)
2124        with_props = self.with_properties(with_props_ast)
2125
2126        if root_props and with_props and not self.pretty:
2127            with_props = " " + with_props
2128
2129        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
2131    def root_properties(self, properties: exp.Properties) -> str:
2132        if properties.expressions:
2133            return self.expressions(properties, indent=False, sep=" ")
2134        return ""
def properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2136    def properties(
2137        self,
2138        properties: exp.Properties,
2139        prefix: str = "",
2140        sep: str = ", ",
2141        suffix: str = "",
2142        wrapped: bool = True,
2143    ) -> str:
2144        if properties.expressions:
2145            expressions = self.expressions(properties, sep=sep, indent=False)
2146            if expressions:
2147                expressions = self.wrap(expressions) if wrapped else expressions
2148                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2149        return ""
def with_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
2151    def with_properties(self, properties: exp.Properties) -> str:
2152        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2154    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2155        properties_locs = defaultdict(list)
2156        for p in properties.expressions:
2157            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2158            if p_loc != exp.Properties.Location.UNSUPPORTED:
2159                properties_locs[p_loc].append(p)
2160            else:
2161                self.unsupported(f"Unsupported property {p.key}")
2162
2163        return properties_locs
def property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2165    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2166        if isinstance(expression.this, exp.Dot):
2167            return self.sql(expression, "this")
2168        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.properties.Property) -> str:
2170    def property_sql(self, expression: exp.Property) -> str:
2171        property_cls = expression.__class__
2172        if property_cls == exp.Property:
2173            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2174
2175        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2176        if not property_name:
2177            self.unsupported(f"Unsupported property {expression.key}")
2178
2179        return f"{property_name}={self.sql(expression, 'this')}"
def uuidproperty_sql(self, expression: sqlglot.expressions.properties.UuidProperty) -> str:
2181    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2182        return f"UUID {self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.properties.LikeProperty) -> str:
2184    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2185        if self.SUPPORTS_CREATE_TABLE_LIKE:
2186            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2187            options = f" {options}" if options else ""
2188
2189            like = f"LIKE {self.sql(expression, 'this')}{options}"
2190            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2191                like = f"({like})"
2192
2193            return like
2194
2195        if expression.expressions:
2196            self.unsupported("Transpilation of LIKE property options is unsupported")
2197
2198        select = exp.select("*").from_(expression.this).limit(0)
2199        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.properties.FallbackProperty) -> str:
2201    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2202        no = "NO " if expression.args.get("no") else ""
2203        protection = " PROTECTION" if expression.args.get("protection") else ""
2204        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.properties.JournalProperty) -> str:
2206    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2207        no = "NO " if expression.args.get("no") else ""
2208        local = expression.args.get("local")
2209        local = f"{local} " if local else ""
2210        dual = "DUAL " if expression.args.get("dual") else ""
2211        before = "BEFORE " if expression.args.get("before") else ""
2212        after = "AFTER " if expression.args.get("after") else ""
2213        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql( self, expression: sqlglot.expressions.properties.FreespaceProperty) -> str:
2215    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2216        freespace = self.sql(expression, "this")
2217        percent = " PERCENT" if expression.args.get("percent") else ""
2218        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.properties.ChecksumProperty) -> str:
2220    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2221        if expression.args.get("default"):
2222            property = "DEFAULT"
2223        elif expression.args.get("on"):
2224            property = "ON"
2225        else:
2226            property = "OFF"
2227        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql( self, expression: sqlglot.expressions.properties.MergeBlockRatioProperty) -> str:
2229    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2230        if expression.args.get("no"):
2231            return "NO MERGEBLOCKRATIO"
2232        if expression.args.get("default"):
2233            return "DEFAULT MERGEBLOCKRATIO"
2234
2235        percent = " PERCENT" if expression.args.get("percent") else ""
2236        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def moduleproperty_sql(self, expression: sqlglot.expressions.properties.ModuleProperty) -> str:
2238    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2239        expressions = self.expressions(expression, flat=True)
2240        expressions = f"({expressions})" if expressions else ""
2241        return f"USING {self.sql(expression, 'this')}{expressions}"
def datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2243    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2244        default = expression.args.get("default")
2245        minimum = expression.args.get("minimum")
2246        maximum = expression.args.get("maximum")
2247        if default or minimum or maximum:
2248            if default:
2249                prop = "DEFAULT"
2250            elif minimum:
2251                prop = "MINIMUM"
2252            else:
2253                prop = "MAXIMUM"
2254            return f"{prop} DATABLOCKSIZE"
2255        units = expression.args.get("units")
2256        units = f" {units}" if units else ""
2257        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2259    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2260        autotemp = expression.args.get("autotemp")
2261        always = expression.args.get("always")
2262        default = expression.args.get("default")
2263        manual = expression.args.get("manual")
2264        never = expression.args.get("never")
2265
2266        if autotemp is not None:
2267            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2268        elif always:
2269            prop = "ALWAYS"
2270        elif default:
2271            prop = "DEFAULT"
2272        elif manual:
2273            prop = "MANUAL"
2274        elif never:
2275            prop = "NEVER"
2276        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2278    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2279        no = expression.args.get("no")
2280        no = " NO" if no else ""
2281        concurrent = expression.args.get("concurrent")
2282        concurrent = " CONCURRENT" if concurrent else ""
2283        target = self.sql(expression, "target")
2284        target = f" {target}" if target else ""
2285        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2287    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2288        if isinstance(expression.this, list):
2289            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2290        if expression.this:
2291            modulus = self.sql(expression, "this")
2292            remainder = self.sql(expression, "expression")
2293            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2294
2295        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2296        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2297        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2299    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2300        this = self.sql(expression, "this")
2301
2302        for_values_or_default = expression.expression
2303        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2304            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2305        else:
2306            for_values_or_default = " DEFAULT"
2307
2308        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.properties.LockingProperty) -> str:
2310    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2311        kind = expression.args.get("kind")
2312        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2313        for_or_in = expression.args.get("for_or_in")
2314        for_or_in = f" {for_or_in}" if for_or_in else ""
2315        lock_type = expression.args.get("lock_type")
2316        override = " OVERRIDE" if expression.args.get("override") else ""
2317        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.properties.WithDataProperty) -> str:
2319    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2320        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2321        statistics = expression.args.get("statistics")
2322        statistics_sql = ""
2323        if statistics is not None:
2324            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2325        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2327    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2328        this = self.sql(expression, "this")
2329        this = f"HISTORY_TABLE={this}" if this else ""
2330        data_consistency: str | None = self.sql(expression, "data_consistency")
2331        data_consistency = (
2332            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2333        )
2334        retention_period: str | None = self.sql(expression, "retention_period")
2335        retention_period = (
2336            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2337        )
2338
2339        if this:
2340            on_sql = self.func("ON", this, data_consistency, retention_period)
2341        else:
2342            on_sql = "ON" if expression.args.get("on") else "OFF"
2343
2344        sql = f"SYSTEM_VERSIONING={on_sql}"
2345
2346        return f"WITH({sql})" if expression.args.get("with_") else sql
def insert_sql(self, expression: sqlglot.expressions.dml.Insert) -> str:
2348    def insert_sql(self, expression: exp.Insert) -> str:
2349        hint = self.sql(expression, "hint")
2350        overwrite = expression.args.get("overwrite")
2351
2352        if isinstance(expression.this, exp.Directory):
2353            this = " OVERWRITE" if overwrite else " INTO"
2354        else:
2355            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2356
2357        stored = self.sql(expression, "stored")
2358        stored = f" {stored}" if stored else ""
2359        alternative = expression.args.get("alternative")
2360        alternative = f" OR {alternative}" if alternative else ""
2361        ignore = " IGNORE" if expression.args.get("ignore") else ""
2362        is_function = expression.args.get("is_function")
2363        if is_function:
2364            this = f"{this} FUNCTION"
2365        this = f"{this} {self.sql(expression, 'this')}"
2366
2367        exists = " IF EXISTS" if expression.args.get("exists") else ""
2368        where = self.sql(expression, "where")
2369        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2370        using = self.expressions(expression, key="using", flat=True)
2371        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2372        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2373        on_conflict = self.sql(expression, "conflict")
2374        on_conflict = f" {on_conflict}" if on_conflict else ""
2375        by_name = " BY NAME" if expression.args.get("by_name") else ""
2376        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2377        returning = self.sql(expression, "returning")
2378
2379        if self.RETURNING_END:
2380            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2381        else:
2382            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2383
2384        partition_by = self.sql(expression, "partition")
2385        partition_by = f" {partition_by}" if partition_by else ""
2386        settings = self.sql(expression, "settings")
2387        settings = f" {settings}" if settings else ""
2388
2389        source = self.sql(expression, "source")
2390        source = f"TABLE {source}" if source else ""
2391
2392        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2393        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.query.Introducer) -> str:
2395    def introducer_sql(self, expression: exp.Introducer) -> str:
2396        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.ddl.Kill) -> str:
2398    def kill_sql(self, expression: exp.Kill) -> str:
2399        kind = self.sql(expression, "kind")
2400        kind = f" {kind}" if kind else ""
2401        this = self.sql(expression, "this")
2402        this = f" {this}" if this else ""
2403        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.datatypes.PseudoType) -> str:
2405    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2406        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.datatypes.ObjectIdentifier) -> str:
2408    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2409        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.dml.OnConflict) -> str:
2411    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2412        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2413
2414        constraint = self.sql(expression, "constraint")
2415        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2416
2417        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2418        if conflict_keys:
2419            conflict_keys = f"({conflict_keys})"
2420
2421        index_predicate = self.sql(expression, "index_predicate")
2422        conflict_keys = f"{conflict_keys}{index_predicate} "
2423
2424        action = self.sql(expression, "action")
2425
2426        expressions = self.expressions(expression, flat=True)
2427        if expressions:
2428            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2429            expressions = f" {set_keyword}{expressions}"
2430
2431        where = self.sql(expression, "where")
2432        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.dml.Returning) -> str:
2434    def returning_sql(self, expression: exp.Returning) -> str:
2435        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2437    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2438        fields = self.sql(expression, "fields")
2439        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2440        escaped = self.sql(expression, "escaped")
2441        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2442        items = self.sql(expression, "collection_items")
2443        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2444        keys = self.sql(expression, "map_keys")
2445        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2446        lines = self.sql(expression, "lines")
2447        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2448        null = self.sql(expression, "null")
2449        null = f" NULL DEFINED AS {null}" if null else ""
2450        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.query.WithTableHint) -> str:
2452    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2453        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.query.IndexTableHint) -> str:
2455    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2456        this = f"{self.sql(expression, 'this')} INDEX"
2457        target = self.sql(expression, "target")
2458        target = f" FOR {target}" if target else ""
2459        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.query.HistoricalData) -> str:
2461    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2462        this = self.sql(expression, "this")
2463        kind = self.sql(expression, "kind")
2464        expr = self.sql(expression, "expression")
2465        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.query.Table) -> str:
2467    def table_parts(self, expression: exp.Table) -> str:
2468        return ".".join(
2469            self.sql(part)
2470            for part in (
2471                expression.args.get("catalog"),
2472                expression.args.get("db"),
2473                expression.args.get("this"),
2474            )
2475            if part is not None
2476        )
def table_sql( self, expression: sqlglot.expressions.query.Table, sep: str = ' AS ') -> str:
2478    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2479        table = self.table_parts(expression)
2480        only = "ONLY " if expression.args.get("only") else ""
2481        partition = self.sql(expression, "partition")
2482        partition = f" {partition}" if partition else ""
2483        version = self.sql(expression, "version")
2484        version = f" {version}" if version else ""
2485        alias = self.sql(expression, "alias")
2486        alias = f"{sep}{alias}" if alias else ""
2487
2488        sample = self.sql(expression, "sample")
2489        post_alias = ""
2490        pre_alias = ""
2491
2492        if self.dialect.ALIAS_POST_TABLESAMPLE:
2493            pre_alias = sample
2494        else:
2495            post_alias = sample
2496
2497        if self.dialect.ALIAS_POST_VERSION:
2498            pre_alias = f"{pre_alias}{version}"
2499        else:
2500            post_alias = f"{post_alias}{version}"
2501
2502        hints = self.expressions(expression, key="hints", sep=" ")
2503        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2504        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2505        joins = self.indent(
2506            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2507        )
2508        laterals = self.expressions(expression, key="laterals", sep="")
2509
2510        file_format = self.sql(expression, "format")
2511        pattern = self.sql(expression, "pattern")
2512        if file_format:
2513            pattern = f", PATTERN => {pattern}" if pattern else ""
2514            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2515        elif pattern:
2516            file_format = f" (PATTERN => {pattern})"
2517
2518        ordinality = expression.args.get("ordinality") or ""
2519        if ordinality:
2520            ordinality = f" WITH ORDINALITY{alias}"
2521            alias = ""
2522
2523        when = self.sql(expression, "when")
2524        if when:
2525            if self.HISTORICAL_DATA_POST_ALIAS:
2526                alias = f"{alias} {when}"
2527            else:
2528                table = f"{table} {when}"
2529
2530        changes = self.sql(expression, "changes")
2531        changes = f" {changes}" if changes else ""
2532
2533        rows_from = self.expressions(expression, key="rows_from")
2534        if rows_from:
2535            table = f"ROWS FROM {self.wrap(rows_from)}"
2536
2537        indexed = expression.args.get("indexed")
2538        if indexed is not None:
2539            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2540        else:
2541            indexed = ""
2542
2543        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
def tablefromrows_sql(self, expression: sqlglot.expressions.query.TableFromRows) -> str:
2545    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2546        table = self.func("TABLE", expression.this)
2547        alias = self.sql(expression, "alias")
2548        alias = f" AS {alias}" if alias else ""
2549        sample = self.sql(expression, "sample")
2550        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2551        joins = self.indent(
2552            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2553        )
2554        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2556    def tablesample_sql(
2557        self,
2558        expression: exp.TableSample,
2559        tablesample_keyword: str | None = None,
2560    ) -> str:
2561        method = self.sql(expression, "method")
2562        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2563        numerator = self.sql(expression, "bucket_numerator")
2564        denominator = self.sql(expression, "bucket_denominator")
2565        field = self.sql(expression, "bucket_field")
2566        field = f" ON {field}" if field else ""
2567        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2568        seed = self.sql(expression, "seed")
2569        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2570
2571        size = self.sql(expression, "size")
2572        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2573            size = f"{size} ROWS"
2574
2575        percent = self.sql(expression, "percent")
2576        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2577            percent = f"{percent} PERCENT"
2578
2579        expr = f"{bucket}{percent}{size}"
2580        if self.TABLESAMPLE_REQUIRES_PARENS:
2581            expr = f"({expr})"
2582
2583        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.query.Pivot) -> str:
2660    def pivot_sql(self, expression: exp.Pivot) -> str:
2661        expressions = self.expressions(expression, flat=True)
2662        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2663
2664        group = self.sql(expression, "group")
2665
2666        if expression.this:
2667            this = self.sql(expression, "this")
2668            if not expressions:
2669                sql = f"UNPIVOT {this}"
2670            else:
2671                on = f"{self.seg('ON')} {expressions}"
2672                into = self.sql(expression, "into")
2673                into = f"{self.seg('INTO')} {into}" if into else ""
2674                using = self.expressions(expression, key="using", flat=True)
2675                using = f"{self.seg('USING')} {using}" if using else ""
2676                sql = f"{direction} {this}{on}{into}{using}{group}"
2677            return self.prepend_ctes(expression, sql)
2678
2679        if not expression.unpivot:
2680            # Wrap IN-list values with explicit aliases where the target dialect would differ
2681            new_field_exprs = self._pivot_in_value_aliases(expression)
2682            if new_field_exprs is not None:
2683                expression.fields[0].set("expressions", new_field_exprs)
2684
2685        alias = self.sql(expression, "alias")
2686        if alias:
2687            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2688
2689        fields = self.expressions(
2690            expression,
2691            "fields",
2692            sep=" ",
2693            dynamic=True,
2694            new_line=True,
2695            skip_first=True,
2696            skip_last=True,
2697        )
2698
2699        include_nulls = expression.args.get("include_nulls")
2700        if include_nulls is not None:
2701            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2702        else:
2703            nulls = ""
2704
2705        default_on_null = self.sql(expression, "default_on_null")
2706        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2707        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2708        return self.prepend_ctes(expression, sql)
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
2710    def version_sql(self, expression: exp.Version) -> str:
2711        this = f"FOR {expression.name}"
2712        kind = expression.text("kind")
2713        expr = self.sql(expression, "expression")
2714        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.query.Tuple) -> str:
2716    def tuple_sql(self, expression: exp.Tuple) -> str:
2717        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
def update_sql(self, expression: sqlglot.expressions.dml.Update) -> str:
2751    def update_sql(self, expression: exp.Update) -> str:
2752        hint = self.sql(expression, "hint")
2753        this = self.sql(expression, "this")
2754        join_sql, from_sql = self._update_from_joins_sql(expression)
2755        set_sql = self.expressions(expression, flat=True)
2756        where_sql = self.sql(expression, "where")
2757        returning = self.sql(expression, "returning")
2758        order = self.sql(expression, "order")
2759        limit = self.sql(expression, "limit")
2760        if self.RETURNING_END:
2761            expression_sql = f"{from_sql}{where_sql}{returning}"
2762        else:
2763            expression_sql = f"{returning}{from_sql}{where_sql}"
2764        options = self.expressions(expression, key="options")
2765        options = f" OPTION({options})" if options else ""
2766        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2767        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2769    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2770        values_as_table = values_as_table and self.VALUES_AS_TABLE
2771
2772        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2773        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2774            args = self.expressions(expression)
2775            alias = self.sql(expression, "alias")
2776            values = f"VALUES{self.seg('')}{args}"
2777            values = (
2778                f"({values})"
2779                if self.WRAP_DERIVED_VALUES
2780                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2781                else values
2782            )
2783            values = self.query_modifiers(expression, values)
2784            return f"{values} AS {alias}" if alias else values
2785
2786        # Converts `VALUES...` expression into a series of select unions.
2787        alias_node = expression.args.get("alias")
2788        column_names = alias_node and alias_node.columns
2789
2790        selects: list[exp.Query] = []
2791
2792        for i, tup in enumerate(expression.expressions):
2793            row = tup.expressions
2794
2795            if i == 0 and column_names:
2796                row = [
2797                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2798                ]
2799
2800            selects.append(exp.Select(expressions=row))
2801
2802        if self.pretty:
2803            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2804            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2805            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2806            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2807            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2808
2809        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2810        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2811        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.core.Var) -> str:
2813    def var_sql(self, expression: exp.Var) -> str:
2814        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2816    @unsupported_args("expressions")
2817    def into_sql(self, expression: exp.Into) -> str:
2818        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2819        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2820        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.query.From) -> str:
2822    def from_sql(self, expression: exp.From) -> str:
2823        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.query.GroupingSets) -> str:
2825    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2826        grouping_sets = self.expressions(expression, indent=False)
2827        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.query.Rollup) -> str:
2829    def rollup_sql(self, expression: exp.Rollup) -> str:
2830        expressions = self.expressions(expression, indent=False)
2831        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def rollupindex_sql(self, expression: sqlglot.expressions.properties.RollupIndex) -> str:
2833    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2834        this = self.sql(expression, "this")
2835
2836        columns = self.expressions(expression, flat=True)
2837
2838        from_sql = self.sql(expression, "from_index")
2839        from_sql = f" FROM {from_sql}" if from_sql else ""
2840
2841        properties = expression.args.get("properties")
2842        properties_sql = (
2843            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2844        )
2845
2846        return f"{this}({columns}){from_sql}{properties_sql}"
def rollupproperty_sql(self, expression: sqlglot.expressions.properties.RollupProperty) -> str:
2848    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2849        return f"ROLLUP ({self.expressions(expression, flat=True)})"
def cube_sql(self, expression: sqlglot.expressions.query.Cube) -> str:
2851    def cube_sql(self, expression: exp.Cube) -> str:
2852        expressions = self.expressions(expression, indent=False)
2853        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.query.Group) -> str:
2855    def group_sql(self, expression: exp.Group) -> str:
2856        group_by_all = expression.args.get("all")
2857        if group_by_all is True:
2858            modifier = " ALL"
2859        elif group_by_all is False:
2860            modifier = " DISTINCT"
2861        else:
2862            modifier = ""
2863
2864        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2865
2866        grouping_sets = self.expressions(expression, key="grouping_sets")
2867        cube = self.expressions(expression, key="cube")
2868        rollup = self.expressions(expression, key="rollup")
2869
2870        groupings = csv(
2871            self.seg(grouping_sets) if grouping_sets else "",
2872            self.seg(cube) if cube else "",
2873            self.seg(rollup) if rollup else "",
2874            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2875            sep=self.GROUPINGS_SEP,
2876        )
2877
2878        if (
2879            expression.expressions
2880            and groupings
2881            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2882        ):
2883            add_separator = True
2884
2885            if grouping_sets:
2886                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2887                    add_separator = False
2888                else:
2889                    self.unsupported(
2890                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2891                    )
2892
2893            if add_separator:
2894                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2895
2896        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.query.Having) -> str:
2898    def having_sql(self, expression: exp.Having) -> str:
2899        this = self.indent(self.sql(expression, "this"))
2900        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.query.Connect) -> str:
2902    def connect_sql(self, expression: exp.Connect) -> str:
2903        start = self.sql(expression, "start")
2904        start = self.seg(f"START WITH {start}") if start else ""
2905        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2906        connect = self.sql(expression, "connect")
2907        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2908        return start + connect
def prior_sql(self, expression: sqlglot.expressions.query.Prior) -> str:
2910    def prior_sql(self, expression: exp.Prior) -> str:
2911        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.query.Join) -> str:
2913    def join_sql(self, expression: exp.Join) -> str:
2914        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2915            side = None
2916        else:
2917            side = expression.side
2918
2919        op_sql = " ".join(
2920            op
2921            for op in (
2922                expression.method,
2923                "GLOBAL" if expression.args.get("global_") else None,
2924                side,
2925                expression.kind,
2926                expression.hint if self.JOIN_HINTS else None,
2927                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2928            )
2929            if op
2930        )
2931        match_cond = self.sql(expression, "match_condition")
2932        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2933        on_sql = self.sql(expression, "on")
2934        using = expression.args.get("using")
2935
2936        if not on_sql and using:
2937            on_sql = csv(*(self.sql(column) for column in using))
2938
2939        this = expression.this
2940        this_sql = self.sql(this)
2941
2942        exprs = self.expressions(expression)
2943        if exprs:
2944            this_sql = f"{this_sql},{self.seg(exprs)}"
2945
2946        if on_sql:
2947            on_sql = self.indent(on_sql, skip_first=True)
2948            space = self.seg(" " * self.pad) if self.pretty else " "
2949            if using:
2950                on_sql = f"{space}USING ({on_sql})"
2951            else:
2952                on_sql = f"{space}ON {on_sql}"
2953        elif not op_sql:
2954            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2955                return f" {this_sql}"
2956
2957            return f", {this_sql}"
2958
2959        if op_sql != "STRAIGHT_JOIN":
2960            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2961
2962        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2963        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2965    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2966        args = self.expressions(expression, flat=True)
2967        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2968        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.query.Lateral) -> str:
2970    def lateral_op(self, expression: exp.Lateral) -> str:
2971        cross_apply = expression.args.get("cross_apply")
2972
2973        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2974        if cross_apply is True:
2975            op = "INNER JOIN "
2976        elif cross_apply is False:
2977            op = "LEFT JOIN "
2978        else:
2979            op = ""
2980
2981        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.query.Lateral) -> str:
2983    def lateral_sql(self, expression: exp.Lateral) -> str:
2984        this = self.sql(expression, "this")
2985
2986        if expression.args.get("view"):
2987            alias = expression.args["alias"]
2988            columns = self.expressions(alias, key="columns", flat=True)
2989            table = f" {alias.name}" if alias.name else ""
2990            columns = f" AS {columns}" if columns else ""
2991            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2992            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2993
2994        table_alias = expression.args.get("alias")
2995        offset = expression.this.args.get("offset")
2996
2997        if (
2998            self.UNNEST_WITH_ORDINALITY
2999            and table_alias
3000            and isinstance(expression.this, exp.Unnest)
3001            and isinstance(offset, exp.Identifier)
3002        ):
3003            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3004            table_alias = table_alias.copy()
3005            table_alias.append("columns", offset.copy())
3006
3007        alias = self.sql(table_alias)
3008        alias = f" AS {alias}" if alias else ""
3009
3010        ordinality = expression.args.get("ordinality") or ""
3011        if ordinality:
3012            ordinality = f" WITH ORDINALITY{alias}"
3013            alias = ""
3014
3015        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql( self, expression: sqlglot.expressions.query.Limit, top: bool = False) -> str:
3017    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3018        this = self.sql(expression, "this")
3019
3020        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3021            return this
3022
3023        args = [
3024            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3025            for e in (expression.args.get(k) for k in ("offset", "expression"))
3026            if e
3027        ]
3028
3029        args_sql = ", ".join(self.sql(e) for e in args)
3030        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3031        expressions = self.expressions(expression, flat=True)
3032        limit_options = self.sql(expression, "limit_options")
3033        expressions = f" BY {expressions}" if expressions else ""
3034
3035        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
def offset_sql(self, expression: sqlglot.expressions.query.Offset) -> str:
3037    def offset_sql(self, expression: exp.Offset) -> str:
3038        this = self.sql(expression, "this")
3039        value = expression.expression
3040        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3041        expressions = self.expressions(expression, flat=True)
3042        expressions = f" BY {expressions}" if expressions else ""
3043        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.ddl.SetItem) -> str:
3045    def setitem_sql(self, expression: exp.SetItem) -> str:
3046        kind = self.sql(expression, "kind")
3047        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3048            kind = ""
3049        else:
3050            kind = f"{kind} " if kind else ""
3051        this = self.sql(expression, "this")
3052        expressions = self.expressions(expression)
3053        collate = self.sql(expression, "collate")
3054        collate = f" COLLATE {collate}" if collate else ""
3055        global_ = "GLOBAL " if expression.args.get("global_") else ""
3056        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.ddl.Set) -> str:
3058    def set_sql(self, expression: exp.Set) -> str:
3059        expressions = f" {self.expressions(expression, flat=True)}"
3060        tag = " TAG" if expression.args.get("tag") else ""
3061        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def queryband_sql(self, expression: sqlglot.expressions.query.QueryBand) -> str:
3063    def queryband_sql(self, expression: exp.QueryBand) -> str:
3064        this = self.sql(expression, "this")
3065        update = " UPDATE" if expression.args.get("update") else ""
3066        scope = self.sql(expression, "scope")
3067        scope = f" FOR {scope}" if scope else ""
3068
3069        return f"QUERY_BAND = {this}{update}{scope}"
def pragma_sql(self, expression: sqlglot.expressions.ddl.Pragma) -> str:
3071    def pragma_sql(self, expression: exp.Pragma) -> str:
3072        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.query.Lock) -> str:
3074    def lock_sql(self, expression: exp.Lock) -> str:
3075        if not self.LOCKING_READS_SUPPORTED:
3076            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3077            return ""
3078
3079        update = expression.args["update"]
3080        key = expression.args.get("key")
3081        if update:
3082            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3083        else:
3084            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3085        expressions = self.expressions(expression, flat=True)
3086        expressions = f" OF {expressions}" if expressions else ""
3087        wait = expression.args.get("wait")
3088
3089        if wait is not None:
3090            if isinstance(wait, exp.Literal):
3091                wait = f" WAIT {self.sql(wait)}"
3092            else:
3093                wait = " NOWAIT" if wait else " SKIP LOCKED"
3094
3095        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.core.Literal) -> str:
3097    def literal_sql(self, expression: exp.Literal) -> str:
3098        text = expression.this or ""
3099        if expression.is_string:
3100            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3101        return text
def escape_str( self, text: str, escape_backslash: bool = True, delimiter: str | None = None, escaped_delimiter: str | None = None, is_byte_string: bool = False, is_bytes: bool = False) -> str:
3103    def escape_str(
3104        self,
3105        text: str,
3106        escape_backslash: bool = True,
3107        delimiter: str | None = None,
3108        escaped_delimiter: str | None = None,
3109        is_byte_string: bool = False,
3110        is_bytes: bool = False,
3111    ) -> str:
3112        if is_byte_string:
3113            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3114        else:
3115            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3116
3117        if supports_escape_sequences:
3118            text = "".join(
3119                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3120                for ch in text
3121            )
3122
3123            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3124                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3125                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3126
3127        delimiter = delimiter or self.dialect.QUOTE_END
3128        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3129
3130        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
def loaddata_sql(self, expression: sqlglot.expressions.dml.LoadData) -> str:
3132    def loaddata_sql(self, expression: exp.LoadData) -> str:
3133        is_overwrite = expression.args.get("overwrite")
3134        overwrite = " OVERWRITE" if is_overwrite else ""
3135        this = self.sql(expression, "this")
3136
3137        files = expression.args.get("files")
3138        if files:
3139            files_sql = self.expressions(files, flat=True)
3140            files_sql = f"FILES{self.wrap(files_sql)}"
3141            if is_overwrite:
3142                this = f" {this}"
3143            elif expression.args.get("temp"):
3144                this = f" INTO TEMP TABLE {this}"
3145            else:
3146                this = f" INTO TABLE {this}"
3147            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3148
3149        local = " LOCAL" if expression.args.get("local") else ""
3150        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3151        this = f" INTO TABLE {this}"
3152        partition = self.sql(expression, "partition")
3153        partition = f" {partition}" if partition else ""
3154        input_format = self.sql(expression, "input_format")
3155        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3156        serde = self.sql(expression, "serde")
3157        serde = f" SERDE {serde}" if serde else ""
3158        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
3160    def null_sql(self, *_) -> str:
3161        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.core.Boolean) -> str:
3163    def boolean_sql(self, expression: exp.Boolean) -> str:
3164        return "TRUE" if expression.this else "FALSE"
def booland_sql(self, expression: sqlglot.expressions.math.Booland) -> str:
3166    def booland_sql(self, expression: exp.Booland) -> str:
3167        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
def boolor_sql(self, expression: sqlglot.expressions.math.Boolor) -> str:
3169    def boolor_sql(self, expression: exp.Boolor) -> str:
3170        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
def order_sql( self, expression: sqlglot.expressions.query.Order, flat: bool = False) -> str:
3172    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3173        this = self.sql(expression, "this")
3174        this = f"{this} " if this else this
3175        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3176        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
def withfill_sql(self, expression: sqlglot.expressions.query.WithFill) -> str:
3178    def withfill_sql(self, expression: exp.WithFill) -> str:
3179        from_sql = self.sql(expression, "from_")
3180        from_sql = f" FROM {from_sql}" if from_sql else ""
3181        to_sql = self.sql(expression, "to")
3182        to_sql = f" TO {to_sql}" if to_sql else ""
3183        step_sql = self.sql(expression, "step")
3184        step_sql = f" STEP {step_sql}" if step_sql else ""
3185        interpolated_values = [
3186            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3187            if isinstance(e, exp.Alias)
3188            else self.sql(e, "this")
3189            for e in expression.args.get("interpolate") or []
3190        ]
3191        interpolate = (
3192            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3193        )
3194        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.query.Cluster) -> str:
3196    def cluster_sql(self, expression: exp.Cluster) -> str:
3197        return self.op_expressions("CLUSTER BY", expression)
def clusterproperty_sql(self, expression: sqlglot.expressions.properties.ClusterProperty) -> str:
3199    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3200        if expression.this:
3201            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3202            return ""
3203        expressions = self.expressions(expression, flat=True)
3204        return f"CLUSTER BY ({expressions})"
def distribute_sql(self, expression: sqlglot.expressions.query.Distribute) -> str:
3206    def distribute_sql(self, expression: exp.Distribute) -> str:
3207        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.query.Sort) -> str:
3209    def sort_sql(self, expression: exp.Sort) -> str:
3210        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.core.Ordered) -> str:
3248    def ordered_sql(self, expression: exp.Ordered) -> str:
3249        desc = expression.args.get("desc")
3250        asc = not desc
3251
3252        nulls_first = expression.args.get("nulls_first")
3253        nulls_last = not nulls_first
3254        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3255        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3256        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3257
3258        this = self.sql(expression, "this")
3259
3260        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3261        nulls_sort_change = ""
3262        if nulls_first and (
3263            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3264        ):
3265            nulls_sort_change = " NULLS FIRST"
3266        elif (
3267            nulls_last
3268            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3269            and not nulls_are_last
3270        ):
3271            nulls_sort_change = " NULLS LAST"
3272
3273        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3274        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3275            window = expression.find_ancestor(exp.Window, exp.Select)
3276
3277            if isinstance(window, exp.Window):
3278                window_this = window.this
3279                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3280                    window_this = window_this.this
3281                spec = window.args.get("spec")
3282            else:
3283                window_this = None
3284                spec = None
3285
3286            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3287            # without a spec or with a ROWS spec, but not with RANGE
3288            if not (
3289                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3290                and (not spec or spec.text("kind").upper() == "ROWS")
3291            ):
3292                if window_this and spec:
3293                    self.unsupported(
3294                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3295                    )
3296                    nulls_sort_change = ""
3297                elif self.NULL_ORDERING_SUPPORTED is False and (
3298                    (asc and nulls_sort_change == " NULLS LAST")
3299                    or (desc and nulls_sort_change == " NULLS FIRST")
3300                ):
3301                    # BigQuery does not allow these ordering/nulls combinations when used under
3302                    # an aggregation func or under a window containing one
3303                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3304
3305                    if isinstance(ancestor, exp.Window):
3306                        ancestor = ancestor.this
3307                    if isinstance(ancestor, exp.AggFunc):
3308                        self.unsupported(
3309                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3310                        )
3311                        nulls_sort_change = ""
3312                elif self.NULL_ORDERING_SUPPORTED is None:
3313                    if expression.this.is_int:
3314                        self.unsupported(
3315                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3316                        )
3317                    elif not isinstance(expression.this, exp.Rand):
3318                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3319                        target = self.sql(resolved) if resolved is not None else this
3320                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3321                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3322                    nulls_sort_change = ""
3323
3324        with_fill = self.sql(expression, "with_fill")
3325        with_fill = f" {with_fill}" if with_fill else ""
3326
3327        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3329    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3330        window_frame = self.sql(expression, "window_frame")
3331        window_frame = f"{window_frame} " if window_frame else ""
3332
3333        this = self.sql(expression, "this")
3334
3335        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.query.MatchRecognize) -> str:
3337    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3338        partition = self.partition_by_sql(expression)
3339        order = self.sql(expression, "order")
3340        measures = self.expressions(expression, key="measures")
3341        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3342        rows = self.sql(expression, "rows")
3343        rows = self.seg(rows) if rows else ""
3344        after = self.sql(expression, "after")
3345        after = self.seg(after) if after else ""
3346        pattern = self.sql(expression, "pattern")
3347        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3348        definition_sqls = [
3349            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3350            for definition in expression.args.get("define", [])
3351        ]
3352        definitions = self.expressions(sqls=definition_sqls)
3353        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3354        body = "".join(
3355            (
3356                partition,
3357                order,
3358                measures,
3359                rows,
3360                after,
3361                pattern,
3362                define,
3363            )
3364        )
3365        alias = self.sql(expression, "alias")
3366        alias = f" {alias}" if alias else ""
3367        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.core.Expr, *sqls: str) -> str:
3369    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3370        limit = expression.args.get("limit")
3371
3372        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3373            count = limit.args.get("count")
3374            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3375            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3376            limit = exp.Limit(
3377                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3378            )
3379        elif (
3380            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3381        ):
3382            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3383
3384        return csv(
3385            *sqls,
3386            *[self.sql(join) for join in expression.args.get("joins") or []],
3387            self.sql(expression, "match"),
3388            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3389            self.sql(expression, "prewhere"),
3390            self.sql(expression, "where"),
3391            self.sql(expression, "connect"),
3392            self.sql(expression, "group"),
3393            self.sql(expression, "having"),
3394            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3395            self.sql(expression, "order"),
3396            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3397            *self.after_limit_modifiers(expression),
3398            self.sql(expression, "for_"),
3399            self.options_modifier(expression),
3400            sep="",
3401        )
def options_modifier(self, expression: sqlglot.expressions.core.Expr) -> str:
3403    def options_modifier(self, expression: exp.Expr) -> str:
3404        options = self.expressions(expression, key="options")
3405        return f" {options}" if options else ""
def forclause_sql(self, expression: sqlglot.expressions.query.ForClause) -> str:
3407    def forclause_sql(self, expression: exp.ForClause) -> str:
3408        kind = expression.args["kind"]
3409        if kind == "BROWSE":
3410            return f"{self.sep()}FOR BROWSE"
3411        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3412        # the target dialect doesn't support QueryOption, so we drop the clause.
3413        options = self.expressions(expression, key="expressions")
3414        if not options:
3415            return ""
3416        return f"{self.sep()}FOR {kind}{self.seg(options)}"
def queryoption_sql(self, expression: sqlglot.expressions.query.QueryOption) -> str:
3418    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3419        self.unsupported("Unsupported query option.")
3420        return ""
def offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
3422    def offset_limit_modifiers(
3423        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3424    ) -> list[str]:
3425        return [
3426            self.sql(expression, "offset") if fetch else self.sql(limit),
3427            self.sql(limit) if fetch else self.sql(expression, "offset"),
3428        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.core.Expr) -> list[str]:
3430    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3431        locks = self.expressions(expression, key="locks", sep=" ")
3432        locks = f" {locks}" if locks else ""
3433        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.query.Select) -> str:
3435    def select_sql(self, expression: exp.Select) -> str:
3436        into = expression.args.get("into")
3437        if not self.SUPPORTS_SELECT_INTO and into:
3438            into.pop()
3439
3440        hint = self.sql(expression, "hint")
3441        distinct = self.sql(expression, "distinct")
3442        distinct = f" {distinct}" if distinct else ""
3443        kind = self.sql(expression, "kind")
3444
3445        limit = expression.args.get("limit")
3446        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3447            top = self.limit_sql(limit, top=True)
3448            limit.pop()
3449        else:
3450            top = ""
3451
3452        expressions = self.expressions(expression)
3453
3454        if kind:
3455            if kind in self.SELECT_KINDS:
3456                kind = f" AS {kind}"
3457            else:
3458                if kind == "STRUCT":
3459                    expressions = self.expressions(
3460                        sqls=[
3461                            self.sql(
3462                                exp.Struct(
3463                                    expressions=[
3464                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3465                                        if isinstance(e, exp.Alias)
3466                                        else e
3467                                        for e in expression.expressions
3468                                    ]
3469                                )
3470                            )
3471                        ]
3472                    )
3473                kind = ""
3474
3475        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3476        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3477
3478        exclude = expression.args.get("exclude")
3479
3480        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3481            exclude_sql = self.expressions(sqls=exclude, flat=True)
3482            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3483
3484        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3485        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3486        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3487        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3488        sql = self.query_modifiers(
3489            expression,
3490            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3491            self.sql(expression, "into", comment=False),
3492            self.sql(expression, "from_", comment=False),
3493        )
3494
3495        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3496        if expression.args.get("with_"):
3497            sql = self.maybe_comment(sql, expression)
3498            expression.pop_comments()
3499
3500        sql = self.prepend_ctes(expression, sql)
3501
3502        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3503            expression.set("exclude", None)
3504            subquery = expression.subquery(copy=False)
3505            star = exp.Star(except_=exclude)
3506            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3507
3508        if not self.SUPPORTS_SELECT_INTO and into:
3509            if into.args.get("temporary"):
3510                table_kind = " TEMPORARY"
3511            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3512                table_kind = " UNLOGGED"
3513            else:
3514                table_kind = ""
3515            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3516
3517        return sql
def schema_sql(self, expression: sqlglot.expressions.query.Schema) -> str:
3519    def schema_sql(self, expression: exp.Schema) -> str:
3520        this = self.sql(expression, "this")
3521        sql = self.schema_columns_sql(expression)
3522        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
3524    def schema_columns_sql(self, expression: exp.Expr) -> str:
3525        if expression.expressions:
3526            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3527        return ""
def star_sql(self, expression: sqlglot.expressions.core.Star) -> str:
3529    def star_sql(self, expression: exp.Star) -> str:
3530        except_ = self.expressions(expression, key="except_", flat=True)
3531        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3532        replace = self.expressions(expression, key="replace", flat=True)
3533        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3534        rename = self.expressions(expression, key="rename", flat=True)
3535        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3536        ilike = self.sql(expression, "ilike")
3537        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3538        return f"*{ilike}{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.core.Parameter) -> str:
3540    def parameter_sql(self, expression: exp.Parameter) -> str:
3541        this = self.sql(expression, "this")
3542        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.core.SessionParameter) -> str:
3544    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3545        this = self.sql(expression, "this")
3546        kind = expression.text("kind")
3547        if kind:
3548            kind = f"{kind}."
3549        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.core.Placeholder) -> str:
3551    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3552        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
def subquery_sql( self, expression: sqlglot.expressions.query.Subquery, sep: str = ' AS ') -> str:
3554    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3555        alias = self.sql(expression, "alias")
3556        alias = f"{sep}{alias}" if alias else ""
3557        sample = self.sql(expression, "sample")
3558        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3559            alias = f"{sample}{alias}"
3560
3561            # Set to None so it's not generated again by self.query_modifiers()
3562            expression.set("sample", None)
3563
3564        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3565        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3566        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.query.Qualify) -> str:
3568    def qualify_sql(self, expression: exp.Qualify) -> str:
3569        this = self.indent(self.sql(expression, "this"))
3570        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.array.Unnest) -> str:
3572    def unnest_sql(self, expression: exp.Unnest) -> str:
3573        args = self.expressions(expression, flat=True)
3574
3575        alias = expression.args.get("alias")
3576        offset = expression.args.get("offset")
3577
3578        if self.UNNEST_WITH_ORDINALITY:
3579            if alias and isinstance(offset, exp.Expr):
3580                alias.append("columns", offset)
3581                expression.set("offset", None)
3582
3583        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3584            columns = alias.columns
3585            alias = self.sql(columns[0]) if columns else ""
3586        else:
3587            alias = self.sql(alias)
3588
3589        alias = f" AS {alias}" if alias else alias
3590        if self.UNNEST_WITH_ORDINALITY:
3591            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3592        else:
3593            if isinstance(offset, exp.Expr):
3594                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3595            elif offset:
3596                suffix = f"{alias} WITH OFFSET"
3597            else:
3598                suffix = alias
3599
3600        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.query.PreWhere) -> str:
3602    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3603        return ""
def where_sql(self, expression: sqlglot.expressions.query.Where) -> str:
3605    def where_sql(self, expression: exp.Where) -> str:
3606        this = self.indent(self.sql(expression, "this"))
3607        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.query.Window) -> str:
3609    def window_sql(self, expression: exp.Window) -> str:
3610        this = self.sql(expression, "this")
3611        partition = self.partition_by_sql(expression)
3612        order = expression.args.get("order")
3613        order = self.order_sql(order, flat=True) if order else ""
3614        spec = self.sql(expression, "spec")
3615        alias = self.sql(expression, "alias")
3616        over = self.sql(expression, "over") or "OVER"
3617
3618        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3619
3620        first = expression.args.get("first")
3621        if first is None:
3622            first = ""
3623        else:
3624            first = "FIRST" if first else "LAST"
3625
3626        if not partition and not order and not spec and alias:
3627            return f"{this} {alias}"
3628
3629        args = self.format_args(
3630            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3631        )
3632        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3634    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3635        partition = self.expressions(expression, key="partition_by", flat=True)
3636        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.query.WindowSpec) -> str:
3638    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3639        kind = self.sql(expression, "kind")
3640        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3641        end = (
3642            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3643            or "CURRENT ROW"
3644        )
3645
3646        window_spec = f"{kind} BETWEEN {start} AND {end}"
3647
3648        exclude = self.sql(expression, "exclude")
3649        if exclude:
3650            if self.SUPPORTS_WINDOW_EXCLUDE:
3651                window_spec += f" EXCLUDE {exclude}"
3652            else:
3653                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3654
3655        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.core.WithinGroup) -> str:
3657    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3658        this = self.sql(expression, "this")
3659        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3660        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.core.Between) -> str:
3662    def between_sql(self, expression: exp.Between) -> str:
3663        this = self.sql(expression, "this")
3664        low = self.sql(expression, "low")
3665        high = self.sql(expression, "high")
3666        symmetric = expression.args.get("symmetric")
3667
3668        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3669            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3670
3671        flag = (
3672            " SYMMETRIC"
3673            if symmetric
3674            else " ASYMMETRIC"
3675            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3676            else ""  # silently drop ASYMMETRIC – semantics identical
3677        )
3678        return f"{this} BETWEEN{flag} {low} AND {high}"
def bracket_offset_expressions( self, expression: sqlglot.expressions.core.Bracket, index_offset: int | None = None) -> list[sqlglot.expressions.core.Expr]:
3680    def bracket_offset_expressions(
3681        self, expression: exp.Bracket, index_offset: int | None = None
3682    ) -> list[exp.Expr]:
3683        if expression.args.get("json_access"):
3684            return expression.expressions
3685
3686        return apply_index_offset(
3687            expression.this,
3688            expression.expressions,
3689            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3690            dialect=self.dialect,
3691        )
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
3693    def bracket_sql(self, expression: exp.Bracket) -> str:
3694        expressions = self.bracket_offset_expressions(expression)
3695        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3696        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.core.All) -> str:
3698    def all_sql(self, expression: exp.All) -> str:
3699        this = self.sql(expression, "this")
3700        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3701            this = self.wrap(this)
3702        return f"ALL {this}"
def any_sql(self, expression: sqlglot.expressions.core.Any) -> str:
3704    def any_sql(self, expression: exp.Any) -> str:
3705        this = self.sql(expression, "this")
3706        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3707            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3708                this = self.wrap(this)
3709            return f"ANY{this}"
3710        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.functions.Exists) -> str:
3712    def exists_sql(self, expression: exp.Exists) -> str:
3713        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.functions.Case) -> str:
3715    def case_sql(self, expression: exp.Case) -> str:
3716        this = self.sql(expression, "this")
3717        statements = [f"CASE {this}" if this else "CASE"]
3718
3719        for e in expression.args["ifs"]:
3720            statements.append(f"WHEN {self.sql(e, 'this')}")
3721            statements.append(f"THEN {self.sql(e, 'true')}")
3722
3723        default = self.sql(expression, "default")
3724
3725        if default:
3726            statements.append(f"ELSE {default}")
3727
3728        statements.append("END")
3729
3730        if self.pretty and self.too_wide(statements):
3731            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3732
3733        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.constraints.Constraint) -> str:
3735    def constraint_sql(self, expression: exp.Constraint) -> str:
3736        this = self.sql(expression, "this")
3737        expressions = self.expressions(expression, flat=True)
3738        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.ddl.NextValueFor) -> str:
3740    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3741        order = expression.args.get("order")
3742        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3743        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
3745    def extract_sql(self, expression: exp.Extract) -> str:
3746        import sqlglot.dialects.dialect
3747
3748        this = (
3749            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3750            if self.NORMALIZE_EXTRACT_DATE_PARTS
3751            else expression.this
3752        )
3753        if self.EXTRACT_ALLOWS_QUOTES:
3754            this_sql = self.sql(this)
3755        elif isinstance(this, exp.WeekStart):
3756            this_sql = self.weekstart_name(this)
3757        else:
3758            this_sql = this.name
3759        expression_sql = self.sql(expression, "expression")
3760
3761        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.string.Trim) -> str:
3763    def trim_sql(self, expression: exp.Trim) -> str:
3764        trim_type = self.sql(expression, "position")
3765
3766        if trim_type == "LEADING":
3767            func_name = "LTRIM"
3768        elif trim_type == "TRAILING":
3769            func_name = "RTRIM"
3770        else:
3771            func_name = "TRIM"
3772
3773        return self.func(func_name, expression.this, expression.expression)
def convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3775    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3776        args = expression.expressions
3777        if isinstance(expression, exp.ConcatWs):
3778            args = args[1:]  # Skip the delimiter
3779
3780        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3781            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3782
3783        concat_coalesce = (
3784            self.dialect.CONCAT_WS_COALESCE
3785            if isinstance(expression, exp.ConcatWs)
3786            else self.dialect.CONCAT_COALESCE
3787        )
3788
3789        if not concat_coalesce and expression.args.get("coalesce"):
3790
3791            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3792                if not e.type:
3793                    import sqlglot.optimizer.annotate_types
3794
3795                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3796
3797                if e.is_string or e.is_type(exp.DType.ARRAY):
3798                    return e
3799
3800                return exp.func("coalesce", e, exp.Literal.string(""))
3801
3802            args = [_wrap_with_coalesce(e) for e in args]
3803
3804        return args
def concat_sql(self, expression: sqlglot.expressions.string.Concat) -> str:
3806    def concat_sql(self, expression: exp.Concat) -> str:
3807        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3808            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3809            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3810            # instead of coalescing them to empty string.
3811            import sqlglot.dialects.dialect
3812
3813            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3814
3815        expressions = self.convert_concat_args(expression)
3816
3817        # Some dialects don't allow a single-argument CONCAT call
3818        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3819            return self.sql(expressions[0])
3820
3821        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.string.ConcatWs) -> str:
3823    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3824        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3825            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3826            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3827            all_args = expression.expressions
3828            expression.set("coalesce", True)
3829            return self.sql(
3830                exp.case()
3831                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3832                .else_(expression)
3833            )
3834
3835        return self.func(
3836            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3837        )
def check_sql(self, expression: sqlglot.expressions.core.Check) -> str:
3839    def check_sql(self, expression: exp.Check) -> str:
3840        this = self.sql(expression, key="this")
3841        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.constraints.ForeignKey) -> str:
3843    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3844        expressions = self.expressions(expression, flat=True)
3845        expressions = f" ({expressions})" if expressions else ""
3846        reference = self.sql(expression, "reference")
3847        reference = f" {reference}" if reference else ""
3848        delete = self.sql(expression, "delete")
3849        delete = f" ON DELETE {delete}" if delete else ""
3850        update = self.sql(expression, "update")
3851        update = f" ON UPDATE {update}" if update else ""
3852        options = self.expressions(expression, key="options", flat=True, sep=" ")
3853        options = f" {options}" if options else ""
3854        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.constraints.PrimaryKey) -> str:
3856    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3857        this = self.sql(expression, "this")
3858        this = f" {this}" if this else ""
3859        expressions = self.expressions(expression, flat=True)
3860        include = self.sql(expression, "include")
3861        options = self.expressions(expression, key="options", flat=True, sep=" ")
3862        options = f" {options}" if options else ""
3863        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
def timeserieskey_sql(self, expression: sqlglot.expressions.constraints.TimeseriesKey) -> str:
3865    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3866        self.unsupported("TIMESERIES primary key columns are not supported")
3867        return self.sql(expression, "this")
def if_sql(self, expression: sqlglot.expressions.functions.If) -> str:
3869    def if_sql(self, expression: exp.If) -> str:
3870        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.string.MatchAgainst) -> str:
3872    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3873        if self.MATCH_AGAINST_TABLE_PREFIX:
3874            expressions = []
3875            for expr in expression.expressions:
3876                if isinstance(expr, exp.Table):
3877                    expressions.append(f"TABLE {self.sql(expr)}")
3878                else:
3879                    expressions.append(expr)
3880        else:
3881            expressions = expression.expressions
3882
3883        modifier = expression.args.get("modifier")
3884        modifier = f" {modifier}" if modifier else ""
3885        return (
3886            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3887        )
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.query.JSONKeyValue) -> str:
3889    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3890        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
def jsonpath_sql(self, expression: sqlglot.expressions.query.JSONPath) -> str:
3892    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3893        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3894
3895        if self.QUOTE_JSON_PATH:
3896            path = self.escape_str(path)
3897            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3898
3899        return path
def json_path_part( self, expression: int | str | sqlglot.expressions.query.JSONPathPart) -> str:
3901    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3902        if isinstance(expression, exp.JSONPathPart):
3903            transform = self.TRANSFORMS.get(expression.__class__)
3904            if not callable(transform):
3905                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3906                return ""
3907
3908            return transform(self, expression)
3909
3910        if isinstance(expression, int):
3911            return str(expression)
3912
3913        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3914            escaped = expression.replace("'", "\\'")
3915            escaped = f"'{escaped}'"
3916        else:
3917            escaped = expression.replace('"', '\\"')
3918            escaped = f'"{escaped}"'
3919
3920        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.query.FormatJson) -> str:
3922    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3923        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.core.FormatPhrase) -> str:
3925    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3926        # Output the Teradata column FORMAT override.
3927        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3928        this = self.sql(expression, "this")
3929        fmt = self.sql(expression, "format")
3930        return f"{this} (FORMAT {fmt})"
def jsonarray_sql(self, expression: sqlglot.expressions.json.JSONArray) -> str:
3958    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3959        null_handling = expression.args.get("null_handling")
3960        null_handling = f" {null_handling}" if null_handling else ""
3961        return_type = self.sql(expression, "return_type")
3962        return_type = f" RETURNING {return_type}" if return_type else ""
3963        strict = " STRICT" if expression.args.get("strict") else ""
3964        return self.func(
3965            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3966        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.json.JSONArrayAgg) -> str:
3968    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3969        this = self.sql(expression, "this")
3970        order = self.sql(expression, "order")
3971        null_handling = expression.args.get("null_handling")
3972        null_handling = f" {null_handling}" if null_handling else ""
3973        return_type = self.sql(expression, "return_type")
3974        return_type = f" RETURNING {return_type}" if return_type else ""
3975        strict = " STRICT" if expression.args.get("strict") else ""
3976        return self.func(
3977            "JSON_ARRAYAGG",
3978            this,
3979            suffix=f"{order}{null_handling}{return_type}{strict})",
3980        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.query.JSONColumnDef) -> str:
3982    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3983        path = self.sql(expression, "path")
3984        path = f" PATH {path}" if path else ""
3985        nested_schema = self.sql(expression, "nested_schema")
3986
3987        if nested_schema:
3988            return f"NESTED{path} {nested_schema}"
3989
3990        this = self.sql(expression, "this")
3991        kind = self.sql(expression, "kind")
3992        kind = f" {kind}" if kind else ""
3993        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3994
3995        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3996        return f"{this}{kind}{format_json}{path}{ordinality}"
def jsonschema_sql(self, expression: sqlglot.expressions.query.JSONSchema) -> str:
3998    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3999        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.json.JSONTable) -> str:
4001    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4002        this = self.sql(expression, "this")
4003        path = self.sql(expression, "path")
4004        path = f", {path}" if path else ""
4005        error_handling = expression.args.get("error_handling")
4006        error_handling = f" {error_handling}" if error_handling else ""
4007        empty_handling = expression.args.get("empty_handling")
4008        empty_handling = f" {empty_handling}" if empty_handling else ""
4009        schema = self.sql(expression, "schema")
4010        return self.func(
4011            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4012        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.query.OpenJSONColumnDef) -> str:
4014    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4015        this = self.sql(expression, "this")
4016        kind = self.sql(expression, "kind")
4017        path = self.sql(expression, "path")
4018        path = f" {path}" if path else ""
4019        as_json = " AS JSON" if expression.args.get("as_json") else ""
4020        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.json.OpenJSON) -> str:
4022    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4023        this = self.sql(expression, "this")
4024        path = self.sql(expression, "path")
4025        path = f", {path}" if path else ""
4026        expressions = self.expressions(expression)
4027        with_ = (
4028            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4029            if expressions
4030            else ""
4031        )
4032        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.core.In) -> str:
4034    def in_sql(self, expression: exp.In) -> str:
4035        query = expression.args.get("query")
4036        unnest = expression.args.get("unnest")
4037        field = expression.args.get("field")
4038        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4039
4040        if query:
4041            in_sql = self.sql(query)
4042        elif unnest:
4043            in_sql = self.in_unnest_op(unnest)
4044        elif field:
4045            in_sql = self.sql(field)
4046        else:
4047            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4048
4049        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.array.Unnest) -> str:
4051    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4052        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
4054    def interval_sql(self, expression: exp.Interval) -> str:
4055        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4056            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4057            exp.AutoRefreshProperty,
4058        )
4059        interval_keyword = "INTERVAL" if include_keyword else ""
4060        unit_expression = expression.args.get("unit")
4061        unit = self.sql(unit_expression) if unit_expression else ""
4062        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4063            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4064        unit = f" {unit}" if unit else ""
4065
4066        if self.SINGLE_STRING_INTERVAL:
4067            this = expression.this.name if expression.this else ""
4068            if this:
4069                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4070                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4071                    return f"{interval_keyword}'{this}'{unit}"
4072                return f"{interval_keyword}'{this}{unit}'"
4073            return f"{interval_keyword}{unit}"
4074
4075        this = self.sql(expression, "this")
4076        if this:
4077            if not include_keyword and expression.this.is_string:
4078                this = expression.this.name
4079            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4080                this = f"({this})"
4081            if include_keyword:
4082                this = f" {this}"
4083
4084        return f"{interval_keyword}{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.query.Return) -> str:
4086    def return_sql(self, expression: exp.Return) -> str:
4087        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.constraints.Reference) -> str:
4089    def reference_sql(self, expression: exp.Reference) -> str:
4090        this = self.sql(expression, "this")
4091        expressions = self.expressions(expression, flat=True)
4092        expressions = f"({expressions})" if expressions else ""
4093        options = self.expressions(expression, key="options", flat=True, sep=" ")
4094        options = f" {options}" if options else ""
4095        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.core.Anonymous) -> str:
4097    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4098        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4099        parent = expression.parent
4100        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4101
4102        return self.func(
4103            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4104        )
def paren_sql(self, expression: sqlglot.expressions.core.Paren) -> str:
4106    def paren_sql(self, expression: exp.Paren) -> str:
4107        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4108        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.core.Neg) -> str:
4110    def neg_sql(self, expression: exp.Neg) -> str:
4111        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4112        this_sql = self.sql(expression, "this")
4113        sep = " " if this_sql[0] == "-" else ""
4114        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.core.Not) -> str:
4116    def not_sql(self, expression: exp.Not) -> str:
4117        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.core.Alias) -> str:
4119    def alias_sql(self, expression: exp.Alias) -> str:
4120        alias = self.sql(expression, "alias")
4121        alias = f" AS {alias}" if alias else ""
4122        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.core.PivotAlias) -> str:
4124    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4125        alias = expression.args["alias"]
4126
4127        parent = expression.parent
4128        pivot = parent and parent.parent
4129
4130        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4131            identifier_alias = isinstance(alias, exp.Identifier)
4132            literal_alias = isinstance(alias, exp.Literal)
4133
4134            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4135                alias.replace(exp.Literal.string(alias.output_name))
4136            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4137                alias.replace(exp.to_identifier(alias.output_name))
4138
4139        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.core.Aliases) -> str:
4141    def aliases_sql(self, expression: exp.Aliases) -> str:
4142        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.core.AtIndex) -> str:
4144    def atindex_sql(self, expression: exp.AtIndex) -> str:
4145        this = self.sql(expression, "this")
4146        index = self.sql(expression, "expression")
4147        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.core.AtTimeZone) -> str:
4149    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4150        this = self.sql(expression, "this")
4151        zone = self.sql(expression, "zone")
4152        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.core.FromTimeZone) -> str:
4154    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4155        this = self.sql(expression, "this")
4156        zone = self.sql(expression, "zone")
4157        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def fromiso8601date_sql(self, expression: sqlglot.expressions.temporal.FromISO8601Date) -> str:
4159    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4160        return self.sql(exp.cast(expression.this, exp.DType.DATE))
def fromiso8601timestamp_sql( self, expression: sqlglot.expressions.temporal.FromISO8601Timestamp) -> str:
4162    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4163        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
def fromiso8601timestampnanos_sql( self, expression: sqlglot.expressions.temporal.FromISO8601TimestampNanos) -> str:
4165    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4166        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
def add_sql(self, expression: sqlglot.expressions.core.Add) -> str:
4168    def add_sql(self, expression: exp.Add) -> str:
4169        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
4171    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4172        return self.connector_sql(expression, "AND", stack)
def or_sql( self, expression: sqlglot.expressions.core.Or, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
4174    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4175        return self.connector_sql(expression, "OR", stack)
def xor_sql( self, expression: sqlglot.expressions.core.Xor, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
4177    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4178        return self.connector_sql(expression, "XOR", stack)
def connector_sql( self, expression: sqlglot.expressions.core.Connector, op: str, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
4180    def connector_sql(
4181        self,
4182        expression: exp.Connector,
4183        op: str,
4184        stack: list[str | exp.Expr] | None = None,
4185    ) -> str:
4186        if stack is not None:
4187            stack.append(expression.right)
4188            if expression.comments and self.comments:
4189                op = self.maybe_comment(op, comments=expression.comments)
4190
4191            stack.extend((op, expression.left))
4192            return op
4193
4194        stack = [expression]
4195        sqls: list[str] = []
4196        ops = set()
4197
4198        while stack:
4199            node = stack.pop()
4200            if isinstance(node, exp.Connector):
4201                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4202            else:
4203                sql = self.sql(node)
4204                if sqls and sqls[-1] in ops:
4205                    sqls[-1] += f" {sql}"
4206                else:
4207                    sqls.append(sql)
4208
4209        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4210        return sep.join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.core.BitwiseAnd) -> str:
4212    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4213        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.core.BitwiseLeftShift) -> str:
4215    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4216        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.core.BitwiseNot) -> str:
4218    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4219        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.core.BitwiseOr) -> str:
4221    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4222        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.core.BitwiseRightShift) -> str:
4224    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4225        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.core.BitwiseXor) -> str:
4227    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4228        return self.binary(expression, "^")
def cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4230    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4231        format_sql = self.sql(expression, "format")
4232        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4233        to_sql = self.sql(expression, "to")
4234        to_sql = f" {to_sql}" if to_sql else ""
4235        action = self.sql(expression, "action")
4236        action = f" {action}" if action else ""
4237        default = self.sql(expression, "default")
4238        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4239        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
def strtotime_sql(self, expression: sqlglot.expressions.temporal.StrToTime) -> str:
4242    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4243        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
def strtodate_sql(self, expression: sqlglot.expressions.temporal.StrToDate) -> str:
4246    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4247        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
def parsedatetime_sql(self, expression: sqlglot.expressions.temporal.ParseDatetime) -> str:
4249    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4250        return self.func(
4251            "PARSE_DATETIME",
4252            expression.this,
4253            expression.args.get("format"),
4254            expression.args.get("zone"),
4255        )
def currentdate_sql(self, expression: sqlglot.expressions.temporal.CurrentDate) -> str:
4257    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4258        zone = self.sql(expression, "this")
4259        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.functions.Collate) -> str:
4261    def collate_sql(self, expression: exp.Collate) -> str:
4262        if self.COLLATE_IS_FUNC:
4263            return self.function_fallback_sql(expression)
4264        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.ddl.Command) -> str:
4266    def command_sql(self, expression: exp.Command) -> str:
4267        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.ddl.Comment) -> str:
4269    def comment_sql(self, expression: exp.Comment) -> str:
4270        this = self.sql(expression, "this")
4271        kind = expression.args["kind"]
4272        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4273        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4274        expression_sql = self.sql(expression, "expression")
4275        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
def mergetreettlaction_sql(self, expression: sqlglot.expressions.ddl.MergeTreeTTLAction) -> str:
4277    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4278        this = self.sql(expression, "this")
4279        delete = " DELETE" if expression.args.get("delete") else ""
4280        recompress = self.sql(expression, "recompress")
4281        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4282        to_disk = self.sql(expression, "to_disk")
4283        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4284        to_volume = self.sql(expression, "to_volume")
4285        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4286        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
def mergetreettl_sql(self, expression: sqlglot.expressions.ddl.MergeTreeTTL) -> str:
4288    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4289        where = self.sql(expression, "where")
4290        group = self.sql(expression, "group")
4291        aggregates = self.expressions(expression, key="aggregates")
4292        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4293
4294        if not (where or group or aggregates) and len(expression.expressions) == 1:
4295            return f"TTL {self.expressions(expression, flat=True)}"
4296
4297        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
4299    def transaction_sql(self, expression: exp.Transaction) -> str:
4300        modes = self.expressions(expression, key="modes")
4301        modes = f" {modes}" if modes else ""
4302        return f"BEGIN{modes}"
def commit_sql(self, expression: sqlglot.expressions.ddl.Commit) -> str:
4304    def commit_sql(self, expression: exp.Commit) -> str:
4305        chain = expression.args.get("chain")
4306        if chain is not None:
4307            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4308
4309        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.ddl.Rollback) -> str:
4311    def rollback_sql(self, expression: exp.Rollback) -> str:
4312        savepoint = expression.args.get("savepoint")
4313        savepoint = f" TO {savepoint}" if savepoint else ""
4314        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.ddl.AlterColumn) -> str:
4316    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4317        this = self.sql(expression, "this")
4318
4319        exists = ""
4320        if expression.args.get("exists"):
4321            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4322                exists = " IF EXISTS"
4323            else:
4324                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4325
4326        dtype = self.sql(expression, "dtype")
4327        if dtype:
4328            collate = self.sql(expression, "collate")
4329            collate = f" COLLATE {collate}" if collate else ""
4330            using = self.sql(expression, "using")
4331            using = f" USING {using}" if using else ""
4332            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4333            null_constraint = self._alter_column_null_constraint_sql(expression)
4334
4335            return (
4336                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4337                f"{collate}{using}{null_constraint}"
4338            )
4339
4340        default = self.sql(expression, "default")
4341        if default:
4342            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4343
4344        comment = self.sql(expression, "comment")
4345        if comment:
4346            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4347
4348        visible = expression.args.get("visible")
4349        if visible:
4350            return f"ALTER COLUMN{exists} {this} SET {visible}"
4351
4352        allow_null = expression.args.get("allow_null")
4353        drop = expression.args.get("drop")
4354
4355        if not drop and not allow_null:
4356            self.unsupported("Unsupported ALTER COLUMN syntax")
4357
4358        if allow_null is not None:
4359            keyword = "DROP" if drop else "SET"
4360            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4361
4362        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
def modifycolumn_sql(self, expression: sqlglot.expressions.ddl.ModifyColumn) -> str:
4375    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4376        this = self.sql(expression, "this")
4377        rename_from = self.sql(expression, "rename_from")
4378        if rename_from:
4379            if not self.SUPPORTS_CHANGE_COLUMN:
4380                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4381            return f"CHANGE COLUMN {rename_from} {this}"
4382        if not self.SUPPORTS_MODIFY_COLUMN:
4383            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4384        return f"MODIFY COLUMN {this}"
def alterindex_sql(self, expression: sqlglot.expressions.ddl.AlterIndex) -> str:
4386    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4387        this = self.sql(expression, "this")
4388
4389        visible = expression.args.get("visible")
4390        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4391
4392        return f"ALTER INDEX {this} {visible_sql}"
def alterdiststyle_sql(self, expression: sqlglot.expressions.ddl.AlterDistStyle) -> str:
4394    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4395        this = self.sql(expression, "this")
4396        if not isinstance(expression.this, exp.Var):
4397            this = f"KEY DISTKEY {this}"
4398        return f"ALTER DISTSTYLE {this}"
def altersortkey_sql(self, expression: sqlglot.expressions.ddl.AlterSortKey) -> str:
4400    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4401        compound = " COMPOUND" if expression.args.get("compound") else ""
4402        this = self.sql(expression, "this")
4403        expressions = self.expressions(expression, flat=True)
4404        expressions = f"({expressions})" if expressions else ""
4405        return f"ALTER{compound} SORTKEY {this or expressions}"
def alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4407    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4408        if not self.RENAME_TABLE_WITH_DB:
4409            # Remove db from tables
4410            expression = expression.transform(
4411                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4412            ).assert_is(exp.AlterRename)
4413        this = self.sql(expression, "this")
4414        to_kw = " TO" if include_to else ""
4415        return f"RENAME{to_kw} {this}"
def renamecolumn_sql(self, expression: sqlglot.expressions.ddl.RenameColumn) -> str:
4417    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4418        exists = " IF EXISTS" if expression.args.get("exists") else ""
4419        old_column = self.sql(expression, "this")
4420        new_column = self.sql(expression, "to")
4421        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
def alterset_sql(self, expression: sqlglot.expressions.ddl.AlterSet) -> str:
4423    def alterset_sql(self, expression: exp.AlterSet) -> str:
4424        exprs = self.expressions(expression, flat=True)
4425        if self.ALTER_SET_WRAPPED:
4426            exprs = f"({exprs})"
4427
4428        return f"SET {exprs}"
def alter_sql(self, expression: sqlglot.expressions.ddl.Alter) -> str:
4430    def alter_sql(self, expression: exp.Alter) -> str:
4431        actions = expression.actions
4432
4433        if (
4434            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4435            and actions
4436            and isinstance(actions[0], exp.ColumnDef)
4437        ):
4438            actions_sql = self.expressions(expression, key="actions", flat=True)
4439            actions_sql = f"ADD {actions_sql}"
4440        else:
4441            actions_list = []
4442            for action in actions:
4443                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4444                    action_sql = self.add_column_sql(action)
4445                else:
4446                    action_sql = self.sql(action)
4447                    if isinstance(action, exp.Query):
4448                        action_sql = f"AS {action_sql}"
4449
4450                actions_list.append(action_sql)
4451
4452            actions_sql = self.format_args(*actions_list).lstrip("\n")
4453
4454        iceberg = (
4455            "ICEBERG "
4456            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4457            else ""
4458        )
4459        exists = " IF EXISTS" if expression.args.get("exists") else ""
4460        on_cluster = self.sql(expression, "cluster")
4461        on_cluster = f" {on_cluster}" if on_cluster else ""
4462        only = " ONLY" if expression.args.get("only") else ""
4463        options = expression.args.get("options") or []
4464        options_sql = self.expressions(
4465            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4466        )
4467        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4468        partitions_sql = self.expressions(
4469            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4470            sep=" ",
4471            flat=True,
4472        )
4473        if partitions_sql and (actions_sql or options_sql):
4474            partitions_sql = f" {partitions_sql}"
4475        kind = self.sql(expression, "kind")
4476        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4477        check = " WITH CHECK" if expression.args.get("check") else ""
4478        cascade = (
4479            " CASCADE"
4480            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4481            else ""
4482        )
4483        this = self.sql(expression, "this")
4484        this = f" {this}" if this else ""
4485
4486        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
def altersession_sql(self, expression: sqlglot.expressions.ddl.AlterSession) -> str:
4488    def altersession_sql(self, expression: exp.AlterSession) -> str:
4489        items_sql = self.expressions(expression, flat=True)
4490        keyword = "UNSET" if expression.args.get("unset") else "SET"
4491        return f"{keyword} {items_sql}"
def add_column_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
4493    def add_column_sql(self, expression: exp.Expr) -> str:
4494        sql = self.sql(expression)
4495        if isinstance(expression, exp.Schema):
4496            column_text = " COLUMNS"
4497        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4498            column_text = " COLUMN"
4499        else:
4500            column_text = ""
4501
4502        return f"ADD{column_text} {sql}"
def droppartition_sql(self, expression: sqlglot.expressions.query.DropPartition) -> str:
4504    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4505        expressions = self.expressions(expression)
4506        exists = " IF EXISTS " if expression.args.get("exists") else " "
4507        return f"DROP{exists}{expressions}"
def dropprimarykey_sql(self, expression: sqlglot.expressions.ddl.DropPrimaryKey) -> str:
4509    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4510        return "DROP PRIMARY KEY"
def addconstraint_sql(self, expression: sqlglot.expressions.constraints.AddConstraint) -> str:
4512    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4513        return f"ADD {self.expressions(expression, indent=False)}"
def addpartition_sql(self, expression: sqlglot.expressions.query.AddPartition) -> str:
4515    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4516        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4517        location = self.sql(expression, "location")
4518        location = f" {location}" if location else ""
4519        return f"ADD {exists}{self.sql(expression.this)}{location}"
def distinct_sql(self, expression: sqlglot.expressions.core.Distinct) -> str:
4521    def distinct_sql(self, expression: exp.Distinct) -> str:
4522        this = self.expressions(expression, flat=True)
4523
4524        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4525            case = exp.case()
4526            for arg in expression.expressions:
4527                case = case.when(arg.is_(exp.null()), exp.null())
4528            this = self.sql(case.else_(f"({this})"))
4529
4530        this = f" {this}" if this else ""
4531
4532        on = self.sql(expression, "on")
4533        on = f" ON {on}" if on else ""
4534        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.core.IgnoreNulls) -> str:
4536    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4537        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
def respectnulls_sql(self, expression: sqlglot.expressions.core.RespectNulls) -> str:
4539    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4540        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
def havingmax_sql(self, expression: sqlglot.expressions.core.HavingMax) -> str:
4542    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4543        this_sql = self.sql(expression, "this")
4544        expression_sql = self.sql(expression, "expression")
4545        kind = "MAX" if expression.args.get("max") else "MIN"
4546        return f"{this_sql} HAVING {kind} {expression_sql}"
def intdiv_sql(self, expression: sqlglot.expressions.core.IntDiv) -> str:
4548    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4549        return self._truncated_div_sql(expression.this, expression.expression)
def dpipe_sql(self, expression: sqlglot.expressions.core.DPipe) -> str:
4558    def dpipe_sql(self, expression: exp.DPipe) -> str:
4559        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4560            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4561        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.core.Div) -> str:
4563    def div_sql(self, expression: exp.Div) -> str:
4564        l, r = expression.left, expression.right
4565
4566        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4567            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4568
4569        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4570            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4571                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4572
4573        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4574            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4575                # Native integer division depends on the target's operand types, which may differ
4576                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4577                # integer literals or casts
4578                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4579                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4580                ):
4581                    return self.sql(exp.IntDiv(this=l, expression=r))
4582                return self._truncated_div_sql(l, r)
4583
4584        return self.binary(expression, "/")
def safedivide_sql(self, expression: sqlglot.expressions.math.SafeDivide) -> str:
4586    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4587        n = exp._wrap(expression.this, exp.Binary)
4588        d = exp._wrap(expression.expression, exp.Binary)
4589        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
def overlaps_sql(self, expression: sqlglot.expressions.core.Overlaps) -> str:
4591    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4592        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.core.Distance) -> str:
4594    def distance_sql(self, expression: exp.Distance) -> str:
4595        return self.binary(expression, "<->")
def distancend_sql(self, expression: sqlglot.expressions.core.DistanceNd) -> str:
4597    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4598        return self.binary(expression, "<<->>")
def dot_sql(self, expression: sqlglot.expressions.core.Dot) -> str:
4600    def dot_sql(self, expression: exp.Dot) -> str:
4601        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.core.EQ) -> str:
4603    def eq_sql(self, expression: exp.EQ) -> str:
4604        return self.binary(expression, "=")
def propertyeq_sql(self, expression: sqlglot.expressions.core.PropertyEQ) -> str:
4606    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4607        return self.binary(expression, ":=")
def escape_sql(self, expression: sqlglot.expressions.core.Escape) -> str:
4609    def escape_sql(self, expression: exp.Escape) -> str:
4610        this = expression.this
4611        if (
4612            isinstance(this, (exp.Like, exp.ILike))
4613            and isinstance(this.expression, (exp.All, exp.Any))
4614            and not self.SUPPORTS_LIKE_QUANTIFIERS
4615        ):
4616            return self._like_sql(this, escape=expression)
4617        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.core.Glob) -> str:
4619    def glob_sql(self, expression: exp.Glob) -> str:
4620        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.core.GT) -> str:
4622    def gt_sql(self, expression: exp.GT) -> str:
4623        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.core.GTE) -> str:
4625    def gte_sql(self, expression: exp.GTE) -> str:
4626        return self.binary(expression, ">=")
def is_sql(self, expression: sqlglot.expressions.core.Is) -> str:
4628    def is_sql(self, expression: exp.Is) -> str:
4629        negate = expression.args.get("negate")
4630        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4631            positive = bool(expression.expression.this) != bool(negate)
4632            return self.sql(expression.this if positive else exp.not_(expression.this))
4633        return self.binary(expression, "IS NOT" if negate else "IS")
def like_sql(self, expression: sqlglot.expressions.core.Like) -> str:
4685    def like_sql(self, expression: exp.Like) -> str:
4686        return self._like_sql(expression)
def ilike_sql(self, expression: sqlglot.expressions.core.ILike) -> str:
4688    def ilike_sql(self, expression: exp.ILike) -> str:
4689        return self._like_sql(expression)
def match_sql(self, expression: sqlglot.expressions.core.Match) -> str:
4691    def match_sql(self, expression: exp.Match) -> str:
4692        return self.binary(expression, "MATCH")
def similarto_sql(self, expression: sqlglot.expressions.core.SimilarTo) -> str:
4694    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4695        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.core.LT) -> str:
4697    def lt_sql(self, expression: exp.LT) -> str:
4698        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.core.LTE) -> str:
4700    def lte_sql(self, expression: exp.LTE) -> str:
4701        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.core.Mod) -> str:
4703    def mod_sql(self, expression: exp.Mod) -> str:
4704        this = self.sql(expression, "this")
4705        expr = self.sql(expression, "expression")
4706        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4707
4708        parent = expression.parent
4709        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4710            return f"({sql})"
4711
4712        return sql
def mul_sql(self, expression: sqlglot.expressions.core.Mul) -> str:
4714    def mul_sql(self, expression: exp.Mul) -> str:
4715        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.core.NEQ) -> str:
4717    def neq_sql(self, expression: exp.NEQ) -> str:
4718        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.core.NullSafeEQ) -> str:
4720    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4721        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.core.NullSafeNEQ) -> str:
4723    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4724        return self.binary(expression, "IS DISTINCT FROM")
def sub_sql(self, expression: sqlglot.expressions.core.Sub) -> str:
4726    def sub_sql(self, expression: exp.Sub) -> str:
4727        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.functions.TryCast) -> str:
4729    def trycast_sql(self, expression: exp.TryCast) -> str:
4730        return self.cast_sql(expression, safe_prefix="TRY_")
def jsoncast_sql(self, expression: sqlglot.expressions.functions.JSONCast) -> str:
4732    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4733        return self.cast_sql(expression)
def try_sql(self, expression: sqlglot.expressions.functions.Try) -> str:
4735    def try_sql(self, expression: exp.Try) -> str:
4736        if not self.TRY_SUPPORTED:
4737            self.unsupported("Unsupported TRY function")
4738            return self.sql(expression, "this")
4739
4740        return self.func("TRY", expression.this)
def log_sql(self, expression: sqlglot.expressions.math.Log) -> str:
4742    def log_sql(self, expression: exp.Log) -> str:
4743        this = expression.this
4744        expr = expression.expression
4745
4746        if self.dialect.LOG_BASE_FIRST is False:
4747            this, expr = expr, this
4748        elif self.dialect.LOG_BASE_FIRST is None and expr:
4749            if this.name in ("2", "10"):
4750                return self.func(f"LOG{this.name}", expr)
4751
4752            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4753
4754        return self.func("LOG", this, expr)
def use_sql(self, expression: sqlglot.expressions.ddl.Use) -> str:
4756    def use_sql(self, expression: exp.Use) -> str:
4757        kind = self.sql(expression, "kind")
4758        kind = f" {kind}" if kind else ""
4759        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4760        this = f" {this}" if this else ""
4761        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.core.Binary, op: str) -> str:
4763    def binary(self, expression: exp.Binary, op: str) -> str:
4764        sqls: list[str] = []
4765        stack: list[None | str | exp.Expr] = [expression]
4766        binary_type = type(expression)
4767
4768        while stack:
4769            node = stack.pop()
4770
4771            if type(node) is binary_type:
4772                op_func = node.args.get("operator")
4773                if op_func:
4774                    op = f"OPERATOR({self.sql(op_func)})"
4775
4776                stack.append(node.args.get("expression"))
4777                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4778                stack.append(node.args.get("this"))
4779            else:
4780                sqls.append(self.sql(node))
4781
4782        return "".join(sqls)
def ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4784    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4785        to_clause = self.sql(expression, "to")
4786        if to_clause:
4787            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4788
4789        return self.function_fallback_sql(expression)
def function_fallback_sql(self, expression: sqlglot.expressions.core.Func) -> str:
4791    def function_fallback_sql(self, expression: exp.Func) -> str:
4792        args = []
4793
4794        for key in expression.arg_types:
4795            arg_value = expression.args.get(key)
4796
4797            if isinstance(arg_value, list):
4798                for value in arg_value:
4799                    args.append(value)
4800            elif arg_value is not None:
4801                args.append(arg_value)
4802
4803        return self.func(self.function_name(expression), *args)
def function_name(self, expression: sqlglot.expressions.core.Func) -> str:
4805    def function_name(self, expression: exp.Func) -> str:
4806        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4807        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
def func( self, name: str, *args: Any, prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
4809    def func(
4810        self,
4811        name: str,
4812        *args: t.Any,
4813        prefix: str = "(",
4814        suffix: str = ")",
4815        normalize: bool = True,
4816    ) -> str:
4817        name = self.normalize_func(name) if normalize else name
4818        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def format_args(self, *args: Any, sep: str = ', ') -> str:
4820    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4821        arg_sqls = tuple(
4822            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4823        )
4824        if self.pretty and self.too_wide(arg_sqls):
4825            return self.indent(
4826                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4827            )
4828        return sep.join(arg_sqls)
def too_wide(self, args: Iterable) -> bool:
4830    def too_wide(self, args: t.Iterable) -> bool:
4831        return sum(len(arg) for arg in args) > self.max_text_width
def format_time( self, expression: sqlglot.expressions.core.Expr, inverse_time_mapping: dict[str, str] | None = None, inverse_time_trie: dict | None = None) -> str | None:
4833    def format_time(
4834        self,
4835        expression: exp.Expr,
4836        inverse_time_mapping: dict[str, str] | None = None,
4837        inverse_time_trie: dict | None = None,
4838    ) -> str | None:
4839        return format_time(
4840            self.sql(expression, "format"),
4841            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4842            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4843        )
def expressions( self, expression: sqlglot.expressions.core.Expr | None = None, key: str | None = None, sqls: Optional[Collection[str | sqlglot.expressions.core.Expr]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
4845    def expressions(
4846        self,
4847        expression: exp.Expr | None = None,
4848        key: str | None = None,
4849        sqls: t.Collection[str | exp.Expr] | None = None,
4850        flat: bool = False,
4851        indent: bool = True,
4852        skip_first: bool = False,
4853        skip_last: bool = False,
4854        sep: str = ", ",
4855        prefix: str = "",
4856        dynamic: bool = False,
4857        new_line: bool = False,
4858    ) -> str:
4859        expressions = expression.args.get(key or "expressions") if expression else sqls
4860
4861        if not expressions:
4862            return ""
4863
4864        if flat:
4865            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4866
4867        num_sqls = len(expressions)
4868        result_sqls = []
4869
4870        for i, e in enumerate(expressions):
4871            sql = self.sql(e, comment=False)
4872            if not sql:
4873                continue
4874
4875            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4876
4877            if self.pretty:
4878                if self.leading_comma:
4879                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4880                else:
4881                    result_sqls.append(
4882                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4883                    )
4884            else:
4885                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4886
4887        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4888            if new_line:
4889                result_sqls.insert(0, "")
4890                result_sqls.append("")
4891            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4892        else:
4893            result_sql = "".join(result_sqls)
4894
4895        return (
4896            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4897            if indent
4898            else result_sql
4899        )
def op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4901    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4902        flat = flat or isinstance(expression.parent, exp.Properties)
4903        expressions_sql = self.expressions(expression, flat=flat)
4904        if flat:
4905            return f"{op} {expressions_sql}"
4906        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.properties.Property) -> str:
4908    def naked_property(self, expression: exp.Property) -> str:
4909        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4910        if not property_name:
4911            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4912        return f"{property_name} {self.sql(expression, 'this')}"
def tag_sql(self, expression: sqlglot.expressions.query.Tag) -> str:
4914    def tag_sql(self, expression: exp.Tag) -> str:
4915        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
def token_sql(self, token_type: sqlglot.tokenizer_core.TokenType) -> str:
4917    def token_sql(self, token_type: TokenType) -> str:
4918        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.ddl.UserDefinedFunction) -> str:
4920    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4921        this = self.sql(expression, "this")
4922        expressions = self.no_identify(self.expressions, expression)
4923        expressions = (
4924            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4925        )
4926        return f"{this}{expressions}" if expressions.strip() != "" else this
def macrooverloads_sql(self, expression: sqlglot.expressions.ddl.MacroOverloads) -> str:
4928    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4929        return self.expressions(expression, flat=True)
def macrooverload_sql(self, expression: sqlglot.expressions.ddl.MacroOverload) -> str:
4931    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4932        params = self.no_identify(self.expressions, expression, flat=True)
4933        body = self.sql(expression, "this")
4934        prefix = "TABLE " if expression.args.get("is_table") else ""
4935        return f"({params}) AS {prefix}{body}"
def joinhint_sql(self, expression: sqlglot.expressions.core.JoinHint) -> str:
4937    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4938        this = self.sql(expression, "this")
4939        expressions = self.expressions(expression, flat=True)
4940        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.core.Kwarg) -> str:
4942    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4943        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.dml.When) -> str:
4945    def when_sql(self, expression: exp.When) -> str:
4946        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4947        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4948        condition = self.sql(expression, "condition")
4949        condition = f" AND {condition}" if condition else ""
4950
4951        then_expression = expression.args.get("then")
4952        if isinstance(then_expression, exp.Insert):
4953            this = self.sql(then_expression, "this")
4954            this = f"INSERT {this}" if this else "INSERT"
4955            then = self.sql(then_expression, "expression")
4956            then = f"{this} VALUES {then}" if then else this
4957        elif isinstance(then_expression, exp.Update):
4958            if isinstance(then_expression.args.get("expressions"), exp.Star):
4959                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4960            else:
4961                expressions_sql = self.expressions(then_expression)
4962                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4963        else:
4964            then = self.sql(then_expression)
4965
4966        if isinstance(then_expression, (exp.Insert, exp.Update)):
4967            where = self.sql(then_expression, "where")
4968            if where and not self.SUPPORTS_MERGE_WHERE:
4969                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4970                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4971                where = ""
4972            then = f"{then}{where}"
4973        return f"WHEN {matched}{source}{condition} THEN {then}"
def whens_sql(self, expression: sqlglot.expressions.dml.Whens) -> str:
4975    def whens_sql(self, expression: exp.Whens) -> str:
4976        return self.expressions(expression, sep=" ", indent=False)
def merge_sql(self, expression: sqlglot.expressions.dml.Merge) -> str:
4978    def merge_sql(self, expression: exp.Merge) -> str:
4979        table = expression.this
4980        table_alias = ""
4981
4982        hints = table.args.get("hints")
4983        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4984            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4985            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4986
4987        this = self.sql(table)
4988        using = f"USING {self.sql(expression, 'using')}"
4989        whens = self.sql(expression, "whens")
4990
4991        on = self.sql(expression, "on")
4992        on = f"ON {on}" if on else ""
4993
4994        if not on:
4995            on = self.expressions(expression, key="using_cond")
4996            on = f"USING ({on})" if on else ""
4997
4998        returning = self.sql(expression, "returning")
4999        if returning:
5000            whens = f"{whens}{returning}"
5001
5002        sep = self.sep()
5003
5004        return self.prepend_ctes(
5005            expression,
5006            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5007        )
@unsupported_args('format')
def tochar_sql(self, expression: sqlglot.expressions.string.ToChar) -> str:
5009    @unsupported_args("format")
5010    def tochar_sql(self, expression: exp.ToChar) -> str:
5011        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
@unsupported_args('default')
def tonumber_sql(self, expression: sqlglot.expressions.string.ToNumber) -> str:
5013    @unsupported_args("default")
5014    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5015        if not self.SUPPORTS_TO_NUMBER:
5016            self.unsupported("Unsupported TO_NUMBER function")
5017            return self._tonumber_cast_sql(expression)
5018
5019        fmt = expression.args.get("format")
5020        if not fmt:
5021            self.unsupported("Conversion format is required for TO_NUMBER")
5022            return self._tonumber_cast_sql(expression)
5023
5024        return self.func("TO_NUMBER", expression.this, fmt)
def dictproperty_sql(self, expression: sqlglot.expressions.properties.DictProperty) -> str:
5031    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5032        this = self.sql(expression, "this")
5033        kind = self.sql(expression, "kind")
5034        settings_sql = self.expressions(expression, key="settings", sep=" ")
5035        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5036        return f"{this}({kind}{args})"
def dictrange_sql(self, expression: sqlglot.expressions.properties.DictRange) -> str:
5038    def dictrange_sql(self, expression: exp.DictRange) -> str:
5039        this = self.sql(expression, "this")
5040        max = self.sql(expression, "max")
5041        min = self.sql(expression, "min")
5042        return f"{this}(MIN {min} MAX {max})"
def dictsubproperty_sql(self, expression: sqlglot.expressions.properties.DictSubProperty) -> str:
5044    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5045        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
def duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
5047    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5048        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
def uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
5051    def uniquekeyproperty_sql(
5052        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5053    ) -> str:
5054        return f"{prefix} ({self.expressions(expression, flat=True)})"
def distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
5057    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5058        expressions = self.expressions(expression, flat=True)
5059        expressions = f" {self.wrap(expressions)}" if expressions else ""
5060        buckets = self.sql(expression, "buckets")
5061        kind = self.sql(expression, "kind")
5062        buckets = f" BUCKETS {buckets}" if buckets else ""
5063        order = self.sql(expression, "order")
5064        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def oncluster_sql(self, expression: sqlglot.expressions.properties.OnCluster) -> str:
5066    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5067        return ""
def clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
5069    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5070        expressions = self.expressions(expression, key="expressions", flat=True)
5071        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5072        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5073        buckets = self.sql(expression, "buckets")
5074        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
def anyvalue_sql(self, expression: sqlglot.expressions.aggregate.AnyValue) -> str:
5076    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5077        this = self.sql(expression, "this")
5078        having = self.sql(expression, "having")
5079
5080        if having:
5081            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5082
5083        return self.func("ANY_VALUE", this)
def querytransform_sql(self, expression: sqlglot.expressions.properties.QueryTransform) -> str:
5085    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5086        transform = self.func("TRANSFORM", *expression.expressions)
5087        row_format_before = self.sql(expression, "row_format_before")
5088        row_format_before = f" {row_format_before}" if row_format_before else ""
5089        record_writer = self.sql(expression, "record_writer")
5090        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5091        using = f" USING {self.sql(expression, 'command_script')}"
5092        schema = self.sql(expression, "schema")
5093        schema = f" AS {schema}" if schema else ""
5094        row_format_after = self.sql(expression, "row_format_after")
5095        row_format_after = f" {row_format_after}" if row_format_after else ""
5096        record_reader = self.sql(expression, "record_reader")
5097        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5098        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
def indexconstraintoption_sql( self, expression: sqlglot.expressions.constraints.IndexConstraintOption) -> str:
5100    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5101        key_block_size = self.sql(expression, "key_block_size")
5102        if key_block_size:
5103            return f"KEY_BLOCK_SIZE = {key_block_size}"
5104
5105        using = self.sql(expression, "using")
5106        if using:
5107            return f"USING {using}"
5108
5109        parser = self.sql(expression, "parser")
5110        if parser:
5111            return f"WITH PARSER {parser}"
5112
5113        comment = self.sql(expression, "comment")
5114        if comment:
5115            return f"COMMENT {comment}"
5116
5117        visible = expression.args.get("visible")
5118        if visible is not None:
5119            return "VISIBLE" if visible else "INVISIBLE"
5120
5121        engine_attr = self.sql(expression, "engine_attr")
5122        if engine_attr:
5123            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5124
5125        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5126        if secondary_engine_attr:
5127            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5128
5129        self.unsupported("Unsupported index constraint option.")
5130        return ""
def checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
5132    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5133        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5134        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
def indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5136    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5137        kind = self.sql(expression, "kind")
5138        kind = f"{kind} INDEX" if kind else "INDEX"
5139        this = self.sql(expression, "this")
5140        this = f" {this}" if this else ""
5141        index_type = self.sql(expression, "index_type")
5142        index_type = f" USING {index_type}" if index_type else ""
5143        expressions = self.expressions(expression, flat=True)
5144        expressions = f" ({expressions})" if expressions else ""
5145        options = self.expressions(expression, key="options", sep=" ")
5146        options = f" {options}" if options else ""
5147        return f"{kind}{this}{index_type}{expressions}{options}"
def nvl2_sql(self, expression: sqlglot.expressions.functions.Nvl2) -> str:
5149    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5150        if self.NVL2_SUPPORTED:
5151            return self.function_fallback_sql(expression)
5152
5153        case = exp.Case().when(
5154            expression.this.is_(exp.null()).not_(copy=False),
5155            expression.args["true"],
5156            copy=False,
5157        )
5158        else_cond = expression.args.get("false")
5159        if else_cond:
5160            case.else_(else_cond, copy=False)
5161
5162        return self.sql(case)
def nthvalue_sql(self, expression: sqlglot.expressions.aggregate.NthValue) -> str:
5164    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5165        if expression.args.get("from_first") is False:
5166            self.unsupported("NTH_VALUE FROM LAST is not supported")
5167
5168        return self.function_fallback_sql(expression)
def comprehension_sql(self, expression: sqlglot.expressions.ddl.Comprehension) -> str:
5170    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5171        this = self.sql(expression, "this")
5172        expr = self.sql(expression, "expression")
5173        position = self.sql(expression, "position")
5174        position = f", {position}" if position else ""
5175        iterator = self.sql(expression, "iterator")
5176        condition = self.sql(expression, "condition")
5177        condition = f" IF {condition}" if condition else ""
5178        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def columnprefix_sql(self, expression: sqlglot.expressions.constraints.ColumnPrefix) -> str:
5180    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5181        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
def opclass_sql(self, expression: sqlglot.expressions.core.Opclass) -> str:
5183    def opclass_sql(self, expression: exp.Opclass) -> str:
5184        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def predict_sql(self, expression: sqlglot.expressions.functions.Predict) -> str:
5200    def predict_sql(self, expression: exp.Predict) -> str:
5201        return self._ml_sql(expression, "PREDICT")
def generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5203    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5204        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5205        return self._ml_sql(expression, name)
def generatetext_sql(self, expression: sqlglot.expressions.functions.GenerateText) -> str:
5207    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5208        return self._ml_sql(expression, "GENERATE_TEXT")
def generatetable_sql(self, expression: sqlglot.expressions.functions.GenerateTable) -> str:
5210    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5211        return self._ml_sql(expression, "GENERATE_TABLE")
def generatebool_sql(self, expression: sqlglot.expressions.functions.GenerateBool) -> str:
5213    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5214        return self._ml_sql(expression, "GENERATE_BOOL")
def generateint_sql(self, expression: sqlglot.expressions.functions.GenerateInt) -> str:
5216    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5217        return self._ml_sql(expression, "GENERATE_INT")
def generatedouble_sql(self, expression: sqlglot.expressions.functions.GenerateDouble) -> str:
5219    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5220        return self._ml_sql(expression, "GENERATE_DOUBLE")
def mltranslate_sql(self, expression: sqlglot.expressions.functions.MLTranslate) -> str:
5222    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5223        return self._ml_sql(expression, "TRANSLATE")
def mlforecast_sql(self, expression: sqlglot.expressions.functions.MLForecast) -> str:
5225    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5226        return self._ml_sql(expression, "FORECAST")
def aiforecast_sql(self, expression: sqlglot.expressions.functions.AIForecast) -> str:
5228    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5229        this_sql = self.sql(expression, "this")
5230        if isinstance(expression.this, exp.Table):
5231            this_sql = f"TABLE {this_sql}"
5232
5233        return self.func(
5234            "FORECAST",
5235            this_sql,
5236            expression.args.get("data_col"),
5237            expression.args.get("timestamp_col"),
5238            expression.args.get("model"),
5239            expression.args.get("id_cols"),
5240            expression.args.get("horizon"),
5241            expression.args.get("forecast_end_timestamp"),
5242            expression.args.get("confidence_level"),
5243            expression.args.get("output_historical_time_series"),
5244            expression.args.get("context_window"),
5245        )
def featuresattime_sql(self, expression: sqlglot.expressions.functions.FeaturesAtTime) -> str:
5247    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5248        this_sql = self.sql(expression, "this")
5249        if isinstance(expression.this, exp.Table):
5250            this_sql = f"TABLE {this_sql}"
5251
5252        return self.func(
5253            "FEATURES_AT_TIME",
5254            this_sql,
5255            expression.args.get("time"),
5256            expression.args.get("num_rows"),
5257            expression.args.get("ignore_feature_nulls"),
5258        )
def vectorsearch_sql(self, expression: sqlglot.expressions.functions.VectorSearch) -> str:
5260    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5261        this_sql = self.sql(expression, "this")
5262        if isinstance(expression.this, exp.Table):
5263            this_sql = f"TABLE {this_sql}"
5264
5265        query_table = self.sql(expression, "query_table")
5266        if isinstance(expression.args["query_table"], exp.Table):
5267            query_table = f"TABLE {query_table}"
5268
5269        return self.func(
5270            "VECTOR_SEARCH",
5271            this_sql,
5272            expression.args.get("column_to_search"),
5273            query_table,
5274            expression.args.get("query_column_to_search"),
5275            expression.args.get("top_k"),
5276            expression.args.get("distance_type"),
5277            expression.args.get("options"),
5278        )
def forin_sql(self, expression: sqlglot.expressions.core.ForIn) -> str:
5280    def forin_sql(self, expression: exp.ForIn) -> str:
5281        this = self.sql(expression, "this")
5282        expression_sql = self.sql(expression, "expression")
5283        return f"FOR {this} DO {expression_sql}"
def refresh_sql(self, expression: sqlglot.expressions.core.Refresh) -> str:
5285    def refresh_sql(self, expression: exp.Refresh) -> str:
5286        this = self.sql(expression, "this")
5287        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5288        return f"REFRESH {kind}{this}"
def toarray_sql(self, expression: sqlglot.expressions.array.ToArray) -> str:
5290    def toarray_sql(self, expression: exp.ToArray) -> str:
5291        arg = expression.this
5292        if not arg.type:
5293            import sqlglot.optimizer.annotate_types
5294
5295            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5296
5297        if arg.is_type(exp.DType.ARRAY):
5298            return self.sql(arg)
5299
5300        cond_for_null = arg.is_(exp.null())
5301        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
def tsordstotime_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToTime) -> str:
5303    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5304        this = expression.this
5305        time_format = self.format_time(expression)
5306
5307        if time_format:
5308            return self.sql(
5309                exp.cast(
5310                    exp.StrToTime(this=this, format=expression.args["format"]),
5311                    exp.DType.TIME,
5312                )
5313            )
5314
5315        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5316            return self.sql(this)
5317
5318        return self.sql(exp.cast(this, exp.DType.TIME))
def tsordstotimestamp_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToTimestamp) -> str:
5320    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5321        this = expression.this
5322        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5323            return self.sql(this)
5324
5325        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
def tsordstodatetime_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToDatetime) -> str:
5327    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5328        this = expression.this
5329        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5330            return self.sql(this)
5331
5332        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
def tsordstodate_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToDate) -> str:
5334    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5335        this = expression.this
5336        time_format = self.format_time(expression)
5337        safe = expression.args.get("safe")
5338        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5339            return self.sql(
5340                exp.cast(
5341                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5342                    exp.DType.DATE,
5343                )
5344            )
5345
5346        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5347            return self.sql(this)
5348
5349        if safe:
5350            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5351
5352        return self.sql(exp.cast(this, exp.DType.DATE))
def unixdate_sql(self, expression: sqlglot.expressions.temporal.UnixDate) -> str:
5354    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5355        return self.sql(
5356            exp.func(
5357                "DATEDIFF",
5358                expression.this,
5359                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5360                "day",
5361            )
5362        )
def lastday_sql(self, expression: sqlglot.expressions.temporal.LastDay) -> str:
5364    def lastday_sql(self, expression: exp.LastDay) -> str:
5365        if self.LAST_DAY_SUPPORTS_DATE_PART:
5366            return self.function_fallback_sql(expression)
5367
5368        unit = expression.args.get("unit")
5369        if unit and unit.name.upper() != "MONTH":
5370            self.unsupported("Date parts are not supported in LAST_DAY.")
5371
5372        return self.func("LAST_DAY", expression.this)
def dateadd_sql(self, expression: sqlglot.expressions.temporal.DateAdd) -> str:
5374    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5375        import sqlglot.dialects.dialect
5376
5377        return self.func(
5378            "DATE_ADD",
5379            expression.this,
5380            expression.expression,
5381            sqlglot.dialects.dialect.unit_to_str(expression),
5382        )
def arrayinsert_sql( self, expression: sqlglot.expressions.array.ArrayInsert, index_offset: int = 0) -> str:
5384    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5385        this = expression.this
5386        position = expression.args["position"]
5387        offset = index_offset - (expression.args.get("offset") or 0)
5388
5389        if offset:
5390            if position.is_int:
5391                value = position.to_py()
5392                if value >= 0:
5393                    position = exp.Literal.number(value + offset)
5394                elif offset < 0 and value == -1:
5395                    # 1-based -1 appends, which a 0-based position can only express as the size
5396                    position = exp.ArraySize(this=this.copy())
5397                else:
5398                    # Negative positions count from the end, so they shift in the opposite
5399                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5400                    position = exp.Literal.number(value - offset)
5401            else:
5402                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5403
5404        return self.func("ARRAY_INSERT", this, position, expression.expression)
def arrayany_sql(self, expression: sqlglot.expressions.array.ArrayAny) -> str:
5406    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5407        if self.CAN_IMPLEMENT_ARRAY_ANY:
5408            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5409            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5410            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5411            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5412
5413        import sqlglot.dialects.dialect
5414
5415        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5416        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5417            self.unsupported("ARRAY_ANY is unsupported")
5418
5419        return self.function_fallback_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
5421    def struct_sql(self, expression: exp.Struct) -> str:
5422        expression.set(
5423            "expressions",
5424            [
5425                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5426                if isinstance(e, exp.PropertyEQ)
5427                else e
5428                for e in expression.expressions
5429            ],
5430        )
5431
5432        return self.function_fallback_sql(expression)
def partitionrange_sql(self, expression: sqlglot.expressions.query.PartitionRange) -> str:
5434    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5435        low = self.sql(expression, "this")
5436        high = self.sql(expression, "expression")
5437
5438        return f"{low} TO {high}"
def truncatetable_sql(self, expression: sqlglot.expressions.ddl.TruncateTable) -> str:
5440    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5441        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5442        tables = f" {self.expressions(expression)}"
5443
5444        exists = " IF EXISTS" if expression.args.get("exists") else ""
5445
5446        on_cluster = self.sql(expression, "cluster")
5447        on_cluster = f" {on_cluster}" if on_cluster else ""
5448
5449        identity = self.sql(expression, "identity")
5450        identity = f" {identity} IDENTITY" if identity else ""
5451
5452        option = self.sql(expression, "option")
5453        option = f" {option}" if option else ""
5454
5455        partition = self.sql(expression, "partition")
5456        partition = f" {partition}" if partition else ""
5457
5458        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
def convert_sql(self, expression: sqlglot.expressions.functions.Convert) -> str:
5462    def convert_sql(self, expression: exp.Convert) -> str:
5463        to = expression.this
5464        value = expression.expression
5465        style = expression.args.get("style")
5466        safe = expression.args.get("safe")
5467        strict = expression.args.get("strict")
5468
5469        if not to or not value:
5470            return ""
5471
5472        # Retrieve length of datatype and override to default if not specified
5473        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5474            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5475
5476        transformed: exp.Expr | None = None
5477        cast = exp.Cast if strict else exp.TryCast
5478
5479        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5480        if isinstance(style, exp.Literal) and style.is_int:
5481            import sqlglot.dialects.tsql
5482
5483            style_value = style.name
5484            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5485            if not converted_style:
5486                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5487
5488            fmt = exp.Literal.string(converted_style)
5489
5490            if to.this == exp.DType.DATE:
5491                transformed = exp.StrToDate(this=value, format=fmt)
5492            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5493                transformed = exp.StrToTime(this=value, format=fmt)
5494            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5495                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5496            elif to.this == exp.DType.TEXT:
5497                transformed = exp.TimeToStr(this=value, format=fmt)
5498
5499        if not transformed:
5500            transformed = cast(this=value, to=to, safe=safe)
5501
5502        return self.sql(transformed)
def copyparameter_sql(self, expression: sqlglot.expressions.dml.CopyParameter) -> str:
5579    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5580        option = self.sql(expression, "this")
5581
5582        if expression.expressions:
5583            upper = option.upper()
5584
5585            # Snowflake FILE_FORMAT options are separated by whitespace
5586            sep = " " if upper == "FILE_FORMAT" else ", "
5587
5588            # Databricks copy/format options do not set their list of values with EQ
5589            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5590            values = self.expressions(expression, flat=True, sep=sep)
5591            return f"{option}{op}({values})"
5592
5593        value = self.sql(expression, "expression")
5594
5595        if not value:
5596            return option
5597
5598        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5599
5600        return f"{option}{op}{value}"
def credentials_sql(self, expression: sqlglot.expressions.dml.Credentials) -> str:
5602    def credentials_sql(self, expression: exp.Credentials) -> str:
5603        cred_expr = expression.args.get("credentials")
5604        if isinstance(cred_expr, exp.Literal):
5605            # Redshift case: CREDENTIALS <string>
5606            credentials = self.sql(expression, "credentials")
5607            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5608        else:
5609            # Snowflake case: CREDENTIALS = (...)
5610            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5611            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5612
5613        storage = self.sql(expression, "storage")
5614        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5615
5616        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5617        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5618
5619        iam_role = self.sql(expression, "iam_role")
5620        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5621
5622        region = self.sql(expression, "region")
5623        region = f" REGION {region}" if region else ""
5624
5625        return f"{credentials}{storage}{encryption}{iam_role}{region}"
def copy_sql(self, expression: sqlglot.expressions.dml.Copy) -> str:
5627    def copy_sql(self, expression: exp.Copy) -> str:
5628        this = self.sql(expression, "this")
5629        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5630
5631        credentials = self.sql(expression, "credentials")
5632        credentials = self.seg(credentials) if credentials else ""
5633        files = self.expressions(expression, key="files", flat=True)
5634        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5635
5636        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5637        params = self.expressions(
5638            expression,
5639            key="params",
5640            sep=sep,
5641            new_line=True,
5642            skip_last=True,
5643            skip_first=True,
5644            indent=self.COPY_PARAMS_ARE_WRAPPED,
5645        )
5646
5647        if params:
5648            if self.COPY_PARAMS_ARE_WRAPPED:
5649                params = f" WITH ({params})"
5650            elif not self.pretty and (files or credentials):
5651                params = f" {params}"
5652
5653        return f"COPY{this}{kind} {files}{credentials}{params}"
def semicolon_sql(self, expression: sqlglot.expressions.query.Semicolon) -> str:
5655    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5656        return ""
def datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5658    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5659        on_sql = "ON" if expression.args.get("on") else "OFF"
5660        filter_col: str | None = self.sql(expression, "filter_column")
5661        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5662        retention_period: str | None = self.sql(expression, "retention_period")
5663        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5664
5665        if filter_col or retention_period:
5666            on_sql = self.func("ON", filter_col, retention_period)
5667
5668        return f"DATA_DELETION={on_sql}"
def maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5670    def maskingpolicycolumnconstraint_sql(
5671        self, expression: exp.MaskingPolicyColumnConstraint
5672    ) -> str:
5673        this = self.sql(expression, "this")
5674        expressions = self.expressions(expression, flat=True)
5675        expressions = f" USING ({expressions})" if expressions else ""
5676        return f"MASKING POLICY {this}{expressions}"
def gapfill_sql(self, expression: sqlglot.expressions.temporal.GapFill) -> str:
5678    def gapfill_sql(self, expression: exp.GapFill) -> str:
5679        this = self.sql(expression, "this")
5680        this = f"TABLE {this}"
5681        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
def scope_resolution(self, rhs: str, scope_name: str) -> str:
5683    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5684        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
def scoperesolution_sql(self, expression: sqlglot.expressions.query.ScopeResolution) -> str:
5686    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5687        this = self.sql(expression, "this")
5688        expr = expression.expression
5689
5690        if isinstance(expr, exp.Func):
5691            # T-SQL's CLR functions are case sensitive
5692            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5693        else:
5694            expr = self.sql(expression, "expression")
5695
5696        return self.scope_resolution(expr, this)
def parsejson_sql(self, expression: sqlglot.expressions.json.ParseJSON) -> str:
5698    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5699        if self.PARSE_JSON_NAME is None:
5700            return self.sql(expression.this)
5701
5702        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
def rand_sql(self, expression: sqlglot.expressions.functions.Rand) -> str:
5704    def rand_sql(self, expression: exp.Rand) -> str:
5705        lower = self.sql(expression, "lower")
5706        upper = self.sql(expression, "upper")
5707
5708        if lower and upper:
5709            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5710        return self.func("RAND", expression.this)
def changes_sql(self, expression: sqlglot.expressions.query.Changes) -> str:
5712    def changes_sql(self, expression: exp.Changes) -> str:
5713        information = self.sql(expression, "information")
5714        information = f"INFORMATION => {information}"
5715        at_before = self.sql(expression, "at_before")
5716        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5717        end = self.sql(expression, "end")
5718        end = f"{self.seg('')}{end}" if end else ""
5719
5720        return f"CHANGES ({information}){at_before}{end}"
def pad_sql(self, expression: sqlglot.expressions.string.Pad) -> str:
5722    def pad_sql(self, expression: exp.Pad) -> str:
5723        prefix = "L" if expression.args.get("is_left") else "R"
5724
5725        fill_pattern = self.sql(expression, "fill_pattern") or None
5726        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5727            fill_pattern = "' '"
5728
5729        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def summarize_sql(self, expression: sqlglot.expressions.ddl.Summarize) -> str:
5731    def summarize_sql(self, expression: exp.Summarize) -> str:
5732        table = " TABLE" if expression.args.get("table") else ""
5733        return f"SUMMARIZE{table} {self.sql(expression.this)}"
def explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5735    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5736        generate_series = exp.GenerateSeries(**expression.args)
5737
5738        parent = expression.parent
5739        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5740            parent = parent.parent
5741
5742        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5743            return self.sql(exp.Unnest(expressions=[generate_series]))
5744
5745        if isinstance(parent, exp.Select):
5746            self.unsupported("GenerateSeries projection unnesting is not supported.")
5747
5748        return self.sql(generate_series)
def converttimezone_sql(self, expression: sqlglot.expressions.temporal.ConvertTimezone) -> str:
5750    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5751        if self.SUPPORTS_CONVERT_TIMEZONE:
5752            return self.function_fallback_sql(expression)
5753
5754        source_tz = expression.args.get("source_tz")
5755        target_tz = expression.args.get("target_tz")
5756        timestamp = expression.args.get("timestamp")
5757
5758        if source_tz and timestamp:
5759            timestamp = exp.AtTimeZone(
5760                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5761            )
5762
5763        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5764
5765        return self.sql(expr)
def json_sql(self, expression: sqlglot.expressions.query.JSON) -> str:
5767    def json_sql(self, expression: exp.JSON) -> str:
5768        this = self.sql(expression, "this")
5769        this = f" {this}" if this else ""
5770
5771        _with = expression.args.get("with_")
5772
5773        if _with is None:
5774            with_sql = ""
5775        elif not _with:
5776            with_sql = " WITHOUT"
5777        else:
5778            with_sql = " WITH"
5779
5780        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5781
5782        return f"JSON{this}{with_sql}{unique_sql}"
def jsonvalue_sql(self, expression: sqlglot.expressions.query.JSONValue) -> str:
5784    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5785        path = self.sql(expression, "path")
5786        returning = self.sql(expression, "returning")
5787        returning = f" RETURNING {returning}" if returning else ""
5788
5789        on_condition = self.sql(expression, "on_condition")
5790        on_condition = f" {on_condition}" if on_condition else ""
5791
5792        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
def skipjsoncolumn_sql(self, expression: sqlglot.expressions.query.SkipJSONColumn) -> str:
5794    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5795        regexp = " REGEXP" if expression.args.get("regexp") else ""
5796        return f"SKIP{regexp} {self.sql(expression.expression)}"
def conditionalinsert_sql(self, expression: sqlglot.expressions.query.ConditionalInsert) -> str:
5798    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5799        else_ = "ELSE " if expression.args.get("else_") else ""
5800        condition = self.sql(expression, "expression")
5801        condition = f"WHEN {condition} THEN " if condition else else_
5802        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5803        return f"{condition}{insert}"
def multitableinserts_sql(self, expression: sqlglot.expressions.query.MultitableInserts) -> str:
5805    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5806        kind = self.sql(expression, "kind")
5807        source = self.sql(expression, "source")
5808
5809        if kind:
5810            expressions = self.seg(self.expressions(expression, sep=" "))
5811            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5812        else:
5813            # If kind isn't present, we have Hive's FROM-first form:
5814            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5815            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5816            sql = f"FROM {source}{self.seg(inserts)}"
5817
5818        return self.prepend_ctes(expression, sql)
def oncondition_sql(self, expression: sqlglot.expressions.query.OnCondition) -> str:
5820    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5821        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5822        empty = expression.args.get("empty")
5823        empty = (
5824            f"DEFAULT {empty} ON EMPTY"
5825            if isinstance(empty, exp.Expr)
5826            else self.sql(expression, "empty")
5827        )
5828
5829        error = expression.args.get("error")
5830        error = (
5831            f"DEFAULT {error} ON ERROR"
5832            if isinstance(error, exp.Expr)
5833            else self.sql(expression, "error")
5834        )
5835
5836        if error and empty:
5837            error = (
5838                f"{empty} {error}"
5839                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5840                else f"{error} {empty}"
5841            )
5842            empty = ""
5843
5844        null = self.sql(expression, "null")
5845
5846        return f"{empty}{error}{null}"
def jsonextractquote_sql(self, expression: sqlglot.expressions.query.JSONExtractQuote) -> str:
5848    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5849        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5850        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
def jsonexists_sql(self, expression: sqlglot.expressions.json.JSONExists) -> str:
5852    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5853        this = self.sql(expression, "this")
5854        path = self.sql(expression, "path")
5855
5856        passing = self.expressions(expression, "passing")
5857        passing = f" PASSING {passing}" if passing else ""
5858
5859        on_condition = self.sql(expression, "on_condition")
5860        on_condition = f" {on_condition}" if on_condition else ""
5861
5862        path = f"{path}{passing}{on_condition}"
5863
5864        return self.func("JSON_EXISTS", this, path)
def arrayagg_sql(self, expression: sqlglot.expressions.aggregate.ArrayAgg) -> str:
5906    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5907        array_agg = self.function_fallback_sql(expression)
5908        column_expr = expression.this
5909        if isinstance(column_expr, exp.Order):
5910            column_expr = column_expr.this
5911
5912        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
def slice_sql(self, expression: sqlglot.expressions.core.Slice) -> str:
5914    def slice_sql(self, expression: exp.Slice) -> str:
5915        step = self.sql(expression, "step")
5916        end = self.sql(expression.expression)
5917        begin = self.sql(expression.this)
5918
5919        sql = f"{end}:{step}" if step else end
5920        return f"{begin}:{sql}" if sql else f"{begin}:"
def apply_sql(self, expression: sqlglot.expressions.array.Apply) -> str:
5922    def apply_sql(self, expression: exp.Apply) -> str:
5923        this = self.sql(expression, "this")
5924        expr = self.sql(expression, "expression")
5925
5926        return f"{this} APPLY({expr})"
def grant_sql(self, expression: sqlglot.expressions.query.Grant) -> str:
5955    def grant_sql(self, expression: exp.Grant) -> str:
5956        return self._grant_or_revoke_sql(
5957            expression,
5958            keyword="GRANT",
5959            preposition="TO",
5960            grant_option_suffix=" WITH GRANT OPTION",
5961        )
def revoke_sql(self, expression: sqlglot.expressions.query.Revoke) -> str:
5963    def revoke_sql(self, expression: exp.Revoke) -> str:
5964        return self._grant_or_revoke_sql(
5965            expression,
5966            keyword="REVOKE",
5967            preposition="FROM",
5968            grant_option_prefix="GRANT OPTION FOR ",
5969        )
def grantprivilege_sql(self, expression: sqlglot.expressions.properties.GrantPrivilege) -> str:
5971    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5972        this = self.sql(expression, "this")
5973        columns = self.expressions(expression, flat=True)
5974        columns = f"({columns})" if columns else ""
5975
5976        return f"{this}{columns}"
def grantprincipal_sql(self, expression: sqlglot.expressions.properties.GrantPrincipal) -> str:
5978    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5979        this = self.sql(expression, "this")
5980
5981        kind = self.sql(expression, "kind")
5982        kind = f"{kind} " if kind else ""
5983
5984        return f"{kind}{this}"
def columns_sql(self, expression: sqlglot.expressions.functions.Columns) -> str:
5986    def columns_sql(self, expression: exp.Columns) -> str:
5987        func = self.function_fallback_sql(expression)
5988        if expression.args.get("unpack"):
5989            func = f"*{func}"
5990
5991        return func
def overlay_sql(self, expression: sqlglot.expressions.string.Overlay) -> str:
5993    def overlay_sql(self, expression: exp.Overlay) -> str:
5994        this = self.sql(expression, "this")
5995        expr = self.sql(expression, "expression")
5996        from_sql = self.sql(expression, "from_")
5997        for_sql = self.sql(expression, "for_")
5998        for_sql = f" FOR {for_sql}" if for_sql else ""
5999
6000        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
6002    @unsupported_args("format")
6003    def todouble_sql(self, expression: exp.ToDouble) -> str:
6004        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6005        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
def string_sql(self, expression: sqlglot.expressions.string.String) -> str:
6007    def string_sql(self, expression: exp.String) -> str:
6008        this = expression.this
6009        zone = expression.args.get("zone")
6010
6011        if zone:
6012            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6013            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6014            # set for source_tz to transpile the time conversion before the STRING cast
6015            this = exp.ConvertTimezone(
6016                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6017            )
6018
6019        return self.sql(exp.cast(this, exp.DType.VARCHAR))
def median_sql(self, expression: sqlglot.expressions.aggregate.Median) -> str:
6021    def median_sql(self, expression: exp.Median) -> str:
6022        if not self.SUPPORTS_MEDIAN:
6023            return self.sql(
6024                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6025            )
6026
6027        return self.function_fallback_sql(expression)
def overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
6029    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6030        filler = self.sql(expression, "this")
6031        filler = f" {filler}" if filler else ""
6032        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6033        return f"TRUNCATE{filler} {with_count}"
def unixseconds_sql(self, expression: sqlglot.expressions.temporal.UnixSeconds) -> str:
6035    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6036        if self.SUPPORTS_UNIX_SECONDS:
6037            return self.function_fallback_sql(expression)
6038
6039        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6040
6041        return self.sql(
6042            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6043        )
def arraysize_sql(self, expression: sqlglot.expressions.array.ArraySize) -> str:
6045    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6046        dim = expression.expression
6047
6048        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6049        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6050            if not (dim.is_int and dim.name == "1"):
6051                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6052            dim = None
6053
6054        # If dimension is required but not specified, default initialize it
6055        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6056            dim = exp.Literal.number(1)
6057
6058        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
def attach_sql(self, expression: sqlglot.expressions.ddl.Attach) -> str:
6060    def attach_sql(self, expression: exp.Attach) -> str:
6061        this = self.sql(expression, "this")
6062        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6063        expressions = self.expressions(expression)
6064        expressions = f" ({expressions})" if expressions else ""
6065
6066        return f"ATTACH{exists_sql} {this}{expressions}"
def detach_sql(self, expression: sqlglot.expressions.ddl.Detach) -> str:
6068    def detach_sql(self, expression: exp.Detach) -> str:
6069        kind = self.sql(expression, "kind")
6070        kind = f" {kind}" if kind else ""
6071        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6072        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6073        exists = " IF EXISTS" if expression.args.get("exists") else ""
6074        if exists:
6075            kind = kind or " DATABASE"
6076
6077        this = self.sql(expression, "this")
6078        this = f" {this}" if this else ""
6079        cluster = self.sql(expression, "cluster")
6080        cluster = f" {cluster}" if cluster else ""
6081        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6082        sync = " SYNC" if expression.args.get("sync") else ""
6083        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def attachoption_sql(self, expression: sqlglot.expressions.query.AttachOption) -> str:
6085    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6086        this = self.sql(expression, "this")
6087        value = self.sql(expression, "expression")
6088        value = f" {value}" if value else ""
6089        return f"{this}{value}"
def watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
6091    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6092        return (
6093            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6094        )
def encodeproperty_sql(self, expression: sqlglot.expressions.properties.EncodeProperty) -> str:
6096    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6097        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6098        encode = f"{encode} {self.sql(expression, 'this')}"
6099
6100        properties = expression.args.get("properties")
6101        if properties:
6102            encode = f"{encode} {self.properties(properties)}"
6103
6104        return encode
def includeproperty_sql(self, expression: sqlglot.expressions.properties.IncludeProperty) -> str:
6106    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6107        this = self.sql(expression, "this")
6108        include = f"INCLUDE {this}"
6109
6110        column_def = self.sql(expression, "column_def")
6111        if column_def:
6112            include = f"{include} {column_def}"
6113
6114        alias = self.sql(expression, "alias")
6115        if alias:
6116            include = f"{include} AS {alias}"
6117
6118        return include
def xmlelement_sql(self, expression: sqlglot.expressions.functions.XMLElement) -> str:
6120    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6121        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6122        name = f"{prefix} {self.sql(expression, 'this')}"
6123        return self.func("XMLELEMENT", name, *expression.expressions)
def xmlkeyvalueoption_sql(self, expression: sqlglot.expressions.query.XMLKeyValueOption) -> str:
6125    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6126        this = self.sql(expression, "this")
6127        expr = self.sql(expression, "expression")
6128        expr = f"({expr})" if expr else ""
6129        return f"{this}{expr}"
def partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6131    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6132        partitions = self.expressions(expression, "partition_expressions")
6133        create = self.expressions(expression, "create_expressions")
6134        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6136    def partitionbyrangepropertydynamic_sql(
6137        self, expression: exp.PartitionByRangePropertyDynamic
6138    ) -> str:
6139        start = self.sql(expression, "start")
6140        end = self.sql(expression, "end")
6141
6142        every = expression.args["every"]
6143        if isinstance(every, exp.Interval) and every.this.is_string:
6144            every.this.replace(exp.Literal.number(every.name))
6145
6146        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
def unpivotcolumns_sql(self, expression: sqlglot.expressions.query.UnpivotColumns) -> str:
6148    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6149        name = self.sql(expression, "this")
6150        values = self.expressions(expression, flat=True)
6151
6152        return f"NAME {name} VALUE {values}"
def analyzesample_sql(self, expression: sqlglot.expressions.query.AnalyzeSample) -> str:
6154    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6155        kind = self.sql(expression, "kind")
6156        sample = self.sql(expression, "sample")
6157        return f"SAMPLE {sample} {kind}"
def analyzestatistics_sql(self, expression: sqlglot.expressions.query.AnalyzeStatistics) -> str:
6159    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6160        kind = self.sql(expression, "kind")
6161        option = self.sql(expression, "option")
6162        option = f" {option}" if option else ""
6163        this = self.sql(expression, "this")
6164        this = f" {this}" if this else ""
6165        columns = self.expressions(expression)
6166        columns = f" {columns}" if columns else ""
6167        return f"{kind}{option} STATISTICS{this}{columns}"
def analyzehistogram_sql(self, expression: sqlglot.expressions.query.AnalyzeHistogram) -> str:
6169    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6170        this = self.sql(expression, "this")
6171        columns = self.expressions(expression)
6172        inner_expression = self.sql(expression, "expression")
6173        inner_expression = f" {inner_expression}" if inner_expression else ""
6174        update_options = self.sql(expression, "update_options")
6175        update_options = f" {update_options} UPDATE" if update_options else ""
6176        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def analyzedelete_sql(self, expression: sqlglot.expressions.query.AnalyzeDelete) -> str:
6178    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6179        kind = self.sql(expression, "kind")
6180        kind = f" {kind}" if kind else ""
6181        return f"DELETE{kind} STATISTICS"
def analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6183    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6184        inner_expression = self.sql(expression, "expression")
6185        return f"LIST CHAINED ROWS{inner_expression}"
def analyzevalidate_sql(self, expression: sqlglot.expressions.query.AnalyzeValidate) -> str:
6187    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6188        kind = self.sql(expression, "kind")
6189        this = self.sql(expression, "this")
6190        this = f" {this}" if this else ""
6191        inner_expression = self.sql(expression, "expression")
6192        return f"VALIDATE {kind}{this}{inner_expression}"
def analyze_sql(self, expression: sqlglot.expressions.query.Analyze) -> str:
6194    def analyze_sql(self, expression: exp.Analyze) -> str:
6195        options = self.expressions(expression, key="options", sep=" ")
6196        options = f" {options}" if options else ""
6197        kind = self.sql(expression, "kind")
6198        kind = f" {kind}" if kind else ""
6199        tables = self.expressions(expression, key="tables", flat=True)
6200        tables = f" {tables}" if tables else ""
6201        mode = self.sql(expression, "mode")
6202        mode = f" {mode}" if mode else ""
6203        properties = self.sql(expression, "properties")
6204        properties = f" {properties}" if properties else ""
6205        partition = self.sql(expression, "partition")
6206        partition = f" {partition}" if partition else ""
6207        inner_expression = self.sql(expression, "expression")
6208        inner_expression = f" {inner_expression}" if inner_expression else ""
6209        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
def xmltable_sql(self, expression: sqlglot.expressions.functions.XMLTable) -> str:
6211    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6212        this = self.sql(expression, "this")
6213        namespaces = self.expressions(expression, key="namespaces")
6214        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6215        passing = self.expressions(expression, key="passing")
6216        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6217        columns = self.expressions(expression, key="columns")
6218        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6219        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6220        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
def xmlnamespace_sql(self, expression: sqlglot.expressions.query.XMLNamespace) -> str:
6222    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6223        this = self.sql(expression, "this")
6224        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
def export_sql(self, expression: sqlglot.expressions.dml.Export) -> str:
6226    def export_sql(self, expression: exp.Export) -> str:
6227        this = self.sql(expression, "this")
6228        connection = self.sql(expression, "connection")
6229        connection = f"WITH CONNECTION {connection} " if connection else ""
6230        options = self.sql(expression, "options")
6231        return f"EXPORT DATA {connection}{options} AS {this}"
def declare_sql(self, expression: sqlglot.expressions.ddl.Declare) -> str:
6233    def declare_sql(self, expression: exp.Declare) -> str:
6234        replace = "OR REPLACE " if expression.args.get("replace") else ""
6235        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
def declareitem_sql(self, expression: sqlglot.expressions.ddl.DeclareItem) -> str:
6237    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6238        variables = self.expressions(expression, "this")
6239        default = self.sql(expression, "default")
6240        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6241
6242        kind = self.sql(expression, "kind")
6243        if isinstance(expression.args.get("kind"), exp.Schema):
6244            kind = f"TABLE {kind}"
6245
6246        kind = f" {kind}" if kind else ""
6247
6248        return f"{variables}{kind}{default}"
def recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6250    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6251        kind = self.sql(expression, "kind")
6252        this = self.sql(expression, "this")
6253        set = self.sql(expression, "expression")
6254        to = self.sql(expression, "to")
6255        to = f" TO {to}" if to else ""
6256        default = self.sql(expression, "default")
6257        default = f" DEFAULT {default}" if default else ""
6258        using = self.sql(expression, "using")
6259        using = f" USING {using}" if using else ""
6260
6261        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6262
6263        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
def parameterizedagg_sql(self, expression: sqlglot.expressions.core.ParameterizedAgg) -> str:
6265    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6266        params = self.expressions(expression, key="params", flat=True)
6267        return self.func(expression.name, *expression.expressions) + f"({params})"
def anonymousaggfunc_sql(self, expression: sqlglot.expressions.core.AnonymousAggFunc) -> str:
6269    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6270        return self.func(expression.name, *expression.expressions)
def combinedaggfunc_sql(self, expression: sqlglot.expressions.core.CombinedAggFunc) -> str:
6272    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6273        return self.anonymousaggfunc_sql(expression)
def combinedparameterizedagg_sql( self, expression: sqlglot.expressions.core.CombinedParameterizedAgg) -> str:
6275    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6276        return self.parameterizedagg_sql(expression)
def show_sql(self, expression: sqlglot.expressions.ddl.Show) -> str:
6278    def show_sql(self, expression: exp.Show) -> str:
6279        self.unsupported("Unsupported SHOW statement")
6280        return ""
def install_sql(self, expression: sqlglot.expressions.ddl.Install) -> str:
6282    def install_sql(self, expression: exp.Install) -> str:
6283        self.unsupported("Unsupported INSTALL statement")
6284        return ""
def liststage_sql(self, expression: sqlglot.expressions.query.ListStage) -> str:
6286    def liststage_sql(self, expression: exp.ListStage) -> str:
6287        pattern = self.sql(expression, "pattern")
6288        pattern = f" PATTERN = {pattern}" if pattern else ""
6289        return f"LIST {self.sql(expression, 'this')}{pattern}"
def get_put_sql( self, expression: sqlglot.expressions.query.Put | sqlglot.expressions.query.Get) -> str:
6291    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6292        # Snowflake GET/PUT statements:
6293        #   PUT <file> <internalStage> <properties>
6294        #   GET <internalStage> <file> <properties>
6295        props = expression.args.get("properties")
6296        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6297        this = self.sql(expression, "this")
6298        target = self.sql(expression, "target")
6299
6300        if isinstance(expression, exp.Put):
6301            return f"PUT {this} {target}{props_sql}"
6302        else:
6303            return f"GET {target} {this}{props_sql}"
def translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6305    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6306        this = self.sql(expression, "this")
6307        expr = self.sql(expression, "expression")
6308        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6309        return f"TRANSLATE({this} USING {expr}{with_error})"
def decodecase_sql(self, expression: sqlglot.expressions.functions.DecodeCase) -> str:
6311    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6312        if self.SUPPORTS_DECODE_CASE:
6313            return self.func("DECODE", *expression.expressions)
6314
6315        decode_expr, *expressions = expression.expressions
6316
6317        ifs = []
6318        for search, result in zip(expressions[::2], expressions[1::2]):
6319            if isinstance(search, exp.Literal):
6320                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6321            elif isinstance(search, exp.Null):
6322                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6323            else:
6324                if isinstance(search, exp.Binary):
6325                    search = exp.paren(search)
6326
6327                cond = exp.or_(
6328                    decode_expr.eq(search),
6329                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6330                    copy=False,
6331                )
6332                ifs.append(exp.If(this=cond, true=result))
6333
6334        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6335        return self.sql(case)
def semanticview_sql(self, expression: sqlglot.expressions.properties.SemanticView) -> str:
6337    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6338        this = self.sql(expression, "this")
6339        this = self.seg(this, sep="")
6340        dimensions = self.expressions(
6341            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6342        )
6343        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6344        metrics = self.expressions(
6345            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6346        )
6347        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6348        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6349        facts = self.seg(f"FACTS {facts}") if facts else ""
6350        where = self.sql(expression, "where")
6351        where = self.seg(f"WHERE {where}") if where else ""
6352        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6353        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
def getextract_sql(self, expression: sqlglot.expressions.temporal.GetExtract) -> str:
6355    def getextract_sql(self, expression: exp.GetExtract) -> str:
6356        this = expression.this
6357        expr = expression.expression
6358
6359        if not this.type or not expression.type:
6360            import sqlglot.optimizer.annotate_types
6361
6362            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6363
6364        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6365            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6366
6367        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def datefromunixdate_sql(self, expression: sqlglot.expressions.temporal.DateFromUnixDate) -> str:
6369    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6370        return self.sql(
6371            exp.DateAdd(
6372                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6373                expression=expression.this,
6374                unit=exp.var("DAY"),
6375            )
6376        )
def space_sql( self: Generator, expression: sqlglot.expressions.string.Space) -> str:
6378    def space_sql(self: Generator, expression: exp.Space) -> str:
6379        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
def buildproperty_sql(self, expression: sqlglot.expressions.properties.BuildProperty) -> str:
6381    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6382        return f"BUILD {self.sql(expression, 'this')}"
def refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6384    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6385        method = self.sql(expression, "method")
6386        kind = expression.args.get("kind")
6387        if not kind:
6388            return f"REFRESH {method}"
6389
6390        every = self.sql(expression, "every")
6391        unit = self.sql(expression, "unit")
6392        every = f" EVERY {every} {unit}" if every else ""
6393        starts = self.sql(expression, "starts")
6394        starts = f" STARTS {starts}" if starts else ""
6395
6396        return f"REFRESH {method} ON {kind}{every}{starts}"
def modelattribute_sql(self, expression: sqlglot.expressions.query.ModelAttribute) -> str:
6398    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6399        self.unsupported("The model!attribute syntax is not supported")
6400        return ""
def directorystage_sql(self, expression: sqlglot.expressions.dml.DirectoryStage) -> str:
6402    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6403        return self.func("DIRECTORY", expression.this)
def uuid_sql(self, expression: sqlglot.expressions.functions.Uuid) -> str:
6405    def uuid_sql(self, expression: exp.Uuid) -> str:
6406        is_string = expression.args.get("is_string", False)
6407        uuid_func_sql = self.func("UUID")
6408
6409        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6410            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6411
6412        return uuid_func_sql
def initcap_sql(self, expression: sqlglot.expressions.string.Initcap) -> str:
6414    def initcap_sql(self, expression: exp.Initcap) -> str:
6415        delimiters = expression.expression
6416
6417        if delimiters:
6418            # do not generate delimiters arg if we are round-tripping from default delimiters
6419            if (
6420                delimiters.is_string
6421                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6422            ):
6423                delimiters = None
6424            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6425                self.unsupported("INITCAP does not support custom delimiters")
6426                delimiters = None
6427
6428        return self.func("INITCAP", expression.this, delimiters)
def localtime_sql(self, expression: sqlglot.expressions.temporal.Localtime) -> str:
6430    def localtime_sql(self, expression: exp.Localtime) -> str:
6431        this = expression.this
6432        return self.func("LOCALTIME", this) if this else "LOCALTIME"
def localtimestamp_sql(self, expression: sqlglot.expressions.temporal.Localtimestamp) -> str:
6434    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6435        this = expression.this
6436        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
def weekstart_name(self, expression: sqlglot.expressions.functions.WeekStart) -> str:
6438    def weekstart_name(self, expression: exp.WeekStart) -> str:
6439        import sqlglot.dialects.dialect
6440
6441        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6442        this = expression.this.name.upper()
6443
6444        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6445        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6446
6447        if dow_from_week_start_day != dow_from_week_offset:
6448            self.unsupported(
6449                f"WEEK({this}) is not supported; falling back to the default week start day"
6450            )
6451
6452        return "WEEK"
def weekstart_sql(self, expression: sqlglot.expressions.functions.WeekStart) -> str:
6454    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6455        name = self.weekstart_name(expression)
6456
6457        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6458        if isinstance(expression.parent, exp.DateTrunc):
6459            return self.sql(exp.Literal.string(name))
6460
6461        return name
def chr_sql( self, expression: sqlglot.expressions.string.Chr, name: str = 'CHR') -> str:
6463    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6464        this = self.expressions(expression)
6465        charset = self.sql(expression, "charset")
6466        using = f" USING {charset}" if charset else ""
6467        return self.func(name, this + using)
def weightstring_sql(self, expression: sqlglot.expressions.string.WeightString) -> str:
6469    def weightstring_sql(self, expression: exp.WeightString) -> str:
6470        to = self.sql(expression, "to")
6471        to = f" AS {to}" if to else ""
6472        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
def block_sql(self, expression: sqlglot.expressions.query.Block) -> str:
6474    def block_sql(self, expression: exp.Block) -> str:
6475        expressions = self.expressions(expression, sep="; ", flat=True)
6476        begin = "BEGIN " if expression.args.get("begin") else ""
6477        return f"{begin}{expressions}" if expressions else ""
def functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
6479    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6480        self.unsupported("Unsupported Inline UDFs syntax")
6481        return ""
def storedprocedure_sql(self, expression: sqlglot.expressions.query.StoredProcedure) -> str:
6483    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6484        self.unsupported("Unsupported Stored Procedure syntax")
6485        return ""
def ifblock_sql(self, expression: sqlglot.expressions.query.IfBlock) -> str:
6487    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6488        self.unsupported("Unsupported If block syntax")
6489        return ""
def casestatement_sql(self, expression: sqlglot.expressions.query.CaseStatement) -> str:
6491    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6492        self.unsupported("Unsupported Case statement syntax")
6493        return ""
def whileblock_sql(self, expression: sqlglot.expressions.query.WhileBlock) -> str:
6495    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6496        self.unsupported("Unsupported While block syntax")
6497        return ""
def loopblock_sql(self, expression: sqlglot.expressions.query.LoopBlock) -> str:
6499    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6500        self.unsupported("Unsupported Loop block syntax")
6501        return ""
def repeatblock_sql(self, expression: sqlglot.expressions.query.RepeatBlock) -> str:
6503    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6504        self.unsupported("Unsupported Repeat block syntax")
6505        return ""
def leave_sql(self, expression: sqlglot.expressions.query.Leave) -> str:
6507    def leave_sql(self, expression: exp.Leave) -> str:
6508        self.unsupported("Unsupported Leave syntax")
6509        return ""
def iterate_sql(self, expression: sqlglot.expressions.query.Iterate) -> str:
6511    def iterate_sql(self, expression: exp.Iterate) -> str:
6512        self.unsupported("Unsupported Iterate syntax")
6513        return ""
def execute_sql(self, expression: sqlglot.expressions.ddl.Execute) -> str:
6515    def execute_sql(self, expression: exp.Execute) -> str:
6516        self.unsupported("Unsupported Execute syntax")
6517        return ""
def executesql_sql(self, expression: sqlglot.expressions.ddl.ExecuteSql) -> str:
6519    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6520        self.unsupported("Unsupported Execute syntax")
6521        return ""
def altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str:
6523    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6524        props = self.expressions(expression, sep=" ")
6525        return f"MODIFY {props}"
def usingproperty_sql(self, expression: sqlglot.expressions.properties.UsingProperty) -> str:
6527    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6528        kind = expression.args.get("kind")
6529        return f"USING {kind} {self.sql(expression, 'this')}"
def renameindex_sql(self, expression: sqlglot.expressions.ddl.RenameIndex) -> str:
6531    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6532        this = self.sql(expression, "this")
6533        to = self.sql(expression, "to")
6534        return f"RENAME INDEX {this} TO {to}"