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
  28ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
  29UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
  30
  31
  32def unsupported_args(
  33    *args: str | tuple[str, str],
  34) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
  35    """
  36    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
  37    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
  38    """
  39    diagnostic_by_arg: dict[str, str | None] = {}
  40    for arg in args:
  41        if isinstance(arg, str):
  42            diagnostic_by_arg[arg] = None
  43        else:
  44            diagnostic_by_arg[arg[0]] = arg[1]
  45
  46    def decorator(func: GeneratorMethod) -> GeneratorMethod:
  47        @wraps(func)
  48        def _func(generator: G, expression: E) -> str:
  49            expression_name = expression.__class__.__name__
  50            dialect_name = generator.dialect.__class__.__name__
  51
  52            for arg_name, diagnostic in diagnostic_by_arg.items():
  53                if expression.args.get(arg_name):
  54                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
  55                        arg_name, expression_name, dialect_name
  56                    )
  57                    generator.unsupported(diagnostic)
  58
  59            return func(generator, expression)
  60
  61        return _func
  62
  63    return decorator
  64
  65
  66AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = {
  67    "windows": lambda self, e: (
  68        self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
  69        if e.args.get("windows")
  70        else ""
  71    ),
  72    "qualify": lambda self, e: self.sql(e, "qualify"),
  73}
  74
  75
  76_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {}
  77
  78
  79def _build_dispatch(
  80    cls: type[Generator],
  81) -> dict[type[exp.Expr], t.Callable[..., str]]:
  82    dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS)
  83
  84    for attr_name in dir(cls):
  85        if not attr_name.endswith("_sql") or attr_name.startswith("_"):
  86            continue
  87
  88        expr_key = attr_name[:-4]
  89        expr_cls = exp.EXPR_CLASSES.get(expr_key)
  90
  91        if expr_cls and expr_cls not in dispatch:
  92            dispatch[expr_cls] = getattr(cls, attr_name)
  93
  94    return dispatch
  95
  96
  97class Generator:
  98    """
  99    Generator converts a given syntax tree to the corresponding SQL string.
 100
 101    Args:
 102        pretty: Whether to format the produced SQL string.
 103            Default: False.
 104        identify: Determines when an identifier should be quoted. Possible values are:
 105            False (default): Never quote, except in cases where it's mandatory by the dialect.
 106            True: Always quote except for specials cases.
 107            'safe': Only quote identifiers that are case insensitive.
 108        normalize: Whether to normalize identifiers to lowercase.
 109            Default: False.
 110        pad: The pad size in a formatted string. For example, this affects the indentation of
 111            a projection in a query, relative to its nesting level.
 112            Default: 2.
 113        indent: The indentation size in a formatted string. For example, this affects the
 114            indentation of subqueries and filters under a `WHERE` clause.
 115            Default: 2.
 116        normalize_functions: How to normalize function names. Possible values are:
 117            "upper" or True (default): Convert names to uppercase.
 118            "lower": Convert names to lowercase.
 119            False: Disables function name normalization.
 120        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 121            Default ErrorLevel.WARN.
 122        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 123            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 124            Default: 3
 125        leading_comma: Whether the comma is leading or trailing in select expressions.
 126            This is only relevant when generating in pretty mode.
 127            Default: False
 128        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 129            The default is on the smaller end because the length only represents a segment and not the true
 130            line length.
 131            Default: 80
 132        comments: Whether to preserve comments in the output SQL code.
 133            Default: True
 134    """
 135
 136    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 137        **JSON_PATH_PART_TRANSFORMS,
 138        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 139        exp.AllowedValuesProperty: lambda self, e: (
 140            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 141        ),
 142        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 143        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 144        exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
 145        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 146        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 147        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 148        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 149        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 150        exp.BinaryColumnConstraint: lambda *_: "BINARY",
 151        exp.CaseSpecificColumnConstraint: lambda _, e: (
 152            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 153        ),
 154        exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
 155        exp.Ceil: lambda self, e: self.ceil_floor(e),
 156        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 157        exp.CharacterSetProperty: lambda self, e: (
 158            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 159        ),
 160        exp.ClusteredColumnConstraint: lambda self, e: (
 161            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 162        ),
 163        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 164        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 165        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 166        exp.ConvertToCharset: lambda self, e: self.func(
 167            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 168        ),
 169        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 170        exp.CredentialsProperty: lambda self, e: (
 171            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 172        ),
 173        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 174        exp.SessionUser: lambda *_: "SESSION_USER",
 175        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 176        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 177        exp.ApiProperty: lambda *_: "API",
 178        exp.ApplicationProperty: lambda *_: "APPLICATION",
 179        exp.CatalogProperty: lambda *_: "CATALOG",
 180        exp.ComputeProperty: lambda *_: "COMPUTE",
 181        exp.DatabaseProperty: lambda *_: "DATABASE",
 182        exp.DynamicProperty: lambda *_: "DYNAMIC",
 183        exp.EmptyProperty: lambda *_: "EMPTY",
 184        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 185        exp.EndStatement: lambda *_: "END",
 186        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 187        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 188        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 189        exp.EphemeralColumnConstraint: lambda self, e: (
 190            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 191        ),
 192        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 193        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 194        exp.Except: lambda self, e: self.set_operations(e),
 195        exp.ExternalProperty: lambda *_: "EXTERNAL",
 196        exp.Floor: lambda self, e: self.ceil_floor(e),
 197        exp.Get: lambda self, e: self.get_put_sql(e),
 198        exp.GlobalProperty: lambda *_: "GLOBAL",
 199        exp.HeapProperty: lambda *_: "HEAP",
 200        exp.HybridProperty: lambda *_: "HYBRID",
 201        exp.IcebergProperty: lambda *_: "ICEBERG",
 202        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 203        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 204        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 205        exp.Intersect: lambda self, e: self.set_operations(e),
 206        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 207        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 208        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 209        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 210        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 211        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 212        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 213        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 214        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 215        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 216        exp.LocationProperty: lambda self, e: self.naked_property(e),
 217        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 218        exp.MaskingProperty: lambda *_: "MASKING",
 219        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 220        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 221        exp.NetworkProperty: lambda *_: "NETWORK",
 222        exp.NonClusteredColumnConstraint: lambda self, e: (
 223            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 224        ),
 225        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 226        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 227        exp.OnCommitProperty: lambda _, e: (
 228            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 229        ),
 230        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 231        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 232        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 233        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 234        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 235        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 236        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 237        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 238        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 239        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 240        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 241        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 242            f"PROJECTION POLICY {self.sql(e, 'this')}"
 243        ),
 244        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 245        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 246        exp.Put: lambda self, e: self.get_put_sql(e),
 247        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 248            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 249        ),
 250        exp.ReturnsProperty: lambda self, e: (
 251            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 252        ),
 253        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 254        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 255        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 256        exp.SecureProperty: lambda *_: "SECURE",
 257        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 258        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 259        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 260        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 261        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 262        exp.SqlReadWriteProperty: lambda _, e: e.name,
 263        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 264        exp.StabilityProperty: lambda _, e: e.name,
 265        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 266        exp.StreamingTableProperty: lambda *_: "STREAMING",
 267        exp.StrictProperty: lambda *_: "STRICT",
 268        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 269        exp.TableColumn: lambda self, e: self.sql(e.this),
 270        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 271        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 272        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 273        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 274        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 275        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 276        exp.TransientProperty: lambda *_: "TRANSIENT",
 277        exp.VirtualProperty: lambda *_: "VIRTUAL",
 278        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 279        exp.Union: lambda self, e: self.set_operations(e),
 280        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 281        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 282        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 283        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 284        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 285        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 286        exp.UtcTimestamp: lambda self, e: self.sql(
 287            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 288        ),
 289        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 290        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 291        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 292        exp.VolatileProperty: lambda *_: "VOLATILE",
 293        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 294        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 295        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 296        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 297        exp.ForceProperty: lambda *_: "FORCE",
 298    }
 299
 300    # Whether null ordering is supported in order by
 301    # True: Full Support, None: No support, False: No support for certain cases
 302    # such as window specifications, aggregate functions etc
 303    NULL_ORDERING_SUPPORTED: bool | None = True
 304
 305    # Window functions that support NULLS FIRST/LAST
 306    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 307
 308    # Whether ignore nulls is inside the agg or outside.
 309    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 310    IGNORE_NULLS_IN_FUNC = False
 311
 312    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 313    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 314    IGNORE_NULLS_BEFORE_ORDER = True
 315
 316    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 317    LOCKING_READS_SUPPORTED = False
 318
 319    # Whether the EXCEPT and INTERSECT operations can return duplicates
 320    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 321
 322    # Wrap derived values in parens, usually standard but spark doesn't support it
 323    WRAP_DERIVED_VALUES = True
 324
 325    # Whether create function uses an AS before the RETURN
 326    CREATE_FUNCTION_RETURN_AS = True
 327
 328    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 329    MATCHED_BY_SOURCE = True
 330
 331    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 332    SUPPORTS_MERGE_WHERE = False
 333
 334    # Whether the INTERVAL expression works only with values like '1 day'
 335    SINGLE_STRING_INTERVAL = False
 336
 337    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 338    INTERVAL_ALLOWS_PLURAL_FORM = True
 339
 340    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 341    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 342    AUTO_REFRESH_BARE_INTERVALS = False
 343
 344    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 345    LIMIT_FETCH = "ALL"
 346
 347    # Whether limit and fetch allows expresions or just limits
 348    LIMIT_ONLY_LITERALS = False
 349
 350    # Whether a table is allowed to be renamed with a db
 351    RENAME_TABLE_WITH_DB = True
 352
 353    # The separator for grouping sets and rollups
 354    GROUPINGS_SEP = ","
 355
 356    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 357    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 358
 359    # The string used for creating an index on a table
 360    INDEX_ON = "ON"
 361
 362    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 363    INOUT_SEPARATOR = " "
 364
 365    # Whether join hints should be generated
 366    JOIN_HINTS = True
 367
 368    # Whether directed joins are supported
 369    DIRECTED_JOINS = False
 370
 371    # Whether table hints should be generated
 372    TABLE_HINTS = True
 373
 374    # Whether query hints should be generated
 375    QUERY_HINTS = True
 376
 377    # What kind of separator to use for query hints
 378    QUERY_HINT_SEP = ", "
 379
 380    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 381    IS_BOOL_ALLOWED = True
 382
 383    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 384    DUPLICATE_KEY_UPDATE_WITH_SET = True
 385
 386    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 387    LIMIT_IS_TOP = False
 388
 389    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 390    RETURNING_END = True
 391
 392    # Whether to generate an unquoted value for EXTRACT's date part argument
 393    EXTRACT_ALLOWS_QUOTES = True
 394
 395    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 396    TZ_TO_WITH_TIME_ZONE = False
 397
 398    # Whether the NVL2 function is supported
 399    NVL2_SUPPORTED = True
 400
 401    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 402    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 403
 404    # Whether VALUES statements can be used as derived tables.
 405    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 406    # SELECT * VALUES into SELECT UNION
 407    VALUES_AS_TABLE = True
 408
 409    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 410    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 411
 412    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 413    UNNEST_WITH_ORDINALITY = True
 414
 415    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 416    SEMI_ANTI_JOIN_WITH_SIDE = True
 417
 418    # Whether to include the type of a computed column in the CREATE DDL
 419    COMPUTED_COLUMN_WITH_TYPE = True
 420
 421    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 422    SUPPORTS_TABLE_COPY = True
 423
 424    # Whether parentheses are required around the table sample's expression
 425    TABLESAMPLE_REQUIRES_PARENS = True
 426
 427    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 428    TABLESAMPLE_SIZE_IS_ROWS = True
 429
 430    # The keyword(s) to use when generating a sample clause
 431    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 432
 433    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 434    TABLESAMPLE_WITH_METHOD = True
 435
 436    # The keyword to use when specifying the seed of a sample clause
 437    TABLESAMPLE_SEED_KEYWORD = "SEED"
 438
 439    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 440    HISTORICAL_DATA_POST_ALIAS = False
 441
 442    # Whether COLLATE is a function instead of a binary operator
 443    COLLATE_IS_FUNC = False
 444
 445    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 446    DATA_TYPE_SPECIFIERS_ALLOWED = False
 447
 448    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 449    ENSURE_BOOLS = False
 450
 451    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 452    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 453
 454    # Whether CONCAT requires >1 arguments
 455    SUPPORTS_SINGLE_ARG_CONCAT = True
 456
 457    # Whether LAST_DAY function supports a date part argument
 458    LAST_DAY_SUPPORTS_DATE_PART = True
 459
 460    # Whether named columns are allowed in table aliases
 461    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 462
 463    # Whether named columns are allowed in CTE definitions
 464    SUPPORTS_NAMED_CTE_COLUMNS = True
 465
 466    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 467    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 468
 469    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 470    PIVOT_ALIAS_WITH_AS = True
 471
 472    # What delimiter to use for separating JSON key/value pairs
 473    JSON_KEY_VALUE_PAIR_SEP = ":"
 474
 475    # INSERT OVERWRITE TABLE x override
 476    INSERT_OVERWRITE = " OVERWRITE TABLE"
 477
 478    # Whether the SELECT .. INTO syntax is used instead of CTAS
 479    SUPPORTS_SELECT_INTO = False
 480
 481    # Whether UNLOGGED tables can be created
 482    SUPPORTS_UNLOGGED_TABLES = False
 483
 484    # Whether the CREATE TABLE LIKE statement is supported
 485    SUPPORTS_CREATE_TABLE_LIKE = True
 486
 487    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 488    SUPPORTS_MODIFY_COLUMN = False
 489
 490    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 491    SUPPORTS_CHANGE_COLUMN = False
 492
 493    # Whether ALTER COLUMN can set a column's nullability together with its type
 494    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 495
 496    # Whether ALTER COLUMN IF EXISTS is supported
 497    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 498
 499    # Whether the LikeProperty needs to be specified inside of the schema clause
 500    LIKE_PROPERTY_INSIDE_SCHEMA = False
 501
 502    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 503    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 504    MULTI_ARG_DISTINCT = True
 505
 506    # Whether the JSON extraction operators expect a value of type JSON
 507    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 508
 509    # Whether bracketed keys like ["foo"] are supported in JSON paths
 510    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 511
 512    # Whether to escape keys using single quotes in JSON paths
 513    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 514
 515    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 516    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 517    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 518    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 519    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 520
 521    # The JSONPathPart expressions supported by this dialect
 522    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 523
 524    # Whether any(f(x) for x in array) can be implemented by this dialect
 525    CAN_IMPLEMENT_ARRAY_ANY = False
 526
 527    # Whether the function TO_NUMBER is supported
 528    SUPPORTS_TO_NUMBER = True
 529
 530    # Whether EXCLUDE in window specification is supported
 531    SUPPORTS_WINDOW_EXCLUDE = False
 532
 533    # Whether or not set op modifiers apply to the outer set op or select.
 534    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 535    # True means limit 1 happens after the set op, False means it it happens on y.
 536    SET_OP_MODIFIERS = True
 537
 538    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 539    SET_OP_LIMITS = False
 540
 541    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 542    SET_OP_PARENTHESIZED_OPERANDS = True
 543
 544    # Whether parameters from COPY statement are wrapped in parentheses
 545    COPY_PARAMS_ARE_WRAPPED = True
 546
 547    # Whether values of params are set with "=" token or empty space
 548    COPY_PARAMS_EQ_REQUIRED = False
 549
 550    # Whether COPY statement has INTO keyword
 551    COPY_HAS_INTO_KEYWORD = True
 552
 553    # Whether the conditional TRY(expression) function is supported
 554    TRY_SUPPORTED = True
 555
 556    # Whether the UESCAPE syntax in unicode strings is supported
 557    SUPPORTS_UESCAPE = True
 558
 559    # Function used to replace escaped unicode codes in unicode strings
 560    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 561
 562    # The keyword to use when generating a star projection with excluded columns
 563    STAR_EXCEPT = "EXCEPT"
 564
 565    # The HEX function name
 566    HEX_FUNC = "HEX"
 567
 568    # The keywords to use when prefixing & separating WITH based properties
 569    WITH_PROPERTIES_PREFIX = "WITH"
 570
 571    # Whether to quote the generated expression of exp.JsonPath
 572    QUOTE_JSON_PATH = True
 573
 574    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 575    PAD_FILL_PATTERN_IS_REQUIRED = False
 576
 577    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 578    SUPPORTS_EXPLODING_PROJECTIONS = True
 579
 580    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 581    ARRAY_CONCAT_IS_VAR_LEN = True
 582
 583    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 584    SUPPORTS_CONVERT_TIMEZONE = False
 585
 586    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 587    SUPPORTS_MEDIAN = True
 588
 589    # Whether UNIX_SECONDS(timestamp) is supported
 590    SUPPORTS_UNIX_SECONDS = False
 591
 592    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 593    ALTER_SET_WRAPPED = False
 594
 595    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 596    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 597    # TODO: The normalization should be done by default once we've tested it across all dialects.
 598    NORMALIZE_EXTRACT_DATE_PARTS = False
 599
 600    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 601    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 602
 603    # The function name of the exp.ArraySize expression
 604    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 605
 606    # The syntax to use when altering the type of a column
 607    ALTER_SET_TYPE = "SET DATA TYPE"
 608
 609    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 610    # None -> Doesn't support it at all
 611    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 612    # True (Postgres) -> Explicitly requires it
 613    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 614
 615    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 616    SUPPORTS_DECODE_CASE = True
 617
 618    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 619    SUPPORTS_BETWEEN_FLAGS = False
 620
 621    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 622    SUPPORTS_LIKE_QUANTIFIERS = True
 623
 624    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 625    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 626
 627    # Whether to include the VARIABLE keyword for SET assignments
 628    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 629
 630    # The keyword to use for default value assignment in DECLARE statements
 631    DECLARE_DEFAULT_ASSIGNMENT = "="
 632
 633    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 634    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 635    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 636    UPDATE_STATEMENT_SUPPORTS_FROM = True
 637
 638    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 639    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 640
 641    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 642    # - Snowflake: DROP ICEBERG TABLE a.b;
 643    # - DuckDB:    DROP TABLE a.b;
 644    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 645
 646    TYPE_MAPPING: t.ClassVar = {
 647        exp.DType.DATETIME2: "TIMESTAMP",
 648        exp.DType.NCHAR: "CHAR",
 649        exp.DType.NVARCHAR: "VARCHAR",
 650        exp.DType.MEDIUMTEXT: "TEXT",
 651        exp.DType.LONGTEXT: "TEXT",
 652        exp.DType.TINYTEXT: "TEXT",
 653        exp.DType.BLOB: "VARBINARY",
 654        exp.DType.MEDIUMBLOB: "BLOB",
 655        exp.DType.LONGBLOB: "BLOB",
 656        exp.DType.TINYBLOB: "BLOB",
 657        exp.DType.INET: "INET",
 658        exp.DType.ROWVERSION: "VARBINARY",
 659        exp.DType.SMALLDATETIME: "TIMESTAMP",
 660    }
 661
 662    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 663
 664    # mapping of DType to its default parameters, bounds
 665    TYPE_PARAM_SETTINGS: t.ClassVar[
 666        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 667    ] = {}
 668
 669    TIME_PART_SINGULARS: t.ClassVar = {
 670        "MICROSECONDS": "MICROSECOND",
 671        "SECONDS": "SECOND",
 672        "MINUTES": "MINUTE",
 673        "HOURS": "HOUR",
 674        "DAYS": "DAY",
 675        "WEEKS": "WEEK",
 676        "MONTHS": "MONTH",
 677        "QUARTERS": "QUARTER",
 678        "YEARS": "YEAR",
 679    }
 680
 681    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 682        "cluster": lambda self, e: self.sql(e, "cluster"),
 683        "distribute": lambda self, e: self.sql(e, "distribute"),
 684        "sort": lambda self, e: self.sql(e, "sort"),
 685        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 686    }
 687
 688    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 689
 690    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 691
 692    PARAMETER_TOKEN = "@"
 693    NAMED_PLACEHOLDER_TOKEN = ":"
 694
 695    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 696
 697    PROPERTIES_LOCATION: t.ClassVar = {
 698        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 699        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 700        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 701        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 702        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 703        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 704        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 705        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 706        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 707        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 708        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 709        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 710        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 711        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 712        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 713        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 714        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 718        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 719        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 720        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 722        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 723        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 724        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 725        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 729        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 731        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 732        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 735        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 736        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 737        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 738        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 739        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 740        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 741        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 743        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 746        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 747        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 748        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 752        exp.LogProperty: exp.Properties.Location.POST_NAME,
 753        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 754        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 755        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 756        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 758        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 759        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 760        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 761        exp.Order: exp.Properties.Location.POST_SCHEMA,
 762        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 764        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 765        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 766        exp.Property: exp.Properties.Location.POST_WITH,
 767        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 770        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 771        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 772        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 773        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 776        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 777        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 778        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 779        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 780        exp.Set: exp.Properties.Location.POST_SCHEMA,
 781        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 782        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 783        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 784        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 785        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 786        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 787        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 788        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 789        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 790        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 791        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 792        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 793        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 794        exp.Tags: exp.Properties.Location.POST_WITH,
 795        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 796        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 797        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 798        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 799        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 800        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 801        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 802        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 803        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 804        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 805        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 806        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 807        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 808        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 809        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 810        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 811        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 812    }
 813
 814    # Keywords that can't be used as unquoted identifier names
 815    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 816
 817    # Exprs whose comments are separated from them for better formatting
 818    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 819        exp.Command,
 820        exp.Create,
 821        exp.Describe,
 822        exp.Delete,
 823        exp.Drop,
 824        exp.From,
 825        exp.Insert,
 826        exp.Join,
 827        exp.MultitableInserts,
 828        exp.Order,
 829        exp.Group,
 830        exp.Having,
 831        exp.Select,
 832        exp.SetOperation,
 833        exp.Update,
 834        exp.Where,
 835        exp.With,
 836    )
 837
 838    # Exprs that should not have their comments generated in maybe_comment
 839    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 840        exp.Binary,
 841        exp.SetOperation,
 842    )
 843
 844    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 845    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 846        exp.Column,
 847        exp.Literal,
 848        exp.Neg,
 849        exp.Paren,
 850    )
 851
 852    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 853        exp.DType.NVARCHAR,
 854        exp.DType.VARCHAR,
 855        exp.DType.CHAR,
 856        exp.DType.NCHAR,
 857    }
 858
 859    # Exprs that need to have all CTEs under them bubbled up to them
 860    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 861
 862    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 863
 864    MOD_OPERATOR = "%"
 865
 866    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 867    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 868        exp.Mul,
 869        exp.Div,
 870        exp.IntDiv,
 871        exp.Mod,
 872    )
 873
 874    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 875
 876    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 877
 878    __slots__ = (
 879        "pretty",
 880        "identify",
 881        "normalize",
 882        "pad",
 883        "_indent",
 884        "normalize_functions",
 885        "unsupported_level",
 886        "max_unsupported",
 887        "leading_comma",
 888        "max_text_width",
 889        "comments",
 890        "dialect",
 891        "unsupported_messages",
 892        "_escaped_quote_end",
 893        "_escaped_byte_quote_end",
 894        "_escaped_identifier_end",
 895        "_next_name",
 896        "_identifier_start",
 897        "_identifier_end",
 898        "_quote_json_path_key_using_brackets",
 899        "_dispatch",
 900    )
 901
 902    def __init__(
 903        self,
 904        pretty: bool | int | None = None,
 905        identify: str | bool = False,
 906        normalize: bool = False,
 907        pad: int = 2,
 908        indent: int = 2,
 909        normalize_functions: str | bool | None = None,
 910        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 911        max_unsupported: int = 3,
 912        leading_comma: bool = False,
 913        max_text_width: int = 80,
 914        comments: bool = True,
 915        dialect: DialectType = None,
 916    ):
 917        import sqlglot
 918        import sqlglot.dialects.dialect
 919
 920        self.pretty = pretty if pretty is not None else sqlglot.pretty
 921        self.identify = identify
 922        self.normalize = normalize
 923        self.pad = pad
 924        self._indent = indent
 925        self.unsupported_level = unsupported_level
 926        self.max_unsupported = max_unsupported
 927        self.leading_comma = leading_comma
 928        self.max_text_width = max_text_width
 929        self.comments = comments
 930        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 931
 932        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 933        self.normalize_functions = (
 934            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 935        )
 936
 937        self.unsupported_messages: list[str] = []
 938        self._escaped_quote_end: str = (
 939            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 940        )
 941        self._escaped_byte_quote_end: str = (
 942            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 943            if self.dialect.BYTE_END
 944            else ""
 945        )
 946        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 947
 948        self._next_name = name_sequence("_t")
 949
 950        self._identifier_start = self.dialect.IDENTIFIER_START
 951        self._identifier_end = self.dialect.IDENTIFIER_END
 952
 953        self._quote_json_path_key_using_brackets = True
 954
 955        cls = type(self)
 956        dispatch = _DISPATCH_CACHE.get(cls)
 957        if dispatch is None:
 958            dispatch = _build_dispatch(cls)
 959            _DISPATCH_CACHE[cls] = dispatch
 960        self._dispatch = dispatch
 961
 962    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 963        """
 964        Generates the SQL string corresponding to the given syntax tree.
 965
 966        Args:
 967            expression: The syntax tree.
 968            copy: Whether to copy the expression. The generator performs mutations so
 969                it is safer to copy.
 970
 971        Returns:
 972            The SQL string corresponding to `expression`.
 973        """
 974        if copy:
 975            expression = expression.copy()
 976
 977        expression = self.preprocess(expression)
 978
 979        self.unsupported_messages = []
 980        sql = self.sql(expression).strip()
 981
 982        if self.pretty:
 983            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 984
 985        if self.unsupported_level == ErrorLevel.IGNORE:
 986            return sql
 987
 988        if self.unsupported_level == ErrorLevel.WARN:
 989            for msg in self.unsupported_messages:
 990                logger.warning(msg)
 991        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 992            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 993
 994        return sql
 995
 996    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 997        """Apply generic preprocessing transformations to a given expression."""
 998        expression = self._move_ctes_to_top_level(expression)
 999
1000        if self.ENSURE_BOOLS:
1001            import sqlglot.transforms
1002
1003            expression = sqlglot.transforms.ensure_bools(expression)
1004
1005        return expression
1006
1007    def _move_ctes_to_top_level(self, expression: E) -> E:
1008        if (
1009            not expression.parent
1010            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1011            and any(node.parent is not expression for node in expression.find_all(exp.With))
1012        ):
1013            import sqlglot.transforms
1014
1015            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1016        return expression
1017
1018    def unsupported(self, message: str) -> None:
1019        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1020            raise UnsupportedError(message)
1021        self.unsupported_messages.append(message)
1022
1023    def sep(self, sep: str = " ") -> str:
1024        return f"{sep.strip()}\n" if self.pretty else sep
1025
1026    def seg(self, sql: str, sep: str = " ") -> str:
1027        return f"{self.sep(sep)}{sql}"
1028
1029    def sanitize_comment(self, comment: str) -> str:
1030        comment = " " + comment if comment[0].strip() else comment
1031        comment = comment + " " if comment[-1].strip() else comment
1032
1033        # Escape block comment markers to prevent premature closure or unintended nesting.
1034        # This is necessary because single-line comments (--) are converted to block comments
1035        # (/* */) on output, and any */ in the original text would close the comment early.
1036        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1037
1038        return comment
1039
1040    def maybe_comment(
1041        self,
1042        sql: str,
1043        expression: exp.Expr | None = None,
1044        comments: list[str] | None = None,
1045        separated: bool = False,
1046    ) -> str:
1047        comments = (
1048            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1049            if self.comments
1050            else None
1051        )
1052
1053        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1054            return sql
1055
1056        comments_list = [
1057            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1058            for comment in comments
1059            if comment
1060        ]
1061
1062        if not comments_list:
1063            return sql
1064
1065        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1066            comments_sql = self.sep().join(comments_list)
1067            return (
1068                f"{self.sep()}{comments_sql}{sql}"
1069                if not sql or sql[0].isspace()
1070                else f"{comments_sql}{self.sep()}{sql}"
1071            )
1072
1073        return f"{sql} {' '.join(comments_list)}"
1074
1075    def wrap(self, expression: exp.Expr | str) -> str:
1076        this_sql = (
1077            self.sql(expression)
1078            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1079            else self.sql(expression, "this")
1080        )
1081        if not this_sql:
1082            return "()"
1083
1084        this_sql = self.indent(this_sql, level=1, pad=0)
1085        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1086
1087    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1088        original = self.identify
1089        self.identify = False
1090        result = func(*args, **kwargs)
1091        self.identify = original
1092        return result
1093
1094    def normalize_func(self, name: str) -> str:
1095        if self.normalize_functions == "upper" or self.normalize_functions is True:
1096            return name.upper()
1097        if self.normalize_functions == "lower":
1098            return name.lower()
1099        return name
1100
1101    def indent(
1102        self,
1103        sql: str,
1104        level: int = 0,
1105        pad: int | None = None,
1106        skip_first: bool = False,
1107        skip_last: bool = False,
1108    ) -> str:
1109        if not self.pretty or not sql:
1110            return sql
1111
1112        pad = self.pad if pad is None else pad
1113        lines = sql.split("\n")
1114
1115        return "\n".join(
1116            (
1117                line
1118                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1119                else f"{' ' * (level * self._indent + pad)}{line}"
1120            )
1121            for i, line in enumerate(lines)
1122        )
1123
1124    def sql(
1125        self,
1126        expression: str | exp.Expr | None,
1127        key: str | None = None,
1128        comment: bool = True,
1129    ) -> str:
1130        if not expression:
1131            return ""
1132
1133        if isinstance(expression, str):
1134            return expression
1135
1136        if key:
1137            value = expression.args.get(key)
1138            if value:
1139                return self.sql(value)
1140            return ""
1141
1142        handler = self._dispatch.get(expression.__class__)
1143
1144        if handler:
1145            sql = handler(self, expression)
1146        elif isinstance(expression, exp.Func):
1147            sql = self.function_fallback_sql(expression)
1148        elif isinstance(expression, exp.Property):
1149            sql = self.property_sql(expression)
1150        else:
1151            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1152
1153        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1154
1155    def uncache_sql(self, expression: exp.Uncache) -> str:
1156        table = self.sql(expression, "this")
1157        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1158        return f"UNCACHE TABLE{exists_sql} {table}"
1159
1160    def cache_sql(self, expression: exp.Cache) -> str:
1161        lazy = " LAZY" if expression.args.get("lazy") else ""
1162        table = self.sql(expression, "this")
1163        options = expression.args.get("options")
1164        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1165        sql = self.sql(expression, "expression")
1166        sql = f" AS{self.sep()}{sql}" if sql else ""
1167        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1168        return self.prepend_ctes(expression, sql)
1169
1170    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1171        default = "DEFAULT " if expression.args.get("default") else ""
1172        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1173
1174    def column_parts(self, expression: exp.Column) -> str:
1175        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1176            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1177            return self.sql(expression, "this")
1178
1179        return ".".join(
1180            self.sql(part)
1181            for part in (
1182                expression.args.get("catalog"),
1183                expression.args.get("db"),
1184                expression.args.get("table"),
1185                expression.args.get("this"),
1186            )
1187            if part
1188        )
1189
1190    def column_sql(self, expression: exp.Column) -> str:
1191        join_mark = " (+)" if expression.args.get("join_mark") else ""
1192
1193        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1194            join_mark = ""
1195            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1196
1197        return f"{self.column_parts(expression)}{join_mark}"
1198
1199    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1200        return self.column_sql(expression)
1201
1202    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1203        this = self.sql(expression, "this")
1204        this = f" {this}" if this else ""
1205        position = self.sql(expression, "position")
1206        return f"{position}{this}"
1207
1208    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1209        column = self.sql(expression, "this")
1210        kind = self.sql(expression, "kind")
1211        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1212        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1213        kind = f"{sep}{kind}" if kind else ""
1214        constraints = f" {constraints}" if constraints else ""
1215        position = self.sql(expression, "position")
1216        position = f" {position}" if position else ""
1217
1218        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1219            kind = ""
1220
1221        return f"{exists}{column}{kind}{constraints}{position}"
1222
1223    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1224        this = self.sql(expression, "this")
1225        kind_sql = self.sql(expression, "kind").strip()
1226        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1227
1228    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1229        this = self.sql(expression, "this")
1230        if expression.args.get("not_null"):
1231            persisted = " PERSISTED NOT NULL"
1232        elif expression.args.get("persisted"):
1233            persisted = " PERSISTED"
1234        else:
1235            persisted = ""
1236
1237        return f"AS {this}{persisted}"
1238
1239    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1240        return self.token_sql(TokenType.AUTO_INCREMENT)
1241
1242    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1243        if isinstance(expression.this, list):
1244            this = self.wrap(self.expressions(expression, key="this", flat=True))
1245        else:
1246            this = self.sql(expression, "this")
1247
1248        return f"COMPRESS {this}"
1249
1250    def generatedasidentitycolumnconstraint_sql(
1251        self, expression: exp.GeneratedAsIdentityColumnConstraint
1252    ) -> str:
1253        this = ""
1254        if expression.this is not None:
1255            on_null = " ON NULL" if expression.args.get("on_null") else ""
1256            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1257
1258        start = expression.args.get("start")
1259        start = f"START WITH {start}" if start else ""
1260        increment = expression.args.get("increment")
1261        increment = f" INCREMENT BY {increment}" if increment else ""
1262        minvalue = expression.args.get("minvalue")
1263        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1264        maxvalue = expression.args.get("maxvalue")
1265        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1266        cycle = expression.args.get("cycle")
1267        cycle_sql = ""
1268
1269        if cycle is not None:
1270            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1271            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1272
1273        sequence_opts = ""
1274        if start or increment or cycle_sql:
1275            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1276            sequence_opts = f" ({sequence_opts.strip()})"
1277
1278        expr = self.sql(expression, "expression")
1279        expr = f"({expr})" if expr else "IDENTITY"
1280
1281        return f"GENERATED{this} AS {expr}{sequence_opts}"
1282
1283    def generatedasrowcolumnconstraint_sql(
1284        self, expression: exp.GeneratedAsRowColumnConstraint
1285    ) -> str:
1286        start = "START" if expression.args.get("start") else "END"
1287        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1288        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1289
1290    def periodforsystemtimeconstraint_sql(
1291        self, expression: exp.PeriodForSystemTimeConstraint
1292    ) -> str:
1293        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1294
1295    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1296        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1297
1298    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1299        desc = expression.args.get("desc")
1300        if desc is not None:
1301            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1302        options = self.expressions(expression, key="options", flat=True, sep=" ")
1303        options = f" {options}" if options else ""
1304        return f"PRIMARY KEY{options}"
1305
1306    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1307        this = self.sql(expression, "this")
1308        this = f" {this}" if this else ""
1309        index_type = expression.args.get("index_type")
1310        index_type = f" USING {index_type}" if index_type else ""
1311        on_conflict = self.sql(expression, "on_conflict")
1312        on_conflict = f" {on_conflict}" if on_conflict else ""
1313        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1314        options = self.expressions(expression, key="options", flat=True, sep=" ")
1315        options = f" {options}" if options else ""
1316        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1317
1318    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1319        input_ = expression.args.get("input_")
1320        output = expression.args.get("output")
1321        variadic = expression.args.get("variadic")
1322
1323        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1324        if variadic:
1325            return "VARIADIC"
1326
1327        if input_ and output:
1328            return f"IN{self.INOUT_SEPARATOR}OUT"
1329        if input_:
1330            return "IN"
1331        if output:
1332            return "OUT"
1333
1334        return ""
1335
1336    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1337        return self.sql(expression, "this")
1338
1339    def create_sql(self, expression: exp.Create) -> str:
1340        kind = self.sql(expression, "kind")
1341        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1342
1343        properties = expression.args.get("properties")
1344
1345        if (
1346            kind == "TRIGGER"
1347            and properties
1348            and properties.expressions
1349            and isinstance(properties.expressions[0], exp.TriggerProperties)
1350            and properties.expressions[0].args.get("constraint")
1351        ):
1352            kind = f"CONSTRAINT {kind}"
1353
1354        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1355
1356        this = self.createable_sql(expression, properties_locs)
1357
1358        properties_sql = ""
1359        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1360            exp.Properties.Location.POST_WITH
1361        ):
1362            props_ast = exp.Properties(
1363                expressions=[
1364                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1365                    *properties_locs[exp.Properties.Location.POST_WITH],
1366                ]
1367            )
1368            props_ast.parent = expression
1369            properties_sql = self.sql(props_ast)
1370
1371            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1372                properties_sql = self.sep() + properties_sql
1373            elif not self.pretty:
1374                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1375                properties_sql = f" {properties_sql}"
1376
1377        begin = " BEGIN" if expression.args.get("begin") else ""
1378
1379        expression_sql = self.sql(expression, "expression")
1380        if expression_sql:
1381            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1382
1383            if not isinstance(expression.expression, exp.MacroOverloads) and (
1384                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1385            ):
1386                postalias_props_sql = ""
1387                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1388                    postalias_props_sql = self.properties(
1389                        exp.Properties(
1390                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1391                        ),
1392                        wrapped=False,
1393                    )
1394                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1395                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1396
1397        postindex_props_sql = ""
1398        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1399            postindex_props_sql = self.properties(
1400                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1401                wrapped=False,
1402                prefix=" ",
1403            )
1404
1405        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1406        indexes = f" {indexes}" if indexes else ""
1407        index_sql = indexes + postindex_props_sql
1408
1409        replace = " OR REPLACE" if expression.args.get("replace") else ""
1410        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1411        unique = " UNIQUE" if expression.args.get("unique") else ""
1412
1413        clustered = expression.args.get("clustered")
1414        if clustered is None:
1415            clustered_sql = ""
1416        elif clustered:
1417            clustered_sql = " CLUSTERED COLUMNSTORE"
1418        else:
1419            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1420
1421        postcreate_props_sql = ""
1422        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1423            postcreate_props_sql = self.properties(
1424                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1425                sep=" ",
1426                prefix=" ",
1427                wrapped=False,
1428            )
1429
1430        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1431
1432        postexpression_props_sql = ""
1433        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1434            postexpression_props_sql = self.properties(
1435                exp.Properties(
1436                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1437                ),
1438                sep=" ",
1439                prefix=" ",
1440                wrapped=False,
1441            )
1442
1443        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1444        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1445        no_schema_binding = (
1446            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1447        )
1448
1449        clone = self.sql(expression, "clone")
1450        clone = f" {clone}" if clone else ""
1451
1452        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1453            properties_expression = f"{expression_sql}{properties_sql}"
1454        else:
1455            properties_expression = f"{properties_sql}{expression_sql}"
1456
1457        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1458        return self.prepend_ctes(expression, expression_sql)
1459
1460    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1461        start = self.sql(expression, "start")
1462        start = f"START WITH {start}" if start else ""
1463        increment = self.sql(expression, "increment")
1464        increment = f" INCREMENT BY {increment}" if increment else ""
1465        minvalue = self.sql(expression, "minvalue")
1466        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1467        maxvalue = self.sql(expression, "maxvalue")
1468        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1469        owned = self.sql(expression, "owned")
1470        owned = f" OWNED BY {owned}" if owned else ""
1471
1472        cache = expression.args.get("cache")
1473        if cache is None:
1474            cache_str = ""
1475        elif cache is True:
1476            cache_str = " CACHE"
1477        else:
1478            cache_str = f" CACHE {cache}"
1479
1480        options = self.expressions(expression, key="options", flat=True, sep=" ")
1481        options = f" {options}" if options else ""
1482
1483        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1484
1485    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1486        timing = expression.args.get("timing", "")
1487        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1488        timing_events = f"{timing} {events}".strip() if timing or events else ""
1489
1490        parts = [timing_events, "ON", self.sql(expression, "table")]
1491
1492        if referenced_table := expression.args.get("referenced_table"):
1493            parts.extend(["FROM", self.sql(referenced_table)])
1494
1495        if deferrable := expression.args.get("deferrable"):
1496            parts.append(deferrable)
1497
1498        if initially := expression.args.get("initially"):
1499            parts.append(f"INITIALLY {initially}")
1500
1501        if referencing := expression.args.get("referencing"):
1502            parts.append(self.sql(referencing))
1503
1504        if for_each := expression.args.get("for_each"):
1505            parts.append(f"FOR EACH {for_each}")
1506
1507        if when := expression.args.get("when"):
1508            parts.append(f"WHEN ({self.sql(when)})")
1509
1510        parts.append(self.sql(expression, "execute"))
1511
1512        return self.sep().join(parts)
1513
1514    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1515        parts = []
1516
1517        if old_alias := expression.args.get("old"):
1518            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1519
1520        if new_alias := expression.args.get("new"):
1521            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1522
1523        return f"REFERENCING {' '.join(parts)}"
1524
1525    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1526        columns = expression.args.get("columns")
1527        if columns:
1528            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1529
1530        return self.sql(expression, "this")
1531
1532    def clone_sql(self, expression: exp.Clone) -> str:
1533        this = self.sql(expression, "this")
1534        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1535        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1536        return f"{shallow}{keyword} {this}"
1537
1538    def describe_sql(self, expression: exp.Describe) -> str:
1539        style = expression.args.get("style")
1540        style = f" {style}" if style else ""
1541        partition = self.sql(expression, "partition")
1542        partition = f" {partition}" if partition else ""
1543        format = self.sql(expression, "format")
1544        format = f" {format}" if format else ""
1545        as_json = " AS JSON" if expression.args.get("as_json") else ""
1546
1547        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1548
1549    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1550        tag = self.sql(expression, "tag")
1551        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1552
1553    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1554        with_ = self.sql(expression, "with_")
1555        if with_:
1556            sql = f"{with_}{self.sep()}{sql}"
1557        return sql
1558
1559    def with_sql(self, expression: exp.With) -> str:
1560        udfs = self.expressions(expression, key="udfs", flat=True)
1561        udfs = f"WITH {udfs}" if udfs else ""
1562
1563        sql = self.expressions(expression, flat=True)
1564
1565        recursive = (
1566            "RECURSIVE "
1567            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1568            else ""
1569        )
1570        search = self.sql(expression, "search")
1571        search = f" {search}" if search else ""
1572
1573        sql = f"WITH {recursive}{sql}{search}" if sql else ""
1574        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1575
1576    def cte_sql(self, expression: exp.CTE) -> str:
1577        alias = expression.args.get("alias")
1578        if alias:
1579            alias.add_comments(expression.pop_comments())
1580
1581        alias_sql = self.sql(expression, "alias")
1582
1583        materialized = expression.args.get("materialized")
1584        if materialized is False:
1585            materialized = "NOT MATERIALIZED "
1586        elif materialized:
1587            materialized = "MATERIALIZED "
1588
1589        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1590        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1591
1592        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1593
1594    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1595        alias = self.sql(expression, "this")
1596        columns = self.expressions(expression, key="columns", flat=True)
1597        columns = f"({columns})" if columns else ""
1598
1599        if (
1600            columns
1601            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1602            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1603        ):
1604            columns = ""
1605            self.unsupported("Named columns are not supported in table alias.")
1606
1607        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1608            alias = self._next_name()
1609
1610        return f"{alias}{columns}"
1611
1612    def bitstring_sql(self, expression: exp.BitString) -> str:
1613        this = self.sql(expression, "this")
1614        if self.dialect.BIT_START:
1615            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1616        return f"{int(this, 2)}"
1617
1618    def hexstring_sql(
1619        self, expression: exp.HexString, binary_function_repr: str | None = None
1620    ) -> str:
1621        this = self.sql(expression, "this")
1622        is_integer_type = expression.args.get("is_integer")
1623
1624        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1625            not self.dialect.HEX_START and not binary_function_repr
1626        ):
1627            # Integer representation will be returned if:
1628            # - The read dialect treats the hex value as integer literal but not the write
1629            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1630            return f"{int(this, 16)}"
1631
1632        if not is_integer_type:
1633            # Read dialect treats the hex value as BINARY/BLOB
1634            if binary_function_repr:
1635                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1636                return self.func(binary_function_repr, exp.Literal.string(this))
1637            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1638                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1639                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1640
1641        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1642
1643    def bytestring_sql(self, expression: exp.ByteString) -> str:
1644        this = self.sql(expression, "this")
1645        if self.dialect.BYTE_START:
1646            escaped_byte_string = self.escape_str(
1647                this,
1648                escape_backslash=False,
1649                delimiter=self.dialect.BYTE_END,
1650                escaped_delimiter=self._escaped_byte_quote_end,
1651                is_byte_string=True,
1652            )
1653            is_bytes = expression.args.get("is_bytes", False)
1654            delimited_byte_string = (
1655                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1656            )
1657            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1658                return self.sql(
1659                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1660                )
1661            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1662                return self.sql(
1663                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1664                )
1665
1666            return delimited_byte_string
1667
1668        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1669            return self.sql(exp.Literal.string(this))
1670
1671        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1672        return ""
1673
1674    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1675        this = self.sql(expression, "this")
1676        escape = expression.args.get("escape")
1677        unicode_start = self.dialect.UNICODE_START
1678
1679        if unicode_start:
1680            escape_substitute = r"\\\1"
1681            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1682        else:
1683            escape_substitute = r"\\u\1"
1684            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1685
1686        if escape:
1687            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1688            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1689        else:
1690            escape_pattern = ESCAPED_UNICODE_RE
1691            escape_sql = ""
1692
1693        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1694            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1695
1696        if unicode_start:
1697            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1698            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1699            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1700        else:
1701            this = self.escape_str(this, escape_backslash=False)
1702
1703        return f"{left_quote}{this}{right_quote}{escape_sql}"
1704
1705    def rawstring_sql(self, expression: exp.RawString) -> str:
1706        string = expression.this
1707        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1708            string = string.replace("\\", "\\\\")
1709
1710        string = self.escape_str(string, escape_backslash=False)
1711        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1712
1713    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1714        this = self.sql(expression, "this")
1715        specifier = self.sql(expression, "expression")
1716        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1717        return f"{this}{specifier}"
1718
1719    def datatype_param_bound_limiter(
1720        self,
1721        expression: exp.DataType,
1722        type_value: exp.DType,
1723        defaults: tuple[int, ...],
1724        bounds: tuple[int | None, ...],
1725    ) -> exp.DataType:
1726        params = expression.expressions
1727
1728        if not params:
1729            if defaults:
1730                expression.set(
1731                    "expressions",
1732                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1733                )
1734            return expression
1735
1736        if not bounds:
1737            return expression
1738
1739        for i, param in enumerate(params):
1740            bound = bounds[i] if i < len(bounds) else None
1741            if bound is None:
1742                continue
1743
1744            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1745            value = (
1746                param_value.to_py()
1747                if isinstance(param_value, exp.Literal) and param_value.is_number
1748                else None
1749            )
1750            if isinstance(value, (int, Decimal)) and value > bound:
1751                self.unsupported(
1752                    f"{type_value.value} parameter {param_value.name} exceeds "
1753                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1754                )
1755                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1756
1757        return expression
1758
1759    def datatype_sql(self, expression: exp.DataType) -> str:
1760        nested = ""
1761        values = ""
1762
1763        expr_nested = expression.args.get("nested")
1764        type_value = expression.this
1765
1766        if (
1767            not expr_nested
1768            and isinstance(type_value, exp.DType)
1769            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1770        ):
1771            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1772
1773        interior = (
1774            self.expressions(
1775                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1776            )
1777            if expr_nested and self.pretty
1778            else self.expressions(expression, flat=True)
1779        )
1780
1781        if type_value in self.UNSUPPORTED_TYPES:
1782            self.unsupported(
1783                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1784            )
1785
1786        type_sql: t.Any = ""
1787        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1788            type_sql = self.sql(expression, "kind")
1789        elif type_value == exp.DType.CHARACTER_SET:
1790            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1791        else:
1792            type_sql = (
1793                self.TYPE_MAPPING.get(type_value, type_value.value)
1794                if isinstance(type_value, exp.DType)
1795                else type_value
1796            )
1797
1798        if interior:
1799            if expr_nested:
1800                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1801                if expression.args.get("values") is not None:
1802                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1803                    values = self.expressions(expression, key="values", flat=True)
1804                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1805            elif type_value == exp.DType.INTERVAL:
1806                nested = f" {interior}"
1807            else:
1808                nested = f"({interior})"
1809
1810        type_sql = f"{type_sql}{nested}{values}"
1811        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1812            exp.DType.TIMETZ,
1813            exp.DType.TIMESTAMPTZ,
1814        ):
1815            type_sql = f"{type_sql} WITH TIME ZONE"
1816
1817        collate = self.sql(expression, "collate")
1818        if collate:
1819            type_sql = f"{type_sql} COLLATE {collate}"
1820
1821        return type_sql
1822
1823    def directory_sql(self, expression: exp.Directory) -> str:
1824        local = "LOCAL " if expression.args.get("local") else ""
1825        row_format = self.sql(expression, "row_format")
1826        row_format = f" {row_format}" if row_format else ""
1827        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1828
1829    def delete_sql(self, expression: exp.Delete) -> str:
1830        hint = self.sql(expression, "hint")
1831        this = self.sql(expression, "this")
1832        this = f" FROM {this}" if this else ""
1833        using = self.expressions(expression, key="using")
1834        using = f" USING {using}" if using else ""
1835        cluster = self.sql(expression, "cluster")
1836        cluster = f" {cluster}" if cluster else ""
1837        where = self.sql(expression, "where")
1838        returning = self.sql(expression, "returning")
1839        order = self.sql(expression, "order")
1840        limit = self.sql(expression, "limit")
1841        tables = self.expressions(expression, key="tables")
1842        tables = f" {tables}" if tables else ""
1843        if self.RETURNING_END:
1844            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1845        else:
1846            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1847        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1848
1849    def drop_sql(self, expression: exp.Drop) -> str:
1850        tables = self.expressions(expression, key="tables", flat=True)
1851        expressions = self.expressions(expression, flat=True)
1852        expressions = f" ({expressions})" if expressions else ""
1853        kind = expression.args["kind"]
1854        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1855        iceberg = (
1856            " ICEBERG"
1857            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1858            else ""
1859        )
1860        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1861        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1862        on_cluster = self.sql(expression, "cluster")
1863        on_cluster = f" {on_cluster}" if on_cluster else ""
1864        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1865        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1866        cascade = " CASCADE" if expression.args.get("cascade") else ""
1867        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1868        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1869        purge = " PURGE" if expression.args.get("purge") else ""
1870        sync = " SYNC" if expression.args.get("sync") else ""
1871        force = " FORCE" if expression.args.get("force") else ""
1872        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1873
1874    def set_operation(self, expression: exp.SetOperation) -> str:
1875        op_type = type(expression)
1876        op_name = op_type.key.upper()
1877
1878        distinct = expression.args.get("distinct")
1879        if (
1880            distinct is False
1881            and op_type in (exp.Except, exp.Intersect)
1882            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1883        ):
1884            self.unsupported(f"{op_name} ALL is not supported")
1885
1886        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1887
1888        if distinct is None:
1889            distinct = default_distinct
1890            if distinct is None:
1891                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1892
1893        if distinct is default_distinct:
1894            distinct_or_all = ""
1895        else:
1896            distinct_or_all = " DISTINCT" if distinct else " ALL"
1897
1898        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1899        side_kind = f"{side_kind} " if side_kind else ""
1900
1901        by_name = " BY NAME" if expression.args.get("by_name") else ""
1902        on = self.expressions(expression, key="on", flat=True)
1903        on = f" ON ({on})" if on else ""
1904
1905        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1906
1907    def set_operations(self, expression: exp.SetOperation) -> str:
1908        if not self.SET_OP_MODIFIERS:
1909            limit = expression.args.get("limit")
1910            order = expression.args.get("order")
1911            offset = expression.args.get("offset")
1912
1913            if limit or order or offset:
1914                select = self._move_ctes_to_top_level(
1915                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1916                )
1917
1918                for arg in ("limit", "order", "offset"):
1919                    if value := expression.args.get(arg):
1920                        select.set(arg, value.pop())
1921                return self.sql(select)
1922
1923        sqls: list[str] = []
1924        stack: list[str | exp.Expr] = [expression]
1925
1926        while stack:
1927            node = stack.pop()
1928
1929            if isinstance(node, exp.SetOperation):
1930                stack.append(node.expression)
1931                stack.append(
1932                    self.maybe_comment(
1933                        self.set_operation(node), comments=node.comments, separated=True
1934                    )
1935                )
1936                stack.append(node.this)
1937            else:
1938                if (
1939                    not self.SET_OP_LIMITS
1940                    and isinstance(node, exp.Select)
1941                    and node.args.get("limit")
1942                ):
1943                    node = node.subquery(copy=False)
1944                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1945                        node = exp.select("*").from_(node, copy=False)
1946                sqls.append(self.sql(node))
1947
1948        this = self.sep().join(sqls)
1949        this = self.query_modifiers(expression, this)
1950        return self.prepend_ctes(expression, this)
1951
1952    def fetch_sql(self, expression: exp.Fetch) -> str:
1953        direction = expression.args.get("direction")
1954        direction = f" {direction}" if direction else ""
1955        count = self.sql(expression, "count")
1956        count = f" {count}" if count else ""
1957        limit_options = self.sql(expression, "limit_options")
1958        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1959        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1960
1961    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1962        percent = " PERCENT" if expression.args.get("percent") else ""
1963        rows = " ROWS" if expression.args.get("rows") else ""
1964        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1965        if not with_ties and rows:
1966            with_ties = " ONLY"
1967        return f"{percent}{rows}{with_ties}"
1968
1969    def filter_sql(self, expression: exp.Filter) -> str:
1970        this = self.sql(expression, "this")
1971        where = self.sql(expression, "expression").strip()
1972        return f"{this} FILTER({where})"
1973
1974    def hint_sql(self, expression: exp.Hint) -> str:
1975        if not self.QUERY_HINTS:
1976            self.unsupported("Hints are not supported")
1977            return ""
1978
1979        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1980
1981    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1982        using = self.sql(expression, "using")
1983        using = f" USING {using}" if using else ""
1984        columns = self.expressions(expression, key="columns", flat=True)
1985        columns = f"({columns})" if columns else ""
1986        partition_by = self.expressions(expression, key="partition_by", flat=True)
1987        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1988        where = self.sql(expression, "where")
1989        include = self.expressions(expression, key="include", flat=True)
1990        if include:
1991            include = f" INCLUDE ({include})"
1992        with_storage = self.expressions(expression, key="with_storage", flat=True)
1993        with_storage = f" WITH ({with_storage})" if with_storage else ""
1994        tablespace = self.sql(expression, "tablespace")
1995        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1996        on = self.sql(expression, "on")
1997        on = f" ON {on}" if on else ""
1998
1999        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2000
2001    def index_sql(self, expression: exp.Index) -> str:
2002        unique = "UNIQUE " if expression.args.get("unique") else ""
2003        primary = "PRIMARY " if expression.args.get("primary") else ""
2004        amp = "AMP " if expression.args.get("amp") else ""
2005        name = self.sql(expression, "this")
2006        name = f"{name} " if name else ""
2007        table = self.sql(expression, "table")
2008        table = f"{self.INDEX_ON} {table}" if table else ""
2009
2010        index = "INDEX " if not table else ""
2011
2012        params = self.sql(expression, "params")
2013        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2014
2015    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2016        this = expression.this
2017        if this and this.is_string:
2018            resolved = maybe_parse(this.name).sql(self.dialect)
2019            if "expressions" in expression.args:
2020                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2021                # We can't safely emit the call to other dialects since name/arg semantics may differ
2022                self.unsupported(
2023                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2024                )
2025            return resolved
2026        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2027        return self.func("IDENTIFIER", this)
2028
2029    def identifier_sql(self, expression: exp.Identifier) -> str:
2030        text = expression.name
2031        lower = text.lower()
2032        quoted = expression.quoted
2033        text = lower if self.normalize and not quoted else text
2034        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2035        if (
2036            quoted
2037            or self.dialect.can_quote(expression, self.identify)
2038            or lower in self.RESERVED_KEYWORDS
2039            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2040        ):
2041            text = (
2042                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2043            )
2044        return text
2045
2046    def hex_sql(self, expression: exp.Hex) -> str:
2047        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2048        if self.dialect.HEX_LOWERCASE:
2049            text = self.func("LOWER", text)
2050
2051        return text
2052
2053    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2054        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2055        if not self.dialect.HEX_LOWERCASE:
2056            text = self.func("LOWER", text)
2057        return text
2058
2059    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2060        input_format = self.sql(expression, "input_format")
2061        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2062        output_format = self.sql(expression, "output_format")
2063        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2064        return self.sep().join((input_format, output_format))
2065
2066    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2067        string = self.sql(exp.Literal.string(expression.name))
2068        return f"{prefix}{string}"
2069
2070    def partition_sql(self, expression: exp.Partition) -> str:
2071        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2072        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2073
2074    def properties_sql(self, expression: exp.Properties) -> str:
2075        root_properties = []
2076        with_properties = []
2077
2078        for p in expression.expressions:
2079            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2080            if p_loc == exp.Properties.Location.POST_WITH:
2081                with_properties.append(p)
2082            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2083                root_properties.append(p)
2084
2085        root_props_ast = exp.Properties(expressions=root_properties)
2086        root_props_ast.parent = expression.parent
2087
2088        with_props_ast = exp.Properties(expressions=with_properties)
2089        with_props_ast.parent = expression.parent
2090
2091        root_props = self.root_properties(root_props_ast)
2092        with_props = self.with_properties(with_props_ast)
2093
2094        if root_props and with_props and not self.pretty:
2095            with_props = " " + with_props
2096
2097        return root_props + with_props
2098
2099    def root_properties(self, properties: exp.Properties) -> str:
2100        if properties.expressions:
2101            return self.expressions(properties, indent=False, sep=" ")
2102        return ""
2103
2104    def properties(
2105        self,
2106        properties: exp.Properties,
2107        prefix: str = "",
2108        sep: str = ", ",
2109        suffix: str = "",
2110        wrapped: bool = True,
2111    ) -> str:
2112        if properties.expressions:
2113            expressions = self.expressions(properties, sep=sep, indent=False)
2114            if expressions:
2115                expressions = self.wrap(expressions) if wrapped else expressions
2116                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2117        return ""
2118
2119    def with_properties(self, properties: exp.Properties) -> str:
2120        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2121
2122    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2123        properties_locs = defaultdict(list)
2124        for p in properties.expressions:
2125            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2126            if p_loc != exp.Properties.Location.UNSUPPORTED:
2127                properties_locs[p_loc].append(p)
2128            else:
2129                self.unsupported(f"Unsupported property {p.key}")
2130
2131        return properties_locs
2132
2133    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2134        if isinstance(expression.this, exp.Dot):
2135            return self.sql(expression, "this")
2136        return f"'{expression.name}'" if string_key else expression.name
2137
2138    def property_sql(self, expression: exp.Property) -> str:
2139        property_cls = expression.__class__
2140        if property_cls == exp.Property:
2141            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2142
2143        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2144        if not property_name:
2145            self.unsupported(f"Unsupported property {expression.key}")
2146
2147        return f"{property_name}={self.sql(expression, 'this')}"
2148
2149    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2150        return f"UUID {self.sql(expression, 'this')}"
2151
2152    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2153        if self.SUPPORTS_CREATE_TABLE_LIKE:
2154            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2155            options = f" {options}" if options else ""
2156
2157            like = f"LIKE {self.sql(expression, 'this')}{options}"
2158            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2159                like = f"({like})"
2160
2161            return like
2162
2163        if expression.expressions:
2164            self.unsupported("Transpilation of LIKE property options is unsupported")
2165
2166        select = exp.select("*").from_(expression.this).limit(0)
2167        return f"AS {self.sql(select)}"
2168
2169    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2170        no = "NO " if expression.args.get("no") else ""
2171        protection = " PROTECTION" if expression.args.get("protection") else ""
2172        return f"{no}FALLBACK{protection}"
2173
2174    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2175        no = "NO " if expression.args.get("no") else ""
2176        local = expression.args.get("local")
2177        local = f"{local} " if local else ""
2178        dual = "DUAL " if expression.args.get("dual") else ""
2179        before = "BEFORE " if expression.args.get("before") else ""
2180        after = "AFTER " if expression.args.get("after") else ""
2181        return f"{no}{local}{dual}{before}{after}JOURNAL"
2182
2183    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2184        freespace = self.sql(expression, "this")
2185        percent = " PERCENT" if expression.args.get("percent") else ""
2186        return f"FREESPACE={freespace}{percent}"
2187
2188    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2189        if expression.args.get("default"):
2190            property = "DEFAULT"
2191        elif expression.args.get("on"):
2192            property = "ON"
2193        else:
2194            property = "OFF"
2195        return f"CHECKSUM={property}"
2196
2197    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2198        if expression.args.get("no"):
2199            return "NO MERGEBLOCKRATIO"
2200        if expression.args.get("default"):
2201            return "DEFAULT MERGEBLOCKRATIO"
2202
2203        percent = " PERCENT" if expression.args.get("percent") else ""
2204        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2205
2206    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2207        expressions = self.expressions(expression, flat=True)
2208        expressions = f"({expressions})" if expressions else ""
2209        return f"USING {self.sql(expression, 'this')}{expressions}"
2210
2211    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2212        default = expression.args.get("default")
2213        minimum = expression.args.get("minimum")
2214        maximum = expression.args.get("maximum")
2215        if default or minimum or maximum:
2216            if default:
2217                prop = "DEFAULT"
2218            elif minimum:
2219                prop = "MINIMUM"
2220            else:
2221                prop = "MAXIMUM"
2222            return f"{prop} DATABLOCKSIZE"
2223        units = expression.args.get("units")
2224        units = f" {units}" if units else ""
2225        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2226
2227    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2228        autotemp = expression.args.get("autotemp")
2229        always = expression.args.get("always")
2230        default = expression.args.get("default")
2231        manual = expression.args.get("manual")
2232        never = expression.args.get("never")
2233
2234        if autotemp is not None:
2235            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2236        elif always:
2237            prop = "ALWAYS"
2238        elif default:
2239            prop = "DEFAULT"
2240        elif manual:
2241            prop = "MANUAL"
2242        elif never:
2243            prop = "NEVER"
2244        return f"BLOCKCOMPRESSION={prop}"
2245
2246    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2247        no = expression.args.get("no")
2248        no = " NO" if no else ""
2249        concurrent = expression.args.get("concurrent")
2250        concurrent = " CONCURRENT" if concurrent else ""
2251        target = self.sql(expression, "target")
2252        target = f" {target}" if target else ""
2253        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2254
2255    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2256        if isinstance(expression.this, list):
2257            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2258        if expression.this:
2259            modulus = self.sql(expression, "this")
2260            remainder = self.sql(expression, "expression")
2261            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2262
2263        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2264        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2265        return f"FROM ({from_expressions}) TO ({to_expressions})"
2266
2267    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2268        this = self.sql(expression, "this")
2269
2270        for_values_or_default = expression.expression
2271        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2272            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2273        else:
2274            for_values_or_default = " DEFAULT"
2275
2276        return f"PARTITION OF {this}{for_values_or_default}"
2277
2278    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2279        kind = expression.args.get("kind")
2280        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2281        for_or_in = expression.args.get("for_or_in")
2282        for_or_in = f" {for_or_in}" if for_or_in else ""
2283        lock_type = expression.args.get("lock_type")
2284        override = " OVERRIDE" if expression.args.get("override") else ""
2285        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2286
2287    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2288        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2289        statistics = expression.args.get("statistics")
2290        statistics_sql = ""
2291        if statistics is not None:
2292            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2293        return f"{data_sql}{statistics_sql}"
2294
2295    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2296        this = self.sql(expression, "this")
2297        this = f"HISTORY_TABLE={this}" if this else ""
2298        data_consistency: str | None = self.sql(expression, "data_consistency")
2299        data_consistency = (
2300            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2301        )
2302        retention_period: str | None = self.sql(expression, "retention_period")
2303        retention_period = (
2304            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2305        )
2306
2307        if this:
2308            on_sql = self.func("ON", this, data_consistency, retention_period)
2309        else:
2310            on_sql = "ON" if expression.args.get("on") else "OFF"
2311
2312        sql = f"SYSTEM_VERSIONING={on_sql}"
2313
2314        return f"WITH({sql})" if expression.args.get("with_") else sql
2315
2316    def insert_sql(self, expression: exp.Insert) -> str:
2317        hint = self.sql(expression, "hint")
2318        overwrite = expression.args.get("overwrite")
2319
2320        if isinstance(expression.this, exp.Directory):
2321            this = " OVERWRITE" if overwrite else " INTO"
2322        else:
2323            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2324
2325        stored = self.sql(expression, "stored")
2326        stored = f" {stored}" if stored else ""
2327        alternative = expression.args.get("alternative")
2328        alternative = f" OR {alternative}" if alternative else ""
2329        ignore = " IGNORE" if expression.args.get("ignore") else ""
2330        is_function = expression.args.get("is_function")
2331        if is_function:
2332            this = f"{this} FUNCTION"
2333        this = f"{this} {self.sql(expression, 'this')}"
2334
2335        exists = " IF EXISTS" if expression.args.get("exists") else ""
2336        where = self.sql(expression, "where")
2337        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2338        using = self.expressions(expression, key="using", flat=True)
2339        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2340        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2341        on_conflict = self.sql(expression, "conflict")
2342        on_conflict = f" {on_conflict}" if on_conflict else ""
2343        by_name = " BY NAME" if expression.args.get("by_name") else ""
2344        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2345        returning = self.sql(expression, "returning")
2346
2347        if self.RETURNING_END:
2348            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2349        else:
2350            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2351
2352        partition_by = self.sql(expression, "partition")
2353        partition_by = f" {partition_by}" if partition_by else ""
2354        settings = self.sql(expression, "settings")
2355        settings = f" {settings}" if settings else ""
2356
2357        source = self.sql(expression, "source")
2358        source = f"TABLE {source}" if source else ""
2359
2360        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2361        return self.prepend_ctes(expression, sql)
2362
2363    def introducer_sql(self, expression: exp.Introducer) -> str:
2364        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2365
2366    def kill_sql(self, expression: exp.Kill) -> str:
2367        kind = self.sql(expression, "kind")
2368        kind = f" {kind}" if kind else ""
2369        this = self.sql(expression, "this")
2370        this = f" {this}" if this else ""
2371        return f"KILL{kind}{this}"
2372
2373    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2374        return expression.name
2375
2376    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2377        return expression.name
2378
2379    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2380        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2381
2382        constraint = self.sql(expression, "constraint")
2383        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2384
2385        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2386        if conflict_keys:
2387            conflict_keys = f"({conflict_keys})"
2388
2389        index_predicate = self.sql(expression, "index_predicate")
2390        conflict_keys = f"{conflict_keys}{index_predicate} "
2391
2392        action = self.sql(expression, "action")
2393
2394        expressions = self.expressions(expression, flat=True)
2395        if expressions:
2396            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2397            expressions = f" {set_keyword}{expressions}"
2398
2399        where = self.sql(expression, "where")
2400        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2401
2402    def returning_sql(self, expression: exp.Returning) -> str:
2403        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2404
2405    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2406        fields = self.sql(expression, "fields")
2407        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2408        escaped = self.sql(expression, "escaped")
2409        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2410        items = self.sql(expression, "collection_items")
2411        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2412        keys = self.sql(expression, "map_keys")
2413        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2414        lines = self.sql(expression, "lines")
2415        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2416        null = self.sql(expression, "null")
2417        null = f" NULL DEFINED AS {null}" if null else ""
2418        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2419
2420    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2421        return f"WITH ({self.expressions(expression, flat=True)})"
2422
2423    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2424        this = f"{self.sql(expression, 'this')} INDEX"
2425        target = self.sql(expression, "target")
2426        target = f" FOR {target}" if target else ""
2427        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2428
2429    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2430        this = self.sql(expression, "this")
2431        kind = self.sql(expression, "kind")
2432        expr = self.sql(expression, "expression")
2433        return f"{this} ({kind} => {expr})"
2434
2435    def table_parts(self, expression: exp.Table) -> str:
2436        return ".".join(
2437            self.sql(part)
2438            for part in (
2439                expression.args.get("catalog"),
2440                expression.args.get("db"),
2441                expression.args.get("this"),
2442            )
2443            if part is not None
2444        )
2445
2446    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2447        table = self.table_parts(expression)
2448        only = "ONLY " if expression.args.get("only") else ""
2449        partition = self.sql(expression, "partition")
2450        partition = f" {partition}" if partition else ""
2451        version = self.sql(expression, "version")
2452        version = f" {version}" if version else ""
2453        alias = self.sql(expression, "alias")
2454        alias = f"{sep}{alias}" if alias else ""
2455
2456        sample = self.sql(expression, "sample")
2457        post_alias = ""
2458        pre_alias = ""
2459
2460        if self.dialect.ALIAS_POST_TABLESAMPLE:
2461            pre_alias = sample
2462        else:
2463            post_alias = sample
2464
2465        if self.dialect.ALIAS_POST_VERSION:
2466            pre_alias = f"{pre_alias}{version}"
2467        else:
2468            post_alias = f"{post_alias}{version}"
2469
2470        hints = self.expressions(expression, key="hints", sep=" ")
2471        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2472        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2473        joins = self.indent(
2474            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2475        )
2476        laterals = self.expressions(expression, key="laterals", sep="")
2477
2478        file_format = self.sql(expression, "format")
2479        pattern = self.sql(expression, "pattern")
2480        if file_format:
2481            pattern = f", PATTERN => {pattern}" if pattern else ""
2482            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2483        elif pattern:
2484            file_format = f" (PATTERN => {pattern})"
2485
2486        ordinality = expression.args.get("ordinality") or ""
2487        if ordinality:
2488            ordinality = f" WITH ORDINALITY{alias}"
2489            alias = ""
2490
2491        when = self.sql(expression, "when")
2492        if when:
2493            if self.HISTORICAL_DATA_POST_ALIAS:
2494                alias = f"{alias} {when}"
2495            else:
2496                table = f"{table} {when}"
2497
2498        changes = self.sql(expression, "changes")
2499        changes = f" {changes}" if changes else ""
2500
2501        rows_from = self.expressions(expression, key="rows_from")
2502        if rows_from:
2503            table = f"ROWS FROM {self.wrap(rows_from)}"
2504
2505        indexed = expression.args.get("indexed")
2506        if indexed is not None:
2507            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2508        else:
2509            indexed = ""
2510
2511        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2512
2513    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2514        table = self.func("TABLE", expression.this)
2515        alias = self.sql(expression, "alias")
2516        alias = f" AS {alias}" if alias else ""
2517        sample = self.sql(expression, "sample")
2518        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2519        joins = self.indent(
2520            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2521        )
2522        return f"{table}{alias}{pivots}{sample}{joins}"
2523
2524    def tablesample_sql(
2525        self,
2526        expression: exp.TableSample,
2527        tablesample_keyword: str | None = None,
2528    ) -> str:
2529        method = self.sql(expression, "method")
2530        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2531        numerator = self.sql(expression, "bucket_numerator")
2532        denominator = self.sql(expression, "bucket_denominator")
2533        field = self.sql(expression, "bucket_field")
2534        field = f" ON {field}" if field else ""
2535        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2536        seed = self.sql(expression, "seed")
2537        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2538
2539        size = self.sql(expression, "size")
2540        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2541            size = f"{size} ROWS"
2542
2543        percent = self.sql(expression, "percent")
2544        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2545            percent = f"{percent} PERCENT"
2546
2547        expr = f"{bucket}{percent}{size}"
2548        if self.TABLESAMPLE_REQUIRES_PARENS:
2549            expr = f"({expr})"
2550
2551        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2552
2553    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2554        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2555        # the stored column name differs from the target dialect's natural output.
2556        columns = expression.args.get("columns")
2557        if not columns or len(expression.fields) != 1:
2558            return None
2559
2560        args = expression.args
2561        parser_cls = self.dialect.parser_class
2562
2563        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2564        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2565        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2566
2567        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2568        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2569        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2570
2571        if (
2572            src_identify_pivot_strings == tgt_identify_pivot_strings
2573            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2574            and src_pivot_column_naming == tgt_pivot_column_naming
2575        ):
2576            return None
2577
2578        in_exprs = expression.fields[0].expressions
2579        step = len(columns) // len(in_exprs)
2580
2581        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2582        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2583        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2584        first_stored = columns[0].name
2585
2586        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2587        if not first_stored.startswith(first_base):
2588            return None
2589
2590        suffix = first_stored[len(first_base) :]
2591
2592        # Whether the target dialect would append an agg-name suffix for this pivot.
2593        # Spark single-agg uniquely drops the agg alias entirely.
2594        target_has_suffix = (
2595            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2596        ) and any(a.alias for a in expression.expressions)
2597        source_has_suffix = suffix != ""
2598
2599        new_exprs: list[exp.Expression] = []
2600        modified = False
2601        for val_idx, e in enumerate(in_exprs):
2602            if isinstance(e, exp.PivotAlias):
2603                new_exprs.append(e)
2604                continue
2605
2606            i = val_idx * step
2607            stored_full = columns[i].name
2608            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2609            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2610
2611            # Source had a suffix, but target won't apply one
2612            if source_has_suffix and not target_has_suffix:
2613                new_exprs.append(
2614                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2615                )
2616                modified = True
2617            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2618            elif stored_value != target_value:
2619                new_exprs.append(
2620                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2621                )
2622                modified = True
2623            else:
2624                new_exprs.append(e)
2625
2626        return new_exprs if modified else None
2627
2628    def pivot_sql(self, expression: exp.Pivot) -> str:
2629        expressions = self.expressions(expression, flat=True)
2630        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2631
2632        group = self.sql(expression, "group")
2633
2634        if expression.this:
2635            this = self.sql(expression, "this")
2636            if not expressions:
2637                sql = f"UNPIVOT {this}"
2638            else:
2639                on = f"{self.seg('ON')} {expressions}"
2640                into = self.sql(expression, "into")
2641                into = f"{self.seg('INTO')} {into}" if into else ""
2642                using = self.expressions(expression, key="using", flat=True)
2643                using = f"{self.seg('USING')} {using}" if using else ""
2644                sql = f"{direction} {this}{on}{into}{using}{group}"
2645            return self.prepend_ctes(expression, sql)
2646
2647        if not expression.unpivot:
2648            # Wrap IN-list values with explicit aliases where the target dialect would differ
2649            new_field_exprs = self._pivot_in_value_aliases(expression)
2650            if new_field_exprs is not None:
2651                expression.fields[0].set("expressions", new_field_exprs)
2652
2653        alias = self.sql(expression, "alias")
2654        if alias:
2655            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2656
2657        fields = self.expressions(
2658            expression,
2659            "fields",
2660            sep=" ",
2661            dynamic=True,
2662            new_line=True,
2663            skip_first=True,
2664            skip_last=True,
2665        )
2666
2667        include_nulls = expression.args.get("include_nulls")
2668        if include_nulls is not None:
2669            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2670        else:
2671            nulls = ""
2672
2673        default_on_null = self.sql(expression, "default_on_null")
2674        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2675        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2676        return self.prepend_ctes(expression, sql)
2677
2678    def version_sql(self, expression: exp.Version) -> str:
2679        this = f"FOR {expression.name}"
2680        kind = expression.text("kind")
2681        expr = self.sql(expression, "expression")
2682        return f"{this} {kind} {expr}"
2683
2684    def tuple_sql(self, expression: exp.Tuple) -> str:
2685        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2686
2687    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2688        """
2689        Returns (join_sql, from_sql) for UPDATE statements.
2690        - join_sql: placed after UPDATE table, before SET
2691        - from_sql: placed after SET clause (standard position)
2692        Dialects like MySQL need to convert FROM to JOIN syntax.
2693        """
2694        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2695            return ("", self.sql(expression, "from_"))
2696
2697        # Qualify unqualified columns in SET clause with the target table
2698        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2699        target_table = expression.this
2700        if isinstance(target_table, exp.Table):
2701            target_name = exp.to_identifier(target_table.alias_or_name)
2702            for eq in expression.expressions:
2703                col = eq.this
2704                if isinstance(col, exp.Column) and not col.table:
2705                    col.set("table", target_name)
2706
2707        table = from_expr.this
2708        if nested_joins := table.args.get("joins", []):
2709            table.set("joins", None)
2710
2711        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2712        for nested in nested_joins:
2713            if not nested.args.get("on") and not nested.args.get("using"):
2714                nested.set("on", exp.true())
2715            join_sql += self.sql(nested)
2716
2717        return (join_sql, "")
2718
2719    def update_sql(self, expression: exp.Update) -> str:
2720        hint = self.sql(expression, "hint")
2721        this = self.sql(expression, "this")
2722        join_sql, from_sql = self._update_from_joins_sql(expression)
2723        set_sql = self.expressions(expression, flat=True)
2724        where_sql = self.sql(expression, "where")
2725        returning = self.sql(expression, "returning")
2726        order = self.sql(expression, "order")
2727        limit = self.sql(expression, "limit")
2728        if self.RETURNING_END:
2729            expression_sql = f"{from_sql}{where_sql}{returning}"
2730        else:
2731            expression_sql = f"{returning}{from_sql}{where_sql}"
2732        options = self.expressions(expression, key="options")
2733        options = f" OPTION({options})" if options else ""
2734        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2735        return self.prepend_ctes(expression, sql)
2736
2737    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2738        values_as_table = values_as_table and self.VALUES_AS_TABLE
2739
2740        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2741        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2742            args = self.expressions(expression)
2743            alias = self.sql(expression, "alias")
2744            values = f"VALUES{self.seg('')}{args}"
2745            values = (
2746                f"({values})"
2747                if self.WRAP_DERIVED_VALUES
2748                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2749                else values
2750            )
2751            values = self.query_modifiers(expression, values)
2752            return f"{values} AS {alias}" if alias else values
2753
2754        # Converts `VALUES...` expression into a series of select unions.
2755        alias_node = expression.args.get("alias")
2756        column_names = alias_node and alias_node.columns
2757
2758        selects: list[exp.Query] = []
2759
2760        for i, tup in enumerate(expression.expressions):
2761            row = tup.expressions
2762
2763            if i == 0 and column_names:
2764                row = [
2765                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2766                ]
2767
2768            selects.append(exp.Select(expressions=row))
2769
2770        if self.pretty:
2771            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2772            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2773            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2774            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2775            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2776
2777        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2778        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2779        return f"({unions}){alias}"
2780
2781    def var_sql(self, expression: exp.Var) -> str:
2782        return self.sql(expression, "this")
2783
2784    @unsupported_args("expressions")
2785    def into_sql(self, expression: exp.Into) -> str:
2786        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2787        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2788        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2789
2790    def from_sql(self, expression: exp.From) -> str:
2791        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2792
2793    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2794        grouping_sets = self.expressions(expression, indent=False)
2795        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2796
2797    def rollup_sql(self, expression: exp.Rollup) -> str:
2798        expressions = self.expressions(expression, indent=False)
2799        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2800
2801    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2802        this = self.sql(expression, "this")
2803
2804        columns = self.expressions(expression, flat=True)
2805
2806        from_sql = self.sql(expression, "from_index")
2807        from_sql = f" FROM {from_sql}" if from_sql else ""
2808
2809        properties = expression.args.get("properties")
2810        properties_sql = (
2811            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2812        )
2813
2814        return f"{this}({columns}){from_sql}{properties_sql}"
2815
2816    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2817        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2818
2819    def cube_sql(self, expression: exp.Cube) -> str:
2820        expressions = self.expressions(expression, indent=False)
2821        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2822
2823    def group_sql(self, expression: exp.Group) -> str:
2824        group_by_all = expression.args.get("all")
2825        if group_by_all is True:
2826            modifier = " ALL"
2827        elif group_by_all is False:
2828            modifier = " DISTINCT"
2829        else:
2830            modifier = ""
2831
2832        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2833
2834        grouping_sets = self.expressions(expression, key="grouping_sets")
2835        cube = self.expressions(expression, key="cube")
2836        rollup = self.expressions(expression, key="rollup")
2837
2838        groupings = csv(
2839            self.seg(grouping_sets) if grouping_sets else "",
2840            self.seg(cube) if cube else "",
2841            self.seg(rollup) if rollup else "",
2842            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2843            sep=self.GROUPINGS_SEP,
2844        )
2845
2846        if (
2847            expression.expressions
2848            and groupings
2849            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2850        ):
2851            add_separator = True
2852
2853            if grouping_sets:
2854                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2855                    add_separator = False
2856                else:
2857                    self.unsupported(
2858                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2859                    )
2860
2861            if add_separator:
2862                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2863
2864        return f"{group_by}{groupings}"
2865
2866    def having_sql(self, expression: exp.Having) -> str:
2867        this = self.indent(self.sql(expression, "this"))
2868        return f"{self.seg('HAVING')}{self.sep()}{this}"
2869
2870    def connect_sql(self, expression: exp.Connect) -> str:
2871        start = self.sql(expression, "start")
2872        start = self.seg(f"START WITH {start}") if start else ""
2873        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2874        connect = self.sql(expression, "connect")
2875        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2876        return start + connect
2877
2878    def prior_sql(self, expression: exp.Prior) -> str:
2879        return f"PRIOR {self.sql(expression, 'this')}"
2880
2881    def join_sql(self, expression: exp.Join) -> str:
2882        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2883            side = None
2884        else:
2885            side = expression.side
2886
2887        op_sql = " ".join(
2888            op
2889            for op in (
2890                expression.method,
2891                "GLOBAL" if expression.args.get("global_") else None,
2892                side,
2893                expression.kind,
2894                expression.hint if self.JOIN_HINTS else None,
2895                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2896            )
2897            if op
2898        )
2899        match_cond = self.sql(expression, "match_condition")
2900        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2901        on_sql = self.sql(expression, "on")
2902        using = expression.args.get("using")
2903
2904        if not on_sql and using:
2905            on_sql = csv(*(self.sql(column) for column in using))
2906
2907        this = expression.this
2908        this_sql = self.sql(this)
2909
2910        exprs = self.expressions(expression)
2911        if exprs:
2912            this_sql = f"{this_sql},{self.seg(exprs)}"
2913
2914        if on_sql:
2915            on_sql = self.indent(on_sql, skip_first=True)
2916            space = self.seg(" " * self.pad) if self.pretty else " "
2917            if using:
2918                on_sql = f"{space}USING ({on_sql})"
2919            else:
2920                on_sql = f"{space}ON {on_sql}"
2921        elif not op_sql:
2922            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2923                return f" {this_sql}"
2924
2925            return f", {this_sql}"
2926
2927        if op_sql != "STRAIGHT_JOIN":
2928            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2929
2930        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2931        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2932
2933    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2934        args = self.expressions(expression, flat=True)
2935        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2936        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2937
2938    def lateral_op(self, expression: exp.Lateral) -> str:
2939        cross_apply = expression.args.get("cross_apply")
2940
2941        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2942        if cross_apply is True:
2943            op = "INNER JOIN "
2944        elif cross_apply is False:
2945            op = "LEFT JOIN "
2946        else:
2947            op = ""
2948
2949        return f"{op}LATERAL"
2950
2951    def lateral_sql(self, expression: exp.Lateral) -> str:
2952        this = self.sql(expression, "this")
2953
2954        if expression.args.get("view"):
2955            alias = expression.args["alias"]
2956            columns = self.expressions(alias, key="columns", flat=True)
2957            table = f" {alias.name}" if alias.name else ""
2958            columns = f" AS {columns}" if columns else ""
2959            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2960            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2961
2962        alias = self.sql(expression, "alias")
2963        alias = f" AS {alias}" if alias else ""
2964
2965        ordinality = expression.args.get("ordinality") or ""
2966        if ordinality:
2967            ordinality = f" WITH ORDINALITY{alias}"
2968            alias = ""
2969
2970        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2971
2972    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2973        this = self.sql(expression, "this")
2974
2975        args = [
2976            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2977            for e in (expression.args.get(k) for k in ("offset", "expression"))
2978            if e
2979        ]
2980
2981        args_sql = ", ".join(self.sql(e) for e in args)
2982        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2983        expressions = self.expressions(expression, flat=True)
2984        limit_options = self.sql(expression, "limit_options")
2985        expressions = f" BY {expressions}" if expressions else ""
2986
2987        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2988
2989    def offset_sql(self, expression: exp.Offset) -> str:
2990        this = self.sql(expression, "this")
2991        value = expression.expression
2992        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2993        expressions = self.expressions(expression, flat=True)
2994        expressions = f" BY {expressions}" if expressions else ""
2995        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2996
2997    def setitem_sql(self, expression: exp.SetItem) -> str:
2998        kind = self.sql(expression, "kind")
2999        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3000            kind = ""
3001        else:
3002            kind = f"{kind} " if kind else ""
3003        this = self.sql(expression, "this")
3004        expressions = self.expressions(expression)
3005        collate = self.sql(expression, "collate")
3006        collate = f" COLLATE {collate}" if collate else ""
3007        global_ = "GLOBAL " if expression.args.get("global_") else ""
3008        return f"{global_}{kind}{this}{expressions}{collate}"
3009
3010    def set_sql(self, expression: exp.Set) -> str:
3011        expressions = f" {self.expressions(expression, flat=True)}"
3012        tag = " TAG" if expression.args.get("tag") else ""
3013        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3014
3015    def queryband_sql(self, expression: exp.QueryBand) -> str:
3016        this = self.sql(expression, "this")
3017        update = " UPDATE" if expression.args.get("update") else ""
3018        scope = self.sql(expression, "scope")
3019        scope = f" FOR {scope}" if scope else ""
3020
3021        return f"QUERY_BAND = {this}{update}{scope}"
3022
3023    def pragma_sql(self, expression: exp.Pragma) -> str:
3024        return f"PRAGMA {self.sql(expression, 'this')}"
3025
3026    def lock_sql(self, expression: exp.Lock) -> str:
3027        if not self.LOCKING_READS_SUPPORTED:
3028            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3029            return ""
3030
3031        update = expression.args["update"]
3032        key = expression.args.get("key")
3033        if update:
3034            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3035        else:
3036            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3037        expressions = self.expressions(expression, flat=True)
3038        expressions = f" OF {expressions}" if expressions else ""
3039        wait = expression.args.get("wait")
3040
3041        if wait is not None:
3042            if isinstance(wait, exp.Literal):
3043                wait = f" WAIT {self.sql(wait)}"
3044            else:
3045                wait = " NOWAIT" if wait else " SKIP LOCKED"
3046
3047        return f"{lock_type}{expressions}{wait or ''}"
3048
3049    def literal_sql(self, expression: exp.Literal) -> str:
3050        text = expression.this or ""
3051        if expression.is_string:
3052            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3053        return text
3054
3055    def escape_str(
3056        self,
3057        text: str,
3058        escape_backslash: bool = True,
3059        delimiter: str | None = None,
3060        escaped_delimiter: str | None = None,
3061        is_byte_string: bool = False,
3062    ) -> str:
3063        if is_byte_string:
3064            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3065        else:
3066            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3067
3068        if supports_escape_sequences:
3069            text = "".join(
3070                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3071                for ch in text
3072            )
3073
3074        delimiter = delimiter or self.dialect.QUOTE_END
3075        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3076
3077        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3078
3079    def loaddata_sql(self, expression: exp.LoadData) -> str:
3080        is_overwrite = expression.args.get("overwrite")
3081        overwrite = " OVERWRITE" if is_overwrite else ""
3082        this = self.sql(expression, "this")
3083
3084        files = expression.args.get("files")
3085        if files:
3086            files_sql = self.expressions(files, flat=True)
3087            files_sql = f"FILES{self.wrap(files_sql)}"
3088            if is_overwrite:
3089                this = f" {this}"
3090            elif expression.args.get("temp"):
3091                this = f" INTO TEMP TABLE {this}"
3092            else:
3093                this = f" INTO TABLE {this}"
3094            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3095
3096        local = " LOCAL" if expression.args.get("local") else ""
3097        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3098        this = f" INTO TABLE {this}"
3099        partition = self.sql(expression, "partition")
3100        partition = f" {partition}" if partition else ""
3101        input_format = self.sql(expression, "input_format")
3102        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3103        serde = self.sql(expression, "serde")
3104        serde = f" SERDE {serde}" if serde else ""
3105        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3106
3107    def null_sql(self, *_) -> str:
3108        return "NULL"
3109
3110    def boolean_sql(self, expression: exp.Boolean) -> str:
3111        return "TRUE" if expression.this else "FALSE"
3112
3113    def booland_sql(self, expression: exp.Booland) -> str:
3114        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3115
3116    def boolor_sql(self, expression: exp.Boolor) -> str:
3117        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3118
3119    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3120        this = self.sql(expression, "this")
3121        this = f"{this} " if this else this
3122        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3123        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3124
3125    def withfill_sql(self, expression: exp.WithFill) -> str:
3126        from_sql = self.sql(expression, "from_")
3127        from_sql = f" FROM {from_sql}" if from_sql else ""
3128        to_sql = self.sql(expression, "to")
3129        to_sql = f" TO {to_sql}" if to_sql else ""
3130        step_sql = self.sql(expression, "step")
3131        step_sql = f" STEP {step_sql}" if step_sql else ""
3132        interpolated_values = [
3133            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3134            if isinstance(e, exp.Alias)
3135            else self.sql(e, "this")
3136            for e in expression.args.get("interpolate") or []
3137        ]
3138        interpolate = (
3139            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3140        )
3141        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3142
3143    def cluster_sql(self, expression: exp.Cluster) -> str:
3144        return self.op_expressions("CLUSTER BY", expression)
3145
3146    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3147        if expression.this:
3148            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3149            return ""
3150        expressions = self.expressions(expression, flat=True)
3151        return f"CLUSTER BY ({expressions})"
3152
3153    def distribute_sql(self, expression: exp.Distribute) -> str:
3154        return self.op_expressions("DISTRIBUTE BY", expression)
3155
3156    def sort_sql(self, expression: exp.Sort) -> str:
3157        return self.op_expressions("SORT BY", expression)
3158
3159    def _resolve_ordered_for_null_ordering_simulation(
3160        self, expression: exp.Ordered
3161    ) -> exp.Expr | None:
3162        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3163
3164        Returns the underlying expression of the uniquely-matching projection
3165        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3166        simulation, since the CASE is evaluated in FROM-clause scope rather
3167        than alias scope (MySQL error 1052). Returns None if no safe
3168        substitution applies, leaving the original behaviour unchanged.
3169        """
3170        this = expression.this
3171        if not (isinstance(this, exp.Column) and not this.table):
3172            return None
3173
3174        ancestor = expression.find_ancestor(exp.Select, exp.Window)
3175        if not isinstance(ancestor, exp.Select):
3176            return None
3177
3178        column_name = this.name
3179        matched: list[exp.Expr] = [
3180            p.this if isinstance(p, exp.Alias) else p
3181            for p in ancestor.selects
3182            if p.output_name == column_name
3183        ]
3184        match = matched[0] if len(matched) == 1 else None
3185
3186        # Skip the substitution when it would be identical to the existing
3187        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3188        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3189            return None
3190
3191        return match
3192
3193    def ordered_sql(self, expression: exp.Ordered) -> str:
3194        desc = expression.args.get("desc")
3195        asc = not desc
3196
3197        nulls_first = expression.args.get("nulls_first")
3198        nulls_last = not nulls_first
3199        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3200        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3201        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3202
3203        this = self.sql(expression, "this")
3204
3205        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3206        nulls_sort_change = ""
3207        if nulls_first and (
3208            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3209        ):
3210            nulls_sort_change = " NULLS FIRST"
3211        elif (
3212            nulls_last
3213            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3214            and not nulls_are_last
3215        ):
3216            nulls_sort_change = " NULLS LAST"
3217
3218        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3219        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3220            window = expression.find_ancestor(exp.Window, exp.Select)
3221
3222            if isinstance(window, exp.Window):
3223                window_this = window.this
3224                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3225                    window_this = window_this.this
3226                spec = window.args.get("spec")
3227            else:
3228                window_this = None
3229                spec = None
3230
3231            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3232            # without a spec or with a ROWS spec, but not with RANGE
3233            if not (
3234                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3235                and (not spec or spec.text("kind").upper() == "ROWS")
3236            ):
3237                if window_this and spec:
3238                    self.unsupported(
3239                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3240                    )
3241                    nulls_sort_change = ""
3242                elif self.NULL_ORDERING_SUPPORTED is False and (
3243                    (asc and nulls_sort_change == " NULLS LAST")
3244                    or (desc and nulls_sort_change == " NULLS FIRST")
3245                ):
3246                    # BigQuery does not allow these ordering/nulls combinations when used under
3247                    # an aggregation func or under a window containing one
3248                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3249
3250                    if isinstance(ancestor, exp.Window):
3251                        ancestor = ancestor.this
3252                    if isinstance(ancestor, exp.AggFunc):
3253                        self.unsupported(
3254                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3255                        )
3256                        nulls_sort_change = ""
3257                elif self.NULL_ORDERING_SUPPORTED is None:
3258                    if expression.this.is_int:
3259                        self.unsupported(
3260                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3261                        )
3262                    elif not isinstance(expression.this, exp.Rand):
3263                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3264                        target = self.sql(resolved) if resolved is not None else this
3265                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3266                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3267                    nulls_sort_change = ""
3268
3269        with_fill = self.sql(expression, "with_fill")
3270        with_fill = f" {with_fill}" if with_fill else ""
3271
3272        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3273
3274    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3275        window_frame = self.sql(expression, "window_frame")
3276        window_frame = f"{window_frame} " if window_frame else ""
3277
3278        this = self.sql(expression, "this")
3279
3280        return f"{window_frame}{this}"
3281
3282    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3283        partition = self.partition_by_sql(expression)
3284        order = self.sql(expression, "order")
3285        measures = self.expressions(expression, key="measures")
3286        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3287        rows = self.sql(expression, "rows")
3288        rows = self.seg(rows) if rows else ""
3289        after = self.sql(expression, "after")
3290        after = self.seg(after) if after else ""
3291        pattern = self.sql(expression, "pattern")
3292        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3293        definition_sqls = [
3294            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3295            for definition in expression.args.get("define", [])
3296        ]
3297        definitions = self.expressions(sqls=definition_sqls)
3298        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3299        body = "".join(
3300            (
3301                partition,
3302                order,
3303                measures,
3304                rows,
3305                after,
3306                pattern,
3307                define,
3308            )
3309        )
3310        alias = self.sql(expression, "alias")
3311        alias = f" {alias}" if alias else ""
3312        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3313
3314    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3315        limit = expression.args.get("limit")
3316
3317        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3318            count = limit.args.get("count")
3319            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3320            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3321            limit = exp.Limit(
3322                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3323            )
3324        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3325            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3326
3327        return csv(
3328            *sqls,
3329            *[self.sql(join) for join in expression.args.get("joins") or []],
3330            self.sql(expression, "match"),
3331            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3332            self.sql(expression, "prewhere"),
3333            self.sql(expression, "where"),
3334            self.sql(expression, "connect"),
3335            self.sql(expression, "group"),
3336            self.sql(expression, "having"),
3337            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3338            self.sql(expression, "order"),
3339            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3340            *self.after_limit_modifiers(expression),
3341            self.sql(expression, "for_"),
3342            self.options_modifier(expression),
3343            sep="",
3344        )
3345
3346    def options_modifier(self, expression: exp.Expr) -> str:
3347        options = self.expressions(expression, key="options")
3348        return f" {options}" if options else ""
3349
3350    def forclause_sql(self, expression: exp.ForClause) -> str:
3351        kind = expression.args["kind"]
3352        if kind == "BROWSE":
3353            return f"{self.sep()}FOR BROWSE"
3354        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3355        # the target dialect doesn't support QueryOption, so we drop the clause.
3356        options = self.expressions(expression, key="expressions")
3357        if not options:
3358            return ""
3359        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3360
3361    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3362        self.unsupported("Unsupported query option.")
3363        return ""
3364
3365    def offset_limit_modifiers(
3366        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3367    ) -> list[str]:
3368        return [
3369            self.sql(expression, "offset") if fetch else self.sql(limit),
3370            self.sql(limit) if fetch else self.sql(expression, "offset"),
3371        ]
3372
3373    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3374        locks = self.expressions(expression, key="locks", sep=" ")
3375        locks = f" {locks}" if locks else ""
3376        return [locks, self.sql(expression, "sample")]
3377
3378    def select_sql(self, expression: exp.Select) -> str:
3379        into = expression.args.get("into")
3380        if not self.SUPPORTS_SELECT_INTO and into:
3381            into.pop()
3382
3383        hint = self.sql(expression, "hint")
3384        distinct = self.sql(expression, "distinct")
3385        distinct = f" {distinct}" if distinct else ""
3386        kind = self.sql(expression, "kind")
3387
3388        limit = expression.args.get("limit")
3389        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3390            top = self.limit_sql(limit, top=True)
3391            limit.pop()
3392        else:
3393            top = ""
3394
3395        expressions = self.expressions(expression)
3396
3397        if kind:
3398            if kind in self.SELECT_KINDS:
3399                kind = f" AS {kind}"
3400            else:
3401                if kind == "STRUCT":
3402                    expressions = self.expressions(
3403                        sqls=[
3404                            self.sql(
3405                                exp.Struct(
3406                                    expressions=[
3407                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3408                                        if isinstance(e, exp.Alias)
3409                                        else e
3410                                        for e in expression.expressions
3411                                    ]
3412                                )
3413                            )
3414                        ]
3415                    )
3416                kind = ""
3417
3418        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3419        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3420
3421        exclude = expression.args.get("exclude")
3422
3423        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3424            exclude_sql = self.expressions(sqls=exclude, flat=True)
3425            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3426
3427        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3428        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3429        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3430        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3431        sql = self.query_modifiers(
3432            expression,
3433            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3434            self.sql(expression, "into", comment=False),
3435            self.sql(expression, "from_", comment=False),
3436        )
3437
3438        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3439        if expression.args.get("with_"):
3440            sql = self.maybe_comment(sql, expression)
3441            expression.pop_comments()
3442
3443        sql = self.prepend_ctes(expression, sql)
3444
3445        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3446            expression.set("exclude", None)
3447            subquery = expression.subquery(copy=False)
3448            star = exp.Star(except_=exclude)
3449            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3450
3451        if not self.SUPPORTS_SELECT_INTO and into:
3452            if into.args.get("temporary"):
3453                table_kind = " TEMPORARY"
3454            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3455                table_kind = " UNLOGGED"
3456            else:
3457                table_kind = ""
3458            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3459
3460        return sql
3461
3462    def schema_sql(self, expression: exp.Schema) -> str:
3463        this = self.sql(expression, "this")
3464        sql = self.schema_columns_sql(expression)
3465        return f"{this} {sql}" if this and sql else this or sql
3466
3467    def schema_columns_sql(self, expression: exp.Expr) -> str:
3468        if expression.expressions:
3469            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3470        return ""
3471
3472    def star_sql(self, expression: exp.Star) -> str:
3473        except_ = self.expressions(expression, key="except_", flat=True)
3474        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3475        replace = self.expressions(expression, key="replace", flat=True)
3476        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3477        rename = self.expressions(expression, key="rename", flat=True)
3478        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3479        ilike = self.sql(expression, "ilike")
3480        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3481        return f"*{ilike}{except_}{replace}{rename}"
3482
3483    def parameter_sql(self, expression: exp.Parameter) -> str:
3484        this = self.sql(expression, "this")
3485        return f"{self.PARAMETER_TOKEN}{this}"
3486
3487    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3488        this = self.sql(expression, "this")
3489        kind = expression.text("kind")
3490        if kind:
3491            kind = f"{kind}."
3492        return f"@@{kind}{this}"
3493
3494    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3495        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3496
3497    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3498        alias = self.sql(expression, "alias")
3499        alias = f"{sep}{alias}" if alias else ""
3500        sample = self.sql(expression, "sample")
3501        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3502            alias = f"{sample}{alias}"
3503
3504            # Set to None so it's not generated again by self.query_modifiers()
3505            expression.set("sample", None)
3506
3507        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3508        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3509        return self.prepend_ctes(expression, sql)
3510
3511    def qualify_sql(self, expression: exp.Qualify) -> str:
3512        this = self.indent(self.sql(expression, "this"))
3513        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3514
3515    def unnest_sql(self, expression: exp.Unnest) -> str:
3516        args = self.expressions(expression, flat=True)
3517
3518        alias = expression.args.get("alias")
3519        offset = expression.args.get("offset")
3520
3521        if self.UNNEST_WITH_ORDINALITY:
3522            if alias and isinstance(offset, exp.Expr):
3523                alias.append("columns", offset)
3524                expression.set("offset", None)
3525
3526        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3527            columns = alias.columns
3528            alias = self.sql(columns[0]) if columns else ""
3529        else:
3530            alias = self.sql(alias)
3531
3532        alias = f" AS {alias}" if alias else alias
3533        if self.UNNEST_WITH_ORDINALITY:
3534            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3535        else:
3536            if isinstance(offset, exp.Expr):
3537                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3538            elif offset:
3539                suffix = f"{alias} WITH OFFSET"
3540            else:
3541                suffix = alias
3542
3543        return f"UNNEST({args}){suffix}"
3544
3545    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3546        return ""
3547
3548    def where_sql(self, expression: exp.Where) -> str:
3549        this = self.indent(self.sql(expression, "this"))
3550        return f"{self.seg('WHERE')}{self.sep()}{this}"
3551
3552    def window_sql(self, expression: exp.Window) -> str:
3553        this = self.sql(expression, "this")
3554        partition = self.partition_by_sql(expression)
3555        order = expression.args.get("order")
3556        order = self.order_sql(order, flat=True) if order else ""
3557        spec = self.sql(expression, "spec")
3558        alias = self.sql(expression, "alias")
3559        over = self.sql(expression, "over") or "OVER"
3560
3561        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3562
3563        first = expression.args.get("first")
3564        if first is None:
3565            first = ""
3566        else:
3567            first = "FIRST" if first else "LAST"
3568
3569        if not partition and not order and not spec and alias:
3570            return f"{this} {alias}"
3571
3572        args = self.format_args(
3573            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3574        )
3575        return f"{this} ({args})"
3576
3577    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3578        partition = self.expressions(expression, key="partition_by", flat=True)
3579        return f"PARTITION BY {partition}" if partition else ""
3580
3581    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3582        kind = self.sql(expression, "kind")
3583        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3584        end = (
3585            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3586            or "CURRENT ROW"
3587        )
3588
3589        window_spec = f"{kind} BETWEEN {start} AND {end}"
3590
3591        exclude = self.sql(expression, "exclude")
3592        if exclude:
3593            if self.SUPPORTS_WINDOW_EXCLUDE:
3594                window_spec += f" EXCLUDE {exclude}"
3595            else:
3596                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3597
3598        return window_spec
3599
3600    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3601        this = self.sql(expression, "this")
3602        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3603        return f"{this} WITHIN GROUP ({expression_sql})"
3604
3605    def between_sql(self, expression: exp.Between) -> str:
3606        this = self.sql(expression, "this")
3607        low = self.sql(expression, "low")
3608        high = self.sql(expression, "high")
3609        symmetric = expression.args.get("symmetric")
3610
3611        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3612            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3613
3614        flag = (
3615            " SYMMETRIC"
3616            if symmetric
3617            else " ASYMMETRIC"
3618            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3619            else ""  # silently drop ASYMMETRIC – semantics identical
3620        )
3621        return f"{this} BETWEEN{flag} {low} AND {high}"
3622
3623    def bracket_offset_expressions(
3624        self, expression: exp.Bracket, index_offset: int | None = None
3625    ) -> list[exp.Expr]:
3626        if expression.args.get("json_access"):
3627            return expression.expressions
3628
3629        return apply_index_offset(
3630            expression.this,
3631            expression.expressions,
3632            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3633            dialect=self.dialect,
3634        )
3635
3636    def bracket_sql(self, expression: exp.Bracket) -> str:
3637        expressions = self.bracket_offset_expressions(expression)
3638        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3639        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3640
3641    def all_sql(self, expression: exp.All) -> str:
3642        this = self.sql(expression, "this")
3643        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3644            this = self.wrap(this)
3645        return f"ALL {this}"
3646
3647    def any_sql(self, expression: exp.Any) -> str:
3648        this = self.sql(expression, "this")
3649        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3650            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3651                this = self.wrap(this)
3652            return f"ANY{this}"
3653        return f"ANY {this}"
3654
3655    def exists_sql(self, expression: exp.Exists) -> str:
3656        return f"EXISTS{self.wrap(expression)}"
3657
3658    def case_sql(self, expression: exp.Case) -> str:
3659        this = self.sql(expression, "this")
3660        statements = [f"CASE {this}" if this else "CASE"]
3661
3662        for e in expression.args["ifs"]:
3663            statements.append(f"WHEN {self.sql(e, 'this')}")
3664            statements.append(f"THEN {self.sql(e, 'true')}")
3665
3666        default = self.sql(expression, "default")
3667
3668        if default:
3669            statements.append(f"ELSE {default}")
3670
3671        statements.append("END")
3672
3673        if self.pretty and self.too_wide(statements):
3674            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3675
3676        return " ".join(statements)
3677
3678    def constraint_sql(self, expression: exp.Constraint) -> str:
3679        this = self.sql(expression, "this")
3680        expressions = self.expressions(expression, flat=True)
3681        return f"CONSTRAINT {this} {expressions}"
3682
3683    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3684        order = expression.args.get("order")
3685        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3686        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3687
3688    def extract_sql(self, expression: exp.Extract) -> str:
3689        import sqlglot.dialects.dialect
3690
3691        this = (
3692            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3693            if self.NORMALIZE_EXTRACT_DATE_PARTS
3694            else expression.this
3695        )
3696        if self.EXTRACT_ALLOWS_QUOTES:
3697            this_sql = self.sql(this)
3698        elif isinstance(this, exp.WeekStart):
3699            this_sql = self.weekstart_name(this)
3700        else:
3701            this_sql = this.name
3702        expression_sql = self.sql(expression, "expression")
3703
3704        return f"EXTRACT({this_sql} FROM {expression_sql})"
3705
3706    def trim_sql(self, expression: exp.Trim) -> str:
3707        trim_type = self.sql(expression, "position")
3708
3709        if trim_type == "LEADING":
3710            func_name = "LTRIM"
3711        elif trim_type == "TRAILING":
3712            func_name = "RTRIM"
3713        else:
3714            func_name = "TRIM"
3715
3716        return self.func(func_name, expression.this, expression.expression)
3717
3718    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3719        args = expression.expressions
3720        if isinstance(expression, exp.ConcatWs):
3721            args = args[1:]  # Skip the delimiter
3722
3723        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3724            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3725
3726        concat_coalesce = (
3727            self.dialect.CONCAT_WS_COALESCE
3728            if isinstance(expression, exp.ConcatWs)
3729            else self.dialect.CONCAT_COALESCE
3730        )
3731
3732        if not concat_coalesce and expression.args.get("coalesce"):
3733
3734            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3735                if not e.type:
3736                    import sqlglot.optimizer.annotate_types
3737
3738                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3739
3740                if e.is_string or e.is_type(exp.DType.ARRAY):
3741                    return e
3742
3743                return exp.func("coalesce", e, exp.Literal.string(""))
3744
3745            args = [_wrap_with_coalesce(e) for e in args]
3746
3747        return args
3748
3749    def concat_sql(self, expression: exp.Concat) -> str:
3750        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3751            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3752            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3753            # instead of coalescing them to empty string.
3754            import sqlglot.dialects.dialect
3755
3756            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3757
3758        expressions = self.convert_concat_args(expression)
3759
3760        # Some dialects don't allow a single-argument CONCAT call
3761        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3762            return self.sql(expressions[0])
3763
3764        return self.func("CONCAT", *expressions)
3765
3766    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3767        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3768            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3769            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3770            all_args = expression.expressions
3771            expression.set("coalesce", True)
3772            return self.sql(
3773                exp.case()
3774                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3775                .else_(expression)
3776            )
3777
3778        return self.func(
3779            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3780        )
3781
3782    def check_sql(self, expression: exp.Check) -> str:
3783        this = self.sql(expression, key="this")
3784        return f"CHECK ({this})"
3785
3786    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3787        expressions = self.expressions(expression, flat=True)
3788        expressions = f" ({expressions})" if expressions else ""
3789        reference = self.sql(expression, "reference")
3790        reference = f" {reference}" if reference else ""
3791        delete = self.sql(expression, "delete")
3792        delete = f" ON DELETE {delete}" if delete else ""
3793        update = self.sql(expression, "update")
3794        update = f" ON UPDATE {update}" if update else ""
3795        options = self.expressions(expression, key="options", flat=True, sep=" ")
3796        options = f" {options}" if options else ""
3797        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3798
3799    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3800        this = self.sql(expression, "this")
3801        this = f" {this}" if this else ""
3802        expressions = self.expressions(expression, flat=True)
3803        include = self.sql(expression, "include")
3804        options = self.expressions(expression, key="options", flat=True, sep=" ")
3805        options = f" {options}" if options else ""
3806        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3807
3808    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3809        self.unsupported("TIMESERIES primary key columns are not supported")
3810        return self.sql(expression, "this")
3811
3812    def if_sql(self, expression: exp.If) -> str:
3813        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3814
3815    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3816        if self.MATCH_AGAINST_TABLE_PREFIX:
3817            expressions = []
3818            for expr in expression.expressions:
3819                if isinstance(expr, exp.Table):
3820                    expressions.append(f"TABLE {self.sql(expr)}")
3821                else:
3822                    expressions.append(expr)
3823        else:
3824            expressions = expression.expressions
3825
3826        modifier = expression.args.get("modifier")
3827        modifier = f" {modifier}" if modifier else ""
3828        return (
3829            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3830        )
3831
3832    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3833        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3834
3835    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3836        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3837
3838        if self.QUOTE_JSON_PATH:
3839            path = self.escape_str(path)
3840            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3841
3842        return path
3843
3844    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3845        if isinstance(expression, exp.JSONPathPart):
3846            transform = self.TRANSFORMS.get(expression.__class__)
3847            if not callable(transform):
3848                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3849                return ""
3850
3851            return transform(self, expression)
3852
3853        if isinstance(expression, int):
3854            return str(expression)
3855
3856        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3857            escaped = expression.replace("'", "\\'")
3858            escaped = f"'{escaped}'"
3859        else:
3860            escaped = expression.replace('"', '\\"')
3861            escaped = f'"{escaped}"'
3862
3863        return escaped
3864
3865    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3866        return f"{self.sql(expression, 'this')} FORMAT JSON"
3867
3868    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3869        # Output the Teradata column FORMAT override.
3870        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3871        this = self.sql(expression, "this")
3872        fmt = self.sql(expression, "format")
3873        return f"{this} (FORMAT {fmt})"
3874
3875    def _jsonobject_sql(
3876        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3877    ) -> str:
3878        null_handling = expression.args.get("null_handling")
3879        null_handling = f" {null_handling}" if null_handling else ""
3880
3881        unique_keys = expression.args.get("unique_keys")
3882        if unique_keys is not None:
3883            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3884        else:
3885            unique_keys = ""
3886
3887        return_type = self.sql(expression, "return_type")
3888        return_type = f" RETURNING {return_type}" if return_type else ""
3889        encoding = self.sql(expression, "encoding")
3890        encoding = f" ENCODING {encoding}" if encoding else ""
3891
3892        if not name:
3893            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3894
3895        return self.func(
3896            name,
3897            *expression.expressions,
3898            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3899        )
3900
3901    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3902        null_handling = expression.args.get("null_handling")
3903        null_handling = f" {null_handling}" if null_handling else ""
3904        return_type = self.sql(expression, "return_type")
3905        return_type = f" RETURNING {return_type}" if return_type else ""
3906        strict = " STRICT" if expression.args.get("strict") else ""
3907        return self.func(
3908            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3909        )
3910
3911    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3912        this = self.sql(expression, "this")
3913        order = self.sql(expression, "order")
3914        null_handling = expression.args.get("null_handling")
3915        null_handling = f" {null_handling}" if null_handling else ""
3916        return_type = self.sql(expression, "return_type")
3917        return_type = f" RETURNING {return_type}" if return_type else ""
3918        strict = " STRICT" if expression.args.get("strict") else ""
3919        return self.func(
3920            "JSON_ARRAYAGG",
3921            this,
3922            suffix=f"{order}{null_handling}{return_type}{strict})",
3923        )
3924
3925    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3926        path = self.sql(expression, "path")
3927        path = f" PATH {path}" if path else ""
3928        nested_schema = self.sql(expression, "nested_schema")
3929
3930        if nested_schema:
3931            return f"NESTED{path} {nested_schema}"
3932
3933        this = self.sql(expression, "this")
3934        kind = self.sql(expression, "kind")
3935        kind = f" {kind}" if kind else ""
3936        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3937
3938        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3939        return f"{this}{kind}{format_json}{path}{ordinality}"
3940
3941    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3942        return self.func("COLUMNS", *expression.expressions)
3943
3944    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3945        this = self.sql(expression, "this")
3946        path = self.sql(expression, "path")
3947        path = f", {path}" if path else ""
3948        error_handling = expression.args.get("error_handling")
3949        error_handling = f" {error_handling}" if error_handling else ""
3950        empty_handling = expression.args.get("empty_handling")
3951        empty_handling = f" {empty_handling}" if empty_handling else ""
3952        schema = self.sql(expression, "schema")
3953        return self.func(
3954            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3955        )
3956
3957    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3958        this = self.sql(expression, "this")
3959        kind = self.sql(expression, "kind")
3960        path = self.sql(expression, "path")
3961        path = f" {path}" if path else ""
3962        as_json = " AS JSON" if expression.args.get("as_json") else ""
3963        return f"{this} {kind}{path}{as_json}"
3964
3965    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3966        this = self.sql(expression, "this")
3967        path = self.sql(expression, "path")
3968        path = f", {path}" if path else ""
3969        expressions = self.expressions(expression)
3970        with_ = (
3971            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3972            if expressions
3973            else ""
3974        )
3975        return f"OPENJSON({this}{path}){with_}"
3976
3977    def in_sql(self, expression: exp.In) -> str:
3978        query = expression.args.get("query")
3979        unnest = expression.args.get("unnest")
3980        field = expression.args.get("field")
3981        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3982
3983        if query:
3984            in_sql = self.sql(query)
3985        elif unnest:
3986            in_sql = self.in_unnest_op(unnest)
3987        elif field:
3988            in_sql = self.sql(field)
3989        else:
3990            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3991
3992        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3993
3994    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3995        return f"(SELECT {self.sql(unnest)})"
3996
3997    def interval_sql(self, expression: exp.Interval) -> str:
3998        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
3999            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4000            exp.AutoRefreshProperty,
4001        )
4002        interval_keyword = "INTERVAL" if include_keyword else ""
4003        unit_expression = expression.args.get("unit")
4004        unit = self.sql(unit_expression) if unit_expression else ""
4005        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4006            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4007        unit = f" {unit}" if unit else ""
4008
4009        if self.SINGLE_STRING_INTERVAL:
4010            this = expression.this.name if expression.this else ""
4011            if this:
4012                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4013                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4014                    return f"{interval_keyword}'{this}'{unit}"
4015                return f"{interval_keyword}'{this}{unit}'"
4016            return f"{interval_keyword}{unit}"
4017
4018        this = self.sql(expression, "this")
4019        if this:
4020            if not include_keyword and expression.this.is_string:
4021                this = expression.this.name
4022            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4023                this = f"({this})"
4024            if include_keyword:
4025                this = f" {this}"
4026
4027        return f"{interval_keyword}{this}{unit}"
4028
4029    def return_sql(self, expression: exp.Return) -> str:
4030        return f"RETURN {self.sql(expression, 'this')}"
4031
4032    def reference_sql(self, expression: exp.Reference) -> str:
4033        this = self.sql(expression, "this")
4034        expressions = self.expressions(expression, flat=True)
4035        expressions = f"({expressions})" if expressions else ""
4036        options = self.expressions(expression, key="options", flat=True, sep=" ")
4037        options = f" {options}" if options else ""
4038        return f"REFERENCES {this}{expressions}{options}"
4039
4040    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4041        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4042        parent = expression.parent
4043        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4044
4045        return self.func(
4046            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4047        )
4048
4049    def paren_sql(self, expression: exp.Paren) -> str:
4050        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4051        return f"({sql}{self.seg(')', sep='')}"
4052
4053    def neg_sql(self, expression: exp.Neg) -> str:
4054        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4055        this_sql = self.sql(expression, "this")
4056        sep = " " if this_sql[0] == "-" else ""
4057        return f"-{sep}{this_sql}"
4058
4059    def not_sql(self, expression: exp.Not) -> str:
4060        return f"NOT {self.sql(expression, 'this')}"
4061
4062    def alias_sql(self, expression: exp.Alias) -> str:
4063        alias = self.sql(expression, "alias")
4064        alias = f" AS {alias}" if alias else ""
4065        return f"{self.sql(expression, 'this')}{alias}"
4066
4067    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4068        alias = expression.args["alias"]
4069
4070        parent = expression.parent
4071        pivot = parent and parent.parent
4072
4073        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4074            identifier_alias = isinstance(alias, exp.Identifier)
4075            literal_alias = isinstance(alias, exp.Literal)
4076
4077            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4078                alias.replace(exp.Literal.string(alias.output_name))
4079            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4080                alias.replace(exp.to_identifier(alias.output_name))
4081
4082        return self.alias_sql(expression)
4083
4084    def aliases_sql(self, expression: exp.Aliases) -> str:
4085        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4086
4087    def atindex_sql(self, expression: exp.AtIndex) -> str:
4088        this = self.sql(expression, "this")
4089        index = self.sql(expression, "expression")
4090        return f"{this} AT {index}"
4091
4092    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4093        this = self.sql(expression, "this")
4094        zone = self.sql(expression, "zone")
4095        return f"{this} AT TIME ZONE {zone}"
4096
4097    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4098        this = self.sql(expression, "this")
4099        zone = self.sql(expression, "zone")
4100        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4101
4102    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4103        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4104
4105    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4106        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4107
4108    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4109        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4110
4111    def add_sql(self, expression: exp.Add) -> str:
4112        return self.binary(expression, "+")
4113
4114    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4115        return self.connector_sql(expression, "AND", stack)
4116
4117    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4118        return self.connector_sql(expression, "OR", stack)
4119
4120    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4121        return self.connector_sql(expression, "XOR", stack)
4122
4123    def connector_sql(
4124        self,
4125        expression: exp.Connector,
4126        op: str,
4127        stack: list[str | exp.Expr] | None = None,
4128    ) -> str:
4129        if stack is not None:
4130            stack.append(expression.right)
4131            if expression.comments and self.comments:
4132                op = self.maybe_comment(op, comments=expression.comments)
4133
4134            stack.extend((op, expression.left))
4135            return op
4136
4137        stack = [expression]
4138        sqls: list[str] = []
4139        ops = set()
4140
4141        while stack:
4142            node = stack.pop()
4143            if isinstance(node, exp.Connector):
4144                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4145            else:
4146                sql = self.sql(node)
4147                if sqls and sqls[-1] in ops:
4148                    sqls[-1] += f" {sql}"
4149                else:
4150                    sqls.append(sql)
4151
4152        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4153        return sep.join(sqls)
4154
4155    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4156        return self.binary(expression, "&")
4157
4158    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4159        return self.binary(expression, "<<")
4160
4161    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4162        return f"~{self.sql(expression, 'this')}"
4163
4164    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4165        return self.binary(expression, "|")
4166
4167    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4168        return self.binary(expression, ">>")
4169
4170    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4171        return self.binary(expression, "^")
4172
4173    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4174        format_sql = self.sql(expression, "format")
4175        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4176        to_sql = self.sql(expression, "to")
4177        to_sql = f" {to_sql}" if to_sql else ""
4178        action = self.sql(expression, "action")
4179        action = f" {action}" if action else ""
4180        default = self.sql(expression, "default")
4181        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4182        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4183
4184    # Base implementation that excludes safe, zone, and target_type metadata args
4185    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4186        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4187
4188    # Base implementation that excludes the safe and default_year metadata args
4189    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4190        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4191
4192    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4193        return self.func(
4194            "PARSE_DATETIME",
4195            expression.this,
4196            expression.args.get("format"),
4197            expression.args.get("zone"),
4198        )
4199
4200    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4201        zone = self.sql(expression, "this")
4202        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4203
4204    def collate_sql(self, expression: exp.Collate) -> str:
4205        if self.COLLATE_IS_FUNC:
4206            return self.function_fallback_sql(expression)
4207        return self.binary(expression, "COLLATE")
4208
4209    def command_sql(self, expression: exp.Command) -> str:
4210        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4211
4212    def comment_sql(self, expression: exp.Comment) -> str:
4213        this = self.sql(expression, "this")
4214        kind = expression.args["kind"]
4215        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4216        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4217        expression_sql = self.sql(expression, "expression")
4218        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4219
4220    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4221        this = self.sql(expression, "this")
4222        delete = " DELETE" if expression.args.get("delete") else ""
4223        recompress = self.sql(expression, "recompress")
4224        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4225        to_disk = self.sql(expression, "to_disk")
4226        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4227        to_volume = self.sql(expression, "to_volume")
4228        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4229        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4230
4231    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4232        where = self.sql(expression, "where")
4233        group = self.sql(expression, "group")
4234        aggregates = self.expressions(expression, key="aggregates")
4235        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4236
4237        if not (where or group or aggregates) and len(expression.expressions) == 1:
4238            return f"TTL {self.expressions(expression, flat=True)}"
4239
4240        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4241
4242    def transaction_sql(self, expression: exp.Transaction) -> str:
4243        modes = self.expressions(expression, key="modes")
4244        modes = f" {modes}" if modes else ""
4245        return f"BEGIN{modes}"
4246
4247    def commit_sql(self, expression: exp.Commit) -> str:
4248        chain = expression.args.get("chain")
4249        if chain is not None:
4250            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4251
4252        return f"COMMIT{chain or ''}"
4253
4254    def rollback_sql(self, expression: exp.Rollback) -> str:
4255        savepoint = expression.args.get("savepoint")
4256        savepoint = f" TO {savepoint}" if savepoint else ""
4257        return f"ROLLBACK{savepoint}"
4258
4259    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4260        this = self.sql(expression, "this")
4261
4262        exists = ""
4263        if expression.args.get("exists"):
4264            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4265                exists = " IF EXISTS"
4266            else:
4267                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4268
4269        dtype = self.sql(expression, "dtype")
4270        if dtype:
4271            collate = self.sql(expression, "collate")
4272            collate = f" COLLATE {collate}" if collate else ""
4273            using = self.sql(expression, "using")
4274            using = f" USING {using}" if using else ""
4275            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4276            null_constraint = self._alter_column_null_constraint_sql(expression)
4277
4278            return (
4279                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4280                f"{collate}{using}{null_constraint}"
4281            )
4282
4283        default = self.sql(expression, "default")
4284        if default:
4285            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4286
4287        comment = self.sql(expression, "comment")
4288        if comment:
4289            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4290
4291        visible = expression.args.get("visible")
4292        if visible:
4293            return f"ALTER COLUMN{exists} {this} SET {visible}"
4294
4295        allow_null = expression.args.get("allow_null")
4296        drop = expression.args.get("drop")
4297
4298        if not drop and not allow_null:
4299            self.unsupported("Unsupported ALTER COLUMN syntax")
4300
4301        if allow_null is not None:
4302            keyword = "DROP" if drop else "SET"
4303            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4304
4305        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4306
4307    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4308        allow_null = expression.args.get("allow_null")
4309        if allow_null is None:
4310            return ""
4311
4312        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4313            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4314            return ""
4315
4316        return " NULL" if allow_null else " NOT NULL"
4317
4318    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4319        this = self.sql(expression, "this")
4320        rename_from = self.sql(expression, "rename_from")
4321        if rename_from:
4322            if not self.SUPPORTS_CHANGE_COLUMN:
4323                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4324            return f"CHANGE COLUMN {rename_from} {this}"
4325        if not self.SUPPORTS_MODIFY_COLUMN:
4326            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4327        return f"MODIFY COLUMN {this}"
4328
4329    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4330        this = self.sql(expression, "this")
4331
4332        visible = expression.args.get("visible")
4333        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4334
4335        return f"ALTER INDEX {this} {visible_sql}"
4336
4337    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4338        this = self.sql(expression, "this")
4339        if not isinstance(expression.this, exp.Var):
4340            this = f"KEY DISTKEY {this}"
4341        return f"ALTER DISTSTYLE {this}"
4342
4343    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4344        compound = " COMPOUND" if expression.args.get("compound") else ""
4345        this = self.sql(expression, "this")
4346        expressions = self.expressions(expression, flat=True)
4347        expressions = f"({expressions})" if expressions else ""
4348        return f"ALTER{compound} SORTKEY {this or expressions}"
4349
4350    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4351        if not self.RENAME_TABLE_WITH_DB:
4352            # Remove db from tables
4353            expression = expression.transform(
4354                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4355            ).assert_is(exp.AlterRename)
4356        this = self.sql(expression, "this")
4357        to_kw = " TO" if include_to else ""
4358        return f"RENAME{to_kw} {this}"
4359
4360    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4361        exists = " IF EXISTS" if expression.args.get("exists") else ""
4362        old_column = self.sql(expression, "this")
4363        new_column = self.sql(expression, "to")
4364        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4365
4366    def alterset_sql(self, expression: exp.AlterSet) -> str:
4367        exprs = self.expressions(expression, flat=True)
4368        if self.ALTER_SET_WRAPPED:
4369            exprs = f"({exprs})"
4370
4371        return f"SET {exprs}"
4372
4373    def alter_sql(self, expression: exp.Alter) -> str:
4374        actions = expression.args["actions"]
4375
4376        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4377            actions[0], exp.ColumnDef
4378        ):
4379            actions_sql = self.expressions(expression, key="actions", flat=True)
4380            actions_sql = f"ADD {actions_sql}"
4381        else:
4382            actions_list = []
4383            for action in actions:
4384                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4385                    action_sql = self.add_column_sql(action)
4386                else:
4387                    action_sql = self.sql(action)
4388                    if isinstance(action, exp.Query):
4389                        action_sql = f"AS {action_sql}"
4390
4391                actions_list.append(action_sql)
4392
4393            actions_sql = self.format_args(*actions_list).lstrip("\n")
4394
4395        iceberg = (
4396            "ICEBERG "
4397            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4398            else ""
4399        )
4400        exists = " IF EXISTS" if expression.args.get("exists") else ""
4401        on_cluster = self.sql(expression, "cluster")
4402        on_cluster = f" {on_cluster}" if on_cluster else ""
4403        only = " ONLY" if expression.args.get("only") else ""
4404        options = self.expressions(expression, key="options")
4405        options = f", {options}" if options else ""
4406        kind = self.sql(expression, "kind")
4407        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4408        check = " WITH CHECK" if expression.args.get("check") else ""
4409        cascade = (
4410            " CASCADE"
4411            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4412            else ""
4413        )
4414        this = self.sql(expression, "this")
4415        this = f" {this}" if this else ""
4416
4417        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4418
4419    def altersession_sql(self, expression: exp.AlterSession) -> str:
4420        items_sql = self.expressions(expression, flat=True)
4421        keyword = "UNSET" if expression.args.get("unset") else "SET"
4422        return f"{keyword} {items_sql}"
4423
4424    def add_column_sql(self, expression: exp.Expr) -> str:
4425        sql = self.sql(expression)
4426        if isinstance(expression, exp.Schema):
4427            column_text = " COLUMNS"
4428        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4429            column_text = " COLUMN"
4430        else:
4431            column_text = ""
4432
4433        return f"ADD{column_text} {sql}"
4434
4435    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4436        expressions = self.expressions(expression)
4437        exists = " IF EXISTS " if expression.args.get("exists") else " "
4438        return f"DROP{exists}{expressions}"
4439
4440    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4441        return "DROP PRIMARY KEY"
4442
4443    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4444        return f"ADD {self.expressions(expression, indent=False)}"
4445
4446    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4447        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4448        location = self.sql(expression, "location")
4449        location = f" {location}" if location else ""
4450        return f"ADD {exists}{self.sql(expression.this)}{location}"
4451
4452    def distinct_sql(self, expression: exp.Distinct) -> str:
4453        this = self.expressions(expression, flat=True)
4454
4455        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4456            case = exp.case()
4457            for arg in expression.expressions:
4458                case = case.when(arg.is_(exp.null()), exp.null())
4459            this = self.sql(case.else_(f"({this})"))
4460
4461        this = f" {this}" if this else ""
4462
4463        on = self.sql(expression, "on")
4464        on = f" ON {on}" if on else ""
4465        return f"DISTINCT{this}{on}"
4466
4467    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4468        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4469
4470    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4471        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4472
4473    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4474        this_sql = self.sql(expression, "this")
4475        expression_sql = self.sql(expression, "expression")
4476        kind = "MAX" if expression.args.get("max") else "MIN"
4477        return f"{this_sql} HAVING {kind} {expression_sql}"
4478
4479    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4480        return self.sql(
4481            exp.Cast(
4482                this=exp.Div(this=expression.this, expression=expression.expression),
4483                to=exp.DataType(this=exp.DType.INT),
4484            )
4485        )
4486
4487    def dpipe_sql(self, expression: exp.DPipe) -> str:
4488        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4489            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4490        return self.binary(expression, "||")
4491
4492    def div_sql(self, expression: exp.Div) -> str:
4493        l, r = expression.left, expression.right
4494
4495        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4496            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4497
4498        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4499            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4500                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4501
4502        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4503            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4504                return self.sql(
4505                    exp.cast(
4506                        l / r,
4507                        to=exp.DType.BIGINT,
4508                    )
4509                )
4510
4511        return self.binary(expression, "/")
4512
4513    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4514        n = exp._wrap(expression.this, exp.Binary)
4515        d = exp._wrap(expression.expression, exp.Binary)
4516        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4517
4518    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4519        return self.binary(expression, "OVERLAPS")
4520
4521    def distance_sql(self, expression: exp.Distance) -> str:
4522        return self.binary(expression, "<->")
4523
4524    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4525        return self.binary(expression, "<<->>")
4526
4527    def dot_sql(self, expression: exp.Dot) -> str:
4528        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4529
4530    def eq_sql(self, expression: exp.EQ) -> str:
4531        return self.binary(expression, "=")
4532
4533    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4534        return self.binary(expression, ":=")
4535
4536    def escape_sql(self, expression: exp.Escape) -> str:
4537        this = expression.this
4538        if (
4539            isinstance(this, (exp.Like, exp.ILike))
4540            and isinstance(this.expression, (exp.All, exp.Any))
4541            and not self.SUPPORTS_LIKE_QUANTIFIERS
4542        ):
4543            return self._like_sql(this, escape=expression)
4544        return self.binary(expression, "ESCAPE")
4545
4546    def glob_sql(self, expression: exp.Glob) -> str:
4547        return self.binary(expression, "GLOB")
4548
4549    def gt_sql(self, expression: exp.GT) -> str:
4550        return self.binary(expression, ">")
4551
4552    def gte_sql(self, expression: exp.GTE) -> str:
4553        return self.binary(expression, ">=")
4554
4555    def is_sql(self, expression: exp.Is) -> str:
4556        negate = expression.args.get("negate")
4557        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4558            positive = bool(expression.expression.this) != bool(negate)
4559            return self.sql(expression.this if positive else exp.not_(expression.this))
4560        return self.binary(expression, "IS NOT" if negate else "IS")
4561
4562    def _like_sql(
4563        self,
4564        expression: exp.Like | exp.ILike,
4565        escape: exp.Escape | None = None,
4566    ) -> str:
4567        this = expression.this
4568        rhs = expression.expression
4569
4570        if isinstance(expression, exp.Like):
4571            exp_class: type[exp.Like | exp.ILike] = exp.Like
4572            op = "LIKE"
4573        else:
4574            exp_class = exp.ILike
4575            op = "ILIKE"
4576
4577        if expression.args.get("negate"):
4578            op = f"NOT {op}"
4579
4580        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4581            exprs = rhs.this.unnest()
4582
4583            if isinstance(exprs, exp.Tuple):
4584                exprs = exprs.expressions
4585            else:
4586                exprs = [exprs]
4587
4588            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4589
4590            def _make_like(expr: exp.Expression) -> exp.Expression:
4591                like: exp.Expression = exp_class(
4592                    this=this, expression=expr, negate=expression.args.get("negate")
4593                )
4594                if escape:
4595                    like = exp.Escape(this=like, expression=escape.expression.copy())
4596                return like
4597
4598            like_expr: exp.Expr = _make_like(exprs[0])
4599            for expr in exprs[1:]:
4600                like_expr = connective(like_expr, _make_like(expr), copy=False)
4601
4602            parent = escape.parent if escape else expression.parent
4603            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4604                parent, exp.Condition
4605            ):
4606                like_expr = exp.paren(like_expr, copy=False)
4607
4608            return self.sql(like_expr)
4609
4610        return self.binary(expression, op)
4611
4612    def like_sql(self, expression: exp.Like) -> str:
4613        return self._like_sql(expression)
4614
4615    def ilike_sql(self, expression: exp.ILike) -> str:
4616        return self._like_sql(expression)
4617
4618    def match_sql(self, expression: exp.Match) -> str:
4619        return self.binary(expression, "MATCH")
4620
4621    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4622        return self.binary(expression, "SIMILAR TO")
4623
4624    def lt_sql(self, expression: exp.LT) -> str:
4625        return self.binary(expression, "<")
4626
4627    def lte_sql(self, expression: exp.LTE) -> str:
4628        return self.binary(expression, "<=")
4629
4630    def mod_sql(self, expression: exp.Mod) -> str:
4631        this = self.sql(expression, "this")
4632        expr = self.sql(expression, "expression")
4633        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4634
4635        parent = expression.parent
4636        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4637            return f"({sql})"
4638
4639        return sql
4640
4641    def mul_sql(self, expression: exp.Mul) -> str:
4642        return self.binary(expression, "*")
4643
4644    def neq_sql(self, expression: exp.NEQ) -> str:
4645        return self.binary(expression, "<>")
4646
4647    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4648        return self.binary(expression, "IS NOT DISTINCT FROM")
4649
4650    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4651        return self.binary(expression, "IS DISTINCT FROM")
4652
4653    def sub_sql(self, expression: exp.Sub) -> str:
4654        return self.binary(expression, "-")
4655
4656    def trycast_sql(self, expression: exp.TryCast) -> str:
4657        return self.cast_sql(expression, safe_prefix="TRY_")
4658
4659    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4660        return self.cast_sql(expression)
4661
4662    def try_sql(self, expression: exp.Try) -> str:
4663        if not self.TRY_SUPPORTED:
4664            self.unsupported("Unsupported TRY function")
4665            return self.sql(expression, "this")
4666
4667        return self.func("TRY", expression.this)
4668
4669    def log_sql(self, expression: exp.Log) -> str:
4670        this = expression.this
4671        expr = expression.expression
4672
4673        if self.dialect.LOG_BASE_FIRST is False:
4674            this, expr = expr, this
4675        elif self.dialect.LOG_BASE_FIRST is None and expr:
4676            if this.name in ("2", "10"):
4677                return self.func(f"LOG{this.name}", expr)
4678
4679            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4680
4681        return self.func("LOG", this, expr)
4682
4683    def use_sql(self, expression: exp.Use) -> str:
4684        kind = self.sql(expression, "kind")
4685        kind = f" {kind}" if kind else ""
4686        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4687        this = f" {this}" if this else ""
4688        return f"USE{kind}{this}"
4689
4690    def binary(self, expression: exp.Binary, op: str) -> str:
4691        sqls: list[str] = []
4692        stack: list[None | str | exp.Expr] = [expression]
4693        binary_type = type(expression)
4694
4695        while stack:
4696            node = stack.pop()
4697
4698            if type(node) is binary_type:
4699                op_func = node.args.get("operator")
4700                if op_func:
4701                    op = f"OPERATOR({self.sql(op_func)})"
4702
4703                stack.append(node.args.get("expression"))
4704                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4705                stack.append(node.args.get("this"))
4706            else:
4707                sqls.append(self.sql(node))
4708
4709        return "".join(sqls)
4710
4711    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4712        to_clause = self.sql(expression, "to")
4713        if to_clause:
4714            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4715
4716        return self.function_fallback_sql(expression)
4717
4718    def function_fallback_sql(self, expression: exp.Func) -> str:
4719        args = []
4720
4721        for key in expression.arg_types:
4722            arg_value = expression.args.get(key)
4723
4724            if isinstance(arg_value, list):
4725                for value in arg_value:
4726                    args.append(value)
4727            elif arg_value is not None:
4728                args.append(arg_value)
4729
4730        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4731            name = expression.meta_get("name") or expression.sql_name()
4732        else:
4733            name = expression.sql_name()
4734
4735        return self.func(name, *args)
4736
4737    def func(
4738        self,
4739        name: str,
4740        *args: t.Any,
4741        prefix: str = "(",
4742        suffix: str = ")",
4743        normalize: bool = True,
4744    ) -> str:
4745        name = self.normalize_func(name) if normalize else name
4746        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4747
4748    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4749        arg_sqls = tuple(
4750            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4751        )
4752        if self.pretty and self.too_wide(arg_sqls):
4753            return self.indent(
4754                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4755            )
4756        return sep.join(arg_sqls)
4757
4758    def too_wide(self, args: t.Iterable) -> bool:
4759        return sum(len(arg) for arg in args) > self.max_text_width
4760
4761    def format_time(
4762        self,
4763        expression: exp.Expr,
4764        inverse_time_mapping: dict[str, str] | None = None,
4765        inverse_time_trie: dict | None = None,
4766    ) -> str | None:
4767        return format_time(
4768            self.sql(expression, "format"),
4769            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4770            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4771        )
4772
4773    def expressions(
4774        self,
4775        expression: exp.Expr | None = None,
4776        key: str | None = None,
4777        sqls: t.Collection[str | exp.Expr] | None = None,
4778        flat: bool = False,
4779        indent: bool = True,
4780        skip_first: bool = False,
4781        skip_last: bool = False,
4782        sep: str = ", ",
4783        prefix: str = "",
4784        dynamic: bool = False,
4785        new_line: bool = False,
4786    ) -> str:
4787        expressions = expression.args.get(key or "expressions") if expression else sqls
4788
4789        if not expressions:
4790            return ""
4791
4792        if flat:
4793            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4794
4795        num_sqls = len(expressions)
4796        result_sqls = []
4797
4798        for i, e in enumerate(expressions):
4799            sql = self.sql(e, comment=False)
4800            if not sql:
4801                continue
4802
4803            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4804
4805            if self.pretty:
4806                if self.leading_comma:
4807                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4808                else:
4809                    result_sqls.append(
4810                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4811                    )
4812            else:
4813                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4814
4815        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4816            if new_line:
4817                result_sqls.insert(0, "")
4818                result_sqls.append("")
4819            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4820        else:
4821            result_sql = "".join(result_sqls)
4822
4823        return (
4824            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4825            if indent
4826            else result_sql
4827        )
4828
4829    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4830        flat = flat or isinstance(expression.parent, exp.Properties)
4831        expressions_sql = self.expressions(expression, flat=flat)
4832        if flat:
4833            return f"{op} {expressions_sql}"
4834        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4835
4836    def naked_property(self, expression: exp.Property) -> str:
4837        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4838        if not property_name:
4839            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4840        return f"{property_name} {self.sql(expression, 'this')}"
4841
4842    def tag_sql(self, expression: exp.Tag) -> str:
4843        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4844
4845    def token_sql(self, token_type: TokenType) -> str:
4846        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4847
4848    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4849        this = self.sql(expression, "this")
4850        expressions = self.no_identify(self.expressions, expression)
4851        expressions = (
4852            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4853        )
4854        return f"{this}{expressions}" if expressions.strip() != "" else this
4855
4856    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4857        return self.expressions(expression, flat=True)
4858
4859    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4860        params = self.no_identify(self.expressions, expression, flat=True)
4861        body = self.sql(expression, "this")
4862        prefix = "TABLE " if expression.args.get("is_table") else ""
4863        return f"({params}) AS {prefix}{body}"
4864
4865    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4866        this = self.sql(expression, "this")
4867        expressions = self.expressions(expression, flat=True)
4868        return f"{this}({expressions})"
4869
4870    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4871        return self.binary(expression, "=>")
4872
4873    def when_sql(self, expression: exp.When) -> str:
4874        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4875        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4876        condition = self.sql(expression, "condition")
4877        condition = f" AND {condition}" if condition else ""
4878
4879        then_expression = expression.args.get("then")
4880        if isinstance(then_expression, exp.Insert):
4881            this = self.sql(then_expression, "this")
4882            this = f"INSERT {this}" if this else "INSERT"
4883            then = self.sql(then_expression, "expression")
4884            then = f"{this} VALUES {then}" if then else this
4885        elif isinstance(then_expression, exp.Update):
4886            if isinstance(then_expression.args.get("expressions"), exp.Star):
4887                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4888            else:
4889                expressions_sql = self.expressions(then_expression)
4890                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4891        else:
4892            then = self.sql(then_expression)
4893
4894        if isinstance(then_expression, (exp.Insert, exp.Update)):
4895            where = self.sql(then_expression, "where")
4896            if where and not self.SUPPORTS_MERGE_WHERE:
4897                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4898                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4899                where = ""
4900            then = f"{then}{where}"
4901        return f"WHEN {matched}{source}{condition} THEN {then}"
4902
4903    def whens_sql(self, expression: exp.Whens) -> str:
4904        return self.expressions(expression, sep=" ", indent=False)
4905
4906    def merge_sql(self, expression: exp.Merge) -> str:
4907        table = expression.this
4908        table_alias = ""
4909
4910        hints = table.args.get("hints")
4911        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4912            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4913            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4914
4915        this = self.sql(table)
4916        using = f"USING {self.sql(expression, 'using')}"
4917        whens = self.sql(expression, "whens")
4918
4919        on = self.sql(expression, "on")
4920        on = f"ON {on}" if on else ""
4921
4922        if not on:
4923            on = self.expressions(expression, key="using_cond")
4924            on = f"USING ({on})" if on else ""
4925
4926        returning = self.sql(expression, "returning")
4927        if returning:
4928            whens = f"{whens}{returning}"
4929
4930        sep = self.sep()
4931
4932        return self.prepend_ctes(
4933            expression,
4934            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4935        )
4936
4937    @unsupported_args("format")
4938    def tochar_sql(self, expression: exp.ToChar) -> str:
4939        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4940
4941    @unsupported_args("default")
4942    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4943        if not self.SUPPORTS_TO_NUMBER:
4944            self.unsupported("Unsupported TO_NUMBER function")
4945            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4946
4947        fmt = expression.args.get("format")
4948        if not fmt:
4949            self.unsupported("Conversion format is required for TO_NUMBER")
4950            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4951
4952        return self.func("TO_NUMBER", expression.this, fmt)
4953
4954    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4955        this = self.sql(expression, "this")
4956        kind = self.sql(expression, "kind")
4957        settings_sql = self.expressions(expression, key="settings", sep=" ")
4958        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4959        return f"{this}({kind}{args})"
4960
4961    def dictrange_sql(self, expression: exp.DictRange) -> str:
4962        this = self.sql(expression, "this")
4963        max = self.sql(expression, "max")
4964        min = self.sql(expression, "min")
4965        return f"{this}(MIN {min} MAX {max})"
4966
4967    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4968        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4969
4970    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4971        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4972
4973    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4974    def uniquekeyproperty_sql(
4975        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4976    ) -> str:
4977        return f"{prefix} ({self.expressions(expression, flat=True)})"
4978
4979    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4980    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4981        expressions = self.expressions(expression, flat=True)
4982        expressions = f" {self.wrap(expressions)}" if expressions else ""
4983        buckets = self.sql(expression, "buckets")
4984        kind = self.sql(expression, "kind")
4985        buckets = f" BUCKETS {buckets}" if buckets else ""
4986        order = self.sql(expression, "order")
4987        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4988
4989    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4990        return ""
4991
4992    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4993        expressions = self.expressions(expression, key="expressions", flat=True)
4994        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4995        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4996        buckets = self.sql(expression, "buckets")
4997        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4998
4999    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5000        this = self.sql(expression, "this")
5001        having = self.sql(expression, "having")
5002
5003        if having:
5004            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5005
5006        return self.func("ANY_VALUE", this)
5007
5008    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5009        transform = self.func("TRANSFORM", *expression.expressions)
5010        row_format_before = self.sql(expression, "row_format_before")
5011        row_format_before = f" {row_format_before}" if row_format_before else ""
5012        record_writer = self.sql(expression, "record_writer")
5013        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5014        using = f" USING {self.sql(expression, 'command_script')}"
5015        schema = self.sql(expression, "schema")
5016        schema = f" AS {schema}" if schema else ""
5017        row_format_after = self.sql(expression, "row_format_after")
5018        row_format_after = f" {row_format_after}" if row_format_after else ""
5019        record_reader = self.sql(expression, "record_reader")
5020        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5021        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5022
5023    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5024        key_block_size = self.sql(expression, "key_block_size")
5025        if key_block_size:
5026            return f"KEY_BLOCK_SIZE = {key_block_size}"
5027
5028        using = self.sql(expression, "using")
5029        if using:
5030            return f"USING {using}"
5031
5032        parser = self.sql(expression, "parser")
5033        if parser:
5034            return f"WITH PARSER {parser}"
5035
5036        comment = self.sql(expression, "comment")
5037        if comment:
5038            return f"COMMENT {comment}"
5039
5040        visible = expression.args.get("visible")
5041        if visible is not None:
5042            return "VISIBLE" if visible else "INVISIBLE"
5043
5044        engine_attr = self.sql(expression, "engine_attr")
5045        if engine_attr:
5046            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5047
5048        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5049        if secondary_engine_attr:
5050            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5051
5052        self.unsupported("Unsupported index constraint option.")
5053        return ""
5054
5055    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5056        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5057        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5058
5059    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5060        kind = self.sql(expression, "kind")
5061        kind = f"{kind} INDEX" if kind else "INDEX"
5062        this = self.sql(expression, "this")
5063        this = f" {this}" if this else ""
5064        index_type = self.sql(expression, "index_type")
5065        index_type = f" USING {index_type}" if index_type else ""
5066        expressions = self.expressions(expression, flat=True)
5067        expressions = f" ({expressions})" if expressions else ""
5068        options = self.expressions(expression, key="options", sep=" ")
5069        options = f" {options}" if options else ""
5070        return f"{kind}{this}{index_type}{expressions}{options}"
5071
5072    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5073        if self.NVL2_SUPPORTED:
5074            return self.function_fallback_sql(expression)
5075
5076        case = exp.Case().when(
5077            expression.this.is_(exp.null()).not_(copy=False),
5078            expression.args["true"],
5079            copy=False,
5080        )
5081        else_cond = expression.args.get("false")
5082        if else_cond:
5083            case.else_(else_cond, copy=False)
5084
5085        return self.sql(case)
5086
5087    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5088        if expression.args.get("from_first") is False:
5089            self.unsupported("NTH_VALUE FROM LAST is not supported")
5090
5091        return self.function_fallback_sql(expression)
5092
5093    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5094        this = self.sql(expression, "this")
5095        expr = self.sql(expression, "expression")
5096        position = self.sql(expression, "position")
5097        position = f", {position}" if position else ""
5098        iterator = self.sql(expression, "iterator")
5099        condition = self.sql(expression, "condition")
5100        condition = f" IF {condition}" if condition else ""
5101        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5102
5103    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5104        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5105
5106    def opclass_sql(self, expression: exp.Opclass) -> str:
5107        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5108
5109    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5110        model = self.sql(expression, "this")
5111        model = f"MODEL {model}"
5112        expr = expression.expression
5113        if expr:
5114            expr_sql = self.sql(expression, "expression")
5115            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5116        else:
5117            expr_sql = None
5118
5119        parameters = self.sql(expression, "params_struct") or None
5120
5121        return self.func(name, model, expr_sql, parameters)
5122
5123    def predict_sql(self, expression: exp.Predict) -> str:
5124        return self._ml_sql(expression, "PREDICT")
5125
5126    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5127        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5128        return self._ml_sql(expression, name)
5129
5130    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5131        return self._ml_sql(expression, "GENERATE_TEXT")
5132
5133    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5134        return self._ml_sql(expression, "GENERATE_TABLE")
5135
5136    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5137        return self._ml_sql(expression, "GENERATE_BOOL")
5138
5139    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5140        return self._ml_sql(expression, "GENERATE_INT")
5141
5142    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5143        return self._ml_sql(expression, "GENERATE_DOUBLE")
5144
5145    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5146        return self._ml_sql(expression, "TRANSLATE")
5147
5148    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5149        return self._ml_sql(expression, "FORECAST")
5150
5151    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5152        this_sql = self.sql(expression, "this")
5153        if isinstance(expression.this, exp.Table):
5154            this_sql = f"TABLE {this_sql}"
5155
5156        return self.func(
5157            "FORECAST",
5158            this_sql,
5159            expression.args.get("data_col"),
5160            expression.args.get("timestamp_col"),
5161            expression.args.get("model"),
5162            expression.args.get("id_cols"),
5163            expression.args.get("horizon"),
5164            expression.args.get("forecast_end_timestamp"),
5165            expression.args.get("confidence_level"),
5166            expression.args.get("output_historical_time_series"),
5167            expression.args.get("context_window"),
5168        )
5169
5170    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5171        this_sql = self.sql(expression, "this")
5172        if isinstance(expression.this, exp.Table):
5173            this_sql = f"TABLE {this_sql}"
5174
5175        return self.func(
5176            "FEATURES_AT_TIME",
5177            this_sql,
5178            expression.args.get("time"),
5179            expression.args.get("num_rows"),
5180            expression.args.get("ignore_feature_nulls"),
5181        )
5182
5183    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5184        this_sql = self.sql(expression, "this")
5185        if isinstance(expression.this, exp.Table):
5186            this_sql = f"TABLE {this_sql}"
5187
5188        query_table = self.sql(expression, "query_table")
5189        if isinstance(expression.args["query_table"], exp.Table):
5190            query_table = f"TABLE {query_table}"
5191
5192        return self.func(
5193            "VECTOR_SEARCH",
5194            this_sql,
5195            expression.args.get("column_to_search"),
5196            query_table,
5197            expression.args.get("query_column_to_search"),
5198            expression.args.get("top_k"),
5199            expression.args.get("distance_type"),
5200            expression.args.get("options"),
5201        )
5202
5203    def forin_sql(self, expression: exp.ForIn) -> str:
5204        this = self.sql(expression, "this")
5205        expression_sql = self.sql(expression, "expression")
5206        return f"FOR {this} DO {expression_sql}"
5207
5208    def refresh_sql(self, expression: exp.Refresh) -> str:
5209        this = self.sql(expression, "this")
5210        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5211        return f"REFRESH {kind}{this}"
5212
5213    def toarray_sql(self, expression: exp.ToArray) -> str:
5214        arg = expression.this
5215        if not arg.type:
5216            import sqlglot.optimizer.annotate_types
5217
5218            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5219
5220        if arg.is_type(exp.DType.ARRAY):
5221            return self.sql(arg)
5222
5223        cond_for_null = arg.is_(exp.null())
5224        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5225
5226    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5227        this = expression.this
5228        time_format = self.format_time(expression)
5229
5230        if time_format:
5231            return self.sql(
5232                exp.cast(
5233                    exp.StrToTime(this=this, format=expression.args["format"]),
5234                    exp.DType.TIME,
5235                )
5236            )
5237
5238        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5239            return self.sql(this)
5240
5241        return self.sql(exp.cast(this, exp.DType.TIME))
5242
5243    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5244        this = expression.this
5245        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5246            return self.sql(this)
5247
5248        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5249
5250    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5251        this = expression.this
5252        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5253            return self.sql(this)
5254
5255        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5256
5257    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5258        this = expression.this
5259        time_format = self.format_time(expression)
5260        safe = expression.args.get("safe")
5261        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5262            return self.sql(
5263                exp.cast(
5264                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5265                    exp.DType.DATE,
5266                )
5267            )
5268
5269        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5270            return self.sql(this)
5271
5272        if safe:
5273            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5274
5275        return self.sql(exp.cast(this, exp.DType.DATE))
5276
5277    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5278        return self.sql(
5279            exp.func(
5280                "DATEDIFF",
5281                expression.this,
5282                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5283                "day",
5284            )
5285        )
5286
5287    def lastday_sql(self, expression: exp.LastDay) -> str:
5288        if self.LAST_DAY_SUPPORTS_DATE_PART:
5289            return self.function_fallback_sql(expression)
5290
5291        unit = expression.args.get("unit")
5292        if unit and unit.name.upper() != "MONTH":
5293            self.unsupported("Date parts are not supported in LAST_DAY.")
5294
5295        return self.func("LAST_DAY", expression.this)
5296
5297    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5298        import sqlglot.dialects.dialect
5299
5300        return self.func(
5301            "DATE_ADD",
5302            expression.this,
5303            expression.expression,
5304            sqlglot.dialects.dialect.unit_to_str(expression),
5305        )
5306
5307    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5308        if self.CAN_IMPLEMENT_ARRAY_ANY:
5309            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5310            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5311            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5312            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5313
5314        import sqlglot.dialects.dialect
5315
5316        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5317        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5318            self.unsupported("ARRAY_ANY is unsupported")
5319
5320        return self.function_fallback_sql(expression)
5321
5322    def struct_sql(self, expression: exp.Struct) -> str:
5323        expression.set(
5324            "expressions",
5325            [
5326                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5327                if isinstance(e, exp.PropertyEQ)
5328                else e
5329                for e in expression.expressions
5330            ],
5331        )
5332
5333        return self.function_fallback_sql(expression)
5334
5335    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5336        low = self.sql(expression, "this")
5337        high = self.sql(expression, "expression")
5338
5339        return f"{low} TO {high}"
5340
5341    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5342        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5343        tables = f" {self.expressions(expression)}"
5344
5345        exists = " IF EXISTS" if expression.args.get("exists") else ""
5346
5347        on_cluster = self.sql(expression, "cluster")
5348        on_cluster = f" {on_cluster}" if on_cluster else ""
5349
5350        identity = self.sql(expression, "identity")
5351        identity = f" {identity} IDENTITY" if identity else ""
5352
5353        option = self.sql(expression, "option")
5354        option = f" {option}" if option else ""
5355
5356        partition = self.sql(expression, "partition")
5357        partition = f" {partition}" if partition else ""
5358
5359        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5360
5361    # This transpiles T-SQL's CONVERT function
5362    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5363    def convert_sql(self, expression: exp.Convert) -> str:
5364        to = expression.this
5365        value = expression.expression
5366        style = expression.args.get("style")
5367        safe = expression.args.get("safe")
5368        strict = expression.args.get("strict")
5369
5370        if not to or not value:
5371            return ""
5372
5373        # Retrieve length of datatype and override to default if not specified
5374        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5375            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5376
5377        transformed: exp.Expr | None = None
5378        cast = exp.Cast if strict else exp.TryCast
5379
5380        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5381        if isinstance(style, exp.Literal) and style.is_int:
5382            import sqlglot.dialects.tsql
5383
5384            style_value = style.name
5385            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5386            if not converted_style:
5387                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5388
5389            fmt = exp.Literal.string(converted_style)
5390
5391            if to.this == exp.DType.DATE:
5392                transformed = exp.StrToDate(this=value, format=fmt)
5393            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5394                transformed = exp.StrToTime(this=value, format=fmt)
5395            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5396                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5397            elif to.this == exp.DType.TEXT:
5398                transformed = exp.TimeToStr(this=value, format=fmt)
5399
5400        if not transformed:
5401            transformed = cast(this=value, to=to, safe=safe)
5402
5403        return self.sql(transformed)
5404
5405    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5406        this = expression.this
5407        if isinstance(this, exp.JSONPathWildcard):
5408            this = self.json_path_part(this)
5409            return f".{this}" if this else ""
5410
5411        quoted = expression.args.get("quoted")
5412        if not (
5413            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5414        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5415            return f".{this}"
5416
5417        this = self.json_path_part(this)
5418
5419        return (
5420            f"[{this}]"
5421            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5422            else f".{this}"
5423        )
5424
5425    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5426        this = self.json_path_part(expression.this)
5427        return f"[{this}]" if this else ""
5428
5429    def _simplify_unless_literal(self, expression: E) -> E:
5430        if not isinstance(expression, exp.Literal):
5431            import sqlglot.optimizer.simplify
5432
5433            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5434
5435        return expression
5436
5437    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5438        this = expression.this
5439        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5440            self.unsupported(
5441                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5442            )
5443            return self.sql(this)
5444
5445        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5446            if self.IGNORE_NULLS_BEFORE_ORDER:
5447                from sqlglot.optimizer.scope import find_all_in_scope
5448
5449                # The first modifier here will be the one closest to the AggFunc's arg
5450                mods = sorted(
5451                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5452                    key=lambda x: (
5453                        0
5454                        if isinstance(x, exp.HavingMax)
5455                        else (1 if isinstance(x, exp.Order) else 2)
5456                    ),
5457                )
5458
5459                if mods:
5460                    mod = mods[0]
5461                    this = expression.__class__(this=mod.this.copy())
5462                    this.meta["inline"] = True
5463                    mod.this.replace(this)
5464                    return self.sql(expression.this)
5465
5466            agg_func = expression.find(exp.AggFunc)
5467
5468            if agg_func:
5469                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5470                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5471
5472        return f"{self.sql(expression, 'this')} {text}"
5473
5474    def _replace_line_breaks(self, string: str) -> str:
5475        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5476        if self.pretty:
5477            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5478        return string
5479
5480    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5481        option = self.sql(expression, "this")
5482
5483        if expression.expressions:
5484            upper = option.upper()
5485
5486            # Snowflake FILE_FORMAT options are separated by whitespace
5487            sep = " " if upper == "FILE_FORMAT" else ", "
5488
5489            # Databricks copy/format options do not set their list of values with EQ
5490            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5491            values = self.expressions(expression, flat=True, sep=sep)
5492            return f"{option}{op}({values})"
5493
5494        value = self.sql(expression, "expression")
5495
5496        if not value:
5497            return option
5498
5499        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5500
5501        return f"{option}{op}{value}"
5502
5503    def credentials_sql(self, expression: exp.Credentials) -> str:
5504        cred_expr = expression.args.get("credentials")
5505        if isinstance(cred_expr, exp.Literal):
5506            # Redshift case: CREDENTIALS <string>
5507            credentials = self.sql(expression, "credentials")
5508            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5509        else:
5510            # Snowflake case: CREDENTIALS = (...)
5511            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5512            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5513
5514        storage = self.sql(expression, "storage")
5515        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5516
5517        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5518        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5519
5520        iam_role = self.sql(expression, "iam_role")
5521        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5522
5523        region = self.sql(expression, "region")
5524        region = f" REGION {region}" if region else ""
5525
5526        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5527
5528    def copy_sql(self, expression: exp.Copy) -> str:
5529        this = self.sql(expression, "this")
5530        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5531
5532        credentials = self.sql(expression, "credentials")
5533        credentials = self.seg(credentials) if credentials else ""
5534        files = self.expressions(expression, key="files", flat=True)
5535        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5536
5537        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5538        params = self.expressions(
5539            expression,
5540            key="params",
5541            sep=sep,
5542            new_line=True,
5543            skip_last=True,
5544            skip_first=True,
5545            indent=self.COPY_PARAMS_ARE_WRAPPED,
5546        )
5547
5548        if params:
5549            if self.COPY_PARAMS_ARE_WRAPPED:
5550                params = f" WITH ({params})"
5551            elif not self.pretty and (files or credentials):
5552                params = f" {params}"
5553
5554        return f"COPY{this}{kind} {files}{credentials}{params}"
5555
5556    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5557        return ""
5558
5559    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5560        on_sql = "ON" if expression.args.get("on") else "OFF"
5561        filter_col: str | None = self.sql(expression, "filter_column")
5562        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5563        retention_period: str | None = self.sql(expression, "retention_period")
5564        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5565
5566        if filter_col or retention_period:
5567            on_sql = self.func("ON", filter_col, retention_period)
5568
5569        return f"DATA_DELETION={on_sql}"
5570
5571    def maskingpolicycolumnconstraint_sql(
5572        self, expression: exp.MaskingPolicyColumnConstraint
5573    ) -> str:
5574        this = self.sql(expression, "this")
5575        expressions = self.expressions(expression, flat=True)
5576        expressions = f" USING ({expressions})" if expressions else ""
5577        return f"MASKING POLICY {this}{expressions}"
5578
5579    def gapfill_sql(self, expression: exp.GapFill) -> str:
5580        this = self.sql(expression, "this")
5581        this = f"TABLE {this}"
5582        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5583
5584    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5585        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5586
5587    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5588        this = self.sql(expression, "this")
5589        expr = expression.expression
5590
5591        if isinstance(expr, exp.Func):
5592            # T-SQL's CLR functions are case sensitive
5593            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5594        else:
5595            expr = self.sql(expression, "expression")
5596
5597        return self.scope_resolution(expr, this)
5598
5599    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5600        if self.PARSE_JSON_NAME is None:
5601            return self.sql(expression.this)
5602
5603        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5604
5605    def rand_sql(self, expression: exp.Rand) -> str:
5606        lower = self.sql(expression, "lower")
5607        upper = self.sql(expression, "upper")
5608
5609        if lower and upper:
5610            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5611        return self.func("RAND", expression.this)
5612
5613    def changes_sql(self, expression: exp.Changes) -> str:
5614        information = self.sql(expression, "information")
5615        information = f"INFORMATION => {information}"
5616        at_before = self.sql(expression, "at_before")
5617        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5618        end = self.sql(expression, "end")
5619        end = f"{self.seg('')}{end}" if end else ""
5620
5621        return f"CHANGES ({information}){at_before}{end}"
5622
5623    def pad_sql(self, expression: exp.Pad) -> str:
5624        prefix = "L" if expression.args.get("is_left") else "R"
5625
5626        fill_pattern = self.sql(expression, "fill_pattern") or None
5627        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5628            fill_pattern = "' '"
5629
5630        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5631
5632    def summarize_sql(self, expression: exp.Summarize) -> str:
5633        table = " TABLE" if expression.args.get("table") else ""
5634        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5635
5636    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5637        generate_series = exp.GenerateSeries(**expression.args)
5638
5639        parent = expression.parent
5640        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5641            parent = parent.parent
5642
5643        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5644            return self.sql(exp.Unnest(expressions=[generate_series]))
5645
5646        if isinstance(parent, exp.Select):
5647            self.unsupported("GenerateSeries projection unnesting is not supported.")
5648
5649        return self.sql(generate_series)
5650
5651    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5652        if self.SUPPORTS_CONVERT_TIMEZONE:
5653            return self.function_fallback_sql(expression)
5654
5655        source_tz = expression.args.get("source_tz")
5656        target_tz = expression.args.get("target_tz")
5657        timestamp = expression.args.get("timestamp")
5658
5659        if source_tz and timestamp:
5660            timestamp = exp.AtTimeZone(
5661                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5662            )
5663
5664        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5665
5666        return self.sql(expr)
5667
5668    def json_sql(self, expression: exp.JSON) -> str:
5669        this = self.sql(expression, "this")
5670        this = f" {this}" if this else ""
5671
5672        _with = expression.args.get("with_")
5673
5674        if _with is None:
5675            with_sql = ""
5676        elif not _with:
5677            with_sql = " WITHOUT"
5678        else:
5679            with_sql = " WITH"
5680
5681        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5682
5683        return f"JSON{this}{with_sql}{unique_sql}"
5684
5685    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5686        path = self.sql(expression, "path")
5687        returning = self.sql(expression, "returning")
5688        returning = f" RETURNING {returning}" if returning else ""
5689
5690        on_condition = self.sql(expression, "on_condition")
5691        on_condition = f" {on_condition}" if on_condition else ""
5692
5693        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5694
5695    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5696        regexp = " REGEXP" if expression.args.get("regexp") else ""
5697        return f"SKIP{regexp} {self.sql(expression.expression)}"
5698
5699    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5700        else_ = "ELSE " if expression.args.get("else_") else ""
5701        condition = self.sql(expression, "expression")
5702        condition = f"WHEN {condition} THEN " if condition else else_
5703        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5704        return f"{condition}{insert}"
5705
5706    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5707        kind = self.sql(expression, "kind")
5708        expressions = self.seg(self.expressions(expression, sep=" "))
5709        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5710        return res
5711
5712    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5713        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5714        empty = expression.args.get("empty")
5715        empty = (
5716            f"DEFAULT {empty} ON EMPTY"
5717            if isinstance(empty, exp.Expr)
5718            else self.sql(expression, "empty")
5719        )
5720
5721        error = expression.args.get("error")
5722        error = (
5723            f"DEFAULT {error} ON ERROR"
5724            if isinstance(error, exp.Expr)
5725            else self.sql(expression, "error")
5726        )
5727
5728        if error and empty:
5729            error = (
5730                f"{empty} {error}"
5731                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5732                else f"{error} {empty}"
5733            )
5734            empty = ""
5735
5736        null = self.sql(expression, "null")
5737
5738        return f"{empty}{error}{null}"
5739
5740    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5741        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5742        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5743
5744    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5745        this = self.sql(expression, "this")
5746        path = self.sql(expression, "path")
5747
5748        passing = self.expressions(expression, "passing")
5749        passing = f" PASSING {passing}" if passing else ""
5750
5751        on_condition = self.sql(expression, "on_condition")
5752        on_condition = f" {on_condition}" if on_condition else ""
5753
5754        path = f"{path}{passing}{on_condition}"
5755
5756        return self.func("JSON_EXISTS", this, path)
5757
5758    def _add_arrayagg_null_filter(
5759        self,
5760        array_agg_sql: str,
5761        array_agg_expr: exp.ArrayAgg,
5762        column_expr: exp.Expr,
5763    ) -> str:
5764        """
5765        Add NULL filter to ARRAY_AGG if dialect requires it.
5766
5767        Args:
5768            array_agg_sql: The generated ARRAY_AGG SQL string
5769            array_agg_expr: The ArrayAgg expression node
5770            column_expr: The column/expression to filter (before ORDER BY wrapping)
5771
5772        Returns:
5773            SQL string with FILTER clause added if needed
5774        """
5775        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5776        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5777        if not (
5778            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5779        ):
5780            return array_agg_sql
5781
5782        parent = array_agg_expr.parent
5783        if isinstance(parent, exp.Filter):
5784            parent_cond = parent.expression.this
5785            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5786        elif column_expr.find(exp.Column):
5787            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5788            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5789            this_sql = (
5790                self.expressions(column_expr)
5791                if isinstance(column_expr, exp.Distinct)
5792                else self.sql(column_expr)
5793            )
5794            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5795
5796        return array_agg_sql
5797
5798    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5799        array_agg = self.function_fallback_sql(expression)
5800        column_expr = expression.this
5801        if isinstance(column_expr, exp.Order):
5802            column_expr = column_expr.this
5803
5804        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5805
5806    def slice_sql(self, expression: exp.Slice) -> str:
5807        step = self.sql(expression, "step")
5808        end = self.sql(expression.expression)
5809        begin = self.sql(expression.this)
5810
5811        sql = f"{end}:{step}" if step else end
5812        return f"{begin}:{sql}" if sql else f"{begin}:"
5813
5814    def apply_sql(self, expression: exp.Apply) -> str:
5815        this = self.sql(expression, "this")
5816        expr = self.sql(expression, "expression")
5817
5818        return f"{this} APPLY({expr})"
5819
5820    def _grant_or_revoke_sql(
5821        self,
5822        expression: exp.Grant | exp.Revoke,
5823        keyword: str,
5824        preposition: str,
5825        grant_option_prefix: str = "",
5826        grant_option_suffix: str = "",
5827    ) -> str:
5828        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5829
5830        kind = self.sql(expression, "kind")
5831        kind = f" {kind}" if kind else ""
5832
5833        securable = self.sql(expression, "securable")
5834        securable = f" {securable}" if securable else ""
5835
5836        principals = self.expressions(expression, key="principals", flat=True)
5837
5838        if not expression.args.get("grant_option"):
5839            grant_option_prefix = grant_option_suffix = ""
5840
5841        # cascade for revoke only
5842        cascade = self.sql(expression, "cascade")
5843        cascade = f" {cascade}" if cascade else ""
5844
5845        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5846
5847    def grant_sql(self, expression: exp.Grant) -> str:
5848        return self._grant_or_revoke_sql(
5849            expression,
5850            keyword="GRANT",
5851            preposition="TO",
5852            grant_option_suffix=" WITH GRANT OPTION",
5853        )
5854
5855    def revoke_sql(self, expression: exp.Revoke) -> str:
5856        return self._grant_or_revoke_sql(
5857            expression,
5858            keyword="REVOKE",
5859            preposition="FROM",
5860            grant_option_prefix="GRANT OPTION FOR ",
5861        )
5862
5863    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5864        this = self.sql(expression, "this")
5865        columns = self.expressions(expression, flat=True)
5866        columns = f"({columns})" if columns else ""
5867
5868        return f"{this}{columns}"
5869
5870    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5871        this = self.sql(expression, "this")
5872
5873        kind = self.sql(expression, "kind")
5874        kind = f"{kind} " if kind else ""
5875
5876        return f"{kind}{this}"
5877
5878    def columns_sql(self, expression: exp.Columns) -> str:
5879        func = self.function_fallback_sql(expression)
5880        if expression.args.get("unpack"):
5881            func = f"*{func}"
5882
5883        return func
5884
5885    def overlay_sql(self, expression: exp.Overlay) -> str:
5886        this = self.sql(expression, "this")
5887        expr = self.sql(expression, "expression")
5888        from_sql = self.sql(expression, "from_")
5889        for_sql = self.sql(expression, "for_")
5890        for_sql = f" FOR {for_sql}" if for_sql else ""
5891
5892        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5893
5894    @unsupported_args("format")
5895    def todouble_sql(self, expression: exp.ToDouble) -> str:
5896        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5897        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5898
5899    def string_sql(self, expression: exp.String) -> str:
5900        this = expression.this
5901        zone = expression.args.get("zone")
5902
5903        if zone:
5904            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5905            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5906            # set for source_tz to transpile the time conversion before the STRING cast
5907            this = exp.ConvertTimezone(
5908                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5909            )
5910
5911        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5912
5913    def median_sql(self, expression: exp.Median) -> str:
5914        if not self.SUPPORTS_MEDIAN:
5915            return self.sql(
5916                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5917            )
5918
5919        return self.function_fallback_sql(expression)
5920
5921    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5922        filler = self.sql(expression, "this")
5923        filler = f" {filler}" if filler else ""
5924        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5925        return f"TRUNCATE{filler} {with_count}"
5926
5927    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5928        if self.SUPPORTS_UNIX_SECONDS:
5929            return self.function_fallback_sql(expression)
5930
5931        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5932
5933        return self.sql(
5934            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5935        )
5936
5937    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5938        dim = expression.expression
5939
5940        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5941        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5942            if not (dim.is_int and dim.name == "1"):
5943                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5944            dim = None
5945
5946        # If dimension is required but not specified, default initialize it
5947        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5948            dim = exp.Literal.number(1)
5949
5950        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5951
5952    def attach_sql(self, expression: exp.Attach) -> str:
5953        this = self.sql(expression, "this")
5954        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5955        expressions = self.expressions(expression)
5956        expressions = f" ({expressions})" if expressions else ""
5957
5958        return f"ATTACH{exists_sql} {this}{expressions}"
5959
5960    def detach_sql(self, expression: exp.Detach) -> str:
5961        kind = self.sql(expression, "kind")
5962        kind = f" {kind}" if kind else ""
5963        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5964        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5965        exists = " IF EXISTS" if expression.args.get("exists") else ""
5966        if exists:
5967            kind = kind or " DATABASE"
5968
5969        this = self.sql(expression, "this")
5970        this = f" {this}" if this else ""
5971        cluster = self.sql(expression, "cluster")
5972        cluster = f" {cluster}" if cluster else ""
5973        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5974        sync = " SYNC" if expression.args.get("sync") else ""
5975        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5976
5977    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5978        this = self.sql(expression, "this")
5979        value = self.sql(expression, "expression")
5980        value = f" {value}" if value else ""
5981        return f"{this}{value}"
5982
5983    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5984        return (
5985            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5986        )
5987
5988    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5989        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5990        encode = f"{encode} {self.sql(expression, 'this')}"
5991
5992        properties = expression.args.get("properties")
5993        if properties:
5994            encode = f"{encode} {self.properties(properties)}"
5995
5996        return encode
5997
5998    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5999        this = self.sql(expression, "this")
6000        include = f"INCLUDE {this}"
6001
6002        column_def = self.sql(expression, "column_def")
6003        if column_def:
6004            include = f"{include} {column_def}"
6005
6006        alias = self.sql(expression, "alias")
6007        if alias:
6008            include = f"{include} AS {alias}"
6009
6010        return include
6011
6012    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6013        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6014        name = f"{prefix} {self.sql(expression, 'this')}"
6015        return self.func("XMLELEMENT", name, *expression.expressions)
6016
6017    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6018        this = self.sql(expression, "this")
6019        expr = self.sql(expression, "expression")
6020        expr = f"({expr})" if expr else ""
6021        return f"{this}{expr}"
6022
6023    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6024        partitions = self.expressions(expression, "partition_expressions")
6025        create = self.expressions(expression, "create_expressions")
6026        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6027
6028    def partitionbyrangepropertydynamic_sql(
6029        self, expression: exp.PartitionByRangePropertyDynamic
6030    ) -> str:
6031        start = self.sql(expression, "start")
6032        end = self.sql(expression, "end")
6033
6034        every = expression.args["every"]
6035        if isinstance(every, exp.Interval) and every.this.is_string:
6036            every.this.replace(exp.Literal.number(every.name))
6037
6038        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6039
6040    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6041        name = self.sql(expression, "this")
6042        values = self.expressions(expression, flat=True)
6043
6044        return f"NAME {name} VALUE {values}"
6045
6046    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6047        kind = self.sql(expression, "kind")
6048        sample = self.sql(expression, "sample")
6049        return f"SAMPLE {sample} {kind}"
6050
6051    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6052        kind = self.sql(expression, "kind")
6053        option = self.sql(expression, "option")
6054        option = f" {option}" if option else ""
6055        this = self.sql(expression, "this")
6056        this = f" {this}" if this else ""
6057        columns = self.expressions(expression)
6058        columns = f" {columns}" if columns else ""
6059        return f"{kind}{option} STATISTICS{this}{columns}"
6060
6061    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6062        this = self.sql(expression, "this")
6063        columns = self.expressions(expression)
6064        inner_expression = self.sql(expression, "expression")
6065        inner_expression = f" {inner_expression}" if inner_expression else ""
6066        update_options = self.sql(expression, "update_options")
6067        update_options = f" {update_options} UPDATE" if update_options else ""
6068        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6069
6070    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6071        kind = self.sql(expression, "kind")
6072        kind = f" {kind}" if kind else ""
6073        return f"DELETE{kind} STATISTICS"
6074
6075    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6076        inner_expression = self.sql(expression, "expression")
6077        return f"LIST CHAINED ROWS{inner_expression}"
6078
6079    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6080        kind = self.sql(expression, "kind")
6081        this = self.sql(expression, "this")
6082        this = f" {this}" if this else ""
6083        inner_expression = self.sql(expression, "expression")
6084        return f"VALIDATE {kind}{this}{inner_expression}"
6085
6086    def analyze_sql(self, expression: exp.Analyze) -> str:
6087        options = self.expressions(expression, key="options", sep=" ")
6088        options = f" {options}" if options else ""
6089        kind = self.sql(expression, "kind")
6090        kind = f" {kind}" if kind else ""
6091        tables = self.expressions(expression, key="tables", flat=True)
6092        tables = f" {tables}" if tables else ""
6093        mode = self.sql(expression, "mode")
6094        mode = f" {mode}" if mode else ""
6095        properties = self.sql(expression, "properties")
6096        properties = f" {properties}" if properties else ""
6097        partition = self.sql(expression, "partition")
6098        partition = f" {partition}" if partition else ""
6099        inner_expression = self.sql(expression, "expression")
6100        inner_expression = f" {inner_expression}" if inner_expression else ""
6101        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6102
6103    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6104        this = self.sql(expression, "this")
6105        namespaces = self.expressions(expression, key="namespaces")
6106        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6107        passing = self.expressions(expression, key="passing")
6108        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6109        columns = self.expressions(expression, key="columns")
6110        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6111        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6112        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6113
6114    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6115        this = self.sql(expression, "this")
6116        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6117
6118    def export_sql(self, expression: exp.Export) -> str:
6119        this = self.sql(expression, "this")
6120        connection = self.sql(expression, "connection")
6121        connection = f"WITH CONNECTION {connection} " if connection else ""
6122        options = self.sql(expression, "options")
6123        return f"EXPORT DATA {connection}{options} AS {this}"
6124
6125    def declare_sql(self, expression: exp.Declare) -> str:
6126        replace = "OR REPLACE " if expression.args.get("replace") else ""
6127        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6128
6129    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6130        variables = self.expressions(expression, "this")
6131        default = self.sql(expression, "default")
6132        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6133
6134        kind = self.sql(expression, "kind")
6135        if isinstance(expression.args.get("kind"), exp.Schema):
6136            kind = f"TABLE {kind}"
6137
6138        kind = f" {kind}" if kind else ""
6139
6140        return f"{variables}{kind}{default}"
6141
6142    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6143        kind = self.sql(expression, "kind")
6144        this = self.sql(expression, "this")
6145        set = self.sql(expression, "expression")
6146        using = self.sql(expression, "using")
6147        using = f" USING {using}" if using else ""
6148
6149        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6150
6151        return f"{kind_sql} {this} SET {set}{using}"
6152
6153    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6154        params = self.expressions(expression, key="params", flat=True)
6155        return self.func(expression.name, *expression.expressions) + f"({params})"
6156
6157    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6158        return self.func(expression.name, *expression.expressions)
6159
6160    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6161        return self.anonymousaggfunc_sql(expression)
6162
6163    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6164        return self.parameterizedagg_sql(expression)
6165
6166    def show_sql(self, expression: exp.Show) -> str:
6167        self.unsupported("Unsupported SHOW statement")
6168        return ""
6169
6170    def install_sql(self, expression: exp.Install) -> str:
6171        self.unsupported("Unsupported INSTALL statement")
6172        return ""
6173
6174    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6175        # Snowflake GET/PUT statements:
6176        #   PUT <file> <internalStage> <properties>
6177        #   GET <internalStage> <file> <properties>
6178        props = expression.args.get("properties")
6179        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6180        this = self.sql(expression, "this")
6181        target = self.sql(expression, "target")
6182
6183        if isinstance(expression, exp.Put):
6184            return f"PUT {this} {target}{props_sql}"
6185        else:
6186            return f"GET {target} {this}{props_sql}"
6187
6188    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6189        this = self.sql(expression, "this")
6190        expr = self.sql(expression, "expression")
6191        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6192        return f"TRANSLATE({this} USING {expr}{with_error})"
6193
6194    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6195        if self.SUPPORTS_DECODE_CASE:
6196            return self.func("DECODE", *expression.expressions)
6197
6198        decode_expr, *expressions = expression.expressions
6199
6200        ifs = []
6201        for search, result in zip(expressions[::2], expressions[1::2]):
6202            if isinstance(search, exp.Literal):
6203                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6204            elif isinstance(search, exp.Null):
6205                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6206            else:
6207                if isinstance(search, exp.Binary):
6208                    search = exp.paren(search)
6209
6210                cond = exp.or_(
6211                    decode_expr.eq(search),
6212                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6213                    copy=False,
6214                )
6215                ifs.append(exp.If(this=cond, true=result))
6216
6217        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6218        return self.sql(case)
6219
6220    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6221        this = self.sql(expression, "this")
6222        this = self.seg(this, sep="")
6223        dimensions = self.expressions(
6224            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6225        )
6226        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6227        metrics = self.expressions(
6228            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6229        )
6230        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6231        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6232        facts = self.seg(f"FACTS {facts}") if facts else ""
6233        where = self.sql(expression, "where")
6234        where = self.seg(f"WHERE {where}") if where else ""
6235        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6236        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6237
6238    def getextract_sql(self, expression: exp.GetExtract) -> str:
6239        this = expression.this
6240        expr = expression.expression
6241
6242        if not this.type or not expression.type:
6243            import sqlglot.optimizer.annotate_types
6244
6245            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6246
6247        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6248            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6249
6250        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6251
6252    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6253        return self.sql(
6254            exp.DateAdd(
6255                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6256                expression=expression.this,
6257                unit=exp.var("DAY"),
6258            )
6259        )
6260
6261    def space_sql(self: Generator, expression: exp.Space) -> str:
6262        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6263
6264    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6265        return f"BUILD {self.sql(expression, 'this')}"
6266
6267    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6268        method = self.sql(expression, "method")
6269        kind = expression.args.get("kind")
6270        if not kind:
6271            return f"REFRESH {method}"
6272
6273        every = self.sql(expression, "every")
6274        unit = self.sql(expression, "unit")
6275        every = f" EVERY {every} {unit}" if every else ""
6276        starts = self.sql(expression, "starts")
6277        starts = f" STARTS {starts}" if starts else ""
6278
6279        return f"REFRESH {method} ON {kind}{every}{starts}"
6280
6281    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6282        self.unsupported("The model!attribute syntax is not supported")
6283        return ""
6284
6285    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6286        return self.func("DIRECTORY", expression.this)
6287
6288    def uuid_sql(self, expression: exp.Uuid) -> str:
6289        is_string = expression.args.get("is_string", False)
6290        uuid_func_sql = self.func("UUID")
6291
6292        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6293            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6294
6295        return uuid_func_sql
6296
6297    def initcap_sql(self, expression: exp.Initcap) -> str:
6298        delimiters = expression.expression
6299
6300        if delimiters:
6301            # do not generate delimiters arg if we are round-tripping from default delimiters
6302            if (
6303                delimiters.is_string
6304                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6305            ):
6306                delimiters = None
6307            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6308                self.unsupported("INITCAP does not support custom delimiters")
6309                delimiters = None
6310
6311        return self.func("INITCAP", expression.this, delimiters)
6312
6313    def localtime_sql(self, expression: exp.Localtime) -> str:
6314        this = expression.this
6315        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6316
6317    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6318        this = expression.this
6319        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6320
6321    def weekstart_name(self, expression: exp.WeekStart) -> str:
6322        import sqlglot.dialects.dialect
6323
6324        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6325        this = expression.this.name.upper()
6326
6327        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6328        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6329
6330        if dow_from_week_start_day != dow_from_week_offset:
6331            self.unsupported(
6332                f"WEEK({this}) is not supported; falling back to the default week start day"
6333            )
6334
6335        return "WEEK"
6336
6337    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6338        name = self.weekstart_name(expression)
6339
6340        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6341        if isinstance(expression.parent, exp.DateTrunc):
6342            return self.sql(exp.Literal.string(name))
6343
6344        return name
6345
6346    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6347        this = self.expressions(expression)
6348        charset = self.sql(expression, "charset")
6349        using = f" USING {charset}" if charset else ""
6350        return self.func(name, this + using)
6351
6352    def block_sql(self, expression: exp.Block) -> str:
6353        expressions = self.expressions(expression, sep="; ", flat=True)
6354        begin = "BEGIN " if expression.args.get("begin") else ""
6355        return f"{begin}{expressions}" if expressions else ""
6356
6357    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6358        self.unsupported("Unsupported Inline UDFs syntax")
6359        return ""
6360
6361    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6362        self.unsupported("Unsupported Stored Procedure syntax")
6363        return ""
6364
6365    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6366        self.unsupported("Unsupported If block syntax")
6367        return ""
6368
6369    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6370        self.unsupported("Unsupported Case statement syntax")
6371        return ""
6372
6373    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6374        self.unsupported("Unsupported While block syntax")
6375        return ""
6376
6377    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6378        self.unsupported("Unsupported Loop block syntax")
6379        return ""
6380
6381    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6382        self.unsupported("Unsupported Repeat block syntax")
6383        return ""
6384
6385    def leave_sql(self, expression: exp.Leave) -> str:
6386        self.unsupported("Unsupported Leave syntax")
6387        return ""
6388
6389    def iterate_sql(self, expression: exp.Iterate) -> str:
6390        self.unsupported("Unsupported Iterate syntax")
6391        return ""
6392
6393    def execute_sql(self, expression: exp.Execute) -> str:
6394        self.unsupported("Unsupported Execute syntax")
6395        return ""
6396
6397    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6398        self.unsupported("Unsupported Execute syntax")
6399        return ""
6400
6401    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6402        props = self.expressions(expression, sep=" ")
6403        return f"MODIFY {props}"
6404
6405    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6406        kind = expression.args.get("kind")
6407        return f"USING {kind} {self.sql(expression, 'this')}"
6408
6409    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6410        this = self.sql(expression, "this")
6411        to = self.sql(expression, "to")
6412        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]]:
33def unsupported_args(
34    *args: str | tuple[str, str],
35) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
36    """
37    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
38    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
39    """
40    diagnostic_by_arg: dict[str, str | None] = {}
41    for arg in args:
42        if isinstance(arg, str):
43            diagnostic_by_arg[arg] = None
44        else:
45            diagnostic_by_arg[arg[0]] = arg[1]
46
47    def decorator(func: GeneratorMethod) -> GeneratorMethod:
48        @wraps(func)
49        def _func(generator: G, expression: E) -> str:
50            expression_name = expression.__class__.__name__
51            dialect_name = generator.dialect.__class__.__name__
52
53            for arg_name, diagnostic in diagnostic_by_arg.items():
54                if expression.args.get(arg_name):
55                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
56                        arg_name, expression_name, dialect_name
57                    )
58                    generator.unsupported(diagnostic)
59
60            return func(generator, expression)
61
62        return _func
63
64    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:
  98class Generator:
  99    """
 100    Generator converts a given syntax tree to the corresponding SQL string.
 101
 102    Args:
 103        pretty: Whether to format the produced SQL string.
 104            Default: False.
 105        identify: Determines when an identifier should be quoted. Possible values are:
 106            False (default): Never quote, except in cases where it's mandatory by the dialect.
 107            True: Always quote except for specials cases.
 108            'safe': Only quote identifiers that are case insensitive.
 109        normalize: Whether to normalize identifiers to lowercase.
 110            Default: False.
 111        pad: The pad size in a formatted string. For example, this affects the indentation of
 112            a projection in a query, relative to its nesting level.
 113            Default: 2.
 114        indent: The indentation size in a formatted string. For example, this affects the
 115            indentation of subqueries and filters under a `WHERE` clause.
 116            Default: 2.
 117        normalize_functions: How to normalize function names. Possible values are:
 118            "upper" or True (default): Convert names to uppercase.
 119            "lower": Convert names to lowercase.
 120            False: Disables function name normalization.
 121        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 122            Default ErrorLevel.WARN.
 123        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 124            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 125            Default: 3
 126        leading_comma: Whether the comma is leading or trailing in select expressions.
 127            This is only relevant when generating in pretty mode.
 128            Default: False
 129        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 130            The default is on the smaller end because the length only represents a segment and not the true
 131            line length.
 132            Default: 80
 133        comments: Whether to preserve comments in the output SQL code.
 134            Default: True
 135    """
 136
 137    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 138        **JSON_PATH_PART_TRANSFORMS,
 139        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 140        exp.AllowedValuesProperty: lambda self, e: (
 141            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 142        ),
 143        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 144        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 145        exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
 146        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 147        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 148        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 149        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 150        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 151        exp.BinaryColumnConstraint: lambda *_: "BINARY",
 152        exp.CaseSpecificColumnConstraint: lambda _, e: (
 153            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 154        ),
 155        exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
 156        exp.Ceil: lambda self, e: self.ceil_floor(e),
 157        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 158        exp.CharacterSetProperty: lambda self, e: (
 159            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 160        ),
 161        exp.ClusteredColumnConstraint: lambda self, e: (
 162            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 163        ),
 164        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 165        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 166        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 167        exp.ConvertToCharset: lambda self, e: self.func(
 168            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 169        ),
 170        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 171        exp.CredentialsProperty: lambda self, e: (
 172            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 173        ),
 174        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 175        exp.SessionUser: lambda *_: "SESSION_USER",
 176        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 177        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 178        exp.ApiProperty: lambda *_: "API",
 179        exp.ApplicationProperty: lambda *_: "APPLICATION",
 180        exp.CatalogProperty: lambda *_: "CATALOG",
 181        exp.ComputeProperty: lambda *_: "COMPUTE",
 182        exp.DatabaseProperty: lambda *_: "DATABASE",
 183        exp.DynamicProperty: lambda *_: "DYNAMIC",
 184        exp.EmptyProperty: lambda *_: "EMPTY",
 185        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 186        exp.EndStatement: lambda *_: "END",
 187        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 188        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 189        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 190        exp.EphemeralColumnConstraint: lambda self, e: (
 191            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 192        ),
 193        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 194        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 195        exp.Except: lambda self, e: self.set_operations(e),
 196        exp.ExternalProperty: lambda *_: "EXTERNAL",
 197        exp.Floor: lambda self, e: self.ceil_floor(e),
 198        exp.Get: lambda self, e: self.get_put_sql(e),
 199        exp.GlobalProperty: lambda *_: "GLOBAL",
 200        exp.HeapProperty: lambda *_: "HEAP",
 201        exp.HybridProperty: lambda *_: "HYBRID",
 202        exp.IcebergProperty: lambda *_: "ICEBERG",
 203        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 204        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 205        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 206        exp.Intersect: lambda self, e: self.set_operations(e),
 207        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 208        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 209        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 210        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 211        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 212        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 213        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 214        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 215        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 216        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 217        exp.LocationProperty: lambda self, e: self.naked_property(e),
 218        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 219        exp.MaskingProperty: lambda *_: "MASKING",
 220        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 221        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 222        exp.NetworkProperty: lambda *_: "NETWORK",
 223        exp.NonClusteredColumnConstraint: lambda self, e: (
 224            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 225        ),
 226        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 227        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 228        exp.OnCommitProperty: lambda _, e: (
 229            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 230        ),
 231        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 232        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 233        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 234        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 235        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 236        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 237        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 238        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 239        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 240        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 241        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 242        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 243            f"PROJECTION POLICY {self.sql(e, 'this')}"
 244        ),
 245        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 246        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 247        exp.Put: lambda self, e: self.get_put_sql(e),
 248        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 249            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 250        ),
 251        exp.ReturnsProperty: lambda self, e: (
 252            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 253        ),
 254        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 255        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 256        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 257        exp.SecureProperty: lambda *_: "SECURE",
 258        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 259        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 260        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 261        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 262        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 263        exp.SqlReadWriteProperty: lambda _, e: e.name,
 264        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 265        exp.StabilityProperty: lambda _, e: e.name,
 266        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 267        exp.StreamingTableProperty: lambda *_: "STREAMING",
 268        exp.StrictProperty: lambda *_: "STRICT",
 269        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 270        exp.TableColumn: lambda self, e: self.sql(e.this),
 271        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 272        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 273        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 274        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 275        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 276        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 277        exp.TransientProperty: lambda *_: "TRANSIENT",
 278        exp.VirtualProperty: lambda *_: "VIRTUAL",
 279        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 280        exp.Union: lambda self, e: self.set_operations(e),
 281        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 282        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 283        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 284        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 285        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 286        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 287        exp.UtcTimestamp: lambda self, e: self.sql(
 288            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 289        ),
 290        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 291        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 292        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 293        exp.VolatileProperty: lambda *_: "VOLATILE",
 294        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 295        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 296        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 297        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 298        exp.ForceProperty: lambda *_: "FORCE",
 299    }
 300
 301    # Whether null ordering is supported in order by
 302    # True: Full Support, None: No support, False: No support for certain cases
 303    # such as window specifications, aggregate functions etc
 304    NULL_ORDERING_SUPPORTED: bool | None = True
 305
 306    # Window functions that support NULLS FIRST/LAST
 307    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 308
 309    # Whether ignore nulls is inside the agg or outside.
 310    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 311    IGNORE_NULLS_IN_FUNC = False
 312
 313    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 314    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 315    IGNORE_NULLS_BEFORE_ORDER = True
 316
 317    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 318    LOCKING_READS_SUPPORTED = False
 319
 320    # Whether the EXCEPT and INTERSECT operations can return duplicates
 321    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 322
 323    # Wrap derived values in parens, usually standard but spark doesn't support it
 324    WRAP_DERIVED_VALUES = True
 325
 326    # Whether create function uses an AS before the RETURN
 327    CREATE_FUNCTION_RETURN_AS = True
 328
 329    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 330    MATCHED_BY_SOURCE = True
 331
 332    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 333    SUPPORTS_MERGE_WHERE = False
 334
 335    # Whether the INTERVAL expression works only with values like '1 day'
 336    SINGLE_STRING_INTERVAL = False
 337
 338    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 339    INTERVAL_ALLOWS_PLURAL_FORM = True
 340
 341    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 342    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 343    AUTO_REFRESH_BARE_INTERVALS = False
 344
 345    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 346    LIMIT_FETCH = "ALL"
 347
 348    # Whether limit and fetch allows expresions or just limits
 349    LIMIT_ONLY_LITERALS = False
 350
 351    # Whether a table is allowed to be renamed with a db
 352    RENAME_TABLE_WITH_DB = True
 353
 354    # The separator for grouping sets and rollups
 355    GROUPINGS_SEP = ","
 356
 357    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 358    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 359
 360    # The string used for creating an index on a table
 361    INDEX_ON = "ON"
 362
 363    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 364    INOUT_SEPARATOR = " "
 365
 366    # Whether join hints should be generated
 367    JOIN_HINTS = True
 368
 369    # Whether directed joins are supported
 370    DIRECTED_JOINS = False
 371
 372    # Whether table hints should be generated
 373    TABLE_HINTS = True
 374
 375    # Whether query hints should be generated
 376    QUERY_HINTS = True
 377
 378    # What kind of separator to use for query hints
 379    QUERY_HINT_SEP = ", "
 380
 381    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 382    IS_BOOL_ALLOWED = True
 383
 384    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 385    DUPLICATE_KEY_UPDATE_WITH_SET = True
 386
 387    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 388    LIMIT_IS_TOP = False
 389
 390    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 391    RETURNING_END = True
 392
 393    # Whether to generate an unquoted value for EXTRACT's date part argument
 394    EXTRACT_ALLOWS_QUOTES = True
 395
 396    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 397    TZ_TO_WITH_TIME_ZONE = False
 398
 399    # Whether the NVL2 function is supported
 400    NVL2_SUPPORTED = True
 401
 402    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 403    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 404
 405    # Whether VALUES statements can be used as derived tables.
 406    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 407    # SELECT * VALUES into SELECT UNION
 408    VALUES_AS_TABLE = True
 409
 410    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 411    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 412
 413    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 414    UNNEST_WITH_ORDINALITY = True
 415
 416    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 417    SEMI_ANTI_JOIN_WITH_SIDE = True
 418
 419    # Whether to include the type of a computed column in the CREATE DDL
 420    COMPUTED_COLUMN_WITH_TYPE = True
 421
 422    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 423    SUPPORTS_TABLE_COPY = True
 424
 425    # Whether parentheses are required around the table sample's expression
 426    TABLESAMPLE_REQUIRES_PARENS = True
 427
 428    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 429    TABLESAMPLE_SIZE_IS_ROWS = True
 430
 431    # The keyword(s) to use when generating a sample clause
 432    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 433
 434    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 435    TABLESAMPLE_WITH_METHOD = True
 436
 437    # The keyword to use when specifying the seed of a sample clause
 438    TABLESAMPLE_SEED_KEYWORD = "SEED"
 439
 440    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 441    HISTORICAL_DATA_POST_ALIAS = False
 442
 443    # Whether COLLATE is a function instead of a binary operator
 444    COLLATE_IS_FUNC = False
 445
 446    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 447    DATA_TYPE_SPECIFIERS_ALLOWED = False
 448
 449    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 450    ENSURE_BOOLS = False
 451
 452    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 453    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 454
 455    # Whether CONCAT requires >1 arguments
 456    SUPPORTS_SINGLE_ARG_CONCAT = True
 457
 458    # Whether LAST_DAY function supports a date part argument
 459    LAST_DAY_SUPPORTS_DATE_PART = True
 460
 461    # Whether named columns are allowed in table aliases
 462    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 463
 464    # Whether named columns are allowed in CTE definitions
 465    SUPPORTS_NAMED_CTE_COLUMNS = True
 466
 467    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 468    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 469
 470    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 471    PIVOT_ALIAS_WITH_AS = True
 472
 473    # What delimiter to use for separating JSON key/value pairs
 474    JSON_KEY_VALUE_PAIR_SEP = ":"
 475
 476    # INSERT OVERWRITE TABLE x override
 477    INSERT_OVERWRITE = " OVERWRITE TABLE"
 478
 479    # Whether the SELECT .. INTO syntax is used instead of CTAS
 480    SUPPORTS_SELECT_INTO = False
 481
 482    # Whether UNLOGGED tables can be created
 483    SUPPORTS_UNLOGGED_TABLES = False
 484
 485    # Whether the CREATE TABLE LIKE statement is supported
 486    SUPPORTS_CREATE_TABLE_LIKE = True
 487
 488    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 489    SUPPORTS_MODIFY_COLUMN = False
 490
 491    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 492    SUPPORTS_CHANGE_COLUMN = False
 493
 494    # Whether ALTER COLUMN can set a column's nullability together with its type
 495    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 496
 497    # Whether ALTER COLUMN IF EXISTS is supported
 498    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 499
 500    # Whether the LikeProperty needs to be specified inside of the schema clause
 501    LIKE_PROPERTY_INSIDE_SCHEMA = False
 502
 503    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 504    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 505    MULTI_ARG_DISTINCT = True
 506
 507    # Whether the JSON extraction operators expect a value of type JSON
 508    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 509
 510    # Whether bracketed keys like ["foo"] are supported in JSON paths
 511    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 512
 513    # Whether to escape keys using single quotes in JSON paths
 514    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 515
 516    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 517    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 518    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 519    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 520    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 521
 522    # The JSONPathPart expressions supported by this dialect
 523    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 524
 525    # Whether any(f(x) for x in array) can be implemented by this dialect
 526    CAN_IMPLEMENT_ARRAY_ANY = False
 527
 528    # Whether the function TO_NUMBER is supported
 529    SUPPORTS_TO_NUMBER = True
 530
 531    # Whether EXCLUDE in window specification is supported
 532    SUPPORTS_WINDOW_EXCLUDE = False
 533
 534    # Whether or not set op modifiers apply to the outer set op or select.
 535    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 536    # True means limit 1 happens after the set op, False means it it happens on y.
 537    SET_OP_MODIFIERS = True
 538
 539    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 540    SET_OP_LIMITS = False
 541
 542    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 543    SET_OP_PARENTHESIZED_OPERANDS = True
 544
 545    # Whether parameters from COPY statement are wrapped in parentheses
 546    COPY_PARAMS_ARE_WRAPPED = True
 547
 548    # Whether values of params are set with "=" token or empty space
 549    COPY_PARAMS_EQ_REQUIRED = False
 550
 551    # Whether COPY statement has INTO keyword
 552    COPY_HAS_INTO_KEYWORD = True
 553
 554    # Whether the conditional TRY(expression) function is supported
 555    TRY_SUPPORTED = True
 556
 557    # Whether the UESCAPE syntax in unicode strings is supported
 558    SUPPORTS_UESCAPE = True
 559
 560    # Function used to replace escaped unicode codes in unicode strings
 561    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 562
 563    # The keyword to use when generating a star projection with excluded columns
 564    STAR_EXCEPT = "EXCEPT"
 565
 566    # The HEX function name
 567    HEX_FUNC = "HEX"
 568
 569    # The keywords to use when prefixing & separating WITH based properties
 570    WITH_PROPERTIES_PREFIX = "WITH"
 571
 572    # Whether to quote the generated expression of exp.JsonPath
 573    QUOTE_JSON_PATH = True
 574
 575    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 576    PAD_FILL_PATTERN_IS_REQUIRED = False
 577
 578    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 579    SUPPORTS_EXPLODING_PROJECTIONS = True
 580
 581    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 582    ARRAY_CONCAT_IS_VAR_LEN = True
 583
 584    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 585    SUPPORTS_CONVERT_TIMEZONE = False
 586
 587    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 588    SUPPORTS_MEDIAN = True
 589
 590    # Whether UNIX_SECONDS(timestamp) is supported
 591    SUPPORTS_UNIX_SECONDS = False
 592
 593    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 594    ALTER_SET_WRAPPED = False
 595
 596    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 597    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 598    # TODO: The normalization should be done by default once we've tested it across all dialects.
 599    NORMALIZE_EXTRACT_DATE_PARTS = False
 600
 601    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 602    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 603
 604    # The function name of the exp.ArraySize expression
 605    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 606
 607    # The syntax to use when altering the type of a column
 608    ALTER_SET_TYPE = "SET DATA TYPE"
 609
 610    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 611    # None -> Doesn't support it at all
 612    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 613    # True (Postgres) -> Explicitly requires it
 614    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 615
 616    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 617    SUPPORTS_DECODE_CASE = True
 618
 619    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 620    SUPPORTS_BETWEEN_FLAGS = False
 621
 622    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 623    SUPPORTS_LIKE_QUANTIFIERS = True
 624
 625    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 626    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 627
 628    # Whether to include the VARIABLE keyword for SET assignments
 629    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 630
 631    # The keyword to use for default value assignment in DECLARE statements
 632    DECLARE_DEFAULT_ASSIGNMENT = "="
 633
 634    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 635    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 636    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 637    UPDATE_STATEMENT_SUPPORTS_FROM = True
 638
 639    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 640    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 641
 642    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 643    # - Snowflake: DROP ICEBERG TABLE a.b;
 644    # - DuckDB:    DROP TABLE a.b;
 645    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 646
 647    TYPE_MAPPING: t.ClassVar = {
 648        exp.DType.DATETIME2: "TIMESTAMP",
 649        exp.DType.NCHAR: "CHAR",
 650        exp.DType.NVARCHAR: "VARCHAR",
 651        exp.DType.MEDIUMTEXT: "TEXT",
 652        exp.DType.LONGTEXT: "TEXT",
 653        exp.DType.TINYTEXT: "TEXT",
 654        exp.DType.BLOB: "VARBINARY",
 655        exp.DType.MEDIUMBLOB: "BLOB",
 656        exp.DType.LONGBLOB: "BLOB",
 657        exp.DType.TINYBLOB: "BLOB",
 658        exp.DType.INET: "INET",
 659        exp.DType.ROWVERSION: "VARBINARY",
 660        exp.DType.SMALLDATETIME: "TIMESTAMP",
 661    }
 662
 663    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 664
 665    # mapping of DType to its default parameters, bounds
 666    TYPE_PARAM_SETTINGS: t.ClassVar[
 667        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 668    ] = {}
 669
 670    TIME_PART_SINGULARS: t.ClassVar = {
 671        "MICROSECONDS": "MICROSECOND",
 672        "SECONDS": "SECOND",
 673        "MINUTES": "MINUTE",
 674        "HOURS": "HOUR",
 675        "DAYS": "DAY",
 676        "WEEKS": "WEEK",
 677        "MONTHS": "MONTH",
 678        "QUARTERS": "QUARTER",
 679        "YEARS": "YEAR",
 680    }
 681
 682    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 683        "cluster": lambda self, e: self.sql(e, "cluster"),
 684        "distribute": lambda self, e: self.sql(e, "distribute"),
 685        "sort": lambda self, e: self.sql(e, "sort"),
 686        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 687    }
 688
 689    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 690
 691    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 692
 693    PARAMETER_TOKEN = "@"
 694    NAMED_PLACEHOLDER_TOKEN = ":"
 695
 696    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 697
 698    PROPERTIES_LOCATION: t.ClassVar = {
 699        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 700        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 701        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 702        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 703        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 704        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 705        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 706        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 707        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 708        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 709        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 710        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 711        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 712        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 713        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 714        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 715        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 718        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 719        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 720        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 721        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 723        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 724        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 726        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 730        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 731        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 732        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 735        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 736        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 737        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 738        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 739        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 740        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 741        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 742        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 744        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 747        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 748        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 752        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 753        exp.LogProperty: exp.Properties.Location.POST_NAME,
 754        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 755        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 756        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 757        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 758        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 759        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 760        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 761        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 762        exp.Order: exp.Properties.Location.POST_SCHEMA,
 763        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 764        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 765        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 766        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 767        exp.Property: exp.Properties.Location.POST_WITH,
 768        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 770        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 771        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 772        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 773        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 776        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 777        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 778        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 779        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 780        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 781        exp.Set: exp.Properties.Location.POST_SCHEMA,
 782        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 783        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 784        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 785        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 786        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 787        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 788        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 789        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 790        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 791        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 792        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 793        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 794        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 795        exp.Tags: exp.Properties.Location.POST_WITH,
 796        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 797        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 798        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 799        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 800        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 801        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 802        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 803        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 804        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 805        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 806        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 807        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 808        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 809        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 810        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 811        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 812        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 813    }
 814
 815    # Keywords that can't be used as unquoted identifier names
 816    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 817
 818    # Exprs whose comments are separated from them for better formatting
 819    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 820        exp.Command,
 821        exp.Create,
 822        exp.Describe,
 823        exp.Delete,
 824        exp.Drop,
 825        exp.From,
 826        exp.Insert,
 827        exp.Join,
 828        exp.MultitableInserts,
 829        exp.Order,
 830        exp.Group,
 831        exp.Having,
 832        exp.Select,
 833        exp.SetOperation,
 834        exp.Update,
 835        exp.Where,
 836        exp.With,
 837    )
 838
 839    # Exprs that should not have their comments generated in maybe_comment
 840    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 841        exp.Binary,
 842        exp.SetOperation,
 843    )
 844
 845    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 846    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 847        exp.Column,
 848        exp.Literal,
 849        exp.Neg,
 850        exp.Paren,
 851    )
 852
 853    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 854        exp.DType.NVARCHAR,
 855        exp.DType.VARCHAR,
 856        exp.DType.CHAR,
 857        exp.DType.NCHAR,
 858    }
 859
 860    # Exprs that need to have all CTEs under them bubbled up to them
 861    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 862
 863    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 864
 865    MOD_OPERATOR = "%"
 866
 867    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 868    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 869        exp.Mul,
 870        exp.Div,
 871        exp.IntDiv,
 872        exp.Mod,
 873    )
 874
 875    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 876
 877    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 878
 879    __slots__ = (
 880        "pretty",
 881        "identify",
 882        "normalize",
 883        "pad",
 884        "_indent",
 885        "normalize_functions",
 886        "unsupported_level",
 887        "max_unsupported",
 888        "leading_comma",
 889        "max_text_width",
 890        "comments",
 891        "dialect",
 892        "unsupported_messages",
 893        "_escaped_quote_end",
 894        "_escaped_byte_quote_end",
 895        "_escaped_identifier_end",
 896        "_next_name",
 897        "_identifier_start",
 898        "_identifier_end",
 899        "_quote_json_path_key_using_brackets",
 900        "_dispatch",
 901    )
 902
 903    def __init__(
 904        self,
 905        pretty: bool | int | None = None,
 906        identify: str | bool = False,
 907        normalize: bool = False,
 908        pad: int = 2,
 909        indent: int = 2,
 910        normalize_functions: str | bool | None = None,
 911        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 912        max_unsupported: int = 3,
 913        leading_comma: bool = False,
 914        max_text_width: int = 80,
 915        comments: bool = True,
 916        dialect: DialectType = None,
 917    ):
 918        import sqlglot
 919        import sqlglot.dialects.dialect
 920
 921        self.pretty = pretty if pretty is not None else sqlglot.pretty
 922        self.identify = identify
 923        self.normalize = normalize
 924        self.pad = pad
 925        self._indent = indent
 926        self.unsupported_level = unsupported_level
 927        self.max_unsupported = max_unsupported
 928        self.leading_comma = leading_comma
 929        self.max_text_width = max_text_width
 930        self.comments = comments
 931        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 932
 933        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 934        self.normalize_functions = (
 935            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 936        )
 937
 938        self.unsupported_messages: list[str] = []
 939        self._escaped_quote_end: str = (
 940            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 941        )
 942        self._escaped_byte_quote_end: str = (
 943            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 944            if self.dialect.BYTE_END
 945            else ""
 946        )
 947        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 948
 949        self._next_name = name_sequence("_t")
 950
 951        self._identifier_start = self.dialect.IDENTIFIER_START
 952        self._identifier_end = self.dialect.IDENTIFIER_END
 953
 954        self._quote_json_path_key_using_brackets = True
 955
 956        cls = type(self)
 957        dispatch = _DISPATCH_CACHE.get(cls)
 958        if dispatch is None:
 959            dispatch = _build_dispatch(cls)
 960            _DISPATCH_CACHE[cls] = dispatch
 961        self._dispatch = dispatch
 962
 963    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 964        """
 965        Generates the SQL string corresponding to the given syntax tree.
 966
 967        Args:
 968            expression: The syntax tree.
 969            copy: Whether to copy the expression. The generator performs mutations so
 970                it is safer to copy.
 971
 972        Returns:
 973            The SQL string corresponding to `expression`.
 974        """
 975        if copy:
 976            expression = expression.copy()
 977
 978        expression = self.preprocess(expression)
 979
 980        self.unsupported_messages = []
 981        sql = self.sql(expression).strip()
 982
 983        if self.pretty:
 984            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 985
 986        if self.unsupported_level == ErrorLevel.IGNORE:
 987            return sql
 988
 989        if self.unsupported_level == ErrorLevel.WARN:
 990            for msg in self.unsupported_messages:
 991                logger.warning(msg)
 992        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 993            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 994
 995        return sql
 996
 997    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 998        """Apply generic preprocessing transformations to a given expression."""
 999        expression = self._move_ctes_to_top_level(expression)
1000
1001        if self.ENSURE_BOOLS:
1002            import sqlglot.transforms
1003
1004            expression = sqlglot.transforms.ensure_bools(expression)
1005
1006        return expression
1007
1008    def _move_ctes_to_top_level(self, expression: E) -> E:
1009        if (
1010            not expression.parent
1011            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1012            and any(node.parent is not expression for node in expression.find_all(exp.With))
1013        ):
1014            import sqlglot.transforms
1015
1016            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1017        return expression
1018
1019    def unsupported(self, message: str) -> None:
1020        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1021            raise UnsupportedError(message)
1022        self.unsupported_messages.append(message)
1023
1024    def sep(self, sep: str = " ") -> str:
1025        return f"{sep.strip()}\n" if self.pretty else sep
1026
1027    def seg(self, sql: str, sep: str = " ") -> str:
1028        return f"{self.sep(sep)}{sql}"
1029
1030    def sanitize_comment(self, comment: str) -> str:
1031        comment = " " + comment if comment[0].strip() else comment
1032        comment = comment + " " if comment[-1].strip() else comment
1033
1034        # Escape block comment markers to prevent premature closure or unintended nesting.
1035        # This is necessary because single-line comments (--) are converted to block comments
1036        # (/* */) on output, and any */ in the original text would close the comment early.
1037        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1038
1039        return comment
1040
1041    def maybe_comment(
1042        self,
1043        sql: str,
1044        expression: exp.Expr | None = None,
1045        comments: list[str] | None = None,
1046        separated: bool = False,
1047    ) -> str:
1048        comments = (
1049            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1050            if self.comments
1051            else None
1052        )
1053
1054        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1055            return sql
1056
1057        comments_list = [
1058            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1059            for comment in comments
1060            if comment
1061        ]
1062
1063        if not comments_list:
1064            return sql
1065
1066        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1067            comments_sql = self.sep().join(comments_list)
1068            return (
1069                f"{self.sep()}{comments_sql}{sql}"
1070                if not sql or sql[0].isspace()
1071                else f"{comments_sql}{self.sep()}{sql}"
1072            )
1073
1074        return f"{sql} {' '.join(comments_list)}"
1075
1076    def wrap(self, expression: exp.Expr | str) -> str:
1077        this_sql = (
1078            self.sql(expression)
1079            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1080            else self.sql(expression, "this")
1081        )
1082        if not this_sql:
1083            return "()"
1084
1085        this_sql = self.indent(this_sql, level=1, pad=0)
1086        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1087
1088    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1089        original = self.identify
1090        self.identify = False
1091        result = func(*args, **kwargs)
1092        self.identify = original
1093        return result
1094
1095    def normalize_func(self, name: str) -> str:
1096        if self.normalize_functions == "upper" or self.normalize_functions is True:
1097            return name.upper()
1098        if self.normalize_functions == "lower":
1099            return name.lower()
1100        return name
1101
1102    def indent(
1103        self,
1104        sql: str,
1105        level: int = 0,
1106        pad: int | None = None,
1107        skip_first: bool = False,
1108        skip_last: bool = False,
1109    ) -> str:
1110        if not self.pretty or not sql:
1111            return sql
1112
1113        pad = self.pad if pad is None else pad
1114        lines = sql.split("\n")
1115
1116        return "\n".join(
1117            (
1118                line
1119                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1120                else f"{' ' * (level * self._indent + pad)}{line}"
1121            )
1122            for i, line in enumerate(lines)
1123        )
1124
1125    def sql(
1126        self,
1127        expression: str | exp.Expr | None,
1128        key: str | None = None,
1129        comment: bool = True,
1130    ) -> str:
1131        if not expression:
1132            return ""
1133
1134        if isinstance(expression, str):
1135            return expression
1136
1137        if key:
1138            value = expression.args.get(key)
1139            if value:
1140                return self.sql(value)
1141            return ""
1142
1143        handler = self._dispatch.get(expression.__class__)
1144
1145        if handler:
1146            sql = handler(self, expression)
1147        elif isinstance(expression, exp.Func):
1148            sql = self.function_fallback_sql(expression)
1149        elif isinstance(expression, exp.Property):
1150            sql = self.property_sql(expression)
1151        else:
1152            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1153
1154        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1155
1156    def uncache_sql(self, expression: exp.Uncache) -> str:
1157        table = self.sql(expression, "this")
1158        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1159        return f"UNCACHE TABLE{exists_sql} {table}"
1160
1161    def cache_sql(self, expression: exp.Cache) -> str:
1162        lazy = " LAZY" if expression.args.get("lazy") else ""
1163        table = self.sql(expression, "this")
1164        options = expression.args.get("options")
1165        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1166        sql = self.sql(expression, "expression")
1167        sql = f" AS{self.sep()}{sql}" if sql else ""
1168        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1169        return self.prepend_ctes(expression, sql)
1170
1171    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1172        default = "DEFAULT " if expression.args.get("default") else ""
1173        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1174
1175    def column_parts(self, expression: exp.Column) -> str:
1176        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1177            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1178            return self.sql(expression, "this")
1179
1180        return ".".join(
1181            self.sql(part)
1182            for part in (
1183                expression.args.get("catalog"),
1184                expression.args.get("db"),
1185                expression.args.get("table"),
1186                expression.args.get("this"),
1187            )
1188            if part
1189        )
1190
1191    def column_sql(self, expression: exp.Column) -> str:
1192        join_mark = " (+)" if expression.args.get("join_mark") else ""
1193
1194        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1195            join_mark = ""
1196            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1197
1198        return f"{self.column_parts(expression)}{join_mark}"
1199
1200    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1201        return self.column_sql(expression)
1202
1203    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1204        this = self.sql(expression, "this")
1205        this = f" {this}" if this else ""
1206        position = self.sql(expression, "position")
1207        return f"{position}{this}"
1208
1209    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1210        column = self.sql(expression, "this")
1211        kind = self.sql(expression, "kind")
1212        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1213        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1214        kind = f"{sep}{kind}" if kind else ""
1215        constraints = f" {constraints}" if constraints else ""
1216        position = self.sql(expression, "position")
1217        position = f" {position}" if position else ""
1218
1219        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1220            kind = ""
1221
1222        return f"{exists}{column}{kind}{constraints}{position}"
1223
1224    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1225        this = self.sql(expression, "this")
1226        kind_sql = self.sql(expression, "kind").strip()
1227        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1228
1229    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1230        this = self.sql(expression, "this")
1231        if expression.args.get("not_null"):
1232            persisted = " PERSISTED NOT NULL"
1233        elif expression.args.get("persisted"):
1234            persisted = " PERSISTED"
1235        else:
1236            persisted = ""
1237
1238        return f"AS {this}{persisted}"
1239
1240    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1241        return self.token_sql(TokenType.AUTO_INCREMENT)
1242
1243    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1244        if isinstance(expression.this, list):
1245            this = self.wrap(self.expressions(expression, key="this", flat=True))
1246        else:
1247            this = self.sql(expression, "this")
1248
1249        return f"COMPRESS {this}"
1250
1251    def generatedasidentitycolumnconstraint_sql(
1252        self, expression: exp.GeneratedAsIdentityColumnConstraint
1253    ) -> str:
1254        this = ""
1255        if expression.this is not None:
1256            on_null = " ON NULL" if expression.args.get("on_null") else ""
1257            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1258
1259        start = expression.args.get("start")
1260        start = f"START WITH {start}" if start else ""
1261        increment = expression.args.get("increment")
1262        increment = f" INCREMENT BY {increment}" if increment else ""
1263        minvalue = expression.args.get("minvalue")
1264        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1265        maxvalue = expression.args.get("maxvalue")
1266        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1267        cycle = expression.args.get("cycle")
1268        cycle_sql = ""
1269
1270        if cycle is not None:
1271            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1272            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1273
1274        sequence_opts = ""
1275        if start or increment or cycle_sql:
1276            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1277            sequence_opts = f" ({sequence_opts.strip()})"
1278
1279        expr = self.sql(expression, "expression")
1280        expr = f"({expr})" if expr else "IDENTITY"
1281
1282        return f"GENERATED{this} AS {expr}{sequence_opts}"
1283
1284    def generatedasrowcolumnconstraint_sql(
1285        self, expression: exp.GeneratedAsRowColumnConstraint
1286    ) -> str:
1287        start = "START" if expression.args.get("start") else "END"
1288        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1289        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1290
1291    def periodforsystemtimeconstraint_sql(
1292        self, expression: exp.PeriodForSystemTimeConstraint
1293    ) -> str:
1294        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1295
1296    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1297        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1298
1299    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1300        desc = expression.args.get("desc")
1301        if desc is not None:
1302            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1303        options = self.expressions(expression, key="options", flat=True, sep=" ")
1304        options = f" {options}" if options else ""
1305        return f"PRIMARY KEY{options}"
1306
1307    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1308        this = self.sql(expression, "this")
1309        this = f" {this}" if this else ""
1310        index_type = expression.args.get("index_type")
1311        index_type = f" USING {index_type}" if index_type else ""
1312        on_conflict = self.sql(expression, "on_conflict")
1313        on_conflict = f" {on_conflict}" if on_conflict else ""
1314        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1315        options = self.expressions(expression, key="options", flat=True, sep=" ")
1316        options = f" {options}" if options else ""
1317        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1318
1319    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1320        input_ = expression.args.get("input_")
1321        output = expression.args.get("output")
1322        variadic = expression.args.get("variadic")
1323
1324        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1325        if variadic:
1326            return "VARIADIC"
1327
1328        if input_ and output:
1329            return f"IN{self.INOUT_SEPARATOR}OUT"
1330        if input_:
1331            return "IN"
1332        if output:
1333            return "OUT"
1334
1335        return ""
1336
1337    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1338        return self.sql(expression, "this")
1339
1340    def create_sql(self, expression: exp.Create) -> str:
1341        kind = self.sql(expression, "kind")
1342        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1343
1344        properties = expression.args.get("properties")
1345
1346        if (
1347            kind == "TRIGGER"
1348            and properties
1349            and properties.expressions
1350            and isinstance(properties.expressions[0], exp.TriggerProperties)
1351            and properties.expressions[0].args.get("constraint")
1352        ):
1353            kind = f"CONSTRAINT {kind}"
1354
1355        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1356
1357        this = self.createable_sql(expression, properties_locs)
1358
1359        properties_sql = ""
1360        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1361            exp.Properties.Location.POST_WITH
1362        ):
1363            props_ast = exp.Properties(
1364                expressions=[
1365                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1366                    *properties_locs[exp.Properties.Location.POST_WITH],
1367                ]
1368            )
1369            props_ast.parent = expression
1370            properties_sql = self.sql(props_ast)
1371
1372            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1373                properties_sql = self.sep() + properties_sql
1374            elif not self.pretty:
1375                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1376                properties_sql = f" {properties_sql}"
1377
1378        begin = " BEGIN" if expression.args.get("begin") else ""
1379
1380        expression_sql = self.sql(expression, "expression")
1381        if expression_sql:
1382            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1383
1384            if not isinstance(expression.expression, exp.MacroOverloads) and (
1385                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1386            ):
1387                postalias_props_sql = ""
1388                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1389                    postalias_props_sql = self.properties(
1390                        exp.Properties(
1391                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1392                        ),
1393                        wrapped=False,
1394                    )
1395                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1396                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1397
1398        postindex_props_sql = ""
1399        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1400            postindex_props_sql = self.properties(
1401                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1402                wrapped=False,
1403                prefix=" ",
1404            )
1405
1406        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1407        indexes = f" {indexes}" if indexes else ""
1408        index_sql = indexes + postindex_props_sql
1409
1410        replace = " OR REPLACE" if expression.args.get("replace") else ""
1411        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1412        unique = " UNIQUE" if expression.args.get("unique") else ""
1413
1414        clustered = expression.args.get("clustered")
1415        if clustered is None:
1416            clustered_sql = ""
1417        elif clustered:
1418            clustered_sql = " CLUSTERED COLUMNSTORE"
1419        else:
1420            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1421
1422        postcreate_props_sql = ""
1423        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1424            postcreate_props_sql = self.properties(
1425                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1426                sep=" ",
1427                prefix=" ",
1428                wrapped=False,
1429            )
1430
1431        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1432
1433        postexpression_props_sql = ""
1434        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1435            postexpression_props_sql = self.properties(
1436                exp.Properties(
1437                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1438                ),
1439                sep=" ",
1440                prefix=" ",
1441                wrapped=False,
1442            )
1443
1444        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1445        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1446        no_schema_binding = (
1447            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1448        )
1449
1450        clone = self.sql(expression, "clone")
1451        clone = f" {clone}" if clone else ""
1452
1453        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1454            properties_expression = f"{expression_sql}{properties_sql}"
1455        else:
1456            properties_expression = f"{properties_sql}{expression_sql}"
1457
1458        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1459        return self.prepend_ctes(expression, expression_sql)
1460
1461    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1462        start = self.sql(expression, "start")
1463        start = f"START WITH {start}" if start else ""
1464        increment = self.sql(expression, "increment")
1465        increment = f" INCREMENT BY {increment}" if increment else ""
1466        minvalue = self.sql(expression, "minvalue")
1467        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1468        maxvalue = self.sql(expression, "maxvalue")
1469        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1470        owned = self.sql(expression, "owned")
1471        owned = f" OWNED BY {owned}" if owned else ""
1472
1473        cache = expression.args.get("cache")
1474        if cache is None:
1475            cache_str = ""
1476        elif cache is True:
1477            cache_str = " CACHE"
1478        else:
1479            cache_str = f" CACHE {cache}"
1480
1481        options = self.expressions(expression, key="options", flat=True, sep=" ")
1482        options = f" {options}" if options else ""
1483
1484        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1485
1486    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1487        timing = expression.args.get("timing", "")
1488        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1489        timing_events = f"{timing} {events}".strip() if timing or events else ""
1490
1491        parts = [timing_events, "ON", self.sql(expression, "table")]
1492
1493        if referenced_table := expression.args.get("referenced_table"):
1494            parts.extend(["FROM", self.sql(referenced_table)])
1495
1496        if deferrable := expression.args.get("deferrable"):
1497            parts.append(deferrable)
1498
1499        if initially := expression.args.get("initially"):
1500            parts.append(f"INITIALLY {initially}")
1501
1502        if referencing := expression.args.get("referencing"):
1503            parts.append(self.sql(referencing))
1504
1505        if for_each := expression.args.get("for_each"):
1506            parts.append(f"FOR EACH {for_each}")
1507
1508        if when := expression.args.get("when"):
1509            parts.append(f"WHEN ({self.sql(when)})")
1510
1511        parts.append(self.sql(expression, "execute"))
1512
1513        return self.sep().join(parts)
1514
1515    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1516        parts = []
1517
1518        if old_alias := expression.args.get("old"):
1519            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1520
1521        if new_alias := expression.args.get("new"):
1522            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1523
1524        return f"REFERENCING {' '.join(parts)}"
1525
1526    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1527        columns = expression.args.get("columns")
1528        if columns:
1529            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1530
1531        return self.sql(expression, "this")
1532
1533    def clone_sql(self, expression: exp.Clone) -> str:
1534        this = self.sql(expression, "this")
1535        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1536        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1537        return f"{shallow}{keyword} {this}"
1538
1539    def describe_sql(self, expression: exp.Describe) -> str:
1540        style = expression.args.get("style")
1541        style = f" {style}" if style else ""
1542        partition = self.sql(expression, "partition")
1543        partition = f" {partition}" if partition else ""
1544        format = self.sql(expression, "format")
1545        format = f" {format}" if format else ""
1546        as_json = " AS JSON" if expression.args.get("as_json") else ""
1547
1548        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1549
1550    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1551        tag = self.sql(expression, "tag")
1552        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1553
1554    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1555        with_ = self.sql(expression, "with_")
1556        if with_:
1557            sql = f"{with_}{self.sep()}{sql}"
1558        return sql
1559
1560    def with_sql(self, expression: exp.With) -> str:
1561        udfs = self.expressions(expression, key="udfs", flat=True)
1562        udfs = f"WITH {udfs}" if udfs else ""
1563
1564        sql = self.expressions(expression, flat=True)
1565
1566        recursive = (
1567            "RECURSIVE "
1568            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1569            else ""
1570        )
1571        search = self.sql(expression, "search")
1572        search = f" {search}" if search else ""
1573
1574        sql = f"WITH {recursive}{sql}{search}" if sql else ""
1575        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1576
1577    def cte_sql(self, expression: exp.CTE) -> str:
1578        alias = expression.args.get("alias")
1579        if alias:
1580            alias.add_comments(expression.pop_comments())
1581
1582        alias_sql = self.sql(expression, "alias")
1583
1584        materialized = expression.args.get("materialized")
1585        if materialized is False:
1586            materialized = "NOT MATERIALIZED "
1587        elif materialized:
1588            materialized = "MATERIALIZED "
1589
1590        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1591        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1592
1593        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1594
1595    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1596        alias = self.sql(expression, "this")
1597        columns = self.expressions(expression, key="columns", flat=True)
1598        columns = f"({columns})" if columns else ""
1599
1600        if (
1601            columns
1602            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1603            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1604        ):
1605            columns = ""
1606            self.unsupported("Named columns are not supported in table alias.")
1607
1608        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1609            alias = self._next_name()
1610
1611        return f"{alias}{columns}"
1612
1613    def bitstring_sql(self, expression: exp.BitString) -> str:
1614        this = self.sql(expression, "this")
1615        if self.dialect.BIT_START:
1616            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1617        return f"{int(this, 2)}"
1618
1619    def hexstring_sql(
1620        self, expression: exp.HexString, binary_function_repr: str | None = None
1621    ) -> str:
1622        this = self.sql(expression, "this")
1623        is_integer_type = expression.args.get("is_integer")
1624
1625        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1626            not self.dialect.HEX_START and not binary_function_repr
1627        ):
1628            # Integer representation will be returned if:
1629            # - The read dialect treats the hex value as integer literal but not the write
1630            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1631            return f"{int(this, 16)}"
1632
1633        if not is_integer_type:
1634            # Read dialect treats the hex value as BINARY/BLOB
1635            if binary_function_repr:
1636                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1637                return self.func(binary_function_repr, exp.Literal.string(this))
1638            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1639                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1640                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1641
1642        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1643
1644    def bytestring_sql(self, expression: exp.ByteString) -> str:
1645        this = self.sql(expression, "this")
1646        if self.dialect.BYTE_START:
1647            escaped_byte_string = self.escape_str(
1648                this,
1649                escape_backslash=False,
1650                delimiter=self.dialect.BYTE_END,
1651                escaped_delimiter=self._escaped_byte_quote_end,
1652                is_byte_string=True,
1653            )
1654            is_bytes = expression.args.get("is_bytes", False)
1655            delimited_byte_string = (
1656                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1657            )
1658            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1659                return self.sql(
1660                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1661                )
1662            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1663                return self.sql(
1664                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1665                )
1666
1667            return delimited_byte_string
1668
1669        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1670            return self.sql(exp.Literal.string(this))
1671
1672        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1673        return ""
1674
1675    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1676        this = self.sql(expression, "this")
1677        escape = expression.args.get("escape")
1678        unicode_start = self.dialect.UNICODE_START
1679
1680        if unicode_start:
1681            escape_substitute = r"\\\1"
1682            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1683        else:
1684            escape_substitute = r"\\u\1"
1685            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1686
1687        if escape:
1688            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1689            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1690        else:
1691            escape_pattern = ESCAPED_UNICODE_RE
1692            escape_sql = ""
1693
1694        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1695            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1696
1697        if unicode_start:
1698            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1699            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1700            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1701        else:
1702            this = self.escape_str(this, escape_backslash=False)
1703
1704        return f"{left_quote}{this}{right_quote}{escape_sql}"
1705
1706    def rawstring_sql(self, expression: exp.RawString) -> str:
1707        string = expression.this
1708        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1709            string = string.replace("\\", "\\\\")
1710
1711        string = self.escape_str(string, escape_backslash=False)
1712        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1713
1714    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1715        this = self.sql(expression, "this")
1716        specifier = self.sql(expression, "expression")
1717        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1718        return f"{this}{specifier}"
1719
1720    def datatype_param_bound_limiter(
1721        self,
1722        expression: exp.DataType,
1723        type_value: exp.DType,
1724        defaults: tuple[int, ...],
1725        bounds: tuple[int | None, ...],
1726    ) -> exp.DataType:
1727        params = expression.expressions
1728
1729        if not params:
1730            if defaults:
1731                expression.set(
1732                    "expressions",
1733                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1734                )
1735            return expression
1736
1737        if not bounds:
1738            return expression
1739
1740        for i, param in enumerate(params):
1741            bound = bounds[i] if i < len(bounds) else None
1742            if bound is None:
1743                continue
1744
1745            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1746            value = (
1747                param_value.to_py()
1748                if isinstance(param_value, exp.Literal) and param_value.is_number
1749                else None
1750            )
1751            if isinstance(value, (int, Decimal)) and value > bound:
1752                self.unsupported(
1753                    f"{type_value.value} parameter {param_value.name} exceeds "
1754                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1755                )
1756                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1757
1758        return expression
1759
1760    def datatype_sql(self, expression: exp.DataType) -> str:
1761        nested = ""
1762        values = ""
1763
1764        expr_nested = expression.args.get("nested")
1765        type_value = expression.this
1766
1767        if (
1768            not expr_nested
1769            and isinstance(type_value, exp.DType)
1770            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1771        ):
1772            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1773
1774        interior = (
1775            self.expressions(
1776                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1777            )
1778            if expr_nested and self.pretty
1779            else self.expressions(expression, flat=True)
1780        )
1781
1782        if type_value in self.UNSUPPORTED_TYPES:
1783            self.unsupported(
1784                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1785            )
1786
1787        type_sql: t.Any = ""
1788        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1789            type_sql = self.sql(expression, "kind")
1790        elif type_value == exp.DType.CHARACTER_SET:
1791            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1792        else:
1793            type_sql = (
1794                self.TYPE_MAPPING.get(type_value, type_value.value)
1795                if isinstance(type_value, exp.DType)
1796                else type_value
1797            )
1798
1799        if interior:
1800            if expr_nested:
1801                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1802                if expression.args.get("values") is not None:
1803                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1804                    values = self.expressions(expression, key="values", flat=True)
1805                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1806            elif type_value == exp.DType.INTERVAL:
1807                nested = f" {interior}"
1808            else:
1809                nested = f"({interior})"
1810
1811        type_sql = f"{type_sql}{nested}{values}"
1812        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1813            exp.DType.TIMETZ,
1814            exp.DType.TIMESTAMPTZ,
1815        ):
1816            type_sql = f"{type_sql} WITH TIME ZONE"
1817
1818        collate = self.sql(expression, "collate")
1819        if collate:
1820            type_sql = f"{type_sql} COLLATE {collate}"
1821
1822        return type_sql
1823
1824    def directory_sql(self, expression: exp.Directory) -> str:
1825        local = "LOCAL " if expression.args.get("local") else ""
1826        row_format = self.sql(expression, "row_format")
1827        row_format = f" {row_format}" if row_format else ""
1828        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1829
1830    def delete_sql(self, expression: exp.Delete) -> str:
1831        hint = self.sql(expression, "hint")
1832        this = self.sql(expression, "this")
1833        this = f" FROM {this}" if this else ""
1834        using = self.expressions(expression, key="using")
1835        using = f" USING {using}" if using else ""
1836        cluster = self.sql(expression, "cluster")
1837        cluster = f" {cluster}" if cluster else ""
1838        where = self.sql(expression, "where")
1839        returning = self.sql(expression, "returning")
1840        order = self.sql(expression, "order")
1841        limit = self.sql(expression, "limit")
1842        tables = self.expressions(expression, key="tables")
1843        tables = f" {tables}" if tables else ""
1844        if self.RETURNING_END:
1845            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1846        else:
1847            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1848        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1849
1850    def drop_sql(self, expression: exp.Drop) -> str:
1851        tables = self.expressions(expression, key="tables", flat=True)
1852        expressions = self.expressions(expression, flat=True)
1853        expressions = f" ({expressions})" if expressions else ""
1854        kind = expression.args["kind"]
1855        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1856        iceberg = (
1857            " ICEBERG"
1858            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1859            else ""
1860        )
1861        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1862        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1863        on_cluster = self.sql(expression, "cluster")
1864        on_cluster = f" {on_cluster}" if on_cluster else ""
1865        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1866        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1867        cascade = " CASCADE" if expression.args.get("cascade") else ""
1868        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1869        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1870        purge = " PURGE" if expression.args.get("purge") else ""
1871        sync = " SYNC" if expression.args.get("sync") else ""
1872        force = " FORCE" if expression.args.get("force") else ""
1873        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1874
1875    def set_operation(self, expression: exp.SetOperation) -> str:
1876        op_type = type(expression)
1877        op_name = op_type.key.upper()
1878
1879        distinct = expression.args.get("distinct")
1880        if (
1881            distinct is False
1882            and op_type in (exp.Except, exp.Intersect)
1883            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1884        ):
1885            self.unsupported(f"{op_name} ALL is not supported")
1886
1887        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1888
1889        if distinct is None:
1890            distinct = default_distinct
1891            if distinct is None:
1892                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1893
1894        if distinct is default_distinct:
1895            distinct_or_all = ""
1896        else:
1897            distinct_or_all = " DISTINCT" if distinct else " ALL"
1898
1899        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1900        side_kind = f"{side_kind} " if side_kind else ""
1901
1902        by_name = " BY NAME" if expression.args.get("by_name") else ""
1903        on = self.expressions(expression, key="on", flat=True)
1904        on = f" ON ({on})" if on else ""
1905
1906        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1907
1908    def set_operations(self, expression: exp.SetOperation) -> str:
1909        if not self.SET_OP_MODIFIERS:
1910            limit = expression.args.get("limit")
1911            order = expression.args.get("order")
1912            offset = expression.args.get("offset")
1913
1914            if limit or order or offset:
1915                select = self._move_ctes_to_top_level(
1916                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1917                )
1918
1919                for arg in ("limit", "order", "offset"):
1920                    if value := expression.args.get(arg):
1921                        select.set(arg, value.pop())
1922                return self.sql(select)
1923
1924        sqls: list[str] = []
1925        stack: list[str | exp.Expr] = [expression]
1926
1927        while stack:
1928            node = stack.pop()
1929
1930            if isinstance(node, exp.SetOperation):
1931                stack.append(node.expression)
1932                stack.append(
1933                    self.maybe_comment(
1934                        self.set_operation(node), comments=node.comments, separated=True
1935                    )
1936                )
1937                stack.append(node.this)
1938            else:
1939                if (
1940                    not self.SET_OP_LIMITS
1941                    and isinstance(node, exp.Select)
1942                    and node.args.get("limit")
1943                ):
1944                    node = node.subquery(copy=False)
1945                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1946                        node = exp.select("*").from_(node, copy=False)
1947                sqls.append(self.sql(node))
1948
1949        this = self.sep().join(sqls)
1950        this = self.query_modifiers(expression, this)
1951        return self.prepend_ctes(expression, this)
1952
1953    def fetch_sql(self, expression: exp.Fetch) -> str:
1954        direction = expression.args.get("direction")
1955        direction = f" {direction}" if direction else ""
1956        count = self.sql(expression, "count")
1957        count = f" {count}" if count else ""
1958        limit_options = self.sql(expression, "limit_options")
1959        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1960        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1961
1962    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1963        percent = " PERCENT" if expression.args.get("percent") else ""
1964        rows = " ROWS" if expression.args.get("rows") else ""
1965        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1966        if not with_ties and rows:
1967            with_ties = " ONLY"
1968        return f"{percent}{rows}{with_ties}"
1969
1970    def filter_sql(self, expression: exp.Filter) -> str:
1971        this = self.sql(expression, "this")
1972        where = self.sql(expression, "expression").strip()
1973        return f"{this} FILTER({where})"
1974
1975    def hint_sql(self, expression: exp.Hint) -> str:
1976        if not self.QUERY_HINTS:
1977            self.unsupported("Hints are not supported")
1978            return ""
1979
1980        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1981
1982    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1983        using = self.sql(expression, "using")
1984        using = f" USING {using}" if using else ""
1985        columns = self.expressions(expression, key="columns", flat=True)
1986        columns = f"({columns})" if columns else ""
1987        partition_by = self.expressions(expression, key="partition_by", flat=True)
1988        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1989        where = self.sql(expression, "where")
1990        include = self.expressions(expression, key="include", flat=True)
1991        if include:
1992            include = f" INCLUDE ({include})"
1993        with_storage = self.expressions(expression, key="with_storage", flat=True)
1994        with_storage = f" WITH ({with_storage})" if with_storage else ""
1995        tablespace = self.sql(expression, "tablespace")
1996        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1997        on = self.sql(expression, "on")
1998        on = f" ON {on}" if on else ""
1999
2000        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2001
2002    def index_sql(self, expression: exp.Index) -> str:
2003        unique = "UNIQUE " if expression.args.get("unique") else ""
2004        primary = "PRIMARY " if expression.args.get("primary") else ""
2005        amp = "AMP " if expression.args.get("amp") else ""
2006        name = self.sql(expression, "this")
2007        name = f"{name} " if name else ""
2008        table = self.sql(expression, "table")
2009        table = f"{self.INDEX_ON} {table}" if table else ""
2010
2011        index = "INDEX " if not table else ""
2012
2013        params = self.sql(expression, "params")
2014        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2015
2016    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2017        this = expression.this
2018        if this and this.is_string:
2019            resolved = maybe_parse(this.name).sql(self.dialect)
2020            if "expressions" in expression.args:
2021                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2022                # We can't safely emit the call to other dialects since name/arg semantics may differ
2023                self.unsupported(
2024                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2025                )
2026            return resolved
2027        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2028        return self.func("IDENTIFIER", this)
2029
2030    def identifier_sql(self, expression: exp.Identifier) -> str:
2031        text = expression.name
2032        lower = text.lower()
2033        quoted = expression.quoted
2034        text = lower if self.normalize and not quoted else text
2035        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2036        if (
2037            quoted
2038            or self.dialect.can_quote(expression, self.identify)
2039            or lower in self.RESERVED_KEYWORDS
2040            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2041        ):
2042            text = (
2043                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2044            )
2045        return text
2046
2047    def hex_sql(self, expression: exp.Hex) -> str:
2048        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2049        if self.dialect.HEX_LOWERCASE:
2050            text = self.func("LOWER", text)
2051
2052        return text
2053
2054    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2055        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2056        if not self.dialect.HEX_LOWERCASE:
2057            text = self.func("LOWER", text)
2058        return text
2059
2060    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2061        input_format = self.sql(expression, "input_format")
2062        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2063        output_format = self.sql(expression, "output_format")
2064        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2065        return self.sep().join((input_format, output_format))
2066
2067    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2068        string = self.sql(exp.Literal.string(expression.name))
2069        return f"{prefix}{string}"
2070
2071    def partition_sql(self, expression: exp.Partition) -> str:
2072        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2073        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2074
2075    def properties_sql(self, expression: exp.Properties) -> str:
2076        root_properties = []
2077        with_properties = []
2078
2079        for p in expression.expressions:
2080            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2081            if p_loc == exp.Properties.Location.POST_WITH:
2082                with_properties.append(p)
2083            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2084                root_properties.append(p)
2085
2086        root_props_ast = exp.Properties(expressions=root_properties)
2087        root_props_ast.parent = expression.parent
2088
2089        with_props_ast = exp.Properties(expressions=with_properties)
2090        with_props_ast.parent = expression.parent
2091
2092        root_props = self.root_properties(root_props_ast)
2093        with_props = self.with_properties(with_props_ast)
2094
2095        if root_props and with_props and not self.pretty:
2096            with_props = " " + with_props
2097
2098        return root_props + with_props
2099
2100    def root_properties(self, properties: exp.Properties) -> str:
2101        if properties.expressions:
2102            return self.expressions(properties, indent=False, sep=" ")
2103        return ""
2104
2105    def properties(
2106        self,
2107        properties: exp.Properties,
2108        prefix: str = "",
2109        sep: str = ", ",
2110        suffix: str = "",
2111        wrapped: bool = True,
2112    ) -> str:
2113        if properties.expressions:
2114            expressions = self.expressions(properties, sep=sep, indent=False)
2115            if expressions:
2116                expressions = self.wrap(expressions) if wrapped else expressions
2117                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2118        return ""
2119
2120    def with_properties(self, properties: exp.Properties) -> str:
2121        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2122
2123    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2124        properties_locs = defaultdict(list)
2125        for p in properties.expressions:
2126            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2127            if p_loc != exp.Properties.Location.UNSUPPORTED:
2128                properties_locs[p_loc].append(p)
2129            else:
2130                self.unsupported(f"Unsupported property {p.key}")
2131
2132        return properties_locs
2133
2134    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2135        if isinstance(expression.this, exp.Dot):
2136            return self.sql(expression, "this")
2137        return f"'{expression.name}'" if string_key else expression.name
2138
2139    def property_sql(self, expression: exp.Property) -> str:
2140        property_cls = expression.__class__
2141        if property_cls == exp.Property:
2142            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2143
2144        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2145        if not property_name:
2146            self.unsupported(f"Unsupported property {expression.key}")
2147
2148        return f"{property_name}={self.sql(expression, 'this')}"
2149
2150    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2151        return f"UUID {self.sql(expression, 'this')}"
2152
2153    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2154        if self.SUPPORTS_CREATE_TABLE_LIKE:
2155            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2156            options = f" {options}" if options else ""
2157
2158            like = f"LIKE {self.sql(expression, 'this')}{options}"
2159            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2160                like = f"({like})"
2161
2162            return like
2163
2164        if expression.expressions:
2165            self.unsupported("Transpilation of LIKE property options is unsupported")
2166
2167        select = exp.select("*").from_(expression.this).limit(0)
2168        return f"AS {self.sql(select)}"
2169
2170    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2171        no = "NO " if expression.args.get("no") else ""
2172        protection = " PROTECTION" if expression.args.get("protection") else ""
2173        return f"{no}FALLBACK{protection}"
2174
2175    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2176        no = "NO " if expression.args.get("no") else ""
2177        local = expression.args.get("local")
2178        local = f"{local} " if local else ""
2179        dual = "DUAL " if expression.args.get("dual") else ""
2180        before = "BEFORE " if expression.args.get("before") else ""
2181        after = "AFTER " if expression.args.get("after") else ""
2182        return f"{no}{local}{dual}{before}{after}JOURNAL"
2183
2184    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2185        freespace = self.sql(expression, "this")
2186        percent = " PERCENT" if expression.args.get("percent") else ""
2187        return f"FREESPACE={freespace}{percent}"
2188
2189    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2190        if expression.args.get("default"):
2191            property = "DEFAULT"
2192        elif expression.args.get("on"):
2193            property = "ON"
2194        else:
2195            property = "OFF"
2196        return f"CHECKSUM={property}"
2197
2198    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2199        if expression.args.get("no"):
2200            return "NO MERGEBLOCKRATIO"
2201        if expression.args.get("default"):
2202            return "DEFAULT MERGEBLOCKRATIO"
2203
2204        percent = " PERCENT" if expression.args.get("percent") else ""
2205        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2206
2207    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2208        expressions = self.expressions(expression, flat=True)
2209        expressions = f"({expressions})" if expressions else ""
2210        return f"USING {self.sql(expression, 'this')}{expressions}"
2211
2212    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2213        default = expression.args.get("default")
2214        minimum = expression.args.get("minimum")
2215        maximum = expression.args.get("maximum")
2216        if default or minimum or maximum:
2217            if default:
2218                prop = "DEFAULT"
2219            elif minimum:
2220                prop = "MINIMUM"
2221            else:
2222                prop = "MAXIMUM"
2223            return f"{prop} DATABLOCKSIZE"
2224        units = expression.args.get("units")
2225        units = f" {units}" if units else ""
2226        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2227
2228    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2229        autotemp = expression.args.get("autotemp")
2230        always = expression.args.get("always")
2231        default = expression.args.get("default")
2232        manual = expression.args.get("manual")
2233        never = expression.args.get("never")
2234
2235        if autotemp is not None:
2236            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2237        elif always:
2238            prop = "ALWAYS"
2239        elif default:
2240            prop = "DEFAULT"
2241        elif manual:
2242            prop = "MANUAL"
2243        elif never:
2244            prop = "NEVER"
2245        return f"BLOCKCOMPRESSION={prop}"
2246
2247    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2248        no = expression.args.get("no")
2249        no = " NO" if no else ""
2250        concurrent = expression.args.get("concurrent")
2251        concurrent = " CONCURRENT" if concurrent else ""
2252        target = self.sql(expression, "target")
2253        target = f" {target}" if target else ""
2254        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2255
2256    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2257        if isinstance(expression.this, list):
2258            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2259        if expression.this:
2260            modulus = self.sql(expression, "this")
2261            remainder = self.sql(expression, "expression")
2262            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2263
2264        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2265        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2266        return f"FROM ({from_expressions}) TO ({to_expressions})"
2267
2268    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2269        this = self.sql(expression, "this")
2270
2271        for_values_or_default = expression.expression
2272        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2273            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2274        else:
2275            for_values_or_default = " DEFAULT"
2276
2277        return f"PARTITION OF {this}{for_values_or_default}"
2278
2279    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2280        kind = expression.args.get("kind")
2281        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2282        for_or_in = expression.args.get("for_or_in")
2283        for_or_in = f" {for_or_in}" if for_or_in else ""
2284        lock_type = expression.args.get("lock_type")
2285        override = " OVERRIDE" if expression.args.get("override") else ""
2286        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2287
2288    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2289        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2290        statistics = expression.args.get("statistics")
2291        statistics_sql = ""
2292        if statistics is not None:
2293            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2294        return f"{data_sql}{statistics_sql}"
2295
2296    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2297        this = self.sql(expression, "this")
2298        this = f"HISTORY_TABLE={this}" if this else ""
2299        data_consistency: str | None = self.sql(expression, "data_consistency")
2300        data_consistency = (
2301            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2302        )
2303        retention_period: str | None = self.sql(expression, "retention_period")
2304        retention_period = (
2305            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2306        )
2307
2308        if this:
2309            on_sql = self.func("ON", this, data_consistency, retention_period)
2310        else:
2311            on_sql = "ON" if expression.args.get("on") else "OFF"
2312
2313        sql = f"SYSTEM_VERSIONING={on_sql}"
2314
2315        return f"WITH({sql})" if expression.args.get("with_") else sql
2316
2317    def insert_sql(self, expression: exp.Insert) -> str:
2318        hint = self.sql(expression, "hint")
2319        overwrite = expression.args.get("overwrite")
2320
2321        if isinstance(expression.this, exp.Directory):
2322            this = " OVERWRITE" if overwrite else " INTO"
2323        else:
2324            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2325
2326        stored = self.sql(expression, "stored")
2327        stored = f" {stored}" if stored else ""
2328        alternative = expression.args.get("alternative")
2329        alternative = f" OR {alternative}" if alternative else ""
2330        ignore = " IGNORE" if expression.args.get("ignore") else ""
2331        is_function = expression.args.get("is_function")
2332        if is_function:
2333            this = f"{this} FUNCTION"
2334        this = f"{this} {self.sql(expression, 'this')}"
2335
2336        exists = " IF EXISTS" if expression.args.get("exists") else ""
2337        where = self.sql(expression, "where")
2338        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2339        using = self.expressions(expression, key="using", flat=True)
2340        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2341        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2342        on_conflict = self.sql(expression, "conflict")
2343        on_conflict = f" {on_conflict}" if on_conflict else ""
2344        by_name = " BY NAME" if expression.args.get("by_name") else ""
2345        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2346        returning = self.sql(expression, "returning")
2347
2348        if self.RETURNING_END:
2349            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2350        else:
2351            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2352
2353        partition_by = self.sql(expression, "partition")
2354        partition_by = f" {partition_by}" if partition_by else ""
2355        settings = self.sql(expression, "settings")
2356        settings = f" {settings}" if settings else ""
2357
2358        source = self.sql(expression, "source")
2359        source = f"TABLE {source}" if source else ""
2360
2361        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2362        return self.prepend_ctes(expression, sql)
2363
2364    def introducer_sql(self, expression: exp.Introducer) -> str:
2365        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2366
2367    def kill_sql(self, expression: exp.Kill) -> str:
2368        kind = self.sql(expression, "kind")
2369        kind = f" {kind}" if kind else ""
2370        this = self.sql(expression, "this")
2371        this = f" {this}" if this else ""
2372        return f"KILL{kind}{this}"
2373
2374    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2375        return expression.name
2376
2377    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2378        return expression.name
2379
2380    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2381        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2382
2383        constraint = self.sql(expression, "constraint")
2384        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2385
2386        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2387        if conflict_keys:
2388            conflict_keys = f"({conflict_keys})"
2389
2390        index_predicate = self.sql(expression, "index_predicate")
2391        conflict_keys = f"{conflict_keys}{index_predicate} "
2392
2393        action = self.sql(expression, "action")
2394
2395        expressions = self.expressions(expression, flat=True)
2396        if expressions:
2397            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2398            expressions = f" {set_keyword}{expressions}"
2399
2400        where = self.sql(expression, "where")
2401        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2402
2403    def returning_sql(self, expression: exp.Returning) -> str:
2404        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2405
2406    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2407        fields = self.sql(expression, "fields")
2408        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2409        escaped = self.sql(expression, "escaped")
2410        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2411        items = self.sql(expression, "collection_items")
2412        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2413        keys = self.sql(expression, "map_keys")
2414        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2415        lines = self.sql(expression, "lines")
2416        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2417        null = self.sql(expression, "null")
2418        null = f" NULL DEFINED AS {null}" if null else ""
2419        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2420
2421    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2422        return f"WITH ({self.expressions(expression, flat=True)})"
2423
2424    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2425        this = f"{self.sql(expression, 'this')} INDEX"
2426        target = self.sql(expression, "target")
2427        target = f" FOR {target}" if target else ""
2428        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2429
2430    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2431        this = self.sql(expression, "this")
2432        kind = self.sql(expression, "kind")
2433        expr = self.sql(expression, "expression")
2434        return f"{this} ({kind} => {expr})"
2435
2436    def table_parts(self, expression: exp.Table) -> str:
2437        return ".".join(
2438            self.sql(part)
2439            for part in (
2440                expression.args.get("catalog"),
2441                expression.args.get("db"),
2442                expression.args.get("this"),
2443            )
2444            if part is not None
2445        )
2446
2447    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2448        table = self.table_parts(expression)
2449        only = "ONLY " if expression.args.get("only") else ""
2450        partition = self.sql(expression, "partition")
2451        partition = f" {partition}" if partition else ""
2452        version = self.sql(expression, "version")
2453        version = f" {version}" if version else ""
2454        alias = self.sql(expression, "alias")
2455        alias = f"{sep}{alias}" if alias else ""
2456
2457        sample = self.sql(expression, "sample")
2458        post_alias = ""
2459        pre_alias = ""
2460
2461        if self.dialect.ALIAS_POST_TABLESAMPLE:
2462            pre_alias = sample
2463        else:
2464            post_alias = sample
2465
2466        if self.dialect.ALIAS_POST_VERSION:
2467            pre_alias = f"{pre_alias}{version}"
2468        else:
2469            post_alias = f"{post_alias}{version}"
2470
2471        hints = self.expressions(expression, key="hints", sep=" ")
2472        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2473        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2474        joins = self.indent(
2475            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2476        )
2477        laterals = self.expressions(expression, key="laterals", sep="")
2478
2479        file_format = self.sql(expression, "format")
2480        pattern = self.sql(expression, "pattern")
2481        if file_format:
2482            pattern = f", PATTERN => {pattern}" if pattern else ""
2483            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2484        elif pattern:
2485            file_format = f" (PATTERN => {pattern})"
2486
2487        ordinality = expression.args.get("ordinality") or ""
2488        if ordinality:
2489            ordinality = f" WITH ORDINALITY{alias}"
2490            alias = ""
2491
2492        when = self.sql(expression, "when")
2493        if when:
2494            if self.HISTORICAL_DATA_POST_ALIAS:
2495                alias = f"{alias} {when}"
2496            else:
2497                table = f"{table} {when}"
2498
2499        changes = self.sql(expression, "changes")
2500        changes = f" {changes}" if changes else ""
2501
2502        rows_from = self.expressions(expression, key="rows_from")
2503        if rows_from:
2504            table = f"ROWS FROM {self.wrap(rows_from)}"
2505
2506        indexed = expression.args.get("indexed")
2507        if indexed is not None:
2508            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2509        else:
2510            indexed = ""
2511
2512        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2513
2514    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2515        table = self.func("TABLE", expression.this)
2516        alias = self.sql(expression, "alias")
2517        alias = f" AS {alias}" if alias else ""
2518        sample = self.sql(expression, "sample")
2519        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2520        joins = self.indent(
2521            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2522        )
2523        return f"{table}{alias}{pivots}{sample}{joins}"
2524
2525    def tablesample_sql(
2526        self,
2527        expression: exp.TableSample,
2528        tablesample_keyword: str | None = None,
2529    ) -> str:
2530        method = self.sql(expression, "method")
2531        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2532        numerator = self.sql(expression, "bucket_numerator")
2533        denominator = self.sql(expression, "bucket_denominator")
2534        field = self.sql(expression, "bucket_field")
2535        field = f" ON {field}" if field else ""
2536        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2537        seed = self.sql(expression, "seed")
2538        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2539
2540        size = self.sql(expression, "size")
2541        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2542            size = f"{size} ROWS"
2543
2544        percent = self.sql(expression, "percent")
2545        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2546            percent = f"{percent} PERCENT"
2547
2548        expr = f"{bucket}{percent}{size}"
2549        if self.TABLESAMPLE_REQUIRES_PARENS:
2550            expr = f"({expr})"
2551
2552        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2553
2554    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2555        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2556        # the stored column name differs from the target dialect's natural output.
2557        columns = expression.args.get("columns")
2558        if not columns or len(expression.fields) != 1:
2559            return None
2560
2561        args = expression.args
2562        parser_cls = self.dialect.parser_class
2563
2564        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2565        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2566        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2567
2568        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2569        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2570        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2571
2572        if (
2573            src_identify_pivot_strings == tgt_identify_pivot_strings
2574            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2575            and src_pivot_column_naming == tgt_pivot_column_naming
2576        ):
2577            return None
2578
2579        in_exprs = expression.fields[0].expressions
2580        step = len(columns) // len(in_exprs)
2581
2582        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2583        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2584        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2585        first_stored = columns[0].name
2586
2587        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2588        if not first_stored.startswith(first_base):
2589            return None
2590
2591        suffix = first_stored[len(first_base) :]
2592
2593        # Whether the target dialect would append an agg-name suffix for this pivot.
2594        # Spark single-agg uniquely drops the agg alias entirely.
2595        target_has_suffix = (
2596            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2597        ) and any(a.alias for a in expression.expressions)
2598        source_has_suffix = suffix != ""
2599
2600        new_exprs: list[exp.Expression] = []
2601        modified = False
2602        for val_idx, e in enumerate(in_exprs):
2603            if isinstance(e, exp.PivotAlias):
2604                new_exprs.append(e)
2605                continue
2606
2607            i = val_idx * step
2608            stored_full = columns[i].name
2609            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2610            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2611
2612            # Source had a suffix, but target won't apply one
2613            if source_has_suffix and not target_has_suffix:
2614                new_exprs.append(
2615                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2616                )
2617                modified = True
2618            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2619            elif stored_value != target_value:
2620                new_exprs.append(
2621                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2622                )
2623                modified = True
2624            else:
2625                new_exprs.append(e)
2626
2627        return new_exprs if modified else None
2628
2629    def pivot_sql(self, expression: exp.Pivot) -> str:
2630        expressions = self.expressions(expression, flat=True)
2631        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2632
2633        group = self.sql(expression, "group")
2634
2635        if expression.this:
2636            this = self.sql(expression, "this")
2637            if not expressions:
2638                sql = f"UNPIVOT {this}"
2639            else:
2640                on = f"{self.seg('ON')} {expressions}"
2641                into = self.sql(expression, "into")
2642                into = f"{self.seg('INTO')} {into}" if into else ""
2643                using = self.expressions(expression, key="using", flat=True)
2644                using = f"{self.seg('USING')} {using}" if using else ""
2645                sql = f"{direction} {this}{on}{into}{using}{group}"
2646            return self.prepend_ctes(expression, sql)
2647
2648        if not expression.unpivot:
2649            # Wrap IN-list values with explicit aliases where the target dialect would differ
2650            new_field_exprs = self._pivot_in_value_aliases(expression)
2651            if new_field_exprs is not None:
2652                expression.fields[0].set("expressions", new_field_exprs)
2653
2654        alias = self.sql(expression, "alias")
2655        if alias:
2656            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2657
2658        fields = self.expressions(
2659            expression,
2660            "fields",
2661            sep=" ",
2662            dynamic=True,
2663            new_line=True,
2664            skip_first=True,
2665            skip_last=True,
2666        )
2667
2668        include_nulls = expression.args.get("include_nulls")
2669        if include_nulls is not None:
2670            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2671        else:
2672            nulls = ""
2673
2674        default_on_null = self.sql(expression, "default_on_null")
2675        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2676        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2677        return self.prepend_ctes(expression, sql)
2678
2679    def version_sql(self, expression: exp.Version) -> str:
2680        this = f"FOR {expression.name}"
2681        kind = expression.text("kind")
2682        expr = self.sql(expression, "expression")
2683        return f"{this} {kind} {expr}"
2684
2685    def tuple_sql(self, expression: exp.Tuple) -> str:
2686        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2687
2688    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2689        """
2690        Returns (join_sql, from_sql) for UPDATE statements.
2691        - join_sql: placed after UPDATE table, before SET
2692        - from_sql: placed after SET clause (standard position)
2693        Dialects like MySQL need to convert FROM to JOIN syntax.
2694        """
2695        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2696            return ("", self.sql(expression, "from_"))
2697
2698        # Qualify unqualified columns in SET clause with the target table
2699        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2700        target_table = expression.this
2701        if isinstance(target_table, exp.Table):
2702            target_name = exp.to_identifier(target_table.alias_or_name)
2703            for eq in expression.expressions:
2704                col = eq.this
2705                if isinstance(col, exp.Column) and not col.table:
2706                    col.set("table", target_name)
2707
2708        table = from_expr.this
2709        if nested_joins := table.args.get("joins", []):
2710            table.set("joins", None)
2711
2712        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2713        for nested in nested_joins:
2714            if not nested.args.get("on") and not nested.args.get("using"):
2715                nested.set("on", exp.true())
2716            join_sql += self.sql(nested)
2717
2718        return (join_sql, "")
2719
2720    def update_sql(self, expression: exp.Update) -> str:
2721        hint = self.sql(expression, "hint")
2722        this = self.sql(expression, "this")
2723        join_sql, from_sql = self._update_from_joins_sql(expression)
2724        set_sql = self.expressions(expression, flat=True)
2725        where_sql = self.sql(expression, "where")
2726        returning = self.sql(expression, "returning")
2727        order = self.sql(expression, "order")
2728        limit = self.sql(expression, "limit")
2729        if self.RETURNING_END:
2730            expression_sql = f"{from_sql}{where_sql}{returning}"
2731        else:
2732            expression_sql = f"{returning}{from_sql}{where_sql}"
2733        options = self.expressions(expression, key="options")
2734        options = f" OPTION({options})" if options else ""
2735        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2736        return self.prepend_ctes(expression, sql)
2737
2738    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2739        values_as_table = values_as_table and self.VALUES_AS_TABLE
2740
2741        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2742        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2743            args = self.expressions(expression)
2744            alias = self.sql(expression, "alias")
2745            values = f"VALUES{self.seg('')}{args}"
2746            values = (
2747                f"({values})"
2748                if self.WRAP_DERIVED_VALUES
2749                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2750                else values
2751            )
2752            values = self.query_modifiers(expression, values)
2753            return f"{values} AS {alias}" if alias else values
2754
2755        # Converts `VALUES...` expression into a series of select unions.
2756        alias_node = expression.args.get("alias")
2757        column_names = alias_node and alias_node.columns
2758
2759        selects: list[exp.Query] = []
2760
2761        for i, tup in enumerate(expression.expressions):
2762            row = tup.expressions
2763
2764            if i == 0 and column_names:
2765                row = [
2766                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2767                ]
2768
2769            selects.append(exp.Select(expressions=row))
2770
2771        if self.pretty:
2772            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2773            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2774            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2775            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2776            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2777
2778        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2779        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2780        return f"({unions}){alias}"
2781
2782    def var_sql(self, expression: exp.Var) -> str:
2783        return self.sql(expression, "this")
2784
2785    @unsupported_args("expressions")
2786    def into_sql(self, expression: exp.Into) -> str:
2787        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2788        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2789        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2790
2791    def from_sql(self, expression: exp.From) -> str:
2792        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2793
2794    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2795        grouping_sets = self.expressions(expression, indent=False)
2796        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2797
2798    def rollup_sql(self, expression: exp.Rollup) -> str:
2799        expressions = self.expressions(expression, indent=False)
2800        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2801
2802    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2803        this = self.sql(expression, "this")
2804
2805        columns = self.expressions(expression, flat=True)
2806
2807        from_sql = self.sql(expression, "from_index")
2808        from_sql = f" FROM {from_sql}" if from_sql else ""
2809
2810        properties = expression.args.get("properties")
2811        properties_sql = (
2812            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2813        )
2814
2815        return f"{this}({columns}){from_sql}{properties_sql}"
2816
2817    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2818        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2819
2820    def cube_sql(self, expression: exp.Cube) -> str:
2821        expressions = self.expressions(expression, indent=False)
2822        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2823
2824    def group_sql(self, expression: exp.Group) -> str:
2825        group_by_all = expression.args.get("all")
2826        if group_by_all is True:
2827            modifier = " ALL"
2828        elif group_by_all is False:
2829            modifier = " DISTINCT"
2830        else:
2831            modifier = ""
2832
2833        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2834
2835        grouping_sets = self.expressions(expression, key="grouping_sets")
2836        cube = self.expressions(expression, key="cube")
2837        rollup = self.expressions(expression, key="rollup")
2838
2839        groupings = csv(
2840            self.seg(grouping_sets) if grouping_sets else "",
2841            self.seg(cube) if cube else "",
2842            self.seg(rollup) if rollup else "",
2843            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2844            sep=self.GROUPINGS_SEP,
2845        )
2846
2847        if (
2848            expression.expressions
2849            and groupings
2850            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2851        ):
2852            add_separator = True
2853
2854            if grouping_sets:
2855                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2856                    add_separator = False
2857                else:
2858                    self.unsupported(
2859                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2860                    )
2861
2862            if add_separator:
2863                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2864
2865        return f"{group_by}{groupings}"
2866
2867    def having_sql(self, expression: exp.Having) -> str:
2868        this = self.indent(self.sql(expression, "this"))
2869        return f"{self.seg('HAVING')}{self.sep()}{this}"
2870
2871    def connect_sql(self, expression: exp.Connect) -> str:
2872        start = self.sql(expression, "start")
2873        start = self.seg(f"START WITH {start}") if start else ""
2874        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2875        connect = self.sql(expression, "connect")
2876        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2877        return start + connect
2878
2879    def prior_sql(self, expression: exp.Prior) -> str:
2880        return f"PRIOR {self.sql(expression, 'this')}"
2881
2882    def join_sql(self, expression: exp.Join) -> str:
2883        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2884            side = None
2885        else:
2886            side = expression.side
2887
2888        op_sql = " ".join(
2889            op
2890            for op in (
2891                expression.method,
2892                "GLOBAL" if expression.args.get("global_") else None,
2893                side,
2894                expression.kind,
2895                expression.hint if self.JOIN_HINTS else None,
2896                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2897            )
2898            if op
2899        )
2900        match_cond = self.sql(expression, "match_condition")
2901        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2902        on_sql = self.sql(expression, "on")
2903        using = expression.args.get("using")
2904
2905        if not on_sql and using:
2906            on_sql = csv(*(self.sql(column) for column in using))
2907
2908        this = expression.this
2909        this_sql = self.sql(this)
2910
2911        exprs = self.expressions(expression)
2912        if exprs:
2913            this_sql = f"{this_sql},{self.seg(exprs)}"
2914
2915        if on_sql:
2916            on_sql = self.indent(on_sql, skip_first=True)
2917            space = self.seg(" " * self.pad) if self.pretty else " "
2918            if using:
2919                on_sql = f"{space}USING ({on_sql})"
2920            else:
2921                on_sql = f"{space}ON {on_sql}"
2922        elif not op_sql:
2923            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2924                return f" {this_sql}"
2925
2926            return f", {this_sql}"
2927
2928        if op_sql != "STRAIGHT_JOIN":
2929            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2930
2931        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2932        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2933
2934    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2935        args = self.expressions(expression, flat=True)
2936        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2937        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2938
2939    def lateral_op(self, expression: exp.Lateral) -> str:
2940        cross_apply = expression.args.get("cross_apply")
2941
2942        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2943        if cross_apply is True:
2944            op = "INNER JOIN "
2945        elif cross_apply is False:
2946            op = "LEFT JOIN "
2947        else:
2948            op = ""
2949
2950        return f"{op}LATERAL"
2951
2952    def lateral_sql(self, expression: exp.Lateral) -> str:
2953        this = self.sql(expression, "this")
2954
2955        if expression.args.get("view"):
2956            alias = expression.args["alias"]
2957            columns = self.expressions(alias, key="columns", flat=True)
2958            table = f" {alias.name}" if alias.name else ""
2959            columns = f" AS {columns}" if columns else ""
2960            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2961            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2962
2963        alias = self.sql(expression, "alias")
2964        alias = f" AS {alias}" if alias else ""
2965
2966        ordinality = expression.args.get("ordinality") or ""
2967        if ordinality:
2968            ordinality = f" WITH ORDINALITY{alias}"
2969            alias = ""
2970
2971        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2972
2973    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2974        this = self.sql(expression, "this")
2975
2976        args = [
2977            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2978            for e in (expression.args.get(k) for k in ("offset", "expression"))
2979            if e
2980        ]
2981
2982        args_sql = ", ".join(self.sql(e) for e in args)
2983        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2984        expressions = self.expressions(expression, flat=True)
2985        limit_options = self.sql(expression, "limit_options")
2986        expressions = f" BY {expressions}" if expressions else ""
2987
2988        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2989
2990    def offset_sql(self, expression: exp.Offset) -> str:
2991        this = self.sql(expression, "this")
2992        value = expression.expression
2993        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2994        expressions = self.expressions(expression, flat=True)
2995        expressions = f" BY {expressions}" if expressions else ""
2996        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2997
2998    def setitem_sql(self, expression: exp.SetItem) -> str:
2999        kind = self.sql(expression, "kind")
3000        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3001            kind = ""
3002        else:
3003            kind = f"{kind} " if kind else ""
3004        this = self.sql(expression, "this")
3005        expressions = self.expressions(expression)
3006        collate = self.sql(expression, "collate")
3007        collate = f" COLLATE {collate}" if collate else ""
3008        global_ = "GLOBAL " if expression.args.get("global_") else ""
3009        return f"{global_}{kind}{this}{expressions}{collate}"
3010
3011    def set_sql(self, expression: exp.Set) -> str:
3012        expressions = f" {self.expressions(expression, flat=True)}"
3013        tag = " TAG" if expression.args.get("tag") else ""
3014        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3015
3016    def queryband_sql(self, expression: exp.QueryBand) -> str:
3017        this = self.sql(expression, "this")
3018        update = " UPDATE" if expression.args.get("update") else ""
3019        scope = self.sql(expression, "scope")
3020        scope = f" FOR {scope}" if scope else ""
3021
3022        return f"QUERY_BAND = {this}{update}{scope}"
3023
3024    def pragma_sql(self, expression: exp.Pragma) -> str:
3025        return f"PRAGMA {self.sql(expression, 'this')}"
3026
3027    def lock_sql(self, expression: exp.Lock) -> str:
3028        if not self.LOCKING_READS_SUPPORTED:
3029            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3030            return ""
3031
3032        update = expression.args["update"]
3033        key = expression.args.get("key")
3034        if update:
3035            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3036        else:
3037            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3038        expressions = self.expressions(expression, flat=True)
3039        expressions = f" OF {expressions}" if expressions else ""
3040        wait = expression.args.get("wait")
3041
3042        if wait is not None:
3043            if isinstance(wait, exp.Literal):
3044                wait = f" WAIT {self.sql(wait)}"
3045            else:
3046                wait = " NOWAIT" if wait else " SKIP LOCKED"
3047
3048        return f"{lock_type}{expressions}{wait or ''}"
3049
3050    def literal_sql(self, expression: exp.Literal) -> str:
3051        text = expression.this or ""
3052        if expression.is_string:
3053            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3054        return text
3055
3056    def escape_str(
3057        self,
3058        text: str,
3059        escape_backslash: bool = True,
3060        delimiter: str | None = None,
3061        escaped_delimiter: str | None = None,
3062        is_byte_string: bool = False,
3063    ) -> str:
3064        if is_byte_string:
3065            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3066        else:
3067            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3068
3069        if supports_escape_sequences:
3070            text = "".join(
3071                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3072                for ch in text
3073            )
3074
3075        delimiter = delimiter or self.dialect.QUOTE_END
3076        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3077
3078        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3079
3080    def loaddata_sql(self, expression: exp.LoadData) -> str:
3081        is_overwrite = expression.args.get("overwrite")
3082        overwrite = " OVERWRITE" if is_overwrite else ""
3083        this = self.sql(expression, "this")
3084
3085        files = expression.args.get("files")
3086        if files:
3087            files_sql = self.expressions(files, flat=True)
3088            files_sql = f"FILES{self.wrap(files_sql)}"
3089            if is_overwrite:
3090                this = f" {this}"
3091            elif expression.args.get("temp"):
3092                this = f" INTO TEMP TABLE {this}"
3093            else:
3094                this = f" INTO TABLE {this}"
3095            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3096
3097        local = " LOCAL" if expression.args.get("local") else ""
3098        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3099        this = f" INTO TABLE {this}"
3100        partition = self.sql(expression, "partition")
3101        partition = f" {partition}" if partition else ""
3102        input_format = self.sql(expression, "input_format")
3103        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3104        serde = self.sql(expression, "serde")
3105        serde = f" SERDE {serde}" if serde else ""
3106        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3107
3108    def null_sql(self, *_) -> str:
3109        return "NULL"
3110
3111    def boolean_sql(self, expression: exp.Boolean) -> str:
3112        return "TRUE" if expression.this else "FALSE"
3113
3114    def booland_sql(self, expression: exp.Booland) -> str:
3115        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3116
3117    def boolor_sql(self, expression: exp.Boolor) -> str:
3118        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3119
3120    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3121        this = self.sql(expression, "this")
3122        this = f"{this} " if this else this
3123        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3124        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3125
3126    def withfill_sql(self, expression: exp.WithFill) -> str:
3127        from_sql = self.sql(expression, "from_")
3128        from_sql = f" FROM {from_sql}" if from_sql else ""
3129        to_sql = self.sql(expression, "to")
3130        to_sql = f" TO {to_sql}" if to_sql else ""
3131        step_sql = self.sql(expression, "step")
3132        step_sql = f" STEP {step_sql}" if step_sql else ""
3133        interpolated_values = [
3134            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3135            if isinstance(e, exp.Alias)
3136            else self.sql(e, "this")
3137            for e in expression.args.get("interpolate") or []
3138        ]
3139        interpolate = (
3140            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3141        )
3142        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3143
3144    def cluster_sql(self, expression: exp.Cluster) -> str:
3145        return self.op_expressions("CLUSTER BY", expression)
3146
3147    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3148        if expression.this:
3149            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3150            return ""
3151        expressions = self.expressions(expression, flat=True)
3152        return f"CLUSTER BY ({expressions})"
3153
3154    def distribute_sql(self, expression: exp.Distribute) -> str:
3155        return self.op_expressions("DISTRIBUTE BY", expression)
3156
3157    def sort_sql(self, expression: exp.Sort) -> str:
3158        return self.op_expressions("SORT BY", expression)
3159
3160    def _resolve_ordered_for_null_ordering_simulation(
3161        self, expression: exp.Ordered
3162    ) -> exp.Expr | None:
3163        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3164
3165        Returns the underlying expression of the uniquely-matching projection
3166        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3167        simulation, since the CASE is evaluated in FROM-clause scope rather
3168        than alias scope (MySQL error 1052). Returns None if no safe
3169        substitution applies, leaving the original behaviour unchanged.
3170        """
3171        this = expression.this
3172        if not (isinstance(this, exp.Column) and not this.table):
3173            return None
3174
3175        ancestor = expression.find_ancestor(exp.Select, exp.Window)
3176        if not isinstance(ancestor, exp.Select):
3177            return None
3178
3179        column_name = this.name
3180        matched: list[exp.Expr] = [
3181            p.this if isinstance(p, exp.Alias) else p
3182            for p in ancestor.selects
3183            if p.output_name == column_name
3184        ]
3185        match = matched[0] if len(matched) == 1 else None
3186
3187        # Skip the substitution when it would be identical to the existing
3188        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3189        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3190            return None
3191
3192        return match
3193
3194    def ordered_sql(self, expression: exp.Ordered) -> str:
3195        desc = expression.args.get("desc")
3196        asc = not desc
3197
3198        nulls_first = expression.args.get("nulls_first")
3199        nulls_last = not nulls_first
3200        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3201        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3202        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3203
3204        this = self.sql(expression, "this")
3205
3206        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3207        nulls_sort_change = ""
3208        if nulls_first and (
3209            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3210        ):
3211            nulls_sort_change = " NULLS FIRST"
3212        elif (
3213            nulls_last
3214            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3215            and not nulls_are_last
3216        ):
3217            nulls_sort_change = " NULLS LAST"
3218
3219        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3220        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3221            window = expression.find_ancestor(exp.Window, exp.Select)
3222
3223            if isinstance(window, exp.Window):
3224                window_this = window.this
3225                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3226                    window_this = window_this.this
3227                spec = window.args.get("spec")
3228            else:
3229                window_this = None
3230                spec = None
3231
3232            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3233            # without a spec or with a ROWS spec, but not with RANGE
3234            if not (
3235                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3236                and (not spec or spec.text("kind").upper() == "ROWS")
3237            ):
3238                if window_this and spec:
3239                    self.unsupported(
3240                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3241                    )
3242                    nulls_sort_change = ""
3243                elif self.NULL_ORDERING_SUPPORTED is False and (
3244                    (asc and nulls_sort_change == " NULLS LAST")
3245                    or (desc and nulls_sort_change == " NULLS FIRST")
3246                ):
3247                    # BigQuery does not allow these ordering/nulls combinations when used under
3248                    # an aggregation func or under a window containing one
3249                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3250
3251                    if isinstance(ancestor, exp.Window):
3252                        ancestor = ancestor.this
3253                    if isinstance(ancestor, exp.AggFunc):
3254                        self.unsupported(
3255                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3256                        )
3257                        nulls_sort_change = ""
3258                elif self.NULL_ORDERING_SUPPORTED is None:
3259                    if expression.this.is_int:
3260                        self.unsupported(
3261                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3262                        )
3263                    elif not isinstance(expression.this, exp.Rand):
3264                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3265                        target = self.sql(resolved) if resolved is not None else this
3266                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3267                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3268                    nulls_sort_change = ""
3269
3270        with_fill = self.sql(expression, "with_fill")
3271        with_fill = f" {with_fill}" if with_fill else ""
3272
3273        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3274
3275    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3276        window_frame = self.sql(expression, "window_frame")
3277        window_frame = f"{window_frame} " if window_frame else ""
3278
3279        this = self.sql(expression, "this")
3280
3281        return f"{window_frame}{this}"
3282
3283    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3284        partition = self.partition_by_sql(expression)
3285        order = self.sql(expression, "order")
3286        measures = self.expressions(expression, key="measures")
3287        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3288        rows = self.sql(expression, "rows")
3289        rows = self.seg(rows) if rows else ""
3290        after = self.sql(expression, "after")
3291        after = self.seg(after) if after else ""
3292        pattern = self.sql(expression, "pattern")
3293        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3294        definition_sqls = [
3295            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3296            for definition in expression.args.get("define", [])
3297        ]
3298        definitions = self.expressions(sqls=definition_sqls)
3299        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3300        body = "".join(
3301            (
3302                partition,
3303                order,
3304                measures,
3305                rows,
3306                after,
3307                pattern,
3308                define,
3309            )
3310        )
3311        alias = self.sql(expression, "alias")
3312        alias = f" {alias}" if alias else ""
3313        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3314
3315    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3316        limit = expression.args.get("limit")
3317
3318        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3319            count = limit.args.get("count")
3320            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3321            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3322            limit = exp.Limit(
3323                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3324            )
3325        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3326            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3327
3328        return csv(
3329            *sqls,
3330            *[self.sql(join) for join in expression.args.get("joins") or []],
3331            self.sql(expression, "match"),
3332            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3333            self.sql(expression, "prewhere"),
3334            self.sql(expression, "where"),
3335            self.sql(expression, "connect"),
3336            self.sql(expression, "group"),
3337            self.sql(expression, "having"),
3338            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3339            self.sql(expression, "order"),
3340            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3341            *self.after_limit_modifiers(expression),
3342            self.sql(expression, "for_"),
3343            self.options_modifier(expression),
3344            sep="",
3345        )
3346
3347    def options_modifier(self, expression: exp.Expr) -> str:
3348        options = self.expressions(expression, key="options")
3349        return f" {options}" if options else ""
3350
3351    def forclause_sql(self, expression: exp.ForClause) -> str:
3352        kind = expression.args["kind"]
3353        if kind == "BROWSE":
3354            return f"{self.sep()}FOR BROWSE"
3355        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3356        # the target dialect doesn't support QueryOption, so we drop the clause.
3357        options = self.expressions(expression, key="expressions")
3358        if not options:
3359            return ""
3360        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3361
3362    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3363        self.unsupported("Unsupported query option.")
3364        return ""
3365
3366    def offset_limit_modifiers(
3367        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3368    ) -> list[str]:
3369        return [
3370            self.sql(expression, "offset") if fetch else self.sql(limit),
3371            self.sql(limit) if fetch else self.sql(expression, "offset"),
3372        ]
3373
3374    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3375        locks = self.expressions(expression, key="locks", sep=" ")
3376        locks = f" {locks}" if locks else ""
3377        return [locks, self.sql(expression, "sample")]
3378
3379    def select_sql(self, expression: exp.Select) -> str:
3380        into = expression.args.get("into")
3381        if not self.SUPPORTS_SELECT_INTO and into:
3382            into.pop()
3383
3384        hint = self.sql(expression, "hint")
3385        distinct = self.sql(expression, "distinct")
3386        distinct = f" {distinct}" if distinct else ""
3387        kind = self.sql(expression, "kind")
3388
3389        limit = expression.args.get("limit")
3390        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3391            top = self.limit_sql(limit, top=True)
3392            limit.pop()
3393        else:
3394            top = ""
3395
3396        expressions = self.expressions(expression)
3397
3398        if kind:
3399            if kind in self.SELECT_KINDS:
3400                kind = f" AS {kind}"
3401            else:
3402                if kind == "STRUCT":
3403                    expressions = self.expressions(
3404                        sqls=[
3405                            self.sql(
3406                                exp.Struct(
3407                                    expressions=[
3408                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3409                                        if isinstance(e, exp.Alias)
3410                                        else e
3411                                        for e in expression.expressions
3412                                    ]
3413                                )
3414                            )
3415                        ]
3416                    )
3417                kind = ""
3418
3419        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3420        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3421
3422        exclude = expression.args.get("exclude")
3423
3424        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3425            exclude_sql = self.expressions(sqls=exclude, flat=True)
3426            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3427
3428        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3429        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3430        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3431        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3432        sql = self.query_modifiers(
3433            expression,
3434            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3435            self.sql(expression, "into", comment=False),
3436            self.sql(expression, "from_", comment=False),
3437        )
3438
3439        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3440        if expression.args.get("with_"):
3441            sql = self.maybe_comment(sql, expression)
3442            expression.pop_comments()
3443
3444        sql = self.prepend_ctes(expression, sql)
3445
3446        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3447            expression.set("exclude", None)
3448            subquery = expression.subquery(copy=False)
3449            star = exp.Star(except_=exclude)
3450            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3451
3452        if not self.SUPPORTS_SELECT_INTO and into:
3453            if into.args.get("temporary"):
3454                table_kind = " TEMPORARY"
3455            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3456                table_kind = " UNLOGGED"
3457            else:
3458                table_kind = ""
3459            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3460
3461        return sql
3462
3463    def schema_sql(self, expression: exp.Schema) -> str:
3464        this = self.sql(expression, "this")
3465        sql = self.schema_columns_sql(expression)
3466        return f"{this} {sql}" if this and sql else this or sql
3467
3468    def schema_columns_sql(self, expression: exp.Expr) -> str:
3469        if expression.expressions:
3470            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3471        return ""
3472
3473    def star_sql(self, expression: exp.Star) -> str:
3474        except_ = self.expressions(expression, key="except_", flat=True)
3475        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3476        replace = self.expressions(expression, key="replace", flat=True)
3477        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3478        rename = self.expressions(expression, key="rename", flat=True)
3479        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3480        ilike = self.sql(expression, "ilike")
3481        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3482        return f"*{ilike}{except_}{replace}{rename}"
3483
3484    def parameter_sql(self, expression: exp.Parameter) -> str:
3485        this = self.sql(expression, "this")
3486        return f"{self.PARAMETER_TOKEN}{this}"
3487
3488    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3489        this = self.sql(expression, "this")
3490        kind = expression.text("kind")
3491        if kind:
3492            kind = f"{kind}."
3493        return f"@@{kind}{this}"
3494
3495    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3496        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3497
3498    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3499        alias = self.sql(expression, "alias")
3500        alias = f"{sep}{alias}" if alias else ""
3501        sample = self.sql(expression, "sample")
3502        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3503            alias = f"{sample}{alias}"
3504
3505            # Set to None so it's not generated again by self.query_modifiers()
3506            expression.set("sample", None)
3507
3508        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3509        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3510        return self.prepend_ctes(expression, sql)
3511
3512    def qualify_sql(self, expression: exp.Qualify) -> str:
3513        this = self.indent(self.sql(expression, "this"))
3514        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3515
3516    def unnest_sql(self, expression: exp.Unnest) -> str:
3517        args = self.expressions(expression, flat=True)
3518
3519        alias = expression.args.get("alias")
3520        offset = expression.args.get("offset")
3521
3522        if self.UNNEST_WITH_ORDINALITY:
3523            if alias and isinstance(offset, exp.Expr):
3524                alias.append("columns", offset)
3525                expression.set("offset", None)
3526
3527        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3528            columns = alias.columns
3529            alias = self.sql(columns[0]) if columns else ""
3530        else:
3531            alias = self.sql(alias)
3532
3533        alias = f" AS {alias}" if alias else alias
3534        if self.UNNEST_WITH_ORDINALITY:
3535            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3536        else:
3537            if isinstance(offset, exp.Expr):
3538                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3539            elif offset:
3540                suffix = f"{alias} WITH OFFSET"
3541            else:
3542                suffix = alias
3543
3544        return f"UNNEST({args}){suffix}"
3545
3546    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3547        return ""
3548
3549    def where_sql(self, expression: exp.Where) -> str:
3550        this = self.indent(self.sql(expression, "this"))
3551        return f"{self.seg('WHERE')}{self.sep()}{this}"
3552
3553    def window_sql(self, expression: exp.Window) -> str:
3554        this = self.sql(expression, "this")
3555        partition = self.partition_by_sql(expression)
3556        order = expression.args.get("order")
3557        order = self.order_sql(order, flat=True) if order else ""
3558        spec = self.sql(expression, "spec")
3559        alias = self.sql(expression, "alias")
3560        over = self.sql(expression, "over") or "OVER"
3561
3562        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3563
3564        first = expression.args.get("first")
3565        if first is None:
3566            first = ""
3567        else:
3568            first = "FIRST" if first else "LAST"
3569
3570        if not partition and not order and not spec and alias:
3571            return f"{this} {alias}"
3572
3573        args = self.format_args(
3574            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3575        )
3576        return f"{this} ({args})"
3577
3578    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3579        partition = self.expressions(expression, key="partition_by", flat=True)
3580        return f"PARTITION BY {partition}" if partition else ""
3581
3582    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3583        kind = self.sql(expression, "kind")
3584        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3585        end = (
3586            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3587            or "CURRENT ROW"
3588        )
3589
3590        window_spec = f"{kind} BETWEEN {start} AND {end}"
3591
3592        exclude = self.sql(expression, "exclude")
3593        if exclude:
3594            if self.SUPPORTS_WINDOW_EXCLUDE:
3595                window_spec += f" EXCLUDE {exclude}"
3596            else:
3597                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3598
3599        return window_spec
3600
3601    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3602        this = self.sql(expression, "this")
3603        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3604        return f"{this} WITHIN GROUP ({expression_sql})"
3605
3606    def between_sql(self, expression: exp.Between) -> str:
3607        this = self.sql(expression, "this")
3608        low = self.sql(expression, "low")
3609        high = self.sql(expression, "high")
3610        symmetric = expression.args.get("symmetric")
3611
3612        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3613            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3614
3615        flag = (
3616            " SYMMETRIC"
3617            if symmetric
3618            else " ASYMMETRIC"
3619            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3620            else ""  # silently drop ASYMMETRIC – semantics identical
3621        )
3622        return f"{this} BETWEEN{flag} {low} AND {high}"
3623
3624    def bracket_offset_expressions(
3625        self, expression: exp.Bracket, index_offset: int | None = None
3626    ) -> list[exp.Expr]:
3627        if expression.args.get("json_access"):
3628            return expression.expressions
3629
3630        return apply_index_offset(
3631            expression.this,
3632            expression.expressions,
3633            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3634            dialect=self.dialect,
3635        )
3636
3637    def bracket_sql(self, expression: exp.Bracket) -> str:
3638        expressions = self.bracket_offset_expressions(expression)
3639        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3640        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3641
3642    def all_sql(self, expression: exp.All) -> str:
3643        this = self.sql(expression, "this")
3644        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3645            this = self.wrap(this)
3646        return f"ALL {this}"
3647
3648    def any_sql(self, expression: exp.Any) -> str:
3649        this = self.sql(expression, "this")
3650        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3651            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3652                this = self.wrap(this)
3653            return f"ANY{this}"
3654        return f"ANY {this}"
3655
3656    def exists_sql(self, expression: exp.Exists) -> str:
3657        return f"EXISTS{self.wrap(expression)}"
3658
3659    def case_sql(self, expression: exp.Case) -> str:
3660        this = self.sql(expression, "this")
3661        statements = [f"CASE {this}" if this else "CASE"]
3662
3663        for e in expression.args["ifs"]:
3664            statements.append(f"WHEN {self.sql(e, 'this')}")
3665            statements.append(f"THEN {self.sql(e, 'true')}")
3666
3667        default = self.sql(expression, "default")
3668
3669        if default:
3670            statements.append(f"ELSE {default}")
3671
3672        statements.append("END")
3673
3674        if self.pretty and self.too_wide(statements):
3675            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3676
3677        return " ".join(statements)
3678
3679    def constraint_sql(self, expression: exp.Constraint) -> str:
3680        this = self.sql(expression, "this")
3681        expressions = self.expressions(expression, flat=True)
3682        return f"CONSTRAINT {this} {expressions}"
3683
3684    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3685        order = expression.args.get("order")
3686        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3687        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3688
3689    def extract_sql(self, expression: exp.Extract) -> str:
3690        import sqlglot.dialects.dialect
3691
3692        this = (
3693            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3694            if self.NORMALIZE_EXTRACT_DATE_PARTS
3695            else expression.this
3696        )
3697        if self.EXTRACT_ALLOWS_QUOTES:
3698            this_sql = self.sql(this)
3699        elif isinstance(this, exp.WeekStart):
3700            this_sql = self.weekstart_name(this)
3701        else:
3702            this_sql = this.name
3703        expression_sql = self.sql(expression, "expression")
3704
3705        return f"EXTRACT({this_sql} FROM {expression_sql})"
3706
3707    def trim_sql(self, expression: exp.Trim) -> str:
3708        trim_type = self.sql(expression, "position")
3709
3710        if trim_type == "LEADING":
3711            func_name = "LTRIM"
3712        elif trim_type == "TRAILING":
3713            func_name = "RTRIM"
3714        else:
3715            func_name = "TRIM"
3716
3717        return self.func(func_name, expression.this, expression.expression)
3718
3719    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3720        args = expression.expressions
3721        if isinstance(expression, exp.ConcatWs):
3722            args = args[1:]  # Skip the delimiter
3723
3724        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3725            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3726
3727        concat_coalesce = (
3728            self.dialect.CONCAT_WS_COALESCE
3729            if isinstance(expression, exp.ConcatWs)
3730            else self.dialect.CONCAT_COALESCE
3731        )
3732
3733        if not concat_coalesce and expression.args.get("coalesce"):
3734
3735            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3736                if not e.type:
3737                    import sqlglot.optimizer.annotate_types
3738
3739                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3740
3741                if e.is_string or e.is_type(exp.DType.ARRAY):
3742                    return e
3743
3744                return exp.func("coalesce", e, exp.Literal.string(""))
3745
3746            args = [_wrap_with_coalesce(e) for e in args]
3747
3748        return args
3749
3750    def concat_sql(self, expression: exp.Concat) -> str:
3751        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3752            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3753            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3754            # instead of coalescing them to empty string.
3755            import sqlglot.dialects.dialect
3756
3757            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3758
3759        expressions = self.convert_concat_args(expression)
3760
3761        # Some dialects don't allow a single-argument CONCAT call
3762        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3763            return self.sql(expressions[0])
3764
3765        return self.func("CONCAT", *expressions)
3766
3767    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3768        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3769            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3770            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3771            all_args = expression.expressions
3772            expression.set("coalesce", True)
3773            return self.sql(
3774                exp.case()
3775                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3776                .else_(expression)
3777            )
3778
3779        return self.func(
3780            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3781        )
3782
3783    def check_sql(self, expression: exp.Check) -> str:
3784        this = self.sql(expression, key="this")
3785        return f"CHECK ({this})"
3786
3787    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3788        expressions = self.expressions(expression, flat=True)
3789        expressions = f" ({expressions})" if expressions else ""
3790        reference = self.sql(expression, "reference")
3791        reference = f" {reference}" if reference else ""
3792        delete = self.sql(expression, "delete")
3793        delete = f" ON DELETE {delete}" if delete else ""
3794        update = self.sql(expression, "update")
3795        update = f" ON UPDATE {update}" if update else ""
3796        options = self.expressions(expression, key="options", flat=True, sep=" ")
3797        options = f" {options}" if options else ""
3798        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3799
3800    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3801        this = self.sql(expression, "this")
3802        this = f" {this}" if this else ""
3803        expressions = self.expressions(expression, flat=True)
3804        include = self.sql(expression, "include")
3805        options = self.expressions(expression, key="options", flat=True, sep=" ")
3806        options = f" {options}" if options else ""
3807        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3808
3809    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3810        self.unsupported("TIMESERIES primary key columns are not supported")
3811        return self.sql(expression, "this")
3812
3813    def if_sql(self, expression: exp.If) -> str:
3814        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3815
3816    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3817        if self.MATCH_AGAINST_TABLE_PREFIX:
3818            expressions = []
3819            for expr in expression.expressions:
3820                if isinstance(expr, exp.Table):
3821                    expressions.append(f"TABLE {self.sql(expr)}")
3822                else:
3823                    expressions.append(expr)
3824        else:
3825            expressions = expression.expressions
3826
3827        modifier = expression.args.get("modifier")
3828        modifier = f" {modifier}" if modifier else ""
3829        return (
3830            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3831        )
3832
3833    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3834        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3835
3836    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3837        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3838
3839        if self.QUOTE_JSON_PATH:
3840            path = self.escape_str(path)
3841            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3842
3843        return path
3844
3845    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3846        if isinstance(expression, exp.JSONPathPart):
3847            transform = self.TRANSFORMS.get(expression.__class__)
3848            if not callable(transform):
3849                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3850                return ""
3851
3852            return transform(self, expression)
3853
3854        if isinstance(expression, int):
3855            return str(expression)
3856
3857        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3858            escaped = expression.replace("'", "\\'")
3859            escaped = f"'{escaped}'"
3860        else:
3861            escaped = expression.replace('"', '\\"')
3862            escaped = f'"{escaped}"'
3863
3864        return escaped
3865
3866    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3867        return f"{self.sql(expression, 'this')} FORMAT JSON"
3868
3869    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3870        # Output the Teradata column FORMAT override.
3871        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3872        this = self.sql(expression, "this")
3873        fmt = self.sql(expression, "format")
3874        return f"{this} (FORMAT {fmt})"
3875
3876    def _jsonobject_sql(
3877        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3878    ) -> str:
3879        null_handling = expression.args.get("null_handling")
3880        null_handling = f" {null_handling}" if null_handling else ""
3881
3882        unique_keys = expression.args.get("unique_keys")
3883        if unique_keys is not None:
3884            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3885        else:
3886            unique_keys = ""
3887
3888        return_type = self.sql(expression, "return_type")
3889        return_type = f" RETURNING {return_type}" if return_type else ""
3890        encoding = self.sql(expression, "encoding")
3891        encoding = f" ENCODING {encoding}" if encoding else ""
3892
3893        if not name:
3894            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3895
3896        return self.func(
3897            name,
3898            *expression.expressions,
3899            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3900        )
3901
3902    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3903        null_handling = expression.args.get("null_handling")
3904        null_handling = f" {null_handling}" if null_handling else ""
3905        return_type = self.sql(expression, "return_type")
3906        return_type = f" RETURNING {return_type}" if return_type else ""
3907        strict = " STRICT" if expression.args.get("strict") else ""
3908        return self.func(
3909            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3910        )
3911
3912    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3913        this = self.sql(expression, "this")
3914        order = self.sql(expression, "order")
3915        null_handling = expression.args.get("null_handling")
3916        null_handling = f" {null_handling}" if null_handling else ""
3917        return_type = self.sql(expression, "return_type")
3918        return_type = f" RETURNING {return_type}" if return_type else ""
3919        strict = " STRICT" if expression.args.get("strict") else ""
3920        return self.func(
3921            "JSON_ARRAYAGG",
3922            this,
3923            suffix=f"{order}{null_handling}{return_type}{strict})",
3924        )
3925
3926    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3927        path = self.sql(expression, "path")
3928        path = f" PATH {path}" if path else ""
3929        nested_schema = self.sql(expression, "nested_schema")
3930
3931        if nested_schema:
3932            return f"NESTED{path} {nested_schema}"
3933
3934        this = self.sql(expression, "this")
3935        kind = self.sql(expression, "kind")
3936        kind = f" {kind}" if kind else ""
3937        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3938
3939        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3940        return f"{this}{kind}{format_json}{path}{ordinality}"
3941
3942    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3943        return self.func("COLUMNS", *expression.expressions)
3944
3945    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3946        this = self.sql(expression, "this")
3947        path = self.sql(expression, "path")
3948        path = f", {path}" if path else ""
3949        error_handling = expression.args.get("error_handling")
3950        error_handling = f" {error_handling}" if error_handling else ""
3951        empty_handling = expression.args.get("empty_handling")
3952        empty_handling = f" {empty_handling}" if empty_handling else ""
3953        schema = self.sql(expression, "schema")
3954        return self.func(
3955            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3956        )
3957
3958    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3959        this = self.sql(expression, "this")
3960        kind = self.sql(expression, "kind")
3961        path = self.sql(expression, "path")
3962        path = f" {path}" if path else ""
3963        as_json = " AS JSON" if expression.args.get("as_json") else ""
3964        return f"{this} {kind}{path}{as_json}"
3965
3966    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3967        this = self.sql(expression, "this")
3968        path = self.sql(expression, "path")
3969        path = f", {path}" if path else ""
3970        expressions = self.expressions(expression)
3971        with_ = (
3972            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3973            if expressions
3974            else ""
3975        )
3976        return f"OPENJSON({this}{path}){with_}"
3977
3978    def in_sql(self, expression: exp.In) -> str:
3979        query = expression.args.get("query")
3980        unnest = expression.args.get("unnest")
3981        field = expression.args.get("field")
3982        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3983
3984        if query:
3985            in_sql = self.sql(query)
3986        elif unnest:
3987            in_sql = self.in_unnest_op(unnest)
3988        elif field:
3989            in_sql = self.sql(field)
3990        else:
3991            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3992
3993        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3994
3995    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3996        return f"(SELECT {self.sql(unnest)})"
3997
3998    def interval_sql(self, expression: exp.Interval) -> str:
3999        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4000            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4001            exp.AutoRefreshProperty,
4002        )
4003        interval_keyword = "INTERVAL" if include_keyword else ""
4004        unit_expression = expression.args.get("unit")
4005        unit = self.sql(unit_expression) if unit_expression else ""
4006        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4007            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4008        unit = f" {unit}" if unit else ""
4009
4010        if self.SINGLE_STRING_INTERVAL:
4011            this = expression.this.name if expression.this else ""
4012            if this:
4013                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4014                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4015                    return f"{interval_keyword}'{this}'{unit}"
4016                return f"{interval_keyword}'{this}{unit}'"
4017            return f"{interval_keyword}{unit}"
4018
4019        this = self.sql(expression, "this")
4020        if this:
4021            if not include_keyword and expression.this.is_string:
4022                this = expression.this.name
4023            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4024                this = f"({this})"
4025            if include_keyword:
4026                this = f" {this}"
4027
4028        return f"{interval_keyword}{this}{unit}"
4029
4030    def return_sql(self, expression: exp.Return) -> str:
4031        return f"RETURN {self.sql(expression, 'this')}"
4032
4033    def reference_sql(self, expression: exp.Reference) -> str:
4034        this = self.sql(expression, "this")
4035        expressions = self.expressions(expression, flat=True)
4036        expressions = f"({expressions})" if expressions else ""
4037        options = self.expressions(expression, key="options", flat=True, sep=" ")
4038        options = f" {options}" if options else ""
4039        return f"REFERENCES {this}{expressions}{options}"
4040
4041    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4042        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4043        parent = expression.parent
4044        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4045
4046        return self.func(
4047            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4048        )
4049
4050    def paren_sql(self, expression: exp.Paren) -> str:
4051        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4052        return f"({sql}{self.seg(')', sep='')}"
4053
4054    def neg_sql(self, expression: exp.Neg) -> str:
4055        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4056        this_sql = self.sql(expression, "this")
4057        sep = " " if this_sql[0] == "-" else ""
4058        return f"-{sep}{this_sql}"
4059
4060    def not_sql(self, expression: exp.Not) -> str:
4061        return f"NOT {self.sql(expression, 'this')}"
4062
4063    def alias_sql(self, expression: exp.Alias) -> str:
4064        alias = self.sql(expression, "alias")
4065        alias = f" AS {alias}" if alias else ""
4066        return f"{self.sql(expression, 'this')}{alias}"
4067
4068    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4069        alias = expression.args["alias"]
4070
4071        parent = expression.parent
4072        pivot = parent and parent.parent
4073
4074        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4075            identifier_alias = isinstance(alias, exp.Identifier)
4076            literal_alias = isinstance(alias, exp.Literal)
4077
4078            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4079                alias.replace(exp.Literal.string(alias.output_name))
4080            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4081                alias.replace(exp.to_identifier(alias.output_name))
4082
4083        return self.alias_sql(expression)
4084
4085    def aliases_sql(self, expression: exp.Aliases) -> str:
4086        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4087
4088    def atindex_sql(self, expression: exp.AtIndex) -> str:
4089        this = self.sql(expression, "this")
4090        index = self.sql(expression, "expression")
4091        return f"{this} AT {index}"
4092
4093    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4094        this = self.sql(expression, "this")
4095        zone = self.sql(expression, "zone")
4096        return f"{this} AT TIME ZONE {zone}"
4097
4098    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4099        this = self.sql(expression, "this")
4100        zone = self.sql(expression, "zone")
4101        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4102
4103    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4104        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4105
4106    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4107        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4108
4109    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4110        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4111
4112    def add_sql(self, expression: exp.Add) -> str:
4113        return self.binary(expression, "+")
4114
4115    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4116        return self.connector_sql(expression, "AND", stack)
4117
4118    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4119        return self.connector_sql(expression, "OR", stack)
4120
4121    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4122        return self.connector_sql(expression, "XOR", stack)
4123
4124    def connector_sql(
4125        self,
4126        expression: exp.Connector,
4127        op: str,
4128        stack: list[str | exp.Expr] | None = None,
4129    ) -> str:
4130        if stack is not None:
4131            stack.append(expression.right)
4132            if expression.comments and self.comments:
4133                op = self.maybe_comment(op, comments=expression.comments)
4134
4135            stack.extend((op, expression.left))
4136            return op
4137
4138        stack = [expression]
4139        sqls: list[str] = []
4140        ops = set()
4141
4142        while stack:
4143            node = stack.pop()
4144            if isinstance(node, exp.Connector):
4145                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4146            else:
4147                sql = self.sql(node)
4148                if sqls and sqls[-1] in ops:
4149                    sqls[-1] += f" {sql}"
4150                else:
4151                    sqls.append(sql)
4152
4153        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4154        return sep.join(sqls)
4155
4156    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4157        return self.binary(expression, "&")
4158
4159    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4160        return self.binary(expression, "<<")
4161
4162    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4163        return f"~{self.sql(expression, 'this')}"
4164
4165    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4166        return self.binary(expression, "|")
4167
4168    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4169        return self.binary(expression, ">>")
4170
4171    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4172        return self.binary(expression, "^")
4173
4174    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4175        format_sql = self.sql(expression, "format")
4176        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4177        to_sql = self.sql(expression, "to")
4178        to_sql = f" {to_sql}" if to_sql else ""
4179        action = self.sql(expression, "action")
4180        action = f" {action}" if action else ""
4181        default = self.sql(expression, "default")
4182        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4183        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4184
4185    # Base implementation that excludes safe, zone, and target_type metadata args
4186    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4187        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4188
4189    # Base implementation that excludes the safe and default_year metadata args
4190    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4191        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4192
4193    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4194        return self.func(
4195            "PARSE_DATETIME",
4196            expression.this,
4197            expression.args.get("format"),
4198            expression.args.get("zone"),
4199        )
4200
4201    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4202        zone = self.sql(expression, "this")
4203        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4204
4205    def collate_sql(self, expression: exp.Collate) -> str:
4206        if self.COLLATE_IS_FUNC:
4207            return self.function_fallback_sql(expression)
4208        return self.binary(expression, "COLLATE")
4209
4210    def command_sql(self, expression: exp.Command) -> str:
4211        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4212
4213    def comment_sql(self, expression: exp.Comment) -> str:
4214        this = self.sql(expression, "this")
4215        kind = expression.args["kind"]
4216        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4217        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4218        expression_sql = self.sql(expression, "expression")
4219        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4220
4221    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4222        this = self.sql(expression, "this")
4223        delete = " DELETE" if expression.args.get("delete") else ""
4224        recompress = self.sql(expression, "recompress")
4225        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4226        to_disk = self.sql(expression, "to_disk")
4227        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4228        to_volume = self.sql(expression, "to_volume")
4229        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4230        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4231
4232    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4233        where = self.sql(expression, "where")
4234        group = self.sql(expression, "group")
4235        aggregates = self.expressions(expression, key="aggregates")
4236        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4237
4238        if not (where or group or aggregates) and len(expression.expressions) == 1:
4239            return f"TTL {self.expressions(expression, flat=True)}"
4240
4241        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4242
4243    def transaction_sql(self, expression: exp.Transaction) -> str:
4244        modes = self.expressions(expression, key="modes")
4245        modes = f" {modes}" if modes else ""
4246        return f"BEGIN{modes}"
4247
4248    def commit_sql(self, expression: exp.Commit) -> str:
4249        chain = expression.args.get("chain")
4250        if chain is not None:
4251            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4252
4253        return f"COMMIT{chain or ''}"
4254
4255    def rollback_sql(self, expression: exp.Rollback) -> str:
4256        savepoint = expression.args.get("savepoint")
4257        savepoint = f" TO {savepoint}" if savepoint else ""
4258        return f"ROLLBACK{savepoint}"
4259
4260    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4261        this = self.sql(expression, "this")
4262
4263        exists = ""
4264        if expression.args.get("exists"):
4265            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4266                exists = " IF EXISTS"
4267            else:
4268                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4269
4270        dtype = self.sql(expression, "dtype")
4271        if dtype:
4272            collate = self.sql(expression, "collate")
4273            collate = f" COLLATE {collate}" if collate else ""
4274            using = self.sql(expression, "using")
4275            using = f" USING {using}" if using else ""
4276            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4277            null_constraint = self._alter_column_null_constraint_sql(expression)
4278
4279            return (
4280                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4281                f"{collate}{using}{null_constraint}"
4282            )
4283
4284        default = self.sql(expression, "default")
4285        if default:
4286            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4287
4288        comment = self.sql(expression, "comment")
4289        if comment:
4290            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4291
4292        visible = expression.args.get("visible")
4293        if visible:
4294            return f"ALTER COLUMN{exists} {this} SET {visible}"
4295
4296        allow_null = expression.args.get("allow_null")
4297        drop = expression.args.get("drop")
4298
4299        if not drop and not allow_null:
4300            self.unsupported("Unsupported ALTER COLUMN syntax")
4301
4302        if allow_null is not None:
4303            keyword = "DROP" if drop else "SET"
4304            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4305
4306        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4307
4308    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4309        allow_null = expression.args.get("allow_null")
4310        if allow_null is None:
4311            return ""
4312
4313        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4314            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4315            return ""
4316
4317        return " NULL" if allow_null else " NOT NULL"
4318
4319    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4320        this = self.sql(expression, "this")
4321        rename_from = self.sql(expression, "rename_from")
4322        if rename_from:
4323            if not self.SUPPORTS_CHANGE_COLUMN:
4324                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4325            return f"CHANGE COLUMN {rename_from} {this}"
4326        if not self.SUPPORTS_MODIFY_COLUMN:
4327            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4328        return f"MODIFY COLUMN {this}"
4329
4330    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4331        this = self.sql(expression, "this")
4332
4333        visible = expression.args.get("visible")
4334        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4335
4336        return f"ALTER INDEX {this} {visible_sql}"
4337
4338    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4339        this = self.sql(expression, "this")
4340        if not isinstance(expression.this, exp.Var):
4341            this = f"KEY DISTKEY {this}"
4342        return f"ALTER DISTSTYLE {this}"
4343
4344    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4345        compound = " COMPOUND" if expression.args.get("compound") else ""
4346        this = self.sql(expression, "this")
4347        expressions = self.expressions(expression, flat=True)
4348        expressions = f"({expressions})" if expressions else ""
4349        return f"ALTER{compound} SORTKEY {this or expressions}"
4350
4351    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4352        if not self.RENAME_TABLE_WITH_DB:
4353            # Remove db from tables
4354            expression = expression.transform(
4355                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4356            ).assert_is(exp.AlterRename)
4357        this = self.sql(expression, "this")
4358        to_kw = " TO" if include_to else ""
4359        return f"RENAME{to_kw} {this}"
4360
4361    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4362        exists = " IF EXISTS" if expression.args.get("exists") else ""
4363        old_column = self.sql(expression, "this")
4364        new_column = self.sql(expression, "to")
4365        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4366
4367    def alterset_sql(self, expression: exp.AlterSet) -> str:
4368        exprs = self.expressions(expression, flat=True)
4369        if self.ALTER_SET_WRAPPED:
4370            exprs = f"({exprs})"
4371
4372        return f"SET {exprs}"
4373
4374    def alter_sql(self, expression: exp.Alter) -> str:
4375        actions = expression.args["actions"]
4376
4377        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4378            actions[0], exp.ColumnDef
4379        ):
4380            actions_sql = self.expressions(expression, key="actions", flat=True)
4381            actions_sql = f"ADD {actions_sql}"
4382        else:
4383            actions_list = []
4384            for action in actions:
4385                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4386                    action_sql = self.add_column_sql(action)
4387                else:
4388                    action_sql = self.sql(action)
4389                    if isinstance(action, exp.Query):
4390                        action_sql = f"AS {action_sql}"
4391
4392                actions_list.append(action_sql)
4393
4394            actions_sql = self.format_args(*actions_list).lstrip("\n")
4395
4396        iceberg = (
4397            "ICEBERG "
4398            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4399            else ""
4400        )
4401        exists = " IF EXISTS" if expression.args.get("exists") else ""
4402        on_cluster = self.sql(expression, "cluster")
4403        on_cluster = f" {on_cluster}" if on_cluster else ""
4404        only = " ONLY" if expression.args.get("only") else ""
4405        options = self.expressions(expression, key="options")
4406        options = f", {options}" if options else ""
4407        kind = self.sql(expression, "kind")
4408        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4409        check = " WITH CHECK" if expression.args.get("check") else ""
4410        cascade = (
4411            " CASCADE"
4412            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4413            else ""
4414        )
4415        this = self.sql(expression, "this")
4416        this = f" {this}" if this else ""
4417
4418        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4419
4420    def altersession_sql(self, expression: exp.AlterSession) -> str:
4421        items_sql = self.expressions(expression, flat=True)
4422        keyword = "UNSET" if expression.args.get("unset") else "SET"
4423        return f"{keyword} {items_sql}"
4424
4425    def add_column_sql(self, expression: exp.Expr) -> str:
4426        sql = self.sql(expression)
4427        if isinstance(expression, exp.Schema):
4428            column_text = " COLUMNS"
4429        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4430            column_text = " COLUMN"
4431        else:
4432            column_text = ""
4433
4434        return f"ADD{column_text} {sql}"
4435
4436    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4437        expressions = self.expressions(expression)
4438        exists = " IF EXISTS " if expression.args.get("exists") else " "
4439        return f"DROP{exists}{expressions}"
4440
4441    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4442        return "DROP PRIMARY KEY"
4443
4444    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4445        return f"ADD {self.expressions(expression, indent=False)}"
4446
4447    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4448        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4449        location = self.sql(expression, "location")
4450        location = f" {location}" if location else ""
4451        return f"ADD {exists}{self.sql(expression.this)}{location}"
4452
4453    def distinct_sql(self, expression: exp.Distinct) -> str:
4454        this = self.expressions(expression, flat=True)
4455
4456        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4457            case = exp.case()
4458            for arg in expression.expressions:
4459                case = case.when(arg.is_(exp.null()), exp.null())
4460            this = self.sql(case.else_(f"({this})"))
4461
4462        this = f" {this}" if this else ""
4463
4464        on = self.sql(expression, "on")
4465        on = f" ON {on}" if on else ""
4466        return f"DISTINCT{this}{on}"
4467
4468    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4469        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4470
4471    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4472        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4473
4474    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4475        this_sql = self.sql(expression, "this")
4476        expression_sql = self.sql(expression, "expression")
4477        kind = "MAX" if expression.args.get("max") else "MIN"
4478        return f"{this_sql} HAVING {kind} {expression_sql}"
4479
4480    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4481        return self.sql(
4482            exp.Cast(
4483                this=exp.Div(this=expression.this, expression=expression.expression),
4484                to=exp.DataType(this=exp.DType.INT),
4485            )
4486        )
4487
4488    def dpipe_sql(self, expression: exp.DPipe) -> str:
4489        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4490            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4491        return self.binary(expression, "||")
4492
4493    def div_sql(self, expression: exp.Div) -> str:
4494        l, r = expression.left, expression.right
4495
4496        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4497            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4498
4499        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4500            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4501                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4502
4503        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4504            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4505                return self.sql(
4506                    exp.cast(
4507                        l / r,
4508                        to=exp.DType.BIGINT,
4509                    )
4510                )
4511
4512        return self.binary(expression, "/")
4513
4514    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4515        n = exp._wrap(expression.this, exp.Binary)
4516        d = exp._wrap(expression.expression, exp.Binary)
4517        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4518
4519    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4520        return self.binary(expression, "OVERLAPS")
4521
4522    def distance_sql(self, expression: exp.Distance) -> str:
4523        return self.binary(expression, "<->")
4524
4525    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4526        return self.binary(expression, "<<->>")
4527
4528    def dot_sql(self, expression: exp.Dot) -> str:
4529        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4530
4531    def eq_sql(self, expression: exp.EQ) -> str:
4532        return self.binary(expression, "=")
4533
4534    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4535        return self.binary(expression, ":=")
4536
4537    def escape_sql(self, expression: exp.Escape) -> str:
4538        this = expression.this
4539        if (
4540            isinstance(this, (exp.Like, exp.ILike))
4541            and isinstance(this.expression, (exp.All, exp.Any))
4542            and not self.SUPPORTS_LIKE_QUANTIFIERS
4543        ):
4544            return self._like_sql(this, escape=expression)
4545        return self.binary(expression, "ESCAPE")
4546
4547    def glob_sql(self, expression: exp.Glob) -> str:
4548        return self.binary(expression, "GLOB")
4549
4550    def gt_sql(self, expression: exp.GT) -> str:
4551        return self.binary(expression, ">")
4552
4553    def gte_sql(self, expression: exp.GTE) -> str:
4554        return self.binary(expression, ">=")
4555
4556    def is_sql(self, expression: exp.Is) -> str:
4557        negate = expression.args.get("negate")
4558        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4559            positive = bool(expression.expression.this) != bool(negate)
4560            return self.sql(expression.this if positive else exp.not_(expression.this))
4561        return self.binary(expression, "IS NOT" if negate else "IS")
4562
4563    def _like_sql(
4564        self,
4565        expression: exp.Like | exp.ILike,
4566        escape: exp.Escape | None = None,
4567    ) -> str:
4568        this = expression.this
4569        rhs = expression.expression
4570
4571        if isinstance(expression, exp.Like):
4572            exp_class: type[exp.Like | exp.ILike] = exp.Like
4573            op = "LIKE"
4574        else:
4575            exp_class = exp.ILike
4576            op = "ILIKE"
4577
4578        if expression.args.get("negate"):
4579            op = f"NOT {op}"
4580
4581        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4582            exprs = rhs.this.unnest()
4583
4584            if isinstance(exprs, exp.Tuple):
4585                exprs = exprs.expressions
4586            else:
4587                exprs = [exprs]
4588
4589            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4590
4591            def _make_like(expr: exp.Expression) -> exp.Expression:
4592                like: exp.Expression = exp_class(
4593                    this=this, expression=expr, negate=expression.args.get("negate")
4594                )
4595                if escape:
4596                    like = exp.Escape(this=like, expression=escape.expression.copy())
4597                return like
4598
4599            like_expr: exp.Expr = _make_like(exprs[0])
4600            for expr in exprs[1:]:
4601                like_expr = connective(like_expr, _make_like(expr), copy=False)
4602
4603            parent = escape.parent if escape else expression.parent
4604            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4605                parent, exp.Condition
4606            ):
4607                like_expr = exp.paren(like_expr, copy=False)
4608
4609            return self.sql(like_expr)
4610
4611        return self.binary(expression, op)
4612
4613    def like_sql(self, expression: exp.Like) -> str:
4614        return self._like_sql(expression)
4615
4616    def ilike_sql(self, expression: exp.ILike) -> str:
4617        return self._like_sql(expression)
4618
4619    def match_sql(self, expression: exp.Match) -> str:
4620        return self.binary(expression, "MATCH")
4621
4622    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4623        return self.binary(expression, "SIMILAR TO")
4624
4625    def lt_sql(self, expression: exp.LT) -> str:
4626        return self.binary(expression, "<")
4627
4628    def lte_sql(self, expression: exp.LTE) -> str:
4629        return self.binary(expression, "<=")
4630
4631    def mod_sql(self, expression: exp.Mod) -> str:
4632        this = self.sql(expression, "this")
4633        expr = self.sql(expression, "expression")
4634        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4635
4636        parent = expression.parent
4637        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4638            return f"({sql})"
4639
4640        return sql
4641
4642    def mul_sql(self, expression: exp.Mul) -> str:
4643        return self.binary(expression, "*")
4644
4645    def neq_sql(self, expression: exp.NEQ) -> str:
4646        return self.binary(expression, "<>")
4647
4648    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4649        return self.binary(expression, "IS NOT DISTINCT FROM")
4650
4651    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4652        return self.binary(expression, "IS DISTINCT FROM")
4653
4654    def sub_sql(self, expression: exp.Sub) -> str:
4655        return self.binary(expression, "-")
4656
4657    def trycast_sql(self, expression: exp.TryCast) -> str:
4658        return self.cast_sql(expression, safe_prefix="TRY_")
4659
4660    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4661        return self.cast_sql(expression)
4662
4663    def try_sql(self, expression: exp.Try) -> str:
4664        if not self.TRY_SUPPORTED:
4665            self.unsupported("Unsupported TRY function")
4666            return self.sql(expression, "this")
4667
4668        return self.func("TRY", expression.this)
4669
4670    def log_sql(self, expression: exp.Log) -> str:
4671        this = expression.this
4672        expr = expression.expression
4673
4674        if self.dialect.LOG_BASE_FIRST is False:
4675            this, expr = expr, this
4676        elif self.dialect.LOG_BASE_FIRST is None and expr:
4677            if this.name in ("2", "10"):
4678                return self.func(f"LOG{this.name}", expr)
4679
4680            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4681
4682        return self.func("LOG", this, expr)
4683
4684    def use_sql(self, expression: exp.Use) -> str:
4685        kind = self.sql(expression, "kind")
4686        kind = f" {kind}" if kind else ""
4687        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4688        this = f" {this}" if this else ""
4689        return f"USE{kind}{this}"
4690
4691    def binary(self, expression: exp.Binary, op: str) -> str:
4692        sqls: list[str] = []
4693        stack: list[None | str | exp.Expr] = [expression]
4694        binary_type = type(expression)
4695
4696        while stack:
4697            node = stack.pop()
4698
4699            if type(node) is binary_type:
4700                op_func = node.args.get("operator")
4701                if op_func:
4702                    op = f"OPERATOR({self.sql(op_func)})"
4703
4704                stack.append(node.args.get("expression"))
4705                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4706                stack.append(node.args.get("this"))
4707            else:
4708                sqls.append(self.sql(node))
4709
4710        return "".join(sqls)
4711
4712    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4713        to_clause = self.sql(expression, "to")
4714        if to_clause:
4715            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4716
4717        return self.function_fallback_sql(expression)
4718
4719    def function_fallback_sql(self, expression: exp.Func) -> str:
4720        args = []
4721
4722        for key in expression.arg_types:
4723            arg_value = expression.args.get(key)
4724
4725            if isinstance(arg_value, list):
4726                for value in arg_value:
4727                    args.append(value)
4728            elif arg_value is not None:
4729                args.append(arg_value)
4730
4731        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4732            name = expression.meta_get("name") or expression.sql_name()
4733        else:
4734            name = expression.sql_name()
4735
4736        return self.func(name, *args)
4737
4738    def func(
4739        self,
4740        name: str,
4741        *args: t.Any,
4742        prefix: str = "(",
4743        suffix: str = ")",
4744        normalize: bool = True,
4745    ) -> str:
4746        name = self.normalize_func(name) if normalize else name
4747        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4748
4749    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4750        arg_sqls = tuple(
4751            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4752        )
4753        if self.pretty and self.too_wide(arg_sqls):
4754            return self.indent(
4755                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4756            )
4757        return sep.join(arg_sqls)
4758
4759    def too_wide(self, args: t.Iterable) -> bool:
4760        return sum(len(arg) for arg in args) > self.max_text_width
4761
4762    def format_time(
4763        self,
4764        expression: exp.Expr,
4765        inverse_time_mapping: dict[str, str] | None = None,
4766        inverse_time_trie: dict | None = None,
4767    ) -> str | None:
4768        return format_time(
4769            self.sql(expression, "format"),
4770            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4771            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4772        )
4773
4774    def expressions(
4775        self,
4776        expression: exp.Expr | None = None,
4777        key: str | None = None,
4778        sqls: t.Collection[str | exp.Expr] | None = None,
4779        flat: bool = False,
4780        indent: bool = True,
4781        skip_first: bool = False,
4782        skip_last: bool = False,
4783        sep: str = ", ",
4784        prefix: str = "",
4785        dynamic: bool = False,
4786        new_line: bool = False,
4787    ) -> str:
4788        expressions = expression.args.get(key or "expressions") if expression else sqls
4789
4790        if not expressions:
4791            return ""
4792
4793        if flat:
4794            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4795
4796        num_sqls = len(expressions)
4797        result_sqls = []
4798
4799        for i, e in enumerate(expressions):
4800            sql = self.sql(e, comment=False)
4801            if not sql:
4802                continue
4803
4804            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4805
4806            if self.pretty:
4807                if self.leading_comma:
4808                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4809                else:
4810                    result_sqls.append(
4811                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4812                    )
4813            else:
4814                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4815
4816        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4817            if new_line:
4818                result_sqls.insert(0, "")
4819                result_sqls.append("")
4820            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4821        else:
4822            result_sql = "".join(result_sqls)
4823
4824        return (
4825            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4826            if indent
4827            else result_sql
4828        )
4829
4830    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4831        flat = flat or isinstance(expression.parent, exp.Properties)
4832        expressions_sql = self.expressions(expression, flat=flat)
4833        if flat:
4834            return f"{op} {expressions_sql}"
4835        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4836
4837    def naked_property(self, expression: exp.Property) -> str:
4838        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4839        if not property_name:
4840            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4841        return f"{property_name} {self.sql(expression, 'this')}"
4842
4843    def tag_sql(self, expression: exp.Tag) -> str:
4844        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4845
4846    def token_sql(self, token_type: TokenType) -> str:
4847        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4848
4849    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4850        this = self.sql(expression, "this")
4851        expressions = self.no_identify(self.expressions, expression)
4852        expressions = (
4853            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4854        )
4855        return f"{this}{expressions}" if expressions.strip() != "" else this
4856
4857    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4858        return self.expressions(expression, flat=True)
4859
4860    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4861        params = self.no_identify(self.expressions, expression, flat=True)
4862        body = self.sql(expression, "this")
4863        prefix = "TABLE " if expression.args.get("is_table") else ""
4864        return f"({params}) AS {prefix}{body}"
4865
4866    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4867        this = self.sql(expression, "this")
4868        expressions = self.expressions(expression, flat=True)
4869        return f"{this}({expressions})"
4870
4871    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4872        return self.binary(expression, "=>")
4873
4874    def when_sql(self, expression: exp.When) -> str:
4875        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4876        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4877        condition = self.sql(expression, "condition")
4878        condition = f" AND {condition}" if condition else ""
4879
4880        then_expression = expression.args.get("then")
4881        if isinstance(then_expression, exp.Insert):
4882            this = self.sql(then_expression, "this")
4883            this = f"INSERT {this}" if this else "INSERT"
4884            then = self.sql(then_expression, "expression")
4885            then = f"{this} VALUES {then}" if then else this
4886        elif isinstance(then_expression, exp.Update):
4887            if isinstance(then_expression.args.get("expressions"), exp.Star):
4888                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4889            else:
4890                expressions_sql = self.expressions(then_expression)
4891                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4892        else:
4893            then = self.sql(then_expression)
4894
4895        if isinstance(then_expression, (exp.Insert, exp.Update)):
4896            where = self.sql(then_expression, "where")
4897            if where and not self.SUPPORTS_MERGE_WHERE:
4898                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4899                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4900                where = ""
4901            then = f"{then}{where}"
4902        return f"WHEN {matched}{source}{condition} THEN {then}"
4903
4904    def whens_sql(self, expression: exp.Whens) -> str:
4905        return self.expressions(expression, sep=" ", indent=False)
4906
4907    def merge_sql(self, expression: exp.Merge) -> str:
4908        table = expression.this
4909        table_alias = ""
4910
4911        hints = table.args.get("hints")
4912        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4913            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4914            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4915
4916        this = self.sql(table)
4917        using = f"USING {self.sql(expression, 'using')}"
4918        whens = self.sql(expression, "whens")
4919
4920        on = self.sql(expression, "on")
4921        on = f"ON {on}" if on else ""
4922
4923        if not on:
4924            on = self.expressions(expression, key="using_cond")
4925            on = f"USING ({on})" if on else ""
4926
4927        returning = self.sql(expression, "returning")
4928        if returning:
4929            whens = f"{whens}{returning}"
4930
4931        sep = self.sep()
4932
4933        return self.prepend_ctes(
4934            expression,
4935            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4936        )
4937
4938    @unsupported_args("format")
4939    def tochar_sql(self, expression: exp.ToChar) -> str:
4940        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4941
4942    @unsupported_args("default")
4943    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4944        if not self.SUPPORTS_TO_NUMBER:
4945            self.unsupported("Unsupported TO_NUMBER function")
4946            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4947
4948        fmt = expression.args.get("format")
4949        if not fmt:
4950            self.unsupported("Conversion format is required for TO_NUMBER")
4951            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4952
4953        return self.func("TO_NUMBER", expression.this, fmt)
4954
4955    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4956        this = self.sql(expression, "this")
4957        kind = self.sql(expression, "kind")
4958        settings_sql = self.expressions(expression, key="settings", sep=" ")
4959        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4960        return f"{this}({kind}{args})"
4961
4962    def dictrange_sql(self, expression: exp.DictRange) -> str:
4963        this = self.sql(expression, "this")
4964        max = self.sql(expression, "max")
4965        min = self.sql(expression, "min")
4966        return f"{this}(MIN {min} MAX {max})"
4967
4968    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4969        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4970
4971    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4972        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4973
4974    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4975    def uniquekeyproperty_sql(
4976        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4977    ) -> str:
4978        return f"{prefix} ({self.expressions(expression, flat=True)})"
4979
4980    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4981    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4982        expressions = self.expressions(expression, flat=True)
4983        expressions = f" {self.wrap(expressions)}" if expressions else ""
4984        buckets = self.sql(expression, "buckets")
4985        kind = self.sql(expression, "kind")
4986        buckets = f" BUCKETS {buckets}" if buckets else ""
4987        order = self.sql(expression, "order")
4988        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4989
4990    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4991        return ""
4992
4993    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4994        expressions = self.expressions(expression, key="expressions", flat=True)
4995        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4996        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4997        buckets = self.sql(expression, "buckets")
4998        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4999
5000    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5001        this = self.sql(expression, "this")
5002        having = self.sql(expression, "having")
5003
5004        if having:
5005            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5006
5007        return self.func("ANY_VALUE", this)
5008
5009    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5010        transform = self.func("TRANSFORM", *expression.expressions)
5011        row_format_before = self.sql(expression, "row_format_before")
5012        row_format_before = f" {row_format_before}" if row_format_before else ""
5013        record_writer = self.sql(expression, "record_writer")
5014        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5015        using = f" USING {self.sql(expression, 'command_script')}"
5016        schema = self.sql(expression, "schema")
5017        schema = f" AS {schema}" if schema else ""
5018        row_format_after = self.sql(expression, "row_format_after")
5019        row_format_after = f" {row_format_after}" if row_format_after else ""
5020        record_reader = self.sql(expression, "record_reader")
5021        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5022        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5023
5024    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5025        key_block_size = self.sql(expression, "key_block_size")
5026        if key_block_size:
5027            return f"KEY_BLOCK_SIZE = {key_block_size}"
5028
5029        using = self.sql(expression, "using")
5030        if using:
5031            return f"USING {using}"
5032
5033        parser = self.sql(expression, "parser")
5034        if parser:
5035            return f"WITH PARSER {parser}"
5036
5037        comment = self.sql(expression, "comment")
5038        if comment:
5039            return f"COMMENT {comment}"
5040
5041        visible = expression.args.get("visible")
5042        if visible is not None:
5043            return "VISIBLE" if visible else "INVISIBLE"
5044
5045        engine_attr = self.sql(expression, "engine_attr")
5046        if engine_attr:
5047            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5048
5049        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5050        if secondary_engine_attr:
5051            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5052
5053        self.unsupported("Unsupported index constraint option.")
5054        return ""
5055
5056    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5057        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5058        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5059
5060    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5061        kind = self.sql(expression, "kind")
5062        kind = f"{kind} INDEX" if kind else "INDEX"
5063        this = self.sql(expression, "this")
5064        this = f" {this}" if this else ""
5065        index_type = self.sql(expression, "index_type")
5066        index_type = f" USING {index_type}" if index_type else ""
5067        expressions = self.expressions(expression, flat=True)
5068        expressions = f" ({expressions})" if expressions else ""
5069        options = self.expressions(expression, key="options", sep=" ")
5070        options = f" {options}" if options else ""
5071        return f"{kind}{this}{index_type}{expressions}{options}"
5072
5073    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5074        if self.NVL2_SUPPORTED:
5075            return self.function_fallback_sql(expression)
5076
5077        case = exp.Case().when(
5078            expression.this.is_(exp.null()).not_(copy=False),
5079            expression.args["true"],
5080            copy=False,
5081        )
5082        else_cond = expression.args.get("false")
5083        if else_cond:
5084            case.else_(else_cond, copy=False)
5085
5086        return self.sql(case)
5087
5088    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5089        if expression.args.get("from_first") is False:
5090            self.unsupported("NTH_VALUE FROM LAST is not supported")
5091
5092        return self.function_fallback_sql(expression)
5093
5094    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5095        this = self.sql(expression, "this")
5096        expr = self.sql(expression, "expression")
5097        position = self.sql(expression, "position")
5098        position = f", {position}" if position else ""
5099        iterator = self.sql(expression, "iterator")
5100        condition = self.sql(expression, "condition")
5101        condition = f" IF {condition}" if condition else ""
5102        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5103
5104    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5105        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5106
5107    def opclass_sql(self, expression: exp.Opclass) -> str:
5108        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5109
5110    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5111        model = self.sql(expression, "this")
5112        model = f"MODEL {model}"
5113        expr = expression.expression
5114        if expr:
5115            expr_sql = self.sql(expression, "expression")
5116            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5117        else:
5118            expr_sql = None
5119
5120        parameters = self.sql(expression, "params_struct") or None
5121
5122        return self.func(name, model, expr_sql, parameters)
5123
5124    def predict_sql(self, expression: exp.Predict) -> str:
5125        return self._ml_sql(expression, "PREDICT")
5126
5127    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5128        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5129        return self._ml_sql(expression, name)
5130
5131    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5132        return self._ml_sql(expression, "GENERATE_TEXT")
5133
5134    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5135        return self._ml_sql(expression, "GENERATE_TABLE")
5136
5137    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5138        return self._ml_sql(expression, "GENERATE_BOOL")
5139
5140    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5141        return self._ml_sql(expression, "GENERATE_INT")
5142
5143    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5144        return self._ml_sql(expression, "GENERATE_DOUBLE")
5145
5146    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5147        return self._ml_sql(expression, "TRANSLATE")
5148
5149    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5150        return self._ml_sql(expression, "FORECAST")
5151
5152    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5153        this_sql = self.sql(expression, "this")
5154        if isinstance(expression.this, exp.Table):
5155            this_sql = f"TABLE {this_sql}"
5156
5157        return self.func(
5158            "FORECAST",
5159            this_sql,
5160            expression.args.get("data_col"),
5161            expression.args.get("timestamp_col"),
5162            expression.args.get("model"),
5163            expression.args.get("id_cols"),
5164            expression.args.get("horizon"),
5165            expression.args.get("forecast_end_timestamp"),
5166            expression.args.get("confidence_level"),
5167            expression.args.get("output_historical_time_series"),
5168            expression.args.get("context_window"),
5169        )
5170
5171    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5172        this_sql = self.sql(expression, "this")
5173        if isinstance(expression.this, exp.Table):
5174            this_sql = f"TABLE {this_sql}"
5175
5176        return self.func(
5177            "FEATURES_AT_TIME",
5178            this_sql,
5179            expression.args.get("time"),
5180            expression.args.get("num_rows"),
5181            expression.args.get("ignore_feature_nulls"),
5182        )
5183
5184    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5185        this_sql = self.sql(expression, "this")
5186        if isinstance(expression.this, exp.Table):
5187            this_sql = f"TABLE {this_sql}"
5188
5189        query_table = self.sql(expression, "query_table")
5190        if isinstance(expression.args["query_table"], exp.Table):
5191            query_table = f"TABLE {query_table}"
5192
5193        return self.func(
5194            "VECTOR_SEARCH",
5195            this_sql,
5196            expression.args.get("column_to_search"),
5197            query_table,
5198            expression.args.get("query_column_to_search"),
5199            expression.args.get("top_k"),
5200            expression.args.get("distance_type"),
5201            expression.args.get("options"),
5202        )
5203
5204    def forin_sql(self, expression: exp.ForIn) -> str:
5205        this = self.sql(expression, "this")
5206        expression_sql = self.sql(expression, "expression")
5207        return f"FOR {this} DO {expression_sql}"
5208
5209    def refresh_sql(self, expression: exp.Refresh) -> str:
5210        this = self.sql(expression, "this")
5211        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5212        return f"REFRESH {kind}{this}"
5213
5214    def toarray_sql(self, expression: exp.ToArray) -> str:
5215        arg = expression.this
5216        if not arg.type:
5217            import sqlglot.optimizer.annotate_types
5218
5219            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5220
5221        if arg.is_type(exp.DType.ARRAY):
5222            return self.sql(arg)
5223
5224        cond_for_null = arg.is_(exp.null())
5225        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5226
5227    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5228        this = expression.this
5229        time_format = self.format_time(expression)
5230
5231        if time_format:
5232            return self.sql(
5233                exp.cast(
5234                    exp.StrToTime(this=this, format=expression.args["format"]),
5235                    exp.DType.TIME,
5236                )
5237            )
5238
5239        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5240            return self.sql(this)
5241
5242        return self.sql(exp.cast(this, exp.DType.TIME))
5243
5244    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5245        this = expression.this
5246        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5247            return self.sql(this)
5248
5249        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5250
5251    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5252        this = expression.this
5253        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5254            return self.sql(this)
5255
5256        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5257
5258    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5259        this = expression.this
5260        time_format = self.format_time(expression)
5261        safe = expression.args.get("safe")
5262        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5263            return self.sql(
5264                exp.cast(
5265                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5266                    exp.DType.DATE,
5267                )
5268            )
5269
5270        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5271            return self.sql(this)
5272
5273        if safe:
5274            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5275
5276        return self.sql(exp.cast(this, exp.DType.DATE))
5277
5278    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5279        return self.sql(
5280            exp.func(
5281                "DATEDIFF",
5282                expression.this,
5283                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5284                "day",
5285            )
5286        )
5287
5288    def lastday_sql(self, expression: exp.LastDay) -> str:
5289        if self.LAST_DAY_SUPPORTS_DATE_PART:
5290            return self.function_fallback_sql(expression)
5291
5292        unit = expression.args.get("unit")
5293        if unit and unit.name.upper() != "MONTH":
5294            self.unsupported("Date parts are not supported in LAST_DAY.")
5295
5296        return self.func("LAST_DAY", expression.this)
5297
5298    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5299        import sqlglot.dialects.dialect
5300
5301        return self.func(
5302            "DATE_ADD",
5303            expression.this,
5304            expression.expression,
5305            sqlglot.dialects.dialect.unit_to_str(expression),
5306        )
5307
5308    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5309        if self.CAN_IMPLEMENT_ARRAY_ANY:
5310            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5311            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5312            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5313            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5314
5315        import sqlglot.dialects.dialect
5316
5317        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5318        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5319            self.unsupported("ARRAY_ANY is unsupported")
5320
5321        return self.function_fallback_sql(expression)
5322
5323    def struct_sql(self, expression: exp.Struct) -> str:
5324        expression.set(
5325            "expressions",
5326            [
5327                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5328                if isinstance(e, exp.PropertyEQ)
5329                else e
5330                for e in expression.expressions
5331            ],
5332        )
5333
5334        return self.function_fallback_sql(expression)
5335
5336    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5337        low = self.sql(expression, "this")
5338        high = self.sql(expression, "expression")
5339
5340        return f"{low} TO {high}"
5341
5342    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5343        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5344        tables = f" {self.expressions(expression)}"
5345
5346        exists = " IF EXISTS" if expression.args.get("exists") else ""
5347
5348        on_cluster = self.sql(expression, "cluster")
5349        on_cluster = f" {on_cluster}" if on_cluster else ""
5350
5351        identity = self.sql(expression, "identity")
5352        identity = f" {identity} IDENTITY" if identity else ""
5353
5354        option = self.sql(expression, "option")
5355        option = f" {option}" if option else ""
5356
5357        partition = self.sql(expression, "partition")
5358        partition = f" {partition}" if partition else ""
5359
5360        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5361
5362    # This transpiles T-SQL's CONVERT function
5363    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5364    def convert_sql(self, expression: exp.Convert) -> str:
5365        to = expression.this
5366        value = expression.expression
5367        style = expression.args.get("style")
5368        safe = expression.args.get("safe")
5369        strict = expression.args.get("strict")
5370
5371        if not to or not value:
5372            return ""
5373
5374        # Retrieve length of datatype and override to default if not specified
5375        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5376            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5377
5378        transformed: exp.Expr | None = None
5379        cast = exp.Cast if strict else exp.TryCast
5380
5381        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5382        if isinstance(style, exp.Literal) and style.is_int:
5383            import sqlglot.dialects.tsql
5384
5385            style_value = style.name
5386            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5387            if not converted_style:
5388                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5389
5390            fmt = exp.Literal.string(converted_style)
5391
5392            if to.this == exp.DType.DATE:
5393                transformed = exp.StrToDate(this=value, format=fmt)
5394            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5395                transformed = exp.StrToTime(this=value, format=fmt)
5396            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5397                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5398            elif to.this == exp.DType.TEXT:
5399                transformed = exp.TimeToStr(this=value, format=fmt)
5400
5401        if not transformed:
5402            transformed = cast(this=value, to=to, safe=safe)
5403
5404        return self.sql(transformed)
5405
5406    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5407        this = expression.this
5408        if isinstance(this, exp.JSONPathWildcard):
5409            this = self.json_path_part(this)
5410            return f".{this}" if this else ""
5411
5412        quoted = expression.args.get("quoted")
5413        if not (
5414            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5415        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5416            return f".{this}"
5417
5418        this = self.json_path_part(this)
5419
5420        return (
5421            f"[{this}]"
5422            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5423            else f".{this}"
5424        )
5425
5426    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5427        this = self.json_path_part(expression.this)
5428        return f"[{this}]" if this else ""
5429
5430    def _simplify_unless_literal(self, expression: E) -> E:
5431        if not isinstance(expression, exp.Literal):
5432            import sqlglot.optimizer.simplify
5433
5434            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5435
5436        return expression
5437
5438    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5439        this = expression.this
5440        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5441            self.unsupported(
5442                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5443            )
5444            return self.sql(this)
5445
5446        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5447            if self.IGNORE_NULLS_BEFORE_ORDER:
5448                from sqlglot.optimizer.scope import find_all_in_scope
5449
5450                # The first modifier here will be the one closest to the AggFunc's arg
5451                mods = sorted(
5452                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5453                    key=lambda x: (
5454                        0
5455                        if isinstance(x, exp.HavingMax)
5456                        else (1 if isinstance(x, exp.Order) else 2)
5457                    ),
5458                )
5459
5460                if mods:
5461                    mod = mods[0]
5462                    this = expression.__class__(this=mod.this.copy())
5463                    this.meta["inline"] = True
5464                    mod.this.replace(this)
5465                    return self.sql(expression.this)
5466
5467            agg_func = expression.find(exp.AggFunc)
5468
5469            if agg_func:
5470                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5471                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5472
5473        return f"{self.sql(expression, 'this')} {text}"
5474
5475    def _replace_line_breaks(self, string: str) -> str:
5476        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5477        if self.pretty:
5478            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5479        return string
5480
5481    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5482        option = self.sql(expression, "this")
5483
5484        if expression.expressions:
5485            upper = option.upper()
5486
5487            # Snowflake FILE_FORMAT options are separated by whitespace
5488            sep = " " if upper == "FILE_FORMAT" else ", "
5489
5490            # Databricks copy/format options do not set their list of values with EQ
5491            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5492            values = self.expressions(expression, flat=True, sep=sep)
5493            return f"{option}{op}({values})"
5494
5495        value = self.sql(expression, "expression")
5496
5497        if not value:
5498            return option
5499
5500        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5501
5502        return f"{option}{op}{value}"
5503
5504    def credentials_sql(self, expression: exp.Credentials) -> str:
5505        cred_expr = expression.args.get("credentials")
5506        if isinstance(cred_expr, exp.Literal):
5507            # Redshift case: CREDENTIALS <string>
5508            credentials = self.sql(expression, "credentials")
5509            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5510        else:
5511            # Snowflake case: CREDENTIALS = (...)
5512            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5513            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5514
5515        storage = self.sql(expression, "storage")
5516        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5517
5518        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5519        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5520
5521        iam_role = self.sql(expression, "iam_role")
5522        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5523
5524        region = self.sql(expression, "region")
5525        region = f" REGION {region}" if region else ""
5526
5527        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5528
5529    def copy_sql(self, expression: exp.Copy) -> str:
5530        this = self.sql(expression, "this")
5531        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5532
5533        credentials = self.sql(expression, "credentials")
5534        credentials = self.seg(credentials) if credentials else ""
5535        files = self.expressions(expression, key="files", flat=True)
5536        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5537
5538        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5539        params = self.expressions(
5540            expression,
5541            key="params",
5542            sep=sep,
5543            new_line=True,
5544            skip_last=True,
5545            skip_first=True,
5546            indent=self.COPY_PARAMS_ARE_WRAPPED,
5547        )
5548
5549        if params:
5550            if self.COPY_PARAMS_ARE_WRAPPED:
5551                params = f" WITH ({params})"
5552            elif not self.pretty and (files or credentials):
5553                params = f" {params}"
5554
5555        return f"COPY{this}{kind} {files}{credentials}{params}"
5556
5557    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5558        return ""
5559
5560    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5561        on_sql = "ON" if expression.args.get("on") else "OFF"
5562        filter_col: str | None = self.sql(expression, "filter_column")
5563        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5564        retention_period: str | None = self.sql(expression, "retention_period")
5565        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5566
5567        if filter_col or retention_period:
5568            on_sql = self.func("ON", filter_col, retention_period)
5569
5570        return f"DATA_DELETION={on_sql}"
5571
5572    def maskingpolicycolumnconstraint_sql(
5573        self, expression: exp.MaskingPolicyColumnConstraint
5574    ) -> str:
5575        this = self.sql(expression, "this")
5576        expressions = self.expressions(expression, flat=True)
5577        expressions = f" USING ({expressions})" if expressions else ""
5578        return f"MASKING POLICY {this}{expressions}"
5579
5580    def gapfill_sql(self, expression: exp.GapFill) -> str:
5581        this = self.sql(expression, "this")
5582        this = f"TABLE {this}"
5583        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5584
5585    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5586        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5587
5588    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5589        this = self.sql(expression, "this")
5590        expr = expression.expression
5591
5592        if isinstance(expr, exp.Func):
5593            # T-SQL's CLR functions are case sensitive
5594            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5595        else:
5596            expr = self.sql(expression, "expression")
5597
5598        return self.scope_resolution(expr, this)
5599
5600    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5601        if self.PARSE_JSON_NAME is None:
5602            return self.sql(expression.this)
5603
5604        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5605
5606    def rand_sql(self, expression: exp.Rand) -> str:
5607        lower = self.sql(expression, "lower")
5608        upper = self.sql(expression, "upper")
5609
5610        if lower and upper:
5611            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5612        return self.func("RAND", expression.this)
5613
5614    def changes_sql(self, expression: exp.Changes) -> str:
5615        information = self.sql(expression, "information")
5616        information = f"INFORMATION => {information}"
5617        at_before = self.sql(expression, "at_before")
5618        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5619        end = self.sql(expression, "end")
5620        end = f"{self.seg('')}{end}" if end else ""
5621
5622        return f"CHANGES ({information}){at_before}{end}"
5623
5624    def pad_sql(self, expression: exp.Pad) -> str:
5625        prefix = "L" if expression.args.get("is_left") else "R"
5626
5627        fill_pattern = self.sql(expression, "fill_pattern") or None
5628        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5629            fill_pattern = "' '"
5630
5631        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5632
5633    def summarize_sql(self, expression: exp.Summarize) -> str:
5634        table = " TABLE" if expression.args.get("table") else ""
5635        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5636
5637    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5638        generate_series = exp.GenerateSeries(**expression.args)
5639
5640        parent = expression.parent
5641        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5642            parent = parent.parent
5643
5644        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5645            return self.sql(exp.Unnest(expressions=[generate_series]))
5646
5647        if isinstance(parent, exp.Select):
5648            self.unsupported("GenerateSeries projection unnesting is not supported.")
5649
5650        return self.sql(generate_series)
5651
5652    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5653        if self.SUPPORTS_CONVERT_TIMEZONE:
5654            return self.function_fallback_sql(expression)
5655
5656        source_tz = expression.args.get("source_tz")
5657        target_tz = expression.args.get("target_tz")
5658        timestamp = expression.args.get("timestamp")
5659
5660        if source_tz and timestamp:
5661            timestamp = exp.AtTimeZone(
5662                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5663            )
5664
5665        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5666
5667        return self.sql(expr)
5668
5669    def json_sql(self, expression: exp.JSON) -> str:
5670        this = self.sql(expression, "this")
5671        this = f" {this}" if this else ""
5672
5673        _with = expression.args.get("with_")
5674
5675        if _with is None:
5676            with_sql = ""
5677        elif not _with:
5678            with_sql = " WITHOUT"
5679        else:
5680            with_sql = " WITH"
5681
5682        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5683
5684        return f"JSON{this}{with_sql}{unique_sql}"
5685
5686    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5687        path = self.sql(expression, "path")
5688        returning = self.sql(expression, "returning")
5689        returning = f" RETURNING {returning}" if returning else ""
5690
5691        on_condition = self.sql(expression, "on_condition")
5692        on_condition = f" {on_condition}" if on_condition else ""
5693
5694        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5695
5696    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5697        regexp = " REGEXP" if expression.args.get("regexp") else ""
5698        return f"SKIP{regexp} {self.sql(expression.expression)}"
5699
5700    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5701        else_ = "ELSE " if expression.args.get("else_") else ""
5702        condition = self.sql(expression, "expression")
5703        condition = f"WHEN {condition} THEN " if condition else else_
5704        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5705        return f"{condition}{insert}"
5706
5707    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5708        kind = self.sql(expression, "kind")
5709        expressions = self.seg(self.expressions(expression, sep=" "))
5710        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5711        return res
5712
5713    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5714        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5715        empty = expression.args.get("empty")
5716        empty = (
5717            f"DEFAULT {empty} ON EMPTY"
5718            if isinstance(empty, exp.Expr)
5719            else self.sql(expression, "empty")
5720        )
5721
5722        error = expression.args.get("error")
5723        error = (
5724            f"DEFAULT {error} ON ERROR"
5725            if isinstance(error, exp.Expr)
5726            else self.sql(expression, "error")
5727        )
5728
5729        if error and empty:
5730            error = (
5731                f"{empty} {error}"
5732                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5733                else f"{error} {empty}"
5734            )
5735            empty = ""
5736
5737        null = self.sql(expression, "null")
5738
5739        return f"{empty}{error}{null}"
5740
5741    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5742        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5743        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5744
5745    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5746        this = self.sql(expression, "this")
5747        path = self.sql(expression, "path")
5748
5749        passing = self.expressions(expression, "passing")
5750        passing = f" PASSING {passing}" if passing else ""
5751
5752        on_condition = self.sql(expression, "on_condition")
5753        on_condition = f" {on_condition}" if on_condition else ""
5754
5755        path = f"{path}{passing}{on_condition}"
5756
5757        return self.func("JSON_EXISTS", this, path)
5758
5759    def _add_arrayagg_null_filter(
5760        self,
5761        array_agg_sql: str,
5762        array_agg_expr: exp.ArrayAgg,
5763        column_expr: exp.Expr,
5764    ) -> str:
5765        """
5766        Add NULL filter to ARRAY_AGG if dialect requires it.
5767
5768        Args:
5769            array_agg_sql: The generated ARRAY_AGG SQL string
5770            array_agg_expr: The ArrayAgg expression node
5771            column_expr: The column/expression to filter (before ORDER BY wrapping)
5772
5773        Returns:
5774            SQL string with FILTER clause added if needed
5775        """
5776        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5777        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5778        if not (
5779            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5780        ):
5781            return array_agg_sql
5782
5783        parent = array_agg_expr.parent
5784        if isinstance(parent, exp.Filter):
5785            parent_cond = parent.expression.this
5786            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5787        elif column_expr.find(exp.Column):
5788            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5789            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5790            this_sql = (
5791                self.expressions(column_expr)
5792                if isinstance(column_expr, exp.Distinct)
5793                else self.sql(column_expr)
5794            )
5795            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5796
5797        return array_agg_sql
5798
5799    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5800        array_agg = self.function_fallback_sql(expression)
5801        column_expr = expression.this
5802        if isinstance(column_expr, exp.Order):
5803            column_expr = column_expr.this
5804
5805        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5806
5807    def slice_sql(self, expression: exp.Slice) -> str:
5808        step = self.sql(expression, "step")
5809        end = self.sql(expression.expression)
5810        begin = self.sql(expression.this)
5811
5812        sql = f"{end}:{step}" if step else end
5813        return f"{begin}:{sql}" if sql else f"{begin}:"
5814
5815    def apply_sql(self, expression: exp.Apply) -> str:
5816        this = self.sql(expression, "this")
5817        expr = self.sql(expression, "expression")
5818
5819        return f"{this} APPLY({expr})"
5820
5821    def _grant_or_revoke_sql(
5822        self,
5823        expression: exp.Grant | exp.Revoke,
5824        keyword: str,
5825        preposition: str,
5826        grant_option_prefix: str = "",
5827        grant_option_suffix: str = "",
5828    ) -> str:
5829        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5830
5831        kind = self.sql(expression, "kind")
5832        kind = f" {kind}" if kind else ""
5833
5834        securable = self.sql(expression, "securable")
5835        securable = f" {securable}" if securable else ""
5836
5837        principals = self.expressions(expression, key="principals", flat=True)
5838
5839        if not expression.args.get("grant_option"):
5840            grant_option_prefix = grant_option_suffix = ""
5841
5842        # cascade for revoke only
5843        cascade = self.sql(expression, "cascade")
5844        cascade = f" {cascade}" if cascade else ""
5845
5846        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5847
5848    def grant_sql(self, expression: exp.Grant) -> str:
5849        return self._grant_or_revoke_sql(
5850            expression,
5851            keyword="GRANT",
5852            preposition="TO",
5853            grant_option_suffix=" WITH GRANT OPTION",
5854        )
5855
5856    def revoke_sql(self, expression: exp.Revoke) -> str:
5857        return self._grant_or_revoke_sql(
5858            expression,
5859            keyword="REVOKE",
5860            preposition="FROM",
5861            grant_option_prefix="GRANT OPTION FOR ",
5862        )
5863
5864    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5865        this = self.sql(expression, "this")
5866        columns = self.expressions(expression, flat=True)
5867        columns = f"({columns})" if columns else ""
5868
5869        return f"{this}{columns}"
5870
5871    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5872        this = self.sql(expression, "this")
5873
5874        kind = self.sql(expression, "kind")
5875        kind = f"{kind} " if kind else ""
5876
5877        return f"{kind}{this}"
5878
5879    def columns_sql(self, expression: exp.Columns) -> str:
5880        func = self.function_fallback_sql(expression)
5881        if expression.args.get("unpack"):
5882            func = f"*{func}"
5883
5884        return func
5885
5886    def overlay_sql(self, expression: exp.Overlay) -> str:
5887        this = self.sql(expression, "this")
5888        expr = self.sql(expression, "expression")
5889        from_sql = self.sql(expression, "from_")
5890        for_sql = self.sql(expression, "for_")
5891        for_sql = f" FOR {for_sql}" if for_sql else ""
5892
5893        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5894
5895    @unsupported_args("format")
5896    def todouble_sql(self, expression: exp.ToDouble) -> str:
5897        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5898        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5899
5900    def string_sql(self, expression: exp.String) -> str:
5901        this = expression.this
5902        zone = expression.args.get("zone")
5903
5904        if zone:
5905            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5906            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5907            # set for source_tz to transpile the time conversion before the STRING cast
5908            this = exp.ConvertTimezone(
5909                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5910            )
5911
5912        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5913
5914    def median_sql(self, expression: exp.Median) -> str:
5915        if not self.SUPPORTS_MEDIAN:
5916            return self.sql(
5917                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5918            )
5919
5920        return self.function_fallback_sql(expression)
5921
5922    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5923        filler = self.sql(expression, "this")
5924        filler = f" {filler}" if filler else ""
5925        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5926        return f"TRUNCATE{filler} {with_count}"
5927
5928    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5929        if self.SUPPORTS_UNIX_SECONDS:
5930            return self.function_fallback_sql(expression)
5931
5932        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5933
5934        return self.sql(
5935            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5936        )
5937
5938    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5939        dim = expression.expression
5940
5941        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5942        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5943            if not (dim.is_int and dim.name == "1"):
5944                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5945            dim = None
5946
5947        # If dimension is required but not specified, default initialize it
5948        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5949            dim = exp.Literal.number(1)
5950
5951        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5952
5953    def attach_sql(self, expression: exp.Attach) -> str:
5954        this = self.sql(expression, "this")
5955        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5956        expressions = self.expressions(expression)
5957        expressions = f" ({expressions})" if expressions else ""
5958
5959        return f"ATTACH{exists_sql} {this}{expressions}"
5960
5961    def detach_sql(self, expression: exp.Detach) -> str:
5962        kind = self.sql(expression, "kind")
5963        kind = f" {kind}" if kind else ""
5964        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5965        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5966        exists = " IF EXISTS" if expression.args.get("exists") else ""
5967        if exists:
5968            kind = kind or " DATABASE"
5969
5970        this = self.sql(expression, "this")
5971        this = f" {this}" if this else ""
5972        cluster = self.sql(expression, "cluster")
5973        cluster = f" {cluster}" if cluster else ""
5974        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5975        sync = " SYNC" if expression.args.get("sync") else ""
5976        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5977
5978    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5979        this = self.sql(expression, "this")
5980        value = self.sql(expression, "expression")
5981        value = f" {value}" if value else ""
5982        return f"{this}{value}"
5983
5984    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5985        return (
5986            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5987        )
5988
5989    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5990        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5991        encode = f"{encode} {self.sql(expression, 'this')}"
5992
5993        properties = expression.args.get("properties")
5994        if properties:
5995            encode = f"{encode} {self.properties(properties)}"
5996
5997        return encode
5998
5999    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6000        this = self.sql(expression, "this")
6001        include = f"INCLUDE {this}"
6002
6003        column_def = self.sql(expression, "column_def")
6004        if column_def:
6005            include = f"{include} {column_def}"
6006
6007        alias = self.sql(expression, "alias")
6008        if alias:
6009            include = f"{include} AS {alias}"
6010
6011        return include
6012
6013    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6014        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6015        name = f"{prefix} {self.sql(expression, 'this')}"
6016        return self.func("XMLELEMENT", name, *expression.expressions)
6017
6018    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6019        this = self.sql(expression, "this")
6020        expr = self.sql(expression, "expression")
6021        expr = f"({expr})" if expr else ""
6022        return f"{this}{expr}"
6023
6024    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6025        partitions = self.expressions(expression, "partition_expressions")
6026        create = self.expressions(expression, "create_expressions")
6027        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6028
6029    def partitionbyrangepropertydynamic_sql(
6030        self, expression: exp.PartitionByRangePropertyDynamic
6031    ) -> str:
6032        start = self.sql(expression, "start")
6033        end = self.sql(expression, "end")
6034
6035        every = expression.args["every"]
6036        if isinstance(every, exp.Interval) and every.this.is_string:
6037            every.this.replace(exp.Literal.number(every.name))
6038
6039        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6040
6041    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6042        name = self.sql(expression, "this")
6043        values = self.expressions(expression, flat=True)
6044
6045        return f"NAME {name} VALUE {values}"
6046
6047    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6048        kind = self.sql(expression, "kind")
6049        sample = self.sql(expression, "sample")
6050        return f"SAMPLE {sample} {kind}"
6051
6052    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6053        kind = self.sql(expression, "kind")
6054        option = self.sql(expression, "option")
6055        option = f" {option}" if option else ""
6056        this = self.sql(expression, "this")
6057        this = f" {this}" if this else ""
6058        columns = self.expressions(expression)
6059        columns = f" {columns}" if columns else ""
6060        return f"{kind}{option} STATISTICS{this}{columns}"
6061
6062    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6063        this = self.sql(expression, "this")
6064        columns = self.expressions(expression)
6065        inner_expression = self.sql(expression, "expression")
6066        inner_expression = f" {inner_expression}" if inner_expression else ""
6067        update_options = self.sql(expression, "update_options")
6068        update_options = f" {update_options} UPDATE" if update_options else ""
6069        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6070
6071    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6072        kind = self.sql(expression, "kind")
6073        kind = f" {kind}" if kind else ""
6074        return f"DELETE{kind} STATISTICS"
6075
6076    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6077        inner_expression = self.sql(expression, "expression")
6078        return f"LIST CHAINED ROWS{inner_expression}"
6079
6080    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6081        kind = self.sql(expression, "kind")
6082        this = self.sql(expression, "this")
6083        this = f" {this}" if this else ""
6084        inner_expression = self.sql(expression, "expression")
6085        return f"VALIDATE {kind}{this}{inner_expression}"
6086
6087    def analyze_sql(self, expression: exp.Analyze) -> str:
6088        options = self.expressions(expression, key="options", sep=" ")
6089        options = f" {options}" if options else ""
6090        kind = self.sql(expression, "kind")
6091        kind = f" {kind}" if kind else ""
6092        tables = self.expressions(expression, key="tables", flat=True)
6093        tables = f" {tables}" if tables else ""
6094        mode = self.sql(expression, "mode")
6095        mode = f" {mode}" if mode else ""
6096        properties = self.sql(expression, "properties")
6097        properties = f" {properties}" if properties else ""
6098        partition = self.sql(expression, "partition")
6099        partition = f" {partition}" if partition else ""
6100        inner_expression = self.sql(expression, "expression")
6101        inner_expression = f" {inner_expression}" if inner_expression else ""
6102        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6103
6104    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6105        this = self.sql(expression, "this")
6106        namespaces = self.expressions(expression, key="namespaces")
6107        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6108        passing = self.expressions(expression, key="passing")
6109        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6110        columns = self.expressions(expression, key="columns")
6111        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6112        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6113        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6114
6115    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6116        this = self.sql(expression, "this")
6117        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6118
6119    def export_sql(self, expression: exp.Export) -> str:
6120        this = self.sql(expression, "this")
6121        connection = self.sql(expression, "connection")
6122        connection = f"WITH CONNECTION {connection} " if connection else ""
6123        options = self.sql(expression, "options")
6124        return f"EXPORT DATA {connection}{options} AS {this}"
6125
6126    def declare_sql(self, expression: exp.Declare) -> str:
6127        replace = "OR REPLACE " if expression.args.get("replace") else ""
6128        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6129
6130    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6131        variables = self.expressions(expression, "this")
6132        default = self.sql(expression, "default")
6133        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6134
6135        kind = self.sql(expression, "kind")
6136        if isinstance(expression.args.get("kind"), exp.Schema):
6137            kind = f"TABLE {kind}"
6138
6139        kind = f" {kind}" if kind else ""
6140
6141        return f"{variables}{kind}{default}"
6142
6143    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6144        kind = self.sql(expression, "kind")
6145        this = self.sql(expression, "this")
6146        set = self.sql(expression, "expression")
6147        using = self.sql(expression, "using")
6148        using = f" USING {using}" if using else ""
6149
6150        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6151
6152        return f"{kind_sql} {this} SET {set}{using}"
6153
6154    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6155        params = self.expressions(expression, key="params", flat=True)
6156        return self.func(expression.name, *expression.expressions) + f"({params})"
6157
6158    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6159        return self.func(expression.name, *expression.expressions)
6160
6161    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6162        return self.anonymousaggfunc_sql(expression)
6163
6164    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6165        return self.parameterizedagg_sql(expression)
6166
6167    def show_sql(self, expression: exp.Show) -> str:
6168        self.unsupported("Unsupported SHOW statement")
6169        return ""
6170
6171    def install_sql(self, expression: exp.Install) -> str:
6172        self.unsupported("Unsupported INSTALL statement")
6173        return ""
6174
6175    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6176        # Snowflake GET/PUT statements:
6177        #   PUT <file> <internalStage> <properties>
6178        #   GET <internalStage> <file> <properties>
6179        props = expression.args.get("properties")
6180        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6181        this = self.sql(expression, "this")
6182        target = self.sql(expression, "target")
6183
6184        if isinstance(expression, exp.Put):
6185            return f"PUT {this} {target}{props_sql}"
6186        else:
6187            return f"GET {target} {this}{props_sql}"
6188
6189    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6190        this = self.sql(expression, "this")
6191        expr = self.sql(expression, "expression")
6192        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6193        return f"TRANSLATE({this} USING {expr}{with_error})"
6194
6195    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6196        if self.SUPPORTS_DECODE_CASE:
6197            return self.func("DECODE", *expression.expressions)
6198
6199        decode_expr, *expressions = expression.expressions
6200
6201        ifs = []
6202        for search, result in zip(expressions[::2], expressions[1::2]):
6203            if isinstance(search, exp.Literal):
6204                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6205            elif isinstance(search, exp.Null):
6206                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6207            else:
6208                if isinstance(search, exp.Binary):
6209                    search = exp.paren(search)
6210
6211                cond = exp.or_(
6212                    decode_expr.eq(search),
6213                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6214                    copy=False,
6215                )
6216                ifs.append(exp.If(this=cond, true=result))
6217
6218        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6219        return self.sql(case)
6220
6221    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6222        this = self.sql(expression, "this")
6223        this = self.seg(this, sep="")
6224        dimensions = self.expressions(
6225            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6226        )
6227        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6228        metrics = self.expressions(
6229            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6230        )
6231        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6232        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6233        facts = self.seg(f"FACTS {facts}") if facts else ""
6234        where = self.sql(expression, "where")
6235        where = self.seg(f"WHERE {where}") if where else ""
6236        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6237        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6238
6239    def getextract_sql(self, expression: exp.GetExtract) -> str:
6240        this = expression.this
6241        expr = expression.expression
6242
6243        if not this.type or not expression.type:
6244            import sqlglot.optimizer.annotate_types
6245
6246            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6247
6248        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6249            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6250
6251        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6252
6253    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6254        return self.sql(
6255            exp.DateAdd(
6256                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6257                expression=expression.this,
6258                unit=exp.var("DAY"),
6259            )
6260        )
6261
6262    def space_sql(self: Generator, expression: exp.Space) -> str:
6263        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6264
6265    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6266        return f"BUILD {self.sql(expression, 'this')}"
6267
6268    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6269        method = self.sql(expression, "method")
6270        kind = expression.args.get("kind")
6271        if not kind:
6272            return f"REFRESH {method}"
6273
6274        every = self.sql(expression, "every")
6275        unit = self.sql(expression, "unit")
6276        every = f" EVERY {every} {unit}" if every else ""
6277        starts = self.sql(expression, "starts")
6278        starts = f" STARTS {starts}" if starts else ""
6279
6280        return f"REFRESH {method} ON {kind}{every}{starts}"
6281
6282    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6283        self.unsupported("The model!attribute syntax is not supported")
6284        return ""
6285
6286    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6287        return self.func("DIRECTORY", expression.this)
6288
6289    def uuid_sql(self, expression: exp.Uuid) -> str:
6290        is_string = expression.args.get("is_string", False)
6291        uuid_func_sql = self.func("UUID")
6292
6293        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6294            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6295
6296        return uuid_func_sql
6297
6298    def initcap_sql(self, expression: exp.Initcap) -> str:
6299        delimiters = expression.expression
6300
6301        if delimiters:
6302            # do not generate delimiters arg if we are round-tripping from default delimiters
6303            if (
6304                delimiters.is_string
6305                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6306            ):
6307                delimiters = None
6308            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6309                self.unsupported("INITCAP does not support custom delimiters")
6310                delimiters = None
6311
6312        return self.func("INITCAP", expression.this, delimiters)
6313
6314    def localtime_sql(self, expression: exp.Localtime) -> str:
6315        this = expression.this
6316        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6317
6318    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6319        this = expression.this
6320        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6321
6322    def weekstart_name(self, expression: exp.WeekStart) -> str:
6323        import sqlglot.dialects.dialect
6324
6325        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6326        this = expression.this.name.upper()
6327
6328        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6329        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6330
6331        if dow_from_week_start_day != dow_from_week_offset:
6332            self.unsupported(
6333                f"WEEK({this}) is not supported; falling back to the default week start day"
6334            )
6335
6336        return "WEEK"
6337
6338    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6339        name = self.weekstart_name(expression)
6340
6341        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6342        if isinstance(expression.parent, exp.DateTrunc):
6343            return self.sql(exp.Literal.string(name))
6344
6345        return name
6346
6347    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6348        this = self.expressions(expression)
6349        charset = self.sql(expression, "charset")
6350        using = f" USING {charset}" if charset else ""
6351        return self.func(name, this + using)
6352
6353    def block_sql(self, expression: exp.Block) -> str:
6354        expressions = self.expressions(expression, sep="; ", flat=True)
6355        begin = "BEGIN " if expression.args.get("begin") else ""
6356        return f"{begin}{expressions}" if expressions else ""
6357
6358    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6359        self.unsupported("Unsupported Inline UDFs syntax")
6360        return ""
6361
6362    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6363        self.unsupported("Unsupported Stored Procedure syntax")
6364        return ""
6365
6366    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6367        self.unsupported("Unsupported If block syntax")
6368        return ""
6369
6370    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6371        self.unsupported("Unsupported Case statement syntax")
6372        return ""
6373
6374    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6375        self.unsupported("Unsupported While block syntax")
6376        return ""
6377
6378    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6379        self.unsupported("Unsupported Loop block syntax")
6380        return ""
6381
6382    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6383        self.unsupported("Unsupported Repeat block syntax")
6384        return ""
6385
6386    def leave_sql(self, expression: exp.Leave) -> str:
6387        self.unsupported("Unsupported Leave syntax")
6388        return ""
6389
6390    def iterate_sql(self, expression: exp.Iterate) -> str:
6391        self.unsupported("Unsupported Iterate syntax")
6392        return ""
6393
6394    def execute_sql(self, expression: exp.Execute) -> str:
6395        self.unsupported("Unsupported Execute syntax")
6396        return ""
6397
6398    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6399        self.unsupported("Unsupported Execute syntax")
6400        return ""
6401
6402    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6403        props = self.expressions(expression, sep=" ")
6404        return f"MODIFY {props}"
6405
6406    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6407        kind = expression.args.get("kind")
6408        return f"USING {kind} {self.sql(expression, 'this')}"
6409
6410    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6411        this = self.sql(expression, "this")
6412        to = self.sql(expression, "to")
6413        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)
903    def __init__(
904        self,
905        pretty: bool | int | None = None,
906        identify: str | bool = False,
907        normalize: bool = False,
908        pad: int = 2,
909        indent: int = 2,
910        normalize_functions: str | bool | None = None,
911        unsupported_level: ErrorLevel = ErrorLevel.WARN,
912        max_unsupported: int = 3,
913        leading_comma: bool = False,
914        max_text_width: int = 80,
915        comments: bool = True,
916        dialect: DialectType = None,
917    ):
918        import sqlglot
919        import sqlglot.dialects.dialect
920
921        self.pretty = pretty if pretty is not None else sqlglot.pretty
922        self.identify = identify
923        self.normalize = normalize
924        self.pad = pad
925        self._indent = indent
926        self.unsupported_level = unsupported_level
927        self.max_unsupported = max_unsupported
928        self.leading_comma = leading_comma
929        self.max_text_width = max_text_width
930        self.comments = comments
931        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
932
933        # This is both a Dialect property and a Generator argument, so we prioritize the latter
934        self.normalize_functions = (
935            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
936        )
937
938        self.unsupported_messages: list[str] = []
939        self._escaped_quote_end: str = (
940            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
941        )
942        self._escaped_byte_quote_end: str = (
943            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
944            if self.dialect.BYTE_END
945            else ""
946        )
947        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
948
949        self._next_name = name_sequence("_t")
950
951        self._identifier_start = self.dialect.IDENTIFIER_START
952        self._identifier_end = self.dialect.IDENTIFIER_END
953
954        self._quote_json_path_key_using_brackets = True
955
956        cls = type(self)
957        dispatch = _DISPATCH_CACHE.get(cls)
958        if dispatch is None:
959            dispatch = _build_dispatch(cls)
960            _DISPATCH_CACHE[cls] = dispatch
961        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
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'
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.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.CHAR: 'CHAR'>, <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'>)
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:
963    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
964        """
965        Generates the SQL string corresponding to the given syntax tree.
966
967        Args:
968            expression: The syntax tree.
969            copy: Whether to copy the expression. The generator performs mutations so
970                it is safer to copy.
971
972        Returns:
973            The SQL string corresponding to `expression`.
974        """
975        if copy:
976            expression = expression.copy()
977
978        expression = self.preprocess(expression)
979
980        self.unsupported_messages = []
981        sql = self.sql(expression).strip()
982
983        if self.pretty:
984            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
985
986        if self.unsupported_level == ErrorLevel.IGNORE:
987            return sql
988
989        if self.unsupported_level == ErrorLevel.WARN:
990            for msg in self.unsupported_messages:
991                logger.warning(msg)
992        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
993            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
994
995        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:
 997    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 998        """Apply generic preprocessing transformations to a given expression."""
 999        expression = self._move_ctes_to_top_level(expression)
1000
1001        if self.ENSURE_BOOLS:
1002            import sqlglot.transforms
1003
1004            expression = sqlglot.transforms.ensure_bools(expression)
1005
1006        return expression

Apply generic preprocessing transformations to a given expression.

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