Edit on GitHub

sqlglot.generator

   1from __future__ import annotations
   2
   3import logging
   4import re
   5import typing as t
   6from collections import defaultdict
   7from functools import reduce, wraps
   8
   9from sqlglot import exp
  10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
  11from sqlglot.expressions import apply_index_offset
  12from sqlglot.helper import csv, name_sequence, seq_get
  13from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS
  14from sqlglot.time import format_time
  15from sqlglot.tokens import TokenType
  16
  17if t.TYPE_CHECKING:
  18    from sqlglot._typing import E
  19    from sqlglot.dialects.dialect import DialectType
  20
  21    G = t.TypeVar("G", bound="Generator")
  22    GeneratorMethod = t.Callable[[G, E], str]
  23
  24logger = logging.getLogger("sqlglot")
  25
  26ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
  27UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
  28
  29
  30def unsupported_args(
  31    *args: str | tuple[str, str],
  32) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
  33    """
  34    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
  35    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
  36    """
  37    diagnostic_by_arg: dict[str, str | None] = {}
  38    for arg in args:
  39        if isinstance(arg, str):
  40            diagnostic_by_arg[arg] = None
  41        else:
  42            diagnostic_by_arg[arg[0]] = arg[1]
  43
  44    def decorator(func: GeneratorMethod) -> GeneratorMethod:
  45        @wraps(func)
  46        def _func(generator: G, expression: E) -> str:
  47            expression_name = expression.__class__.__name__
  48            dialect_name = generator.dialect.__class__.__name__
  49
  50            for arg_name, diagnostic in diagnostic_by_arg.items():
  51                if expression.args.get(arg_name):
  52                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
  53                        arg_name, expression_name, dialect_name
  54                    )
  55                    generator.unsupported(diagnostic)
  56
  57            return func(generator, expression)
  58
  59        return _func
  60
  61    return decorator
  62
  63
  64AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = {
  65    "windows": lambda self, e: (
  66        self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
  67        if e.args.get("windows")
  68        else ""
  69    ),
  70    "qualify": lambda self, e: self.sql(e, "qualify"),
  71}
  72
  73
  74_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {}
  75
  76
  77def _build_dispatch(
  78    cls: type[Generator],
  79) -> dict[type[exp.Expr], t.Callable[..., str]]:
  80    dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS)
  81
  82    for attr_name in dir(cls):
  83        if not attr_name.endswith("_sql") or attr_name.startswith("_"):
  84            continue
  85
  86        expr_key = attr_name[:-4]
  87        expr_cls = exp.EXPR_CLASSES.get(expr_key)
  88
  89        if expr_cls and expr_cls not in dispatch:
  90            dispatch[expr_cls] = getattr(cls, attr_name)
  91
  92    return dispatch
  93
  94
  95class Generator:
  96    """
  97    Generator converts a given syntax tree to the corresponding SQL string.
  98
  99    Args:
 100        pretty: Whether to format the produced SQL string.
 101            Default: False.
 102        identify: Determines when an identifier should be quoted. Possible values are:
 103            False (default): Never quote, except in cases where it's mandatory by the dialect.
 104            True: Always quote except for specials cases.
 105            'safe': Only quote identifiers that are case insensitive.
 106        normalize: Whether to normalize identifiers to lowercase.
 107            Default: False.
 108        pad: The pad size in a formatted string. For example, this affects the indentation of
 109            a projection in a query, relative to its nesting level.
 110            Default: 2.
 111        indent: The indentation size in a formatted string. For example, this affects the
 112            indentation of subqueries and filters under a `WHERE` clause.
 113            Default: 2.
 114        normalize_functions: How to normalize function names. Possible values are:
 115            "upper" or True (default): Convert names to uppercase.
 116            "lower": Convert names to lowercase.
 117            False: Disables function name normalization.
 118        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 119            Default ErrorLevel.WARN.
 120        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 121            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 122            Default: 3
 123        leading_comma: Whether the comma is leading or trailing in select expressions.
 124            This is only relevant when generating in pretty mode.
 125            Default: False
 126        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 127            The default is on the smaller end because the length only represents a segment and not the true
 128            line length.
 129            Default: 80
 130        comments: Whether to preserve comments in the output SQL code.
 131            Default: True
 132    """
 133
 134    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 135        **JSON_PATH_PART_TRANSFORMS,
 136        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 137        exp.AllowedValuesProperty: lambda self, e: (
 138            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 139        ),
 140        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 141        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 142        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 143        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 144        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 145        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 146        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 147        exp.CaseSpecificColumnConstraint: lambda _, e: (
 148            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 149        ),
 150        exp.Ceil: lambda self, e: self.ceil_floor(e),
 151        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 152        exp.CharacterSetProperty: lambda self, e: (
 153            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 154        ),
 155        exp.ClusteredColumnConstraint: lambda self, e: (
 156            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 157        ),
 158        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 159        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 160        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 161        exp.ConvertToCharset: lambda self, e: self.func(
 162            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 163        ),
 164        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 165        exp.CredentialsProperty: lambda self, e: (
 166            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 167        ),
 168        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 169        exp.SessionUser: lambda *_: "SESSION_USER",
 170        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 171        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 172        exp.ApiProperty: lambda *_: "API",
 173        exp.ApplicationProperty: lambda *_: "APPLICATION",
 174        exp.CatalogProperty: lambda *_: "CATALOG",
 175        exp.ComputeProperty: lambda *_: "COMPUTE",
 176        exp.DatabaseProperty: lambda *_: "DATABASE",
 177        exp.DynamicProperty: lambda *_: "DYNAMIC",
 178        exp.EmptyProperty: lambda *_: "EMPTY",
 179        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 180        exp.EndStatement: lambda *_: "END",
 181        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 182        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 183        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 184        exp.EphemeralColumnConstraint: lambda self, e: (
 185            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 186        ),
 187        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 188        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 189        exp.Except: lambda self, e: self.set_operations(e),
 190        exp.ExternalProperty: lambda *_: "EXTERNAL",
 191        exp.Floor: lambda self, e: self.ceil_floor(e),
 192        exp.Get: lambda self, e: self.get_put_sql(e),
 193        exp.GlobalProperty: lambda *_: "GLOBAL",
 194        exp.HeapProperty: lambda *_: "HEAP",
 195        exp.HybridProperty: lambda *_: "HYBRID",
 196        exp.IcebergProperty: lambda *_: "ICEBERG",
 197        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 198        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 199        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 200        exp.Intersect: lambda self, e: self.set_operations(e),
 201        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 202        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 203        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 204        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 205        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 206        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 207        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 208        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 209        exp.LocationProperty: lambda self, e: self.naked_property(e),
 210        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 211        exp.MaskingProperty: lambda *_: "MASKING",
 212        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 213        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 214        exp.NetworkProperty: lambda *_: "NETWORK",
 215        exp.NonClusteredColumnConstraint: lambda self, e: (
 216            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 217        ),
 218        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 219        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 220        exp.OnCommitProperty: lambda _, e: (
 221            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 222        ),
 223        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 224        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 225        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 226        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 227        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 228        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 229        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 230        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 231        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 232        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 233        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 234        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 235            f"PROJECTION POLICY {self.sql(e, 'this')}"
 236        ),
 237        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 238        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 239        exp.Put: lambda self, e: self.get_put_sql(e),
 240        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 241            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 242        ),
 243        exp.ReturnsProperty: lambda self, e: (
 244            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 245        ),
 246        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 247        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 248        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 249        exp.SecureProperty: lambda *_: "SECURE",
 250        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 251        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 252        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 253        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 254        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 255        exp.SqlReadWriteProperty: lambda _, e: e.name,
 256        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 257        exp.StabilityProperty: lambda _, e: e.name,
 258        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 259        exp.StreamingTableProperty: lambda *_: "STREAMING",
 260        exp.StrictProperty: lambda *_: "STRICT",
 261        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 262        exp.TableColumn: lambda self, e: self.sql(e.this),
 263        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 264        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 265        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 266        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 267        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 268        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 269        exp.TransientProperty: lambda *_: "TRANSIENT",
 270        exp.VirtualProperty: lambda *_: "VIRTUAL",
 271        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 272        exp.Union: lambda self, e: self.set_operations(e),
 273        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 274        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 275        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 276        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 277        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 278        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 279        exp.UtcTimestamp: lambda self, e: self.sql(
 280            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 281        ),
 282        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 283        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 284        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 285        exp.VolatileProperty: lambda *_: "VOLATILE",
 286        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 287        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 288        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 289        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 290        exp.ForceProperty: lambda *_: "FORCE",
 291    }
 292
 293    # Whether null ordering is supported in order by
 294    # True: Full Support, None: No support, False: No support for certain cases
 295    # such as window specifications, aggregate functions etc
 296    NULL_ORDERING_SUPPORTED: bool | None = True
 297
 298    # Window functions that support NULLS FIRST/LAST
 299    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 300
 301    # Whether ignore nulls is inside the agg or outside.
 302    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 303    IGNORE_NULLS_IN_FUNC = False
 304
 305    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 306    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 307    IGNORE_NULLS_BEFORE_ORDER = True
 308
 309    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 310    LOCKING_READS_SUPPORTED = False
 311
 312    # Whether the EXCEPT and INTERSECT operations can return duplicates
 313    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 314
 315    # Wrap derived values in parens, usually standard but spark doesn't support it
 316    WRAP_DERIVED_VALUES = True
 317
 318    # Whether create function uses an AS before the RETURN
 319    CREATE_FUNCTION_RETURN_AS = True
 320
 321    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 322    MATCHED_BY_SOURCE = True
 323
 324    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 325    SUPPORTS_MERGE_WHERE = False
 326
 327    # Whether the INTERVAL expression works only with values like '1 day'
 328    SINGLE_STRING_INTERVAL = False
 329
 330    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 331    INTERVAL_ALLOWS_PLURAL_FORM = True
 332
 333    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 334    LIMIT_FETCH = "ALL"
 335
 336    # Whether limit and fetch allows expresions or just limits
 337    LIMIT_ONLY_LITERALS = False
 338
 339    # Whether a table is allowed to be renamed with a db
 340    RENAME_TABLE_WITH_DB = True
 341
 342    # The separator for grouping sets and rollups
 343    GROUPINGS_SEP = ","
 344
 345    # The string used for creating an index on a table
 346    INDEX_ON = "ON"
 347
 348    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 349    INOUT_SEPARATOR = " "
 350
 351    # Whether join hints should be generated
 352    JOIN_HINTS = True
 353
 354    # Whether directed joins are supported
 355    DIRECTED_JOINS = False
 356
 357    # Whether table hints should be generated
 358    TABLE_HINTS = True
 359
 360    # Whether query hints should be generated
 361    QUERY_HINTS = True
 362
 363    # What kind of separator to use for query hints
 364    QUERY_HINT_SEP = ", "
 365
 366    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 367    IS_BOOL_ALLOWED = True
 368
 369    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 370    DUPLICATE_KEY_UPDATE_WITH_SET = True
 371
 372    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 373    LIMIT_IS_TOP = False
 374
 375    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 376    RETURNING_END = True
 377
 378    # Whether to generate an unquoted value for EXTRACT's date part argument
 379    EXTRACT_ALLOWS_QUOTES = True
 380
 381    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 382    TZ_TO_WITH_TIME_ZONE = False
 383
 384    # Whether the NVL2 function is supported
 385    NVL2_SUPPORTED = True
 386
 387    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 388    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 389
 390    # Whether VALUES statements can be used as derived tables.
 391    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 392    # SELECT * VALUES into SELECT UNION
 393    VALUES_AS_TABLE = True
 394
 395    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 396    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 397
 398    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 399    UNNEST_WITH_ORDINALITY = True
 400
 401    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 402    AGGREGATE_FILTER_SUPPORTED = True
 403
 404    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 405    SEMI_ANTI_JOIN_WITH_SIDE = True
 406
 407    # Whether to include the type of a computed column in the CREATE DDL
 408    COMPUTED_COLUMN_WITH_TYPE = True
 409
 410    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 411    SUPPORTS_TABLE_COPY = True
 412
 413    # Whether parentheses are required around the table sample's expression
 414    TABLESAMPLE_REQUIRES_PARENS = True
 415
 416    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 417    TABLESAMPLE_SIZE_IS_ROWS = True
 418
 419    # The keyword(s) to use when generating a sample clause
 420    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 421
 422    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 423    TABLESAMPLE_WITH_METHOD = True
 424
 425    # The keyword to use when specifying the seed of a sample clause
 426    TABLESAMPLE_SEED_KEYWORD = "SEED"
 427
 428    # Whether COLLATE is a function instead of a binary operator
 429    COLLATE_IS_FUNC = False
 430
 431    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 432    DATA_TYPE_SPECIFIERS_ALLOWED = False
 433
 434    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 435    ENSURE_BOOLS = False
 436
 437    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 438    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 439
 440    # Whether CONCAT requires >1 arguments
 441    SUPPORTS_SINGLE_ARG_CONCAT = True
 442
 443    # Whether LAST_DAY function supports a date part argument
 444    LAST_DAY_SUPPORTS_DATE_PART = True
 445
 446    # Whether named columns are allowed in table aliases
 447    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 448
 449    # Whether named columns are allowed in CTE definitions
 450    SUPPORTS_NAMED_CTE_COLUMNS = True
 451
 452    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 453    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 454
 455    # What delimiter to use for separating JSON key/value pairs
 456    JSON_KEY_VALUE_PAIR_SEP = ":"
 457
 458    # INSERT OVERWRITE TABLE x override
 459    INSERT_OVERWRITE = " OVERWRITE TABLE"
 460
 461    # Whether the SELECT .. INTO syntax is used instead of CTAS
 462    SUPPORTS_SELECT_INTO = False
 463
 464    # Whether UNLOGGED tables can be created
 465    SUPPORTS_UNLOGGED_TABLES = False
 466
 467    # Whether the CREATE TABLE LIKE statement is supported
 468    SUPPORTS_CREATE_TABLE_LIKE = True
 469
 470    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 471    SUPPORTS_MODIFY_COLUMN = False
 472
 473    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 474    SUPPORTS_CHANGE_COLUMN = False
 475
 476    # Whether the LikeProperty needs to be specified inside of the schema clause
 477    LIKE_PROPERTY_INSIDE_SCHEMA = False
 478
 479    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 480    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 481    MULTI_ARG_DISTINCT = True
 482
 483    # Whether the JSON extraction operators expect a value of type JSON
 484    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 485
 486    # Whether bracketed keys like ["foo"] are supported in JSON paths
 487    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 488
 489    # Whether to escape keys using single quotes in JSON paths
 490    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 491
 492    # The JSONPathPart expressions supported by this dialect
 493    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 494
 495    # Whether any(f(x) for x in array) can be implemented by this dialect
 496    CAN_IMPLEMENT_ARRAY_ANY = False
 497
 498    # Whether the function TO_NUMBER is supported
 499    SUPPORTS_TO_NUMBER = True
 500
 501    # Whether EXCLUDE in window specification is supported
 502    SUPPORTS_WINDOW_EXCLUDE = False
 503
 504    # Whether or not set op modifiers apply to the outer set op or select.
 505    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 506    # True means limit 1 happens after the set op, False means it it happens on y.
 507    SET_OP_MODIFIERS = True
 508
 509    # Whether parameters from COPY statement are wrapped in parentheses
 510    COPY_PARAMS_ARE_WRAPPED = True
 511
 512    # Whether values of params are set with "=" token or empty space
 513    COPY_PARAMS_EQ_REQUIRED = False
 514
 515    # Whether COPY statement has INTO keyword
 516    COPY_HAS_INTO_KEYWORD = True
 517
 518    # Whether the conditional TRY(expression) function is supported
 519    TRY_SUPPORTED = True
 520
 521    # Whether the UESCAPE syntax in unicode strings is supported
 522    SUPPORTS_UESCAPE = True
 523
 524    # Function used to replace escaped unicode codes in unicode strings
 525    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 526
 527    # The keyword to use when generating a star projection with excluded columns
 528    STAR_EXCEPT = "EXCEPT"
 529
 530    # The HEX function name
 531    HEX_FUNC = "HEX"
 532
 533    # The keywords to use when prefixing & separating WITH based properties
 534    WITH_PROPERTIES_PREFIX = "WITH"
 535
 536    # Whether to quote the generated expression of exp.JsonPath
 537    QUOTE_JSON_PATH = True
 538
 539    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 540    PAD_FILL_PATTERN_IS_REQUIRED = False
 541
 542    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 543    SUPPORTS_EXPLODING_PROJECTIONS = True
 544
 545    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 546    ARRAY_CONCAT_IS_VAR_LEN = True
 547
 548    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 549    SUPPORTS_CONVERT_TIMEZONE = False
 550
 551    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 552    SUPPORTS_MEDIAN = True
 553
 554    # Whether UNIX_SECONDS(timestamp) is supported
 555    SUPPORTS_UNIX_SECONDS = False
 556
 557    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 558    ALTER_SET_WRAPPED = False
 559
 560    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 561    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 562    # TODO: The normalization should be done by default once we've tested it across all dialects.
 563    NORMALIZE_EXTRACT_DATE_PARTS = False
 564
 565    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 566    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 567
 568    # The function name of the exp.ArraySize expression
 569    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 570
 571    # The syntax to use when altering the type of a column
 572    ALTER_SET_TYPE = "SET DATA TYPE"
 573
 574    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 575    # None -> Doesn't support it at all
 576    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 577    # True (Postgres) -> Explicitly requires it
 578    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 579
 580    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 581    SUPPORTS_DECODE_CASE = True
 582
 583    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 584    SUPPORTS_BETWEEN_FLAGS = False
 585
 586    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 587    SUPPORTS_LIKE_QUANTIFIERS = True
 588
 589    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 590    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 591
 592    # Whether to include the VARIABLE keyword for SET assignments
 593    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 594
 595    # The keyword to use for default value assignment in DECLARE statements
 596    DECLARE_DEFAULT_ASSIGNMENT = "="
 597
 598    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 599    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 600    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 601    UPDATE_STATEMENT_SUPPORTS_FROM = True
 602
 603    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 604    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 605
 606    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 607    # - Snowflake: DROP ICEBERG TABLE a.b;
 608    # - DuckDB:    DROP TABLE a.b;
 609    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 610
 611    TYPE_MAPPING: t.ClassVar = {
 612        exp.DType.DATETIME2: "TIMESTAMP",
 613        exp.DType.NCHAR: "CHAR",
 614        exp.DType.NVARCHAR: "VARCHAR",
 615        exp.DType.MEDIUMTEXT: "TEXT",
 616        exp.DType.LONGTEXT: "TEXT",
 617        exp.DType.TINYTEXT: "TEXT",
 618        exp.DType.BLOB: "VARBINARY",
 619        exp.DType.MEDIUMBLOB: "BLOB",
 620        exp.DType.LONGBLOB: "BLOB",
 621        exp.DType.TINYBLOB: "BLOB",
 622        exp.DType.INET: "INET",
 623        exp.DType.ROWVERSION: "VARBINARY",
 624        exp.DType.SMALLDATETIME: "TIMESTAMP",
 625    }
 626
 627    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 628
 629    # mapping of DType to its default parameters, bounds
 630    TYPE_PARAM_SETTINGS: t.ClassVar[
 631        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 632    ] = {}
 633
 634    TIME_PART_SINGULARS: t.ClassVar = {
 635        "MICROSECONDS": "MICROSECOND",
 636        "SECONDS": "SECOND",
 637        "MINUTES": "MINUTE",
 638        "HOURS": "HOUR",
 639        "DAYS": "DAY",
 640        "WEEKS": "WEEK",
 641        "MONTHS": "MONTH",
 642        "QUARTERS": "QUARTER",
 643        "YEARS": "YEAR",
 644    }
 645
 646    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 647        "cluster": lambda self, e: self.sql(e, "cluster"),
 648        "distribute": lambda self, e: self.sql(e, "distribute"),
 649        "sort": lambda self, e: self.sql(e, "sort"),
 650        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 651    }
 652
 653    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 654
 655    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 656
 657    PARAMETER_TOKEN = "@"
 658    NAMED_PLACEHOLDER_TOKEN = ":"
 659
 660    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 661
 662    PROPERTIES_LOCATION: t.ClassVar = {
 663        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 664        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 665        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 666        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 667        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 668        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 669        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 671        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 672        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 673        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 674        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 675        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 676        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 677        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 678        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 681        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 682        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 683        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 684        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 685        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 686        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 687        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 688        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 689        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 690        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 691        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 692        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 693        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 694        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 695        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 696        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 697        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 698        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 699        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 700        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 701        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 702        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 703        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 704        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 705        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 706        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 707        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 708        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 709        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 710        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 711        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 712        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 713        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 714        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 716        exp.LogProperty: exp.Properties.Location.POST_NAME,
 717        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 718        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 719        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 720        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 722        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 723        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 724        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 725        exp.Order: exp.Properties.Location.POST_SCHEMA,
 726        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 728        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 730        exp.Property: exp.Properties.Location.POST_WITH,
 731        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 732        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 735        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 736        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 737        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 742        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 743        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 744        exp.Set: exp.Properties.Location.POST_SCHEMA,
 745        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 747        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 748        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 749        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 750        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 751        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 752        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 753        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 754        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 757        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 758        exp.Tags: exp.Properties.Location.POST_WITH,
 759        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 760        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 761        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 762        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 764        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 765        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 766        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 767        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 769        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 770        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 771        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 772        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 773        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 776    }
 777
 778    # Keywords that can't be used as unquoted identifier names
 779    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 780
 781    # Exprs whose comments are separated from them for better formatting
 782    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 783        exp.Command,
 784        exp.Create,
 785        exp.Describe,
 786        exp.Delete,
 787        exp.Drop,
 788        exp.From,
 789        exp.Insert,
 790        exp.Join,
 791        exp.MultitableInserts,
 792        exp.Order,
 793        exp.Group,
 794        exp.Having,
 795        exp.Select,
 796        exp.SetOperation,
 797        exp.Update,
 798        exp.Where,
 799        exp.With,
 800    )
 801
 802    # Exprs that should not have their comments generated in maybe_comment
 803    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 804        exp.Binary,
 805        exp.SetOperation,
 806    )
 807
 808    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 809    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 810        exp.Column,
 811        exp.Literal,
 812        exp.Neg,
 813        exp.Paren,
 814    )
 815
 816    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 817        exp.DType.NVARCHAR,
 818        exp.DType.VARCHAR,
 819        exp.DType.CHAR,
 820        exp.DType.NCHAR,
 821    }
 822
 823    # Exprs that need to have all CTEs under them bubbled up to them
 824    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 825
 826    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 827
 828    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 829
 830    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 831
 832    __slots__ = (
 833        "pretty",
 834        "identify",
 835        "normalize",
 836        "pad",
 837        "_indent",
 838        "normalize_functions",
 839        "unsupported_level",
 840        "max_unsupported",
 841        "leading_comma",
 842        "max_text_width",
 843        "comments",
 844        "dialect",
 845        "unsupported_messages",
 846        "_escaped_quote_end",
 847        "_escaped_byte_quote_end",
 848        "_escaped_identifier_end",
 849        "_next_name",
 850        "_identifier_start",
 851        "_identifier_end",
 852        "_quote_json_path_key_using_brackets",
 853        "_dispatch",
 854    )
 855
 856    def __init__(
 857        self,
 858        pretty: bool | int | None = None,
 859        identify: str | bool = False,
 860        normalize: bool = False,
 861        pad: int = 2,
 862        indent: int = 2,
 863        normalize_functions: str | bool | None = None,
 864        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 865        max_unsupported: int = 3,
 866        leading_comma: bool = False,
 867        max_text_width: int = 80,
 868        comments: bool = True,
 869        dialect: DialectType = None,
 870    ):
 871        import sqlglot
 872        import sqlglot.dialects.dialect
 873
 874        self.pretty = pretty if pretty is not None else sqlglot.pretty
 875        self.identify = identify
 876        self.normalize = normalize
 877        self.pad = pad
 878        self._indent = indent
 879        self.unsupported_level = unsupported_level
 880        self.max_unsupported = max_unsupported
 881        self.leading_comma = leading_comma
 882        self.max_text_width = max_text_width
 883        self.comments = comments
 884        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 885
 886        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 887        self.normalize_functions = (
 888            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 889        )
 890
 891        self.unsupported_messages: list[str] = []
 892        self._escaped_quote_end: str = (
 893            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 894        )
 895        self._escaped_byte_quote_end: str = (
 896            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 897            if self.dialect.BYTE_END
 898            else ""
 899        )
 900        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 901
 902        self._next_name = name_sequence("_t")
 903
 904        self._identifier_start = self.dialect.IDENTIFIER_START
 905        self._identifier_end = self.dialect.IDENTIFIER_END
 906
 907        self._quote_json_path_key_using_brackets = True
 908
 909        cls = type(self)
 910        dispatch = _DISPATCH_CACHE.get(cls)
 911        if dispatch is None:
 912            dispatch = _build_dispatch(cls)
 913            _DISPATCH_CACHE[cls] = dispatch
 914        self._dispatch = dispatch
 915
 916    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 917        """
 918        Generates the SQL string corresponding to the given syntax tree.
 919
 920        Args:
 921            expression: The syntax tree.
 922            copy: Whether to copy the expression. The generator performs mutations so
 923                it is safer to copy.
 924
 925        Returns:
 926            The SQL string corresponding to `expression`.
 927        """
 928        if copy:
 929            expression = expression.copy()
 930
 931        expression = self.preprocess(expression)
 932
 933        self.unsupported_messages = []
 934        sql = self.sql(expression).strip()
 935
 936        if self.pretty:
 937            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 938
 939        if self.unsupported_level == ErrorLevel.IGNORE:
 940            return sql
 941
 942        if self.unsupported_level == ErrorLevel.WARN:
 943            for msg in self.unsupported_messages:
 944                logger.warning(msg)
 945        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 946            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 947
 948        return sql
 949
 950    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 951        """Apply generic preprocessing transformations to a given expression."""
 952        expression = self._move_ctes_to_top_level(expression)
 953
 954        if self.ENSURE_BOOLS:
 955            import sqlglot.transforms
 956
 957            expression = sqlglot.transforms.ensure_bools(expression)
 958
 959        return expression
 960
 961    def _move_ctes_to_top_level(self, expression: E) -> E:
 962        if (
 963            not expression.parent
 964            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 965            and any(node.parent is not expression for node in expression.find_all(exp.With))
 966        ):
 967            import sqlglot.transforms
 968
 969            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
 970        return expression
 971
 972    def unsupported(self, message: str) -> None:
 973        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 974            raise UnsupportedError(message)
 975        self.unsupported_messages.append(message)
 976
 977    def sep(self, sep: str = " ") -> str:
 978        return f"{sep.strip()}\n" if self.pretty else sep
 979
 980    def seg(self, sql: str, sep: str = " ") -> str:
 981        return f"{self.sep(sep)}{sql}"
 982
 983    def sanitize_comment(self, comment: str) -> str:
 984        comment = " " + comment if comment[0].strip() else comment
 985        comment = comment + " " if comment[-1].strip() else comment
 986
 987        # Escape block comment markers to prevent premature closure or unintended nesting.
 988        # This is necessary because single-line comments (--) are converted to block comments
 989        # (/* */) on output, and any */ in the original text would close the comment early.
 990        comment = comment.replace("*/", "* /").replace("/*", "/ *")
 991
 992        return comment
 993
 994    def maybe_comment(
 995        self,
 996        sql: str,
 997        expression: exp.Expr | None = None,
 998        comments: list[str] | None = None,
 999        separated: bool = False,
1000    ) -> str:
1001        comments = (
1002            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1003            if self.comments
1004            else None
1005        )
1006
1007        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1008            return sql
1009
1010        comments_sql = " ".join(
1011            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1012        )
1013
1014        if not comments_sql:
1015            return sql
1016
1017        comments_sql = self._replace_line_breaks(comments_sql)
1018
1019        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1020            return (
1021                f"{self.sep()}{comments_sql}{sql}"
1022                if not sql or sql[0].isspace()
1023                else f"{comments_sql}{self.sep()}{sql}"
1024            )
1025
1026        return f"{sql} {comments_sql}"
1027
1028    def wrap(self, expression: exp.Expr | str) -> str:
1029        this_sql = (
1030            self.sql(expression)
1031            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1032            else self.sql(expression, "this")
1033        )
1034        if not this_sql:
1035            return "()"
1036
1037        this_sql = self.indent(this_sql, level=1, pad=0)
1038        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1039
1040    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1041        original = self.identify
1042        self.identify = False
1043        result = func(*args, **kwargs)
1044        self.identify = original
1045        return result
1046
1047    def normalize_func(self, name: str) -> str:
1048        if self.normalize_functions == "upper" or self.normalize_functions is True:
1049            return name.upper()
1050        if self.normalize_functions == "lower":
1051            return name.lower()
1052        return name
1053
1054    def indent(
1055        self,
1056        sql: str,
1057        level: int = 0,
1058        pad: int | None = None,
1059        skip_first: bool = False,
1060        skip_last: bool = False,
1061    ) -> str:
1062        if not self.pretty or not sql:
1063            return sql
1064
1065        pad = self.pad if pad is None else pad
1066        lines = sql.split("\n")
1067
1068        return "\n".join(
1069            (
1070                line
1071                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1072                else f"{' ' * (level * self._indent + pad)}{line}"
1073            )
1074            for i, line in enumerate(lines)
1075        )
1076
1077    def sql(
1078        self,
1079        expression: str | exp.Expr | None,
1080        key: str | None = None,
1081        comment: bool = True,
1082    ) -> str:
1083        if not expression:
1084            return ""
1085
1086        if isinstance(expression, str):
1087            return expression
1088
1089        if key:
1090            value = expression.args.get(key)
1091            if value:
1092                return self.sql(value)
1093            return ""
1094
1095        handler = self._dispatch.get(expression.__class__)
1096
1097        if handler:
1098            sql = handler(self, expression)
1099        elif isinstance(expression, exp.Func):
1100            sql = self.function_fallback_sql(expression)
1101        elif isinstance(expression, exp.Property):
1102            sql = self.property_sql(expression)
1103        else:
1104            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1105
1106        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1107
1108    def uncache_sql(self, expression: exp.Uncache) -> str:
1109        table = self.sql(expression, "this")
1110        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1111        return f"UNCACHE TABLE{exists_sql} {table}"
1112
1113    def cache_sql(self, expression: exp.Cache) -> str:
1114        lazy = " LAZY" if expression.args.get("lazy") else ""
1115        table = self.sql(expression, "this")
1116        options = expression.args.get("options")
1117        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1118        sql = self.sql(expression, "expression")
1119        sql = f" AS{self.sep()}{sql}" if sql else ""
1120        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1121        return self.prepend_ctes(expression, sql)
1122
1123    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1124        default = "DEFAULT " if expression.args.get("default") else ""
1125        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1126
1127    def column_parts(self, expression: exp.Column) -> str:
1128        return ".".join(
1129            self.sql(part)
1130            for part in (
1131                expression.args.get("catalog"),
1132                expression.args.get("db"),
1133                expression.args.get("table"),
1134                expression.args.get("this"),
1135            )
1136            if part
1137        )
1138
1139    def column_sql(self, expression: exp.Column) -> str:
1140        join_mark = " (+)" if expression.args.get("join_mark") else ""
1141
1142        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1143            join_mark = ""
1144            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1145
1146        return f"{self.column_parts(expression)}{join_mark}"
1147
1148    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1149        return self.column_sql(expression)
1150
1151    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1152        this = self.sql(expression, "this")
1153        this = f" {this}" if this else ""
1154        position = self.sql(expression, "position")
1155        return f"{position}{this}"
1156
1157    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1158        column = self.sql(expression, "this")
1159        kind = self.sql(expression, "kind")
1160        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1161        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1162        kind = f"{sep}{kind}" if kind else ""
1163        constraints = f" {constraints}" if constraints else ""
1164        position = self.sql(expression, "position")
1165        position = f" {position}" if position else ""
1166
1167        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1168            kind = ""
1169
1170        return f"{exists}{column}{kind}{constraints}{position}"
1171
1172    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1173        this = self.sql(expression, "this")
1174        kind_sql = self.sql(expression, "kind").strip()
1175        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1176
1177    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1178        this = self.sql(expression, "this")
1179        if expression.args.get("not_null"):
1180            persisted = " PERSISTED NOT NULL"
1181        elif expression.args.get("persisted"):
1182            persisted = " PERSISTED"
1183        else:
1184            persisted = ""
1185
1186        return f"AS {this}{persisted}"
1187
1188    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1189        return self.token_sql(TokenType.AUTO_INCREMENT)
1190
1191    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1192        if isinstance(expression.this, list):
1193            this = self.wrap(self.expressions(expression, key="this", flat=True))
1194        else:
1195            this = self.sql(expression, "this")
1196
1197        return f"COMPRESS {this}"
1198
1199    def generatedasidentitycolumnconstraint_sql(
1200        self, expression: exp.GeneratedAsIdentityColumnConstraint
1201    ) -> str:
1202        this = ""
1203        if expression.this is not None:
1204            on_null = " ON NULL" if expression.args.get("on_null") else ""
1205            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1206
1207        start = expression.args.get("start")
1208        start = f"START WITH {start}" if start else ""
1209        increment = expression.args.get("increment")
1210        increment = f" INCREMENT BY {increment}" if increment else ""
1211        minvalue = expression.args.get("minvalue")
1212        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1213        maxvalue = expression.args.get("maxvalue")
1214        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1215        cycle = expression.args.get("cycle")
1216        cycle_sql = ""
1217
1218        if cycle is not None:
1219            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1220            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1221
1222        sequence_opts = ""
1223        if start or increment or cycle_sql:
1224            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1225            sequence_opts = f" ({sequence_opts.strip()})"
1226
1227        expr = self.sql(expression, "expression")
1228        expr = f"({expr})" if expr else "IDENTITY"
1229
1230        return f"GENERATED{this} AS {expr}{sequence_opts}"
1231
1232    def generatedasrowcolumnconstraint_sql(
1233        self, expression: exp.GeneratedAsRowColumnConstraint
1234    ) -> str:
1235        start = "START" if expression.args.get("start") else "END"
1236        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1237        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1238
1239    def periodforsystemtimeconstraint_sql(
1240        self, expression: exp.PeriodForSystemTimeConstraint
1241    ) -> str:
1242        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1243
1244    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1245        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1246
1247    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1248        desc = expression.args.get("desc")
1249        if desc is not None:
1250            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1251        options = self.expressions(expression, key="options", flat=True, sep=" ")
1252        options = f" {options}" if options else ""
1253        return f"PRIMARY KEY{options}"
1254
1255    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1256        this = self.sql(expression, "this")
1257        this = f" {this}" if this else ""
1258        index_type = expression.args.get("index_type")
1259        index_type = f" USING {index_type}" if index_type else ""
1260        on_conflict = self.sql(expression, "on_conflict")
1261        on_conflict = f" {on_conflict}" if on_conflict else ""
1262        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1263        options = self.expressions(expression, key="options", flat=True, sep=" ")
1264        options = f" {options}" if options else ""
1265        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1266
1267    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1268        input_ = expression.args.get("input_")
1269        output = expression.args.get("output")
1270        variadic = expression.args.get("variadic")
1271
1272        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1273        if variadic:
1274            return "VARIADIC"
1275
1276        if input_ and output:
1277            return f"IN{self.INOUT_SEPARATOR}OUT"
1278        if input_:
1279            return "IN"
1280        if output:
1281            return "OUT"
1282
1283        return ""
1284
1285    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1286        return self.sql(expression, "this")
1287
1288    def create_sql(self, expression: exp.Create) -> str:
1289        kind = self.sql(expression, "kind")
1290        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1291
1292        properties = expression.args.get("properties")
1293
1294        if (
1295            kind == "TRIGGER"
1296            and properties
1297            and properties.expressions
1298            and isinstance(properties.expressions[0], exp.TriggerProperties)
1299            and properties.expressions[0].args.get("constraint")
1300        ):
1301            kind = f"CONSTRAINT {kind}"
1302
1303        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1304
1305        this = self.createable_sql(expression, properties_locs)
1306
1307        properties_sql = ""
1308        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1309            exp.Properties.Location.POST_WITH
1310        ):
1311            props_ast = exp.Properties(
1312                expressions=[
1313                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1314                    *properties_locs[exp.Properties.Location.POST_WITH],
1315                ]
1316            )
1317            props_ast.parent = expression
1318            properties_sql = self.sql(props_ast)
1319
1320            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1321                properties_sql = self.sep() + properties_sql
1322            elif not self.pretty:
1323                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1324                properties_sql = f" {properties_sql}"
1325
1326        begin = " BEGIN" if expression.args.get("begin") else ""
1327
1328        expression_sql = self.sql(expression, "expression")
1329        if expression_sql:
1330            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1331
1332            if not isinstance(expression.expression, exp.MacroOverloads) and (
1333                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1334            ):
1335                postalias_props_sql = ""
1336                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1337                    postalias_props_sql = self.properties(
1338                        exp.Properties(
1339                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1340                        ),
1341                        wrapped=False,
1342                    )
1343                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1344                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1345
1346        postindex_props_sql = ""
1347        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1348            postindex_props_sql = self.properties(
1349                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1350                wrapped=False,
1351                prefix=" ",
1352            )
1353
1354        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1355        indexes = f" {indexes}" if indexes else ""
1356        index_sql = indexes + postindex_props_sql
1357
1358        replace = " OR REPLACE" if expression.args.get("replace") else ""
1359        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1360        unique = " UNIQUE" if expression.args.get("unique") else ""
1361
1362        clustered = expression.args.get("clustered")
1363        if clustered is None:
1364            clustered_sql = ""
1365        elif clustered:
1366            clustered_sql = " CLUSTERED COLUMNSTORE"
1367        else:
1368            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1369
1370        postcreate_props_sql = ""
1371        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1372            postcreate_props_sql = self.properties(
1373                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1374                sep=" ",
1375                prefix=" ",
1376                wrapped=False,
1377            )
1378
1379        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1380
1381        postexpression_props_sql = ""
1382        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1383            postexpression_props_sql = self.properties(
1384                exp.Properties(
1385                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1386                ),
1387                sep=" ",
1388                prefix=" ",
1389                wrapped=False,
1390            )
1391
1392        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1393        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1394        no_schema_binding = (
1395            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1396        )
1397
1398        clone = self.sql(expression, "clone")
1399        clone = f" {clone}" if clone else ""
1400
1401        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1402            properties_expression = f"{expression_sql}{properties_sql}"
1403        else:
1404            properties_expression = f"{properties_sql}{expression_sql}"
1405
1406        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1407        return self.prepend_ctes(expression, expression_sql)
1408
1409    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1410        start = self.sql(expression, "start")
1411        start = f"START WITH {start}" if start else ""
1412        increment = self.sql(expression, "increment")
1413        increment = f" INCREMENT BY {increment}" if increment else ""
1414        minvalue = self.sql(expression, "minvalue")
1415        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1416        maxvalue = self.sql(expression, "maxvalue")
1417        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1418        owned = self.sql(expression, "owned")
1419        owned = f" OWNED BY {owned}" if owned else ""
1420
1421        cache = expression.args.get("cache")
1422        if cache is None:
1423            cache_str = ""
1424        elif cache is True:
1425            cache_str = " CACHE"
1426        else:
1427            cache_str = f" CACHE {cache}"
1428
1429        options = self.expressions(expression, key="options", flat=True, sep=" ")
1430        options = f" {options}" if options else ""
1431
1432        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1433
1434    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1435        timing = expression.args.get("timing", "")
1436        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1437        timing_events = f"{timing} {events}".strip() if timing or events else ""
1438
1439        parts = [timing_events, "ON", self.sql(expression, "table")]
1440
1441        if referenced_table := expression.args.get("referenced_table"):
1442            parts.extend(["FROM", self.sql(referenced_table)])
1443
1444        if deferrable := expression.args.get("deferrable"):
1445            parts.append(deferrable)
1446
1447        if initially := expression.args.get("initially"):
1448            parts.append(f"INITIALLY {initially}")
1449
1450        if referencing := expression.args.get("referencing"):
1451            parts.append(self.sql(referencing))
1452
1453        if for_each := expression.args.get("for_each"):
1454            parts.append(f"FOR EACH {for_each}")
1455
1456        if when := expression.args.get("when"):
1457            parts.append(f"WHEN ({self.sql(when)})")
1458
1459        parts.append(self.sql(expression, "execute"))
1460
1461        return self.sep().join(parts)
1462
1463    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1464        parts = []
1465
1466        if old_alias := expression.args.get("old"):
1467            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1468
1469        if new_alias := expression.args.get("new"):
1470            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1471
1472        return f"REFERENCING {' '.join(parts)}"
1473
1474    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1475        columns = expression.args.get("columns")
1476        if columns:
1477            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1478
1479        return self.sql(expression, "this")
1480
1481    def clone_sql(self, expression: exp.Clone) -> str:
1482        this = self.sql(expression, "this")
1483        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1484        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1485        return f"{shallow}{keyword} {this}"
1486
1487    def describe_sql(self, expression: exp.Describe) -> str:
1488        style = expression.args.get("style")
1489        style = f" {style}" if style else ""
1490        partition = self.sql(expression, "partition")
1491        partition = f" {partition}" if partition else ""
1492        format = self.sql(expression, "format")
1493        format = f" {format}" if format else ""
1494        as_json = " AS JSON" if expression.args.get("as_json") else ""
1495
1496        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1497
1498    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1499        tag = self.sql(expression, "tag")
1500        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1501
1502    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1503        with_ = self.sql(expression, "with_")
1504        if with_:
1505            sql = f"{with_}{self.sep()}{sql}"
1506        return sql
1507
1508    def with_sql(self, expression: exp.With) -> str:
1509        sql = self.expressions(expression, flat=True)
1510        recursive = (
1511            "RECURSIVE "
1512            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1513            else ""
1514        )
1515        search = self.sql(expression, "search")
1516        search = f" {search}" if search else ""
1517
1518        return f"WITH {recursive}{sql}{search}"
1519
1520    def cte_sql(self, expression: exp.CTE) -> str:
1521        alias = expression.args.get("alias")
1522        if alias:
1523            alias.add_comments(expression.pop_comments())
1524
1525        alias_sql = self.sql(expression, "alias")
1526
1527        materialized = expression.args.get("materialized")
1528        if materialized is False:
1529            materialized = "NOT MATERIALIZED "
1530        elif materialized:
1531            materialized = "MATERIALIZED "
1532
1533        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1534        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1535
1536        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1537
1538    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1539        alias = self.sql(expression, "this")
1540        columns = self.expressions(expression, key="columns", flat=True)
1541        columns = f"({columns})" if columns else ""
1542
1543        if (
1544            columns
1545            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1546            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1547        ):
1548            columns = ""
1549            self.unsupported("Named columns are not supported in table alias.")
1550
1551        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1552            alias = self._next_name()
1553
1554        return f"{alias}{columns}"
1555
1556    def bitstring_sql(self, expression: exp.BitString) -> str:
1557        this = self.sql(expression, "this")
1558        if self.dialect.BIT_START:
1559            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1560        return f"{int(this, 2)}"
1561
1562    def hexstring_sql(
1563        self, expression: exp.HexString, binary_function_repr: str | None = None
1564    ) -> str:
1565        this = self.sql(expression, "this")
1566        is_integer_type = expression.args.get("is_integer")
1567
1568        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1569            not self.dialect.HEX_START and not binary_function_repr
1570        ):
1571            # Integer representation will be returned if:
1572            # - The read dialect treats the hex value as integer literal but not the write
1573            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1574            return f"{int(this, 16)}"
1575
1576        if not is_integer_type:
1577            # Read dialect treats the hex value as BINARY/BLOB
1578            if binary_function_repr:
1579                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1580                return self.func(binary_function_repr, exp.Literal.string(this))
1581            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1582                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1583                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1584
1585        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1586
1587    def bytestring_sql(self, expression: exp.ByteString) -> str:
1588        this = self.sql(expression, "this")
1589        if self.dialect.BYTE_START:
1590            escaped_byte_string = self.escape_str(
1591                this,
1592                escape_backslash=False,
1593                delimiter=self.dialect.BYTE_END,
1594                escaped_delimiter=self._escaped_byte_quote_end,
1595                is_byte_string=True,
1596            )
1597            is_bytes = expression.args.get("is_bytes", False)
1598            delimited_byte_string = (
1599                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1600            )
1601            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1602                return self.sql(
1603                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1604                )
1605            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1606                return self.sql(
1607                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1608                )
1609
1610            return delimited_byte_string
1611
1612        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1613            return self.sql(exp.Literal.string(this))
1614
1615        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1616        return ""
1617
1618    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1619        this = self.sql(expression, "this")
1620        escape = expression.args.get("escape")
1621
1622        if self.dialect.UNICODE_START:
1623            escape_substitute = r"\\\1"
1624            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1625        else:
1626            escape_substitute = r"\\u\1"
1627            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1628
1629        if escape:
1630            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1631            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1632        else:
1633            escape_pattern = ESCAPED_UNICODE_RE
1634            escape_sql = ""
1635
1636        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1637            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1638
1639        return f"{left_quote}{this}{right_quote}{escape_sql}"
1640
1641    def rawstring_sql(self, expression: exp.RawString) -> str:
1642        string = expression.this
1643        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1644            string = string.replace("\\", "\\\\")
1645
1646        string = self.escape_str(string, escape_backslash=False)
1647        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1648
1649    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1650        this = self.sql(expression, "this")
1651        specifier = self.sql(expression, "expression")
1652        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1653        return f"{this}{specifier}"
1654
1655    def datatype_param_bound_limiter(
1656        self,
1657        expression: exp.DataType,
1658        type_value: exp.DType,
1659        defaults: tuple[int, ...],
1660        bounds: tuple[int | None, ...],
1661    ) -> exp.DataType:
1662        params = expression.expressions
1663
1664        if not params:
1665            if defaults:
1666                expression.set(
1667                    "expressions",
1668                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1669                )
1670            return expression
1671
1672        if not bounds:
1673            return expression
1674
1675        for i, param in enumerate(params):
1676            bound = bounds[i] if i < len(bounds) else None
1677            if bound is None:
1678                continue
1679
1680            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1681            if (
1682                isinstance(param_value, exp.Literal)
1683                and param_value.is_number
1684                and int(param_value.to_py()) > bound
1685            ):
1686                self.unsupported(
1687                    f"{type_value.value} parameter {param_value.name} exceeds "
1688                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1689                )
1690                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1691
1692        return expression
1693
1694    def datatype_sql(self, expression: exp.DataType) -> str:
1695        nested = ""
1696        values = ""
1697
1698        expr_nested = expression.args.get("nested")
1699        type_value = expression.this
1700
1701        if (
1702            not expr_nested
1703            and isinstance(type_value, exp.DType)
1704            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1705        ):
1706            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1707
1708        interior = (
1709            self.expressions(
1710                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1711            )
1712            if expr_nested and self.pretty
1713            else self.expressions(expression, flat=True)
1714        )
1715
1716        if type_value in self.UNSUPPORTED_TYPES:
1717            self.unsupported(
1718                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1719            )
1720
1721        type_sql: t.Any = ""
1722        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1723            type_sql = self.sql(expression, "kind")
1724        elif type_value == exp.DType.CHARACTER_SET:
1725            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1726        else:
1727            type_sql = (
1728                self.TYPE_MAPPING.get(type_value, type_value.value)
1729                if isinstance(type_value, exp.DType)
1730                else type_value
1731            )
1732
1733        if interior:
1734            if expr_nested:
1735                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1736                if expression.args.get("values") is not None:
1737                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1738                    values = self.expressions(expression, key="values", flat=True)
1739                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1740            elif type_value == exp.DType.INTERVAL:
1741                nested = f" {interior}"
1742            else:
1743                nested = f"({interior})"
1744
1745        type_sql = f"{type_sql}{nested}{values}"
1746        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1747            exp.DType.TIMETZ,
1748            exp.DType.TIMESTAMPTZ,
1749        ):
1750            type_sql = f"{type_sql} WITH TIME ZONE"
1751
1752        collate = self.sql(expression, "collate")
1753        if collate:
1754            type_sql = f"{type_sql} COLLATE {collate}"
1755
1756        return type_sql
1757
1758    def directory_sql(self, expression: exp.Directory) -> str:
1759        local = "LOCAL " if expression.args.get("local") else ""
1760        row_format = self.sql(expression, "row_format")
1761        row_format = f" {row_format}" if row_format else ""
1762        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1763
1764    def delete_sql(self, expression: exp.Delete) -> str:
1765        hint = self.sql(expression, "hint")
1766        this = self.sql(expression, "this")
1767        this = f" FROM {this}" if this else ""
1768        using = self.expressions(expression, key="using")
1769        using = f" USING {using}" if using else ""
1770        cluster = self.sql(expression, "cluster")
1771        cluster = f" {cluster}" if cluster else ""
1772        where = self.sql(expression, "where")
1773        returning = self.sql(expression, "returning")
1774        order = self.sql(expression, "order")
1775        limit = self.sql(expression, "limit")
1776        tables = self.expressions(expression, key="tables")
1777        tables = f" {tables}" if tables else ""
1778        if self.RETURNING_END:
1779            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1780        else:
1781            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1782        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1783
1784    def drop_sql(self, expression: exp.Drop) -> str:
1785        this = self.sql(expression, "this")
1786        expressions = self.expressions(expression, flat=True)
1787        expressions = f" ({expressions})" if expressions else ""
1788        kind = expression.args["kind"]
1789        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1790        iceberg = (
1791            " ICEBERG"
1792            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1793            else ""
1794        )
1795        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1796        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1797        on_cluster = self.sql(expression, "cluster")
1798        on_cluster = f" {on_cluster}" if on_cluster else ""
1799        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1800        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1801        cascade = " CASCADE" if expression.args.get("cascade") else ""
1802        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1803        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1804        purge = " PURGE" if expression.args.get("purge") else ""
1805        sync = " SYNC" if expression.args.get("sync") else ""
1806        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1807
1808    def set_operation(self, expression: exp.SetOperation) -> str:
1809        op_type = type(expression)
1810        op_name = op_type.key.upper()
1811
1812        distinct = expression.args.get("distinct")
1813        if (
1814            distinct is False
1815            and op_type in (exp.Except, exp.Intersect)
1816            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1817        ):
1818            self.unsupported(f"{op_name} ALL is not supported")
1819
1820        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1821
1822        if distinct is None:
1823            distinct = default_distinct
1824            if distinct is None:
1825                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1826
1827        if distinct is default_distinct:
1828            distinct_or_all = ""
1829        else:
1830            distinct_or_all = " DISTINCT" if distinct else " ALL"
1831
1832        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1833        side_kind = f"{side_kind} " if side_kind else ""
1834
1835        by_name = " BY NAME" if expression.args.get("by_name") else ""
1836        on = self.expressions(expression, key="on", flat=True)
1837        on = f" ON ({on})" if on else ""
1838
1839        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1840
1841    def set_operations(self, expression: exp.SetOperation) -> str:
1842        if not self.SET_OP_MODIFIERS:
1843            limit = expression.args.get("limit")
1844            order = expression.args.get("order")
1845
1846            if limit or order:
1847                select = self._move_ctes_to_top_level(
1848                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1849                )
1850
1851                if limit:
1852                    select = select.limit(limit.pop(), copy=False)
1853                if order:
1854                    select = select.order_by(order.pop(), copy=False)
1855                return self.sql(select)
1856
1857        sqls: list[str] = []
1858        stack: list[str | exp.Expr] = [expression]
1859
1860        while stack:
1861            node = stack.pop()
1862
1863            if isinstance(node, exp.SetOperation):
1864                stack.append(node.expression)
1865                stack.append(
1866                    self.maybe_comment(
1867                        self.set_operation(node), comments=node.comments, separated=True
1868                    )
1869                )
1870                stack.append(node.this)
1871            else:
1872                sqls.append(self.sql(node))
1873
1874        this = self.sep().join(sqls)
1875        this = self.query_modifiers(expression, this)
1876        return self.prepend_ctes(expression, this)
1877
1878    def fetch_sql(self, expression: exp.Fetch) -> str:
1879        direction = expression.args.get("direction")
1880        direction = f" {direction}" if direction else ""
1881        count = self.sql(expression, "count")
1882        count = f" {count}" if count else ""
1883        limit_options = self.sql(expression, "limit_options")
1884        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1885        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1886
1887    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1888        percent = " PERCENT" if expression.args.get("percent") else ""
1889        rows = " ROWS" if expression.args.get("rows") else ""
1890        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1891        if not with_ties and rows:
1892            with_ties = " ONLY"
1893        return f"{percent}{rows}{with_ties}"
1894
1895    def filter_sql(self, expression: exp.Filter) -> str:
1896        if self.AGGREGATE_FILTER_SUPPORTED:
1897            this = self.sql(expression, "this")
1898            where = self.sql(expression, "expression").strip()
1899            return f"{this} FILTER({where})"
1900
1901        agg = expression.this
1902        agg_arg = agg.this
1903        cond = expression.expression.this
1904        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1905        return self.sql(agg)
1906
1907    def hint_sql(self, expression: exp.Hint) -> str:
1908        if not self.QUERY_HINTS:
1909            self.unsupported("Hints are not supported")
1910            return ""
1911
1912        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1913
1914    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1915        using = self.sql(expression, "using")
1916        using = f" USING {using}" if using else ""
1917        columns = self.expressions(expression, key="columns", flat=True)
1918        columns = f"({columns})" if columns else ""
1919        partition_by = self.expressions(expression, key="partition_by", flat=True)
1920        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1921        where = self.sql(expression, "where")
1922        include = self.expressions(expression, key="include", flat=True)
1923        if include:
1924            include = f" INCLUDE ({include})"
1925        with_storage = self.expressions(expression, key="with_storage", flat=True)
1926        with_storage = f" WITH ({with_storage})" if with_storage else ""
1927        tablespace = self.sql(expression, "tablespace")
1928        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1929        on = self.sql(expression, "on")
1930        on = f" ON {on}" if on else ""
1931
1932        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1933
1934    def index_sql(self, expression: exp.Index) -> str:
1935        unique = "UNIQUE " if expression.args.get("unique") else ""
1936        primary = "PRIMARY " if expression.args.get("primary") else ""
1937        amp = "AMP " if expression.args.get("amp") else ""
1938        name = self.sql(expression, "this")
1939        name = f"{name} " if name else ""
1940        table = self.sql(expression, "table")
1941        table = f"{self.INDEX_ON} {table}" if table else ""
1942
1943        index = "INDEX " if not table else ""
1944
1945        params = self.sql(expression, "params")
1946        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1947
1948    def identifier_sql(self, expression: exp.Identifier) -> str:
1949        text = expression.name
1950        lower = text.lower()
1951        quoted = expression.quoted
1952        text = lower if self.normalize and not quoted else text
1953        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1954        if (
1955            quoted
1956            or self.dialect.can_quote(expression, self.identify)
1957            or lower in self.RESERVED_KEYWORDS
1958            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1959        ):
1960            text = (
1961                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1962            )
1963        return text
1964
1965    def hex_sql(self, expression: exp.Hex) -> str:
1966        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1967        if self.dialect.HEX_LOWERCASE:
1968            text = self.func("LOWER", text)
1969
1970        return text
1971
1972    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1973        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1974        if not self.dialect.HEX_LOWERCASE:
1975            text = self.func("LOWER", text)
1976        return text
1977
1978    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1979        input_format = self.sql(expression, "input_format")
1980        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1981        output_format = self.sql(expression, "output_format")
1982        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1983        return self.sep().join((input_format, output_format))
1984
1985    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1986        string = self.sql(exp.Literal.string(expression.name))
1987        return f"{prefix}{string}"
1988
1989    def partition_sql(self, expression: exp.Partition) -> str:
1990        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1991        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1992
1993    def properties_sql(self, expression: exp.Properties) -> str:
1994        root_properties = []
1995        with_properties = []
1996
1997        for p in expression.expressions:
1998            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1999            if p_loc == exp.Properties.Location.POST_WITH:
2000                with_properties.append(p)
2001            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2002                root_properties.append(p)
2003
2004        root_props_ast = exp.Properties(expressions=root_properties)
2005        root_props_ast.parent = expression.parent
2006
2007        with_props_ast = exp.Properties(expressions=with_properties)
2008        with_props_ast.parent = expression.parent
2009
2010        root_props = self.root_properties(root_props_ast)
2011        with_props = self.with_properties(with_props_ast)
2012
2013        if root_props and with_props and not self.pretty:
2014            with_props = " " + with_props
2015
2016        return root_props + with_props
2017
2018    def root_properties(self, properties: exp.Properties) -> str:
2019        if properties.expressions:
2020            return self.expressions(properties, indent=False, sep=" ")
2021        return ""
2022
2023    def properties(
2024        self,
2025        properties: exp.Properties,
2026        prefix: str = "",
2027        sep: str = ", ",
2028        suffix: str = "",
2029        wrapped: bool = True,
2030    ) -> str:
2031        if properties.expressions:
2032            expressions = self.expressions(properties, sep=sep, indent=False)
2033            if expressions:
2034                expressions = self.wrap(expressions) if wrapped else expressions
2035                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2036        return ""
2037
2038    def with_properties(self, properties: exp.Properties) -> str:
2039        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2040
2041    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2042        properties_locs = defaultdict(list)
2043        for p in properties.expressions:
2044            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2045            if p_loc != exp.Properties.Location.UNSUPPORTED:
2046                properties_locs[p_loc].append(p)
2047            else:
2048                self.unsupported(f"Unsupported property {p.key}")
2049
2050        return properties_locs
2051
2052    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2053        if isinstance(expression.this, exp.Dot):
2054            return self.sql(expression, "this")
2055        return f"'{expression.name}'" if string_key else expression.name
2056
2057    def property_sql(self, expression: exp.Property) -> str:
2058        property_cls = expression.__class__
2059        if property_cls == exp.Property:
2060            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2061
2062        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2063        if not property_name:
2064            self.unsupported(f"Unsupported property {expression.key}")
2065
2066        return f"{property_name}={self.sql(expression, 'this')}"
2067
2068    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2069        return f"UUID {self.sql(expression, 'this')}"
2070
2071    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2072        if self.SUPPORTS_CREATE_TABLE_LIKE:
2073            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2074            options = f" {options}" if options else ""
2075
2076            like = f"LIKE {self.sql(expression, 'this')}{options}"
2077            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2078                like = f"({like})"
2079
2080            return like
2081
2082        if expression.expressions:
2083            self.unsupported("Transpilation of LIKE property options is unsupported")
2084
2085        select = exp.select("*").from_(expression.this).limit(0)
2086        return f"AS {self.sql(select)}"
2087
2088    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2089        no = "NO " if expression.args.get("no") else ""
2090        protection = " PROTECTION" if expression.args.get("protection") else ""
2091        return f"{no}FALLBACK{protection}"
2092
2093    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2094        no = "NO " if expression.args.get("no") else ""
2095        local = expression.args.get("local")
2096        local = f"{local} " if local else ""
2097        dual = "DUAL " if expression.args.get("dual") else ""
2098        before = "BEFORE " if expression.args.get("before") else ""
2099        after = "AFTER " if expression.args.get("after") else ""
2100        return f"{no}{local}{dual}{before}{after}JOURNAL"
2101
2102    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2103        freespace = self.sql(expression, "this")
2104        percent = " PERCENT" if expression.args.get("percent") else ""
2105        return f"FREESPACE={freespace}{percent}"
2106
2107    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2108        if expression.args.get("default"):
2109            property = "DEFAULT"
2110        elif expression.args.get("on"):
2111            property = "ON"
2112        else:
2113            property = "OFF"
2114        return f"CHECKSUM={property}"
2115
2116    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2117        if expression.args.get("no"):
2118            return "NO MERGEBLOCKRATIO"
2119        if expression.args.get("default"):
2120            return "DEFAULT MERGEBLOCKRATIO"
2121
2122        percent = " PERCENT" if expression.args.get("percent") else ""
2123        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2124
2125    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2126        expressions = self.expressions(expression, flat=True)
2127        expressions = f"({expressions})" if expressions else ""
2128        return f"USING {self.sql(expression, 'this')}{expressions}"
2129
2130    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2131        default = expression.args.get("default")
2132        minimum = expression.args.get("minimum")
2133        maximum = expression.args.get("maximum")
2134        if default or minimum or maximum:
2135            if default:
2136                prop = "DEFAULT"
2137            elif minimum:
2138                prop = "MINIMUM"
2139            else:
2140                prop = "MAXIMUM"
2141            return f"{prop} DATABLOCKSIZE"
2142        units = expression.args.get("units")
2143        units = f" {units}" if units else ""
2144        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2145
2146    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2147        autotemp = expression.args.get("autotemp")
2148        always = expression.args.get("always")
2149        default = expression.args.get("default")
2150        manual = expression.args.get("manual")
2151        never = expression.args.get("never")
2152
2153        if autotemp is not None:
2154            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2155        elif always:
2156            prop = "ALWAYS"
2157        elif default:
2158            prop = "DEFAULT"
2159        elif manual:
2160            prop = "MANUAL"
2161        elif never:
2162            prop = "NEVER"
2163        return f"BLOCKCOMPRESSION={prop}"
2164
2165    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2166        no = expression.args.get("no")
2167        no = " NO" if no else ""
2168        concurrent = expression.args.get("concurrent")
2169        concurrent = " CONCURRENT" if concurrent else ""
2170        target = self.sql(expression, "target")
2171        target = f" {target}" if target else ""
2172        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2173
2174    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2175        if isinstance(expression.this, list):
2176            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2177        if expression.this:
2178            modulus = self.sql(expression, "this")
2179            remainder = self.sql(expression, "expression")
2180            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2181
2182        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2183        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2184        return f"FROM ({from_expressions}) TO ({to_expressions})"
2185
2186    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2187        this = self.sql(expression, "this")
2188
2189        for_values_or_default = expression.expression
2190        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2191            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2192        else:
2193            for_values_or_default = " DEFAULT"
2194
2195        return f"PARTITION OF {this}{for_values_or_default}"
2196
2197    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2198        kind = expression.args.get("kind")
2199        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2200        for_or_in = expression.args.get("for_or_in")
2201        for_or_in = f" {for_or_in}" if for_or_in else ""
2202        lock_type = expression.args.get("lock_type")
2203        override = " OVERRIDE" if expression.args.get("override") else ""
2204        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2205
2206    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2207        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2208        statistics = expression.args.get("statistics")
2209        statistics_sql = ""
2210        if statistics is not None:
2211            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2212        return f"{data_sql}{statistics_sql}"
2213
2214    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2215        this = self.sql(expression, "this")
2216        this = f"HISTORY_TABLE={this}" if this else ""
2217        data_consistency: str | None = self.sql(expression, "data_consistency")
2218        data_consistency = (
2219            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2220        )
2221        retention_period: str | None = self.sql(expression, "retention_period")
2222        retention_period = (
2223            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2224        )
2225
2226        if this:
2227            on_sql = self.func("ON", this, data_consistency, retention_period)
2228        else:
2229            on_sql = "ON" if expression.args.get("on") else "OFF"
2230
2231        sql = f"SYSTEM_VERSIONING={on_sql}"
2232
2233        return f"WITH({sql})" if expression.args.get("with_") else sql
2234
2235    def insert_sql(self, expression: exp.Insert) -> str:
2236        hint = self.sql(expression, "hint")
2237        overwrite = expression.args.get("overwrite")
2238
2239        if isinstance(expression.this, exp.Directory):
2240            this = " OVERWRITE" if overwrite else " INTO"
2241        else:
2242            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2243
2244        stored = self.sql(expression, "stored")
2245        stored = f" {stored}" if stored else ""
2246        alternative = expression.args.get("alternative")
2247        alternative = f" OR {alternative}" if alternative else ""
2248        ignore = " IGNORE" if expression.args.get("ignore") else ""
2249        is_function = expression.args.get("is_function")
2250        if is_function:
2251            this = f"{this} FUNCTION"
2252        this = f"{this} {self.sql(expression, 'this')}"
2253
2254        exists = " IF EXISTS" if expression.args.get("exists") else ""
2255        where = self.sql(expression, "where")
2256        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2257        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2258        on_conflict = self.sql(expression, "conflict")
2259        on_conflict = f" {on_conflict}" if on_conflict else ""
2260        by_name = " BY NAME" if expression.args.get("by_name") else ""
2261        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2262        returning = self.sql(expression, "returning")
2263
2264        if self.RETURNING_END:
2265            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2266        else:
2267            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2268
2269        partition_by = self.sql(expression, "partition")
2270        partition_by = f" {partition_by}" if partition_by else ""
2271        settings = self.sql(expression, "settings")
2272        settings = f" {settings}" if settings else ""
2273
2274        source = self.sql(expression, "source")
2275        source = f"TABLE {source}" if source else ""
2276
2277        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2278        return self.prepend_ctes(expression, sql)
2279
2280    def introducer_sql(self, expression: exp.Introducer) -> str:
2281        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2282
2283    def kill_sql(self, expression: exp.Kill) -> str:
2284        kind = self.sql(expression, "kind")
2285        kind = f" {kind}" if kind else ""
2286        this = self.sql(expression, "this")
2287        this = f" {this}" if this else ""
2288        return f"KILL{kind}{this}"
2289
2290    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2291        return expression.name
2292
2293    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2294        return expression.name
2295
2296    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2297        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2298
2299        constraint = self.sql(expression, "constraint")
2300        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2301
2302        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2303        if conflict_keys:
2304            conflict_keys = f"({conflict_keys})"
2305
2306        index_predicate = self.sql(expression, "index_predicate")
2307        conflict_keys = f"{conflict_keys}{index_predicate} "
2308
2309        action = self.sql(expression, "action")
2310
2311        expressions = self.expressions(expression, flat=True)
2312        if expressions:
2313            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2314            expressions = f" {set_keyword}{expressions}"
2315
2316        where = self.sql(expression, "where")
2317        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2318
2319    def returning_sql(self, expression: exp.Returning) -> str:
2320        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2321
2322    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2323        fields = self.sql(expression, "fields")
2324        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2325        escaped = self.sql(expression, "escaped")
2326        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2327        items = self.sql(expression, "collection_items")
2328        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2329        keys = self.sql(expression, "map_keys")
2330        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2331        lines = self.sql(expression, "lines")
2332        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2333        null = self.sql(expression, "null")
2334        null = f" NULL DEFINED AS {null}" if null else ""
2335        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2336
2337    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2338        return f"WITH ({self.expressions(expression, flat=True)})"
2339
2340    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2341        this = f"{self.sql(expression, 'this')} INDEX"
2342        target = self.sql(expression, "target")
2343        target = f" FOR {target}" if target else ""
2344        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2345
2346    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2347        this = self.sql(expression, "this")
2348        kind = self.sql(expression, "kind")
2349        expr = self.sql(expression, "expression")
2350        return f"{this} ({kind} => {expr})"
2351
2352    def table_parts(self, expression: exp.Table) -> str:
2353        return ".".join(
2354            self.sql(part)
2355            for part in (
2356                expression.args.get("catalog"),
2357                expression.args.get("db"),
2358                expression.args.get("this"),
2359            )
2360            if part is not None
2361        )
2362
2363    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2364        table = self.table_parts(expression)
2365        only = "ONLY " if expression.args.get("only") else ""
2366        partition = self.sql(expression, "partition")
2367        partition = f" {partition}" if partition else ""
2368        version = self.sql(expression, "version")
2369        version = f" {version}" if version else ""
2370        alias = self.sql(expression, "alias")
2371        alias = f"{sep}{alias}" if alias else ""
2372
2373        sample = self.sql(expression, "sample")
2374        post_alias = ""
2375        pre_alias = ""
2376
2377        if self.dialect.ALIAS_POST_TABLESAMPLE:
2378            pre_alias = sample
2379        else:
2380            post_alias = sample
2381
2382        if self.dialect.ALIAS_POST_VERSION:
2383            pre_alias = f"{pre_alias}{version}"
2384        else:
2385            post_alias = f"{post_alias}{version}"
2386
2387        hints = self.expressions(expression, key="hints", sep=" ")
2388        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2389        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2390        joins = self.indent(
2391            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2392        )
2393        laterals = self.expressions(expression, key="laterals", sep="")
2394
2395        file_format = self.sql(expression, "format")
2396        if file_format:
2397            pattern = self.sql(expression, "pattern")
2398            pattern = f", PATTERN => {pattern}" if pattern else ""
2399            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2400
2401        ordinality = expression.args.get("ordinality") or ""
2402        if ordinality:
2403            ordinality = f" WITH ORDINALITY{alias}"
2404            alias = ""
2405
2406        when = self.sql(expression, "when")
2407        if when:
2408            table = f"{table} {when}"
2409
2410        changes = self.sql(expression, "changes")
2411        changes = f" {changes}" if changes else ""
2412
2413        rows_from = self.expressions(expression, key="rows_from")
2414        if rows_from:
2415            table = f"ROWS FROM {self.wrap(rows_from)}"
2416
2417        indexed = expression.args.get("indexed")
2418        if indexed is not None:
2419            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2420        else:
2421            indexed = ""
2422
2423        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2424
2425    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2426        table = self.func("TABLE", expression.this)
2427        alias = self.sql(expression, "alias")
2428        alias = f" AS {alias}" if alias else ""
2429        sample = self.sql(expression, "sample")
2430        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2431        joins = self.indent(
2432            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2433        )
2434        return f"{table}{alias}{pivots}{sample}{joins}"
2435
2436    def tablesample_sql(
2437        self,
2438        expression: exp.TableSample,
2439        tablesample_keyword: str | None = None,
2440    ) -> str:
2441        method = self.sql(expression, "method")
2442        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2443        numerator = self.sql(expression, "bucket_numerator")
2444        denominator = self.sql(expression, "bucket_denominator")
2445        field = self.sql(expression, "bucket_field")
2446        field = f" ON {field}" if field else ""
2447        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2448        seed = self.sql(expression, "seed")
2449        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2450
2451        size = self.sql(expression, "size")
2452        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2453            size = f"{size} ROWS"
2454
2455        percent = self.sql(expression, "percent")
2456        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2457            percent = f"{percent} PERCENT"
2458
2459        expr = f"{bucket}{percent}{size}"
2460        if self.TABLESAMPLE_REQUIRES_PARENS:
2461            expr = f"({expr})"
2462
2463        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2464
2465    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2466        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2467        # the stored column name differs from the target dialect's natural output.
2468        columns = expression.args.get("columns")
2469        if not columns or len(expression.fields) != 1:
2470            return None
2471
2472        args = expression.args
2473        parser_cls = self.dialect.parser_class
2474
2475        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2476        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2477        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2478
2479        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2480        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2481        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2482
2483        if (
2484            src_identify_pivot_strings == tgt_identify_pivot_strings
2485            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2486            and src_pivot_column_naming == tgt_pivot_column_naming
2487        ):
2488            return None
2489
2490        in_exprs = expression.fields[0].expressions
2491        step = len(columns) // len(in_exprs)
2492
2493        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2494        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2495        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2496        first_stored = columns[0].name
2497
2498        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2499        if not first_stored.startswith(first_base):
2500            return None
2501
2502        suffix = first_stored[len(first_base) :]
2503
2504        # Whether the target dialect would append an agg-name suffix for this pivot.
2505        # Spark single-agg uniquely drops the agg alias entirely.
2506        target_has_suffix = (
2507            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2508        ) and any(a.alias for a in expression.expressions)
2509        source_has_suffix = suffix != ""
2510
2511        new_exprs: list[exp.Expression] = []
2512        modified = False
2513        for val_idx, e in enumerate(in_exprs):
2514            if isinstance(e, exp.PivotAlias):
2515                new_exprs.append(e)
2516                continue
2517
2518            i = val_idx * step
2519            stored_full = columns[i].name
2520            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2521            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2522
2523            # Source had a suffix, but target won't apply one
2524            if source_has_suffix and not target_has_suffix:
2525                new_exprs.append(
2526                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2527                )
2528                modified = True
2529            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2530            elif stored_value != target_value:
2531                new_exprs.append(
2532                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2533                )
2534                modified = True
2535            else:
2536                new_exprs.append(e)
2537
2538        return new_exprs if modified else None
2539
2540    def pivot_sql(self, expression: exp.Pivot) -> str:
2541        expressions = self.expressions(expression, flat=True)
2542        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2543
2544        group = self.sql(expression, "group")
2545
2546        if expression.this:
2547            this = self.sql(expression, "this")
2548            if not expressions:
2549                sql = f"UNPIVOT {this}"
2550            else:
2551                on = f"{self.seg('ON')} {expressions}"
2552                into = self.sql(expression, "into")
2553                into = f"{self.seg('INTO')} {into}" if into else ""
2554                using = self.expressions(expression, key="using", flat=True)
2555                using = f"{self.seg('USING')} {using}" if using else ""
2556                sql = f"{direction} {this}{on}{into}{using}{group}"
2557            return self.prepend_ctes(expression, sql)
2558
2559        if not expression.unpivot:
2560            # Wrap IN-list values with explicit aliases where the target dialect would differ
2561            new_field_exprs = self._pivot_in_value_aliases(expression)
2562            if new_field_exprs is not None:
2563                expression.fields[0].set("expressions", new_field_exprs)
2564
2565        alias = self.sql(expression, "alias")
2566        alias = f" AS {alias}" if alias else ""
2567
2568        fields = self.expressions(
2569            expression,
2570            "fields",
2571            sep=" ",
2572            dynamic=True,
2573            new_line=True,
2574            skip_first=True,
2575            skip_last=True,
2576        )
2577
2578        include_nulls = expression.args.get("include_nulls")
2579        if include_nulls is not None:
2580            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2581        else:
2582            nulls = ""
2583
2584        default_on_null = self.sql(expression, "default_on_null")
2585        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2586        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2587        return self.prepend_ctes(expression, sql)
2588
2589    def version_sql(self, expression: exp.Version) -> str:
2590        this = f"FOR {expression.name}"
2591        kind = expression.text("kind")
2592        expr = self.sql(expression, "expression")
2593        return f"{this} {kind} {expr}"
2594
2595    def tuple_sql(self, expression: exp.Tuple) -> str:
2596        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2597
2598    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2599        """
2600        Returns (join_sql, from_sql) for UPDATE statements.
2601        - join_sql: placed after UPDATE table, before SET
2602        - from_sql: placed after SET clause (standard position)
2603        Dialects like MySQL need to convert FROM to JOIN syntax.
2604        """
2605        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2606            return ("", self.sql(expression, "from_"))
2607
2608        # Qualify unqualified columns in SET clause with the target table
2609        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2610        target_table = expression.this
2611        if isinstance(target_table, exp.Table):
2612            target_name = exp.to_identifier(target_table.alias_or_name)
2613            for eq in expression.expressions:
2614                col = eq.this
2615                if isinstance(col, exp.Column) and not col.table:
2616                    col.set("table", target_name)
2617
2618        table = from_expr.this
2619        if nested_joins := table.args.get("joins", []):
2620            table.set("joins", None)
2621
2622        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2623        for nested in nested_joins:
2624            if not nested.args.get("on") and not nested.args.get("using"):
2625                nested.set("on", exp.true())
2626            join_sql += self.sql(nested)
2627
2628        return (join_sql, "")
2629
2630    def update_sql(self, expression: exp.Update) -> str:
2631        hint = self.sql(expression, "hint")
2632        this = self.sql(expression, "this")
2633        join_sql, from_sql = self._update_from_joins_sql(expression)
2634        set_sql = self.expressions(expression, flat=True)
2635        where_sql = self.sql(expression, "where")
2636        returning = self.sql(expression, "returning")
2637        order = self.sql(expression, "order")
2638        limit = self.sql(expression, "limit")
2639        if self.RETURNING_END:
2640            expression_sql = f"{from_sql}{where_sql}{returning}"
2641        else:
2642            expression_sql = f"{returning}{from_sql}{where_sql}"
2643        options = self.expressions(expression, key="options")
2644        options = f" OPTION({options})" if options else ""
2645        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2646        return self.prepend_ctes(expression, sql)
2647
2648    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2649        values_as_table = values_as_table and self.VALUES_AS_TABLE
2650
2651        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2652        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2653            args = self.expressions(expression)
2654            alias = self.sql(expression, "alias")
2655            values = f"VALUES{self.seg('')}{args}"
2656            values = (
2657                f"({values})"
2658                if self.WRAP_DERIVED_VALUES
2659                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2660                else values
2661            )
2662            values = self.query_modifiers(expression, values)
2663            return f"{values} AS {alias}" if alias else values
2664
2665        # Converts `VALUES...` expression into a series of select unions.
2666        alias_node = expression.args.get("alias")
2667        column_names = alias_node and alias_node.columns
2668
2669        selects: list[exp.Query] = []
2670
2671        for i, tup in enumerate(expression.expressions):
2672            row = tup.expressions
2673
2674            if i == 0 and column_names:
2675                row = [
2676                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2677                ]
2678
2679            selects.append(exp.Select(expressions=row))
2680
2681        if self.pretty:
2682            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2683            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2684            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2685            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2686            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2687
2688        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2689        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2690        return f"({unions}){alias}"
2691
2692    def var_sql(self, expression: exp.Var) -> str:
2693        return self.sql(expression, "this")
2694
2695    @unsupported_args("expressions")
2696    def into_sql(self, expression: exp.Into) -> str:
2697        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2698        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2699        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2700
2701    def from_sql(self, expression: exp.From) -> str:
2702        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2703
2704    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2705        grouping_sets = self.expressions(expression, indent=False)
2706        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2707
2708    def rollup_sql(self, expression: exp.Rollup) -> str:
2709        expressions = self.expressions(expression, indent=False)
2710        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2711
2712    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2713        this = self.sql(expression, "this")
2714
2715        columns = self.expressions(expression, flat=True)
2716
2717        from_sql = self.sql(expression, "from_index")
2718        from_sql = f" FROM {from_sql}" if from_sql else ""
2719
2720        properties = expression.args.get("properties")
2721        properties_sql = (
2722            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2723        )
2724
2725        return f"{this}({columns}){from_sql}{properties_sql}"
2726
2727    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2728        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2729
2730    def cube_sql(self, expression: exp.Cube) -> str:
2731        expressions = self.expressions(expression, indent=False)
2732        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2733
2734    def group_sql(self, expression: exp.Group) -> str:
2735        group_by_all = expression.args.get("all")
2736        if group_by_all is True:
2737            modifier = " ALL"
2738        elif group_by_all is False:
2739            modifier = " DISTINCT"
2740        else:
2741            modifier = ""
2742
2743        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2744
2745        grouping_sets = self.expressions(expression, key="grouping_sets")
2746        cube = self.expressions(expression, key="cube")
2747        rollup = self.expressions(expression, key="rollup")
2748
2749        groupings = csv(
2750            self.seg(grouping_sets) if grouping_sets else "",
2751            self.seg(cube) if cube else "",
2752            self.seg(rollup) if rollup else "",
2753            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2754            sep=self.GROUPINGS_SEP,
2755        )
2756
2757        if (
2758            expression.expressions
2759            and groupings
2760            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2761        ):
2762            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2763
2764        return f"{group_by}{groupings}"
2765
2766    def having_sql(self, expression: exp.Having) -> str:
2767        this = self.indent(self.sql(expression, "this"))
2768        return f"{self.seg('HAVING')}{self.sep()}{this}"
2769
2770    def connect_sql(self, expression: exp.Connect) -> str:
2771        start = self.sql(expression, "start")
2772        start = self.seg(f"START WITH {start}") if start else ""
2773        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2774        connect = self.sql(expression, "connect")
2775        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2776        return start + connect
2777
2778    def prior_sql(self, expression: exp.Prior) -> str:
2779        return f"PRIOR {self.sql(expression, 'this')}"
2780
2781    def join_sql(self, expression: exp.Join) -> str:
2782        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2783            side = None
2784        else:
2785            side = expression.side
2786
2787        op_sql = " ".join(
2788            op
2789            for op in (
2790                expression.method,
2791                "GLOBAL" if expression.args.get("global_") else None,
2792                side,
2793                expression.kind,
2794                expression.hint if self.JOIN_HINTS else None,
2795                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2796            )
2797            if op
2798        )
2799        match_cond = self.sql(expression, "match_condition")
2800        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2801        on_sql = self.sql(expression, "on")
2802        using = expression.args.get("using")
2803
2804        if not on_sql and using:
2805            on_sql = csv(*(self.sql(column) for column in using))
2806
2807        this = expression.this
2808        this_sql = self.sql(this)
2809
2810        exprs = self.expressions(expression)
2811        if exprs:
2812            this_sql = f"{this_sql},{self.seg(exprs)}"
2813
2814        if on_sql:
2815            on_sql = self.indent(on_sql, skip_first=True)
2816            space = self.seg(" " * self.pad) if self.pretty else " "
2817            if using:
2818                on_sql = f"{space}USING ({on_sql})"
2819            else:
2820                on_sql = f"{space}ON {on_sql}"
2821        elif not op_sql:
2822            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2823                return f" {this_sql}"
2824
2825            return f", {this_sql}"
2826
2827        if op_sql != "STRAIGHT_JOIN":
2828            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2829
2830        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2831        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2832
2833    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2834        args = self.expressions(expression, flat=True)
2835        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2836        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2837
2838    def lateral_op(self, expression: exp.Lateral) -> str:
2839        cross_apply = expression.args.get("cross_apply")
2840
2841        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2842        if cross_apply is True:
2843            op = "INNER JOIN "
2844        elif cross_apply is False:
2845            op = "LEFT JOIN "
2846        else:
2847            op = ""
2848
2849        return f"{op}LATERAL"
2850
2851    def lateral_sql(self, expression: exp.Lateral) -> str:
2852        this = self.sql(expression, "this")
2853
2854        if expression.args.get("view"):
2855            alias = expression.args["alias"]
2856            columns = self.expressions(alias, key="columns", flat=True)
2857            table = f" {alias.name}" if alias.name else ""
2858            columns = f" AS {columns}" if columns else ""
2859            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2860            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2861
2862        alias = self.sql(expression, "alias")
2863        alias = f" AS {alias}" if alias else ""
2864
2865        ordinality = expression.args.get("ordinality") or ""
2866        if ordinality:
2867            ordinality = f" WITH ORDINALITY{alias}"
2868            alias = ""
2869
2870        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2871
2872    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2873        this = self.sql(expression, "this")
2874
2875        args = [
2876            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2877            for e in (expression.args.get(k) for k in ("offset", "expression"))
2878            if e
2879        ]
2880
2881        args_sql = ", ".join(self.sql(e) for e in args)
2882        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2883        expressions = self.expressions(expression, flat=True)
2884        limit_options = self.sql(expression, "limit_options")
2885        expressions = f" BY {expressions}" if expressions else ""
2886
2887        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2888
2889    def offset_sql(self, expression: exp.Offset) -> str:
2890        this = self.sql(expression, "this")
2891        value = expression.expression
2892        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2893        expressions = self.expressions(expression, flat=True)
2894        expressions = f" BY {expressions}" if expressions else ""
2895        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2896
2897    def setitem_sql(self, expression: exp.SetItem) -> str:
2898        kind = self.sql(expression, "kind")
2899        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2900            kind = ""
2901        else:
2902            kind = f"{kind} " if kind else ""
2903        this = self.sql(expression, "this")
2904        expressions = self.expressions(expression)
2905        collate = self.sql(expression, "collate")
2906        collate = f" COLLATE {collate}" if collate else ""
2907        global_ = "GLOBAL " if expression.args.get("global_") else ""
2908        return f"{global_}{kind}{this}{expressions}{collate}"
2909
2910    def set_sql(self, expression: exp.Set) -> str:
2911        expressions = f" {self.expressions(expression, flat=True)}"
2912        tag = " TAG" if expression.args.get("tag") else ""
2913        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2914
2915    def queryband_sql(self, expression: exp.QueryBand) -> str:
2916        this = self.sql(expression, "this")
2917        update = " UPDATE" if expression.args.get("update") else ""
2918        scope = self.sql(expression, "scope")
2919        scope = f" FOR {scope}" if scope else ""
2920
2921        return f"QUERY_BAND = {this}{update}{scope}"
2922
2923    def pragma_sql(self, expression: exp.Pragma) -> str:
2924        return f"PRAGMA {self.sql(expression, 'this')}"
2925
2926    def lock_sql(self, expression: exp.Lock) -> str:
2927        if not self.LOCKING_READS_SUPPORTED:
2928            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2929            return ""
2930
2931        update = expression.args["update"]
2932        key = expression.args.get("key")
2933        if update:
2934            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2935        else:
2936            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2937        expressions = self.expressions(expression, flat=True)
2938        expressions = f" OF {expressions}" if expressions else ""
2939        wait = expression.args.get("wait")
2940
2941        if wait is not None:
2942            if isinstance(wait, exp.Literal):
2943                wait = f" WAIT {self.sql(wait)}"
2944            else:
2945                wait = " NOWAIT" if wait else " SKIP LOCKED"
2946
2947        return f"{lock_type}{expressions}{wait or ''}"
2948
2949    def literal_sql(self, expression: exp.Literal) -> str:
2950        text = expression.this or ""
2951        if expression.is_string:
2952            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2953        return text
2954
2955    def escape_str(
2956        self,
2957        text: str,
2958        escape_backslash: bool = True,
2959        delimiter: str | None = None,
2960        escaped_delimiter: str | None = None,
2961        is_byte_string: bool = False,
2962    ) -> str:
2963        if is_byte_string:
2964            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2965        else:
2966            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2967
2968        if supports_escape_sequences:
2969            text = "".join(
2970                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2971                for ch in text
2972            )
2973
2974        delimiter = delimiter or self.dialect.QUOTE_END
2975        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2976
2977        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2978
2979    def loaddata_sql(self, expression: exp.LoadData) -> str:
2980        is_overwrite = expression.args.get("overwrite")
2981        overwrite = " OVERWRITE" if is_overwrite else ""
2982        this = self.sql(expression, "this")
2983
2984        files = expression.args.get("files")
2985        if files:
2986            files_sql = self.expressions(files, flat=True)
2987            files_sql = f"FILES{self.wrap(files_sql)}"
2988            if is_overwrite:
2989                this = f" {this}"
2990            elif expression.args.get("temp"):
2991                this = f" INTO TEMP TABLE {this}"
2992            else:
2993                this = f" INTO TABLE {this}"
2994            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2995
2996        local = " LOCAL" if expression.args.get("local") else ""
2997        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2998        this = f" INTO TABLE {this}"
2999        partition = self.sql(expression, "partition")
3000        partition = f" {partition}" if partition else ""
3001        input_format = self.sql(expression, "input_format")
3002        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3003        serde = self.sql(expression, "serde")
3004        serde = f" SERDE {serde}" if serde else ""
3005        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3006
3007    def null_sql(self, *_) -> str:
3008        return "NULL"
3009
3010    def boolean_sql(self, expression: exp.Boolean) -> str:
3011        return "TRUE" if expression.this else "FALSE"
3012
3013    def booland_sql(self, expression: exp.Booland) -> str:
3014        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3015
3016    def boolor_sql(self, expression: exp.Boolor) -> str:
3017        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3018
3019    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3020        this = self.sql(expression, "this")
3021        this = f"{this} " if this else this
3022        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3023        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3024
3025    def withfill_sql(self, expression: exp.WithFill) -> str:
3026        from_sql = self.sql(expression, "from_")
3027        from_sql = f" FROM {from_sql}" if from_sql else ""
3028        to_sql = self.sql(expression, "to")
3029        to_sql = f" TO {to_sql}" if to_sql else ""
3030        step_sql = self.sql(expression, "step")
3031        step_sql = f" STEP {step_sql}" if step_sql else ""
3032        interpolated_values = [
3033            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3034            if isinstance(e, exp.Alias)
3035            else self.sql(e, "this")
3036            for e in expression.args.get("interpolate") or []
3037        ]
3038        interpolate = (
3039            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3040        )
3041        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3042
3043    def cluster_sql(self, expression: exp.Cluster) -> str:
3044        return self.op_expressions("CLUSTER BY", expression)
3045
3046    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3047        if expression.this:
3048            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3049            return ""
3050        expressions = self.expressions(expression, flat=True)
3051        return f"CLUSTER BY ({expressions})"
3052
3053    def distribute_sql(self, expression: exp.Distribute) -> str:
3054        return self.op_expressions("DISTRIBUTE BY", expression)
3055
3056    def sort_sql(self, expression: exp.Sort) -> str:
3057        return self.op_expressions("SORT BY", expression)
3058
3059    def _resolve_ordered_for_null_ordering_simulation(
3060        self, expression: exp.Ordered
3061    ) -> exp.Expr | None:
3062        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3063
3064        Returns the underlying expression of the uniquely-matching projection
3065        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3066        simulation, since the CASE is evaluated in FROM-clause scope rather
3067        than alias scope (MySQL error 1052). Returns None if no safe
3068        substitution applies, leaving the original behaviour unchanged.
3069        """
3070        this = expression.this
3071        if not (isinstance(this, exp.Column) and not this.table):
3072            return None
3073
3074        ancestor = expression.find_ancestor(exp.Select, exp.Window)
3075        if not isinstance(ancestor, exp.Select):
3076            return None
3077
3078        column_name = this.name
3079        matched: list[exp.Expr] = [
3080            p.this if isinstance(p, exp.Alias) else p
3081            for p in ancestor.selects
3082            if p.output_name == column_name
3083        ]
3084        match = matched[0] if len(matched) == 1 else None
3085
3086        # Skip the substitution when it would be identical to the existing
3087        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3088        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3089            return None
3090
3091        return match
3092
3093    def ordered_sql(self, expression: exp.Ordered) -> str:
3094        desc = expression.args.get("desc")
3095        asc = not desc
3096
3097        nulls_first = expression.args.get("nulls_first")
3098        nulls_last = not nulls_first
3099        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3100        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3101        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3102
3103        this = self.sql(expression, "this")
3104
3105        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3106        nulls_sort_change = ""
3107        if nulls_first and (
3108            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3109        ):
3110            nulls_sort_change = " NULLS FIRST"
3111        elif (
3112            nulls_last
3113            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3114            and not nulls_are_last
3115        ):
3116            nulls_sort_change = " NULLS LAST"
3117
3118        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3119        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3120            window = expression.find_ancestor(exp.Window, exp.Select)
3121
3122            if isinstance(window, exp.Window):
3123                window_this = window.this
3124                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3125                    window_this = window_this.this
3126                spec = window.args.get("spec")
3127            else:
3128                window_this = None
3129                spec = None
3130
3131            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3132            # without a spec or with a ROWS spec, but not with RANGE
3133            if not (
3134                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3135                and (not spec or spec.text("kind").upper() == "ROWS")
3136            ):
3137                if window_this and spec:
3138                    self.unsupported(
3139                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3140                    )
3141                    nulls_sort_change = ""
3142                elif self.NULL_ORDERING_SUPPORTED is False and (
3143                    (asc and nulls_sort_change == " NULLS LAST")
3144                    or (desc and nulls_sort_change == " NULLS FIRST")
3145                ):
3146                    # BigQuery does not allow these ordering/nulls combinations when used under
3147                    # an aggregation func or under a window containing one
3148                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3149
3150                    if isinstance(ancestor, exp.Window):
3151                        ancestor = ancestor.this
3152                    if isinstance(ancestor, exp.AggFunc):
3153                        self.unsupported(
3154                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3155                        )
3156                        nulls_sort_change = ""
3157                elif self.NULL_ORDERING_SUPPORTED is None:
3158                    if expression.this.is_int:
3159                        self.unsupported(
3160                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3161                        )
3162                    elif not isinstance(expression.this, exp.Rand):
3163                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3164                        target = self.sql(resolved) if resolved is not None else this
3165                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3166                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3167                    nulls_sort_change = ""
3168
3169        with_fill = self.sql(expression, "with_fill")
3170        with_fill = f" {with_fill}" if with_fill else ""
3171
3172        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3173
3174    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3175        window_frame = self.sql(expression, "window_frame")
3176        window_frame = f"{window_frame} " if window_frame else ""
3177
3178        this = self.sql(expression, "this")
3179
3180        return f"{window_frame}{this}"
3181
3182    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3183        partition = self.partition_by_sql(expression)
3184        order = self.sql(expression, "order")
3185        measures = self.expressions(expression, key="measures")
3186        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3187        rows = self.sql(expression, "rows")
3188        rows = self.seg(rows) if rows else ""
3189        after = self.sql(expression, "after")
3190        after = self.seg(after) if after else ""
3191        pattern = self.sql(expression, "pattern")
3192        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3193        definition_sqls = [
3194            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3195            for definition in expression.args.get("define", [])
3196        ]
3197        definitions = self.expressions(sqls=definition_sqls)
3198        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3199        body = "".join(
3200            (
3201                partition,
3202                order,
3203                measures,
3204                rows,
3205                after,
3206                pattern,
3207                define,
3208            )
3209        )
3210        alias = self.sql(expression, "alias")
3211        alias = f" {alias}" if alias else ""
3212        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3213
3214    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3215        limit = expression.args.get("limit")
3216
3217        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3218            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3219        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3220            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3221
3222        return csv(
3223            *sqls,
3224            *[self.sql(join) for join in expression.args.get("joins") or []],
3225            self.sql(expression, "match"),
3226            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3227            self.sql(expression, "prewhere"),
3228            self.sql(expression, "where"),
3229            self.sql(expression, "connect"),
3230            self.sql(expression, "group"),
3231            self.sql(expression, "having"),
3232            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3233            self.sql(expression, "order"),
3234            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3235            *self.after_limit_modifiers(expression),
3236            self.options_modifier(expression),
3237            self.sql(expression, "for_"),
3238            sep="",
3239        )
3240
3241    def options_modifier(self, expression: exp.Expr) -> str:
3242        options = self.expressions(expression, key="options")
3243        return f" {options}" if options else ""
3244
3245    def forclause_sql(self, expression: exp.ForClause) -> str:
3246        kind = expression.args["kind"]
3247        if kind == "BROWSE":
3248            return f"{self.sep()}FOR BROWSE"
3249        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3250        # the target dialect doesn't support QueryOption, so we drop the clause.
3251        options = self.expressions(expression, key="expressions")
3252        if not options:
3253            return ""
3254        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3255
3256    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3257        self.unsupported("Unsupported query option.")
3258        return ""
3259
3260    def offset_limit_modifiers(
3261        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3262    ) -> list[str]:
3263        return [
3264            self.sql(expression, "offset") if fetch else self.sql(limit),
3265            self.sql(limit) if fetch else self.sql(expression, "offset"),
3266        ]
3267
3268    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3269        locks = self.expressions(expression, key="locks", sep=" ")
3270        locks = f" {locks}" if locks else ""
3271        return [locks, self.sql(expression, "sample")]
3272
3273    def select_sql(self, expression: exp.Select) -> str:
3274        into = expression.args.get("into")
3275        if not self.SUPPORTS_SELECT_INTO and into:
3276            into.pop()
3277
3278        hint = self.sql(expression, "hint")
3279        distinct = self.sql(expression, "distinct")
3280        distinct = f" {distinct}" if distinct else ""
3281        kind = self.sql(expression, "kind")
3282
3283        limit = expression.args.get("limit")
3284        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3285            top = self.limit_sql(limit, top=True)
3286            limit.pop()
3287        else:
3288            top = ""
3289
3290        expressions = self.expressions(expression)
3291
3292        if kind:
3293            if kind in self.SELECT_KINDS:
3294                kind = f" AS {kind}"
3295            else:
3296                if kind == "STRUCT":
3297                    expressions = self.expressions(
3298                        sqls=[
3299                            self.sql(
3300                                exp.Struct(
3301                                    expressions=[
3302                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3303                                        if isinstance(e, exp.Alias)
3304                                        else e
3305                                        for e in expression.expressions
3306                                    ]
3307                                )
3308                            )
3309                        ]
3310                    )
3311                kind = ""
3312
3313        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3314        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3315
3316        exclude = expression.args.get("exclude")
3317
3318        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3319            exclude_sql = self.expressions(sqls=exclude, flat=True)
3320            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3321
3322        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3323        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3324        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3325        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3326        sql = self.query_modifiers(
3327            expression,
3328            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3329            self.sql(expression, "into", comment=False),
3330            self.sql(expression, "from_", comment=False),
3331        )
3332
3333        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3334        if expression.args.get("with_"):
3335            sql = self.maybe_comment(sql, expression)
3336            expression.pop_comments()
3337
3338        sql = self.prepend_ctes(expression, sql)
3339
3340        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3341            expression.set("exclude", None)
3342            subquery = expression.subquery(copy=False)
3343            star = exp.Star(except_=exclude)
3344            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3345
3346        if not self.SUPPORTS_SELECT_INTO and into:
3347            if into.args.get("temporary"):
3348                table_kind = " TEMPORARY"
3349            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3350                table_kind = " UNLOGGED"
3351            else:
3352                table_kind = ""
3353            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3354
3355        return sql
3356
3357    def schema_sql(self, expression: exp.Schema) -> str:
3358        this = self.sql(expression, "this")
3359        sql = self.schema_columns_sql(expression)
3360        return f"{this} {sql}" if this and sql else this or sql
3361
3362    def schema_columns_sql(self, expression: exp.Expr) -> str:
3363        if expression.expressions:
3364            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3365        return ""
3366
3367    def star_sql(self, expression: exp.Star) -> str:
3368        except_ = self.expressions(expression, key="except_", flat=True)
3369        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3370        replace = self.expressions(expression, key="replace", flat=True)
3371        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3372        rename = self.expressions(expression, key="rename", flat=True)
3373        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3374        return f"*{except_}{replace}{rename}"
3375
3376    def parameter_sql(self, expression: exp.Parameter) -> str:
3377        this = self.sql(expression, "this")
3378        return f"{self.PARAMETER_TOKEN}{this}"
3379
3380    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3381        this = self.sql(expression, "this")
3382        kind = expression.text("kind")
3383        if kind:
3384            kind = f"{kind}."
3385        return f"@@{kind}{this}"
3386
3387    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3388        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3389
3390    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3391        alias = self.sql(expression, "alias")
3392        alias = f"{sep}{alias}" if alias else ""
3393        sample = self.sql(expression, "sample")
3394        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3395            alias = f"{sample}{alias}"
3396
3397            # Set to None so it's not generated again by self.query_modifiers()
3398            expression.set("sample", None)
3399
3400        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3401        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3402        return self.prepend_ctes(expression, sql)
3403
3404    def qualify_sql(self, expression: exp.Qualify) -> str:
3405        this = self.indent(self.sql(expression, "this"))
3406        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3407
3408    def unnest_sql(self, expression: exp.Unnest) -> str:
3409        args = self.expressions(expression, flat=True)
3410
3411        alias = expression.args.get("alias")
3412        offset = expression.args.get("offset")
3413
3414        if self.UNNEST_WITH_ORDINALITY:
3415            if alias and isinstance(offset, exp.Expr):
3416                alias.append("columns", offset)
3417                expression.set("offset", None)
3418
3419        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3420            columns = alias.columns
3421            alias = self.sql(columns[0]) if columns else ""
3422        else:
3423            alias = self.sql(alias)
3424
3425        alias = f" AS {alias}" if alias else alias
3426        if self.UNNEST_WITH_ORDINALITY:
3427            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3428        else:
3429            if isinstance(offset, exp.Expr):
3430                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3431            elif offset:
3432                suffix = f"{alias} WITH OFFSET"
3433            else:
3434                suffix = alias
3435
3436        return f"UNNEST({args}){suffix}"
3437
3438    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3439        return ""
3440
3441    def where_sql(self, expression: exp.Where) -> str:
3442        this = self.indent(self.sql(expression, "this"))
3443        return f"{self.seg('WHERE')}{self.sep()}{this}"
3444
3445    def window_sql(self, expression: exp.Window) -> str:
3446        this = self.sql(expression, "this")
3447        partition = self.partition_by_sql(expression)
3448        order = expression.args.get("order")
3449        order = self.order_sql(order, flat=True) if order else ""
3450        spec = self.sql(expression, "spec")
3451        alias = self.sql(expression, "alias")
3452        over = self.sql(expression, "over") or "OVER"
3453
3454        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3455
3456        first = expression.args.get("first")
3457        if first is None:
3458            first = ""
3459        else:
3460            first = "FIRST" if first else "LAST"
3461
3462        if not partition and not order and not spec and alias:
3463            return f"{this} {alias}"
3464
3465        args = self.format_args(
3466            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3467        )
3468        return f"{this} ({args})"
3469
3470    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3471        partition = self.expressions(expression, key="partition_by", flat=True)
3472        return f"PARTITION BY {partition}" if partition else ""
3473
3474    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3475        kind = self.sql(expression, "kind")
3476        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3477        end = (
3478            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3479            or "CURRENT ROW"
3480        )
3481
3482        window_spec = f"{kind} BETWEEN {start} AND {end}"
3483
3484        exclude = self.sql(expression, "exclude")
3485        if exclude:
3486            if self.SUPPORTS_WINDOW_EXCLUDE:
3487                window_spec += f" EXCLUDE {exclude}"
3488            else:
3489                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3490
3491        return window_spec
3492
3493    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3494        this = self.sql(expression, "this")
3495        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3496        return f"{this} WITHIN GROUP ({expression_sql})"
3497
3498    def between_sql(self, expression: exp.Between) -> str:
3499        this = self.sql(expression, "this")
3500        low = self.sql(expression, "low")
3501        high = self.sql(expression, "high")
3502        symmetric = expression.args.get("symmetric")
3503
3504        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3505            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3506
3507        flag = (
3508            " SYMMETRIC"
3509            if symmetric
3510            else " ASYMMETRIC"
3511            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3512            else ""  # silently drop ASYMMETRIC – semantics identical
3513        )
3514        return f"{this} BETWEEN{flag} {low} AND {high}"
3515
3516    def bracket_offset_expressions(
3517        self, expression: exp.Bracket, index_offset: int | None = None
3518    ) -> list[exp.Expr]:
3519        if expression.args.get("json_access"):
3520            return expression.expressions
3521
3522        return apply_index_offset(
3523            expression.this,
3524            expression.expressions,
3525            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3526            dialect=self.dialect,
3527        )
3528
3529    def bracket_sql(self, expression: exp.Bracket) -> str:
3530        expressions = self.bracket_offset_expressions(expression)
3531        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3532        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3533
3534    def all_sql(self, expression: exp.All) -> str:
3535        this = self.sql(expression, "this")
3536        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3537            this = self.wrap(this)
3538        return f"ALL {this}"
3539
3540    def any_sql(self, expression: exp.Any) -> str:
3541        this = self.sql(expression, "this")
3542        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3543            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3544                this = self.wrap(this)
3545            return f"ANY{this}"
3546        return f"ANY {this}"
3547
3548    def exists_sql(self, expression: exp.Exists) -> str:
3549        return f"EXISTS{self.wrap(expression)}"
3550
3551    def case_sql(self, expression: exp.Case) -> str:
3552        this = self.sql(expression, "this")
3553        statements = [f"CASE {this}" if this else "CASE"]
3554
3555        for e in expression.args["ifs"]:
3556            statements.append(f"WHEN {self.sql(e, 'this')}")
3557            statements.append(f"THEN {self.sql(e, 'true')}")
3558
3559        default = self.sql(expression, "default")
3560
3561        if default:
3562            statements.append(f"ELSE {default}")
3563
3564        statements.append("END")
3565
3566        if self.pretty and self.too_wide(statements):
3567            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3568
3569        return " ".join(statements)
3570
3571    def constraint_sql(self, expression: exp.Constraint) -> str:
3572        this = self.sql(expression, "this")
3573        expressions = self.expressions(expression, flat=True)
3574        return f"CONSTRAINT {this} {expressions}"
3575
3576    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3577        order = expression.args.get("order")
3578        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3579        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3580
3581    def extract_sql(self, expression: exp.Extract) -> str:
3582        import sqlglot.dialects.dialect
3583
3584        this = (
3585            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3586            if self.NORMALIZE_EXTRACT_DATE_PARTS
3587            else expression.this
3588        )
3589        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3590        expression_sql = self.sql(expression, "expression")
3591
3592        return f"EXTRACT({this_sql} FROM {expression_sql})"
3593
3594    def trim_sql(self, expression: exp.Trim) -> str:
3595        trim_type = self.sql(expression, "position")
3596
3597        if trim_type == "LEADING":
3598            func_name = "LTRIM"
3599        elif trim_type == "TRAILING":
3600            func_name = "RTRIM"
3601        else:
3602            func_name = "TRIM"
3603
3604        return self.func(func_name, expression.this, expression.expression)
3605
3606    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3607        args = expression.expressions
3608        if isinstance(expression, exp.ConcatWs):
3609            args = args[1:]  # Skip the delimiter
3610
3611        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3612            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3613
3614        concat_coalesce = (
3615            self.dialect.CONCAT_WS_COALESCE
3616            if isinstance(expression, exp.ConcatWs)
3617            else self.dialect.CONCAT_COALESCE
3618        )
3619
3620        if not concat_coalesce and expression.args.get("coalesce"):
3621
3622            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3623                if not e.type:
3624                    import sqlglot.optimizer.annotate_types
3625
3626                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3627
3628                if e.is_string or e.is_type(exp.DType.ARRAY):
3629                    return e
3630
3631                return exp.func("coalesce", e, exp.Literal.string(""))
3632
3633            args = [_wrap_with_coalesce(e) for e in args]
3634
3635        return args
3636
3637    def concat_sql(self, expression: exp.Concat) -> str:
3638        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3639            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3640            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3641            # instead of coalescing them to empty string.
3642            import sqlglot.dialects.dialect
3643
3644            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3645
3646        expressions = self.convert_concat_args(expression)
3647
3648        # Some dialects don't allow a single-argument CONCAT call
3649        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3650            return self.sql(expressions[0])
3651
3652        return self.func("CONCAT", *expressions)
3653
3654    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3655        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3656            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3657            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3658            all_args = expression.expressions
3659            expression.set("coalesce", True)
3660            return self.sql(
3661                exp.case()
3662                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3663                .else_(expression)
3664            )
3665
3666        return self.func(
3667            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3668        )
3669
3670    def check_sql(self, expression: exp.Check) -> str:
3671        this = self.sql(expression, key="this")
3672        return f"CHECK ({this})"
3673
3674    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3675        expressions = self.expressions(expression, flat=True)
3676        expressions = f" ({expressions})" if expressions else ""
3677        reference = self.sql(expression, "reference")
3678        reference = f" {reference}" if reference else ""
3679        delete = self.sql(expression, "delete")
3680        delete = f" ON DELETE {delete}" if delete else ""
3681        update = self.sql(expression, "update")
3682        update = f" ON UPDATE {update}" if update else ""
3683        options = self.expressions(expression, key="options", flat=True, sep=" ")
3684        options = f" {options}" if options else ""
3685        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3686
3687    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3688        this = self.sql(expression, "this")
3689        this = f" {this}" if this else ""
3690        expressions = self.expressions(expression, flat=True)
3691        include = self.sql(expression, "include")
3692        options = self.expressions(expression, key="options", flat=True, sep=" ")
3693        options = f" {options}" if options else ""
3694        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3695
3696    def if_sql(self, expression: exp.If) -> str:
3697        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3698
3699    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3700        if self.MATCH_AGAINST_TABLE_PREFIX:
3701            expressions = []
3702            for expr in expression.expressions:
3703                if isinstance(expr, exp.Table):
3704                    expressions.append(f"TABLE {self.sql(expr)}")
3705                else:
3706                    expressions.append(expr)
3707        else:
3708            expressions = expression.expressions
3709
3710        modifier = expression.args.get("modifier")
3711        modifier = f" {modifier}" if modifier else ""
3712        return (
3713            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3714        )
3715
3716    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3717        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3718
3719    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3720        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3721
3722        if expression.args.get("escape"):
3723            path = self.escape_str(path)
3724
3725        if self.QUOTE_JSON_PATH:
3726            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3727
3728        return path
3729
3730    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3731        if isinstance(expression, exp.JSONPathPart):
3732            transform = self.TRANSFORMS.get(expression.__class__)
3733            if not callable(transform):
3734                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3735                return ""
3736
3737            return transform(self, expression)
3738
3739        if isinstance(expression, int):
3740            return str(expression)
3741
3742        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3743            escaped = expression.replace("'", "\\'")
3744            escaped = f"\\'{expression}\\'"
3745        else:
3746            escaped = expression.replace('"', '\\"')
3747            escaped = f'"{escaped}"'
3748
3749        return escaped
3750
3751    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3752        return f"{self.sql(expression, 'this')} FORMAT JSON"
3753
3754    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3755        # Output the Teradata column FORMAT override.
3756        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3757        this = self.sql(expression, "this")
3758        fmt = self.sql(expression, "format")
3759        return f"{this} (FORMAT {fmt})"
3760
3761    def _jsonobject_sql(
3762        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3763    ) -> str:
3764        null_handling = expression.args.get("null_handling")
3765        null_handling = f" {null_handling}" if null_handling else ""
3766
3767        unique_keys = expression.args.get("unique_keys")
3768        if unique_keys is not None:
3769            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3770        else:
3771            unique_keys = ""
3772
3773        return_type = self.sql(expression, "return_type")
3774        return_type = f" RETURNING {return_type}" if return_type else ""
3775        encoding = self.sql(expression, "encoding")
3776        encoding = f" ENCODING {encoding}" if encoding else ""
3777
3778        if not name:
3779            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3780
3781        return self.func(
3782            name,
3783            *expression.expressions,
3784            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3785        )
3786
3787    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3788        null_handling = expression.args.get("null_handling")
3789        null_handling = f" {null_handling}" if null_handling else ""
3790        return_type = self.sql(expression, "return_type")
3791        return_type = f" RETURNING {return_type}" if return_type else ""
3792        strict = " STRICT" if expression.args.get("strict") else ""
3793        return self.func(
3794            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3795        )
3796
3797    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3798        this = self.sql(expression, "this")
3799        order = self.sql(expression, "order")
3800        null_handling = expression.args.get("null_handling")
3801        null_handling = f" {null_handling}" if null_handling else ""
3802        return_type = self.sql(expression, "return_type")
3803        return_type = f" RETURNING {return_type}" if return_type else ""
3804        strict = " STRICT" if expression.args.get("strict") else ""
3805        return self.func(
3806            "JSON_ARRAYAGG",
3807            this,
3808            suffix=f"{order}{null_handling}{return_type}{strict})",
3809        )
3810
3811    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3812        path = self.sql(expression, "path")
3813        path = f" PATH {path}" if path else ""
3814        nested_schema = self.sql(expression, "nested_schema")
3815
3816        if nested_schema:
3817            return f"NESTED{path} {nested_schema}"
3818
3819        this = self.sql(expression, "this")
3820        kind = self.sql(expression, "kind")
3821        kind = f" {kind}" if kind else ""
3822        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3823
3824        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3825        return f"{this}{kind}{format_json}{path}{ordinality}"
3826
3827    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3828        return self.func("COLUMNS", *expression.expressions)
3829
3830    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3831        this = self.sql(expression, "this")
3832        path = self.sql(expression, "path")
3833        path = f", {path}" if path else ""
3834        error_handling = expression.args.get("error_handling")
3835        error_handling = f" {error_handling}" if error_handling else ""
3836        empty_handling = expression.args.get("empty_handling")
3837        empty_handling = f" {empty_handling}" if empty_handling else ""
3838        schema = self.sql(expression, "schema")
3839        return self.func(
3840            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3841        )
3842
3843    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3844        this = self.sql(expression, "this")
3845        kind = self.sql(expression, "kind")
3846        path = self.sql(expression, "path")
3847        path = f" {path}" if path else ""
3848        as_json = " AS JSON" if expression.args.get("as_json") else ""
3849        return f"{this} {kind}{path}{as_json}"
3850
3851    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3852        this = self.sql(expression, "this")
3853        path = self.sql(expression, "path")
3854        path = f", {path}" if path else ""
3855        expressions = self.expressions(expression)
3856        with_ = (
3857            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3858            if expressions
3859            else ""
3860        )
3861        return f"OPENJSON({this}{path}){with_}"
3862
3863    def in_sql(self, expression: exp.In) -> str:
3864        query = expression.args.get("query")
3865        unnest = expression.args.get("unnest")
3866        field = expression.args.get("field")
3867        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3868
3869        if query:
3870            in_sql = self.sql(query)
3871        elif unnest:
3872            in_sql = self.in_unnest_op(unnest)
3873        elif field:
3874            in_sql = self.sql(field)
3875        else:
3876            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3877
3878        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3879
3880    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3881        return f"(SELECT {self.sql(unnest)})"
3882
3883    def interval_sql(self, expression: exp.Interval) -> str:
3884        unit_expression = expression.args.get("unit")
3885        unit = self.sql(unit_expression) if unit_expression else ""
3886        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3887            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3888        unit = f" {unit}" if unit else ""
3889
3890        if self.SINGLE_STRING_INTERVAL:
3891            this = expression.this.name if expression.this else ""
3892            if this:
3893                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3894                    return f"INTERVAL '{this}'{unit}"
3895                return f"INTERVAL '{this}{unit}'"
3896            return f"INTERVAL{unit}"
3897
3898        this = self.sql(expression, "this")
3899        if this:
3900            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3901            this = f" {this}" if unwrapped else f" ({this})"
3902
3903        return f"INTERVAL{this}{unit}"
3904
3905    def return_sql(self, expression: exp.Return) -> str:
3906        return f"RETURN {self.sql(expression, 'this')}"
3907
3908    def reference_sql(self, expression: exp.Reference) -> str:
3909        this = self.sql(expression, "this")
3910        expressions = self.expressions(expression, flat=True)
3911        expressions = f"({expressions})" if expressions else ""
3912        options = self.expressions(expression, key="options", flat=True, sep=" ")
3913        options = f" {options}" if options else ""
3914        return f"REFERENCES {this}{expressions}{options}"
3915
3916    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3917        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3918        parent = expression.parent
3919        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3920
3921        return self.func(
3922            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3923        )
3924
3925    def paren_sql(self, expression: exp.Paren) -> str:
3926        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3927        return f"({sql}{self.seg(')', sep='')}"
3928
3929    def neg_sql(self, expression: exp.Neg) -> str:
3930        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3931        this_sql = self.sql(expression, "this")
3932        sep = " " if this_sql[0] == "-" else ""
3933        return f"-{sep}{this_sql}"
3934
3935    def not_sql(self, expression: exp.Not) -> str:
3936        return f"NOT {self.sql(expression, 'this')}"
3937
3938    def alias_sql(self, expression: exp.Alias) -> str:
3939        alias = self.sql(expression, "alias")
3940        alias = f" AS {alias}" if alias else ""
3941        return f"{self.sql(expression, 'this')}{alias}"
3942
3943    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3944        alias = expression.args["alias"]
3945
3946        parent = expression.parent
3947        pivot = parent and parent.parent
3948
3949        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3950            identifier_alias = isinstance(alias, exp.Identifier)
3951            literal_alias = isinstance(alias, exp.Literal)
3952
3953            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3954                alias.replace(exp.Literal.string(alias.output_name))
3955            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3956                alias.replace(exp.to_identifier(alias.output_name))
3957
3958        return self.alias_sql(expression)
3959
3960    def aliases_sql(self, expression: exp.Aliases) -> str:
3961        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3962
3963    def atindex_sql(self, expression: exp.AtIndex) -> str:
3964        this = self.sql(expression, "this")
3965        index = self.sql(expression, "expression")
3966        return f"{this} AT {index}"
3967
3968    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3969        this = self.sql(expression, "this")
3970        zone = self.sql(expression, "zone")
3971        return f"{this} AT TIME ZONE {zone}"
3972
3973    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3974        this = self.sql(expression, "this")
3975        zone = self.sql(expression, "zone")
3976        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3977
3978    def add_sql(self, expression: exp.Add) -> str:
3979        return self.binary(expression, "+")
3980
3981    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
3982        return self.connector_sql(expression, "AND", stack)
3983
3984    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
3985        return self.connector_sql(expression, "OR", stack)
3986
3987    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
3988        return self.connector_sql(expression, "XOR", stack)
3989
3990    def connector_sql(
3991        self,
3992        expression: exp.Connector,
3993        op: str,
3994        stack: list[str | exp.Expr] | None = None,
3995    ) -> str:
3996        if stack is not None:
3997            if expression.expressions:
3998                stack.append(self.expressions(expression, sep=f" {op} "))
3999            else:
4000                stack.append(expression.right)
4001                if expression.comments and self.comments:
4002                    op = self.maybe_comment(op, comments=expression.comments)
4003                stack.extend((op, expression.left))
4004            return op
4005
4006        stack = [expression]
4007        sqls: list[str] = []
4008        ops = set()
4009
4010        while stack:
4011            node = stack.pop()
4012            if isinstance(node, exp.Connector):
4013                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4014            else:
4015                sql = self.sql(node)
4016                if sqls and sqls[-1] in ops:
4017                    sqls[-1] += f" {sql}"
4018                else:
4019                    sqls.append(sql)
4020
4021        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4022        return sep.join(sqls)
4023
4024    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4025        return self.binary(expression, "&")
4026
4027    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4028        return self.binary(expression, "<<")
4029
4030    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4031        return f"~{self.sql(expression, 'this')}"
4032
4033    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4034        return self.binary(expression, "|")
4035
4036    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4037        return self.binary(expression, ">>")
4038
4039    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4040        return self.binary(expression, "^")
4041
4042    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4043        format_sql = self.sql(expression, "format")
4044        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4045        to_sql = self.sql(expression, "to")
4046        to_sql = f" {to_sql}" if to_sql else ""
4047        action = self.sql(expression, "action")
4048        action = f" {action}" if action else ""
4049        default = self.sql(expression, "default")
4050        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4051        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4052
4053    # Base implementation that excludes safe, zone, and target_type metadata args
4054    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4055        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4056
4057    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4058        zone = self.sql(expression, "this")
4059        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4060
4061    def collate_sql(self, expression: exp.Collate) -> str:
4062        if self.COLLATE_IS_FUNC:
4063            return self.function_fallback_sql(expression)
4064        return self.binary(expression, "COLLATE")
4065
4066    def command_sql(self, expression: exp.Command) -> str:
4067        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4068
4069    def comment_sql(self, expression: exp.Comment) -> str:
4070        this = self.sql(expression, "this")
4071        kind = expression.args["kind"]
4072        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4073        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4074        expression_sql = self.sql(expression, "expression")
4075        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4076
4077    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4078        this = self.sql(expression, "this")
4079        delete = " DELETE" if expression.args.get("delete") else ""
4080        recompress = self.sql(expression, "recompress")
4081        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4082        to_disk = self.sql(expression, "to_disk")
4083        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4084        to_volume = self.sql(expression, "to_volume")
4085        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4086        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4087
4088    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4089        where = self.sql(expression, "where")
4090        group = self.sql(expression, "group")
4091        aggregates = self.expressions(expression, key="aggregates")
4092        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4093
4094        if not (where or group or aggregates) and len(expression.expressions) == 1:
4095            return f"TTL {self.expressions(expression, flat=True)}"
4096
4097        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4098
4099    def transaction_sql(self, expression: exp.Transaction) -> str:
4100        modes = self.expressions(expression, key="modes")
4101        modes = f" {modes}" if modes else ""
4102        return f"BEGIN{modes}"
4103
4104    def commit_sql(self, expression: exp.Commit) -> str:
4105        chain = expression.args.get("chain")
4106        if chain is not None:
4107            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4108
4109        return f"COMMIT{chain or ''}"
4110
4111    def rollback_sql(self, expression: exp.Rollback) -> str:
4112        savepoint = expression.args.get("savepoint")
4113        savepoint = f" TO {savepoint}" if savepoint else ""
4114        return f"ROLLBACK{savepoint}"
4115
4116    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4117        this = self.sql(expression, "this")
4118
4119        dtype = self.sql(expression, "dtype")
4120        if dtype:
4121            collate = self.sql(expression, "collate")
4122            collate = f" COLLATE {collate}" if collate else ""
4123            using = self.sql(expression, "using")
4124            using = f" USING {using}" if using else ""
4125            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4126            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
4127
4128        default = self.sql(expression, "default")
4129        if default:
4130            return f"ALTER COLUMN {this} SET DEFAULT {default}"
4131
4132        comment = self.sql(expression, "comment")
4133        if comment:
4134            return f"ALTER COLUMN {this} COMMENT {comment}"
4135
4136        visible = expression.args.get("visible")
4137        if visible:
4138            return f"ALTER COLUMN {this} SET {visible}"
4139
4140        allow_null = expression.args.get("allow_null")
4141        drop = expression.args.get("drop")
4142
4143        if not drop and not allow_null:
4144            self.unsupported("Unsupported ALTER COLUMN syntax")
4145
4146        if allow_null is not None:
4147            keyword = "DROP" if drop else "SET"
4148            return f"ALTER COLUMN {this} {keyword} NOT NULL"
4149
4150        return f"ALTER COLUMN {this} DROP DEFAULT"
4151
4152    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4153        this = self.sql(expression, "this")
4154        rename_from = self.sql(expression, "rename_from")
4155        if rename_from:
4156            if not self.SUPPORTS_CHANGE_COLUMN:
4157                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4158            return f"CHANGE COLUMN {rename_from} {this}"
4159        if not self.SUPPORTS_MODIFY_COLUMN:
4160            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4161        return f"MODIFY COLUMN {this}"
4162
4163    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4164        this = self.sql(expression, "this")
4165
4166        visible = expression.args.get("visible")
4167        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4168
4169        return f"ALTER INDEX {this} {visible_sql}"
4170
4171    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4172        this = self.sql(expression, "this")
4173        if not isinstance(expression.this, exp.Var):
4174            this = f"KEY DISTKEY {this}"
4175        return f"ALTER DISTSTYLE {this}"
4176
4177    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4178        compound = " COMPOUND" if expression.args.get("compound") else ""
4179        this = self.sql(expression, "this")
4180        expressions = self.expressions(expression, flat=True)
4181        expressions = f"({expressions})" if expressions else ""
4182        return f"ALTER{compound} SORTKEY {this or expressions}"
4183
4184    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4185        if not self.RENAME_TABLE_WITH_DB:
4186            # Remove db from tables
4187            expression = expression.transform(
4188                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4189            ).assert_is(exp.AlterRename)
4190        this = self.sql(expression, "this")
4191        to_kw = " TO" if include_to else ""
4192        return f"RENAME{to_kw} {this}"
4193
4194    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4195        exists = " IF EXISTS" if expression.args.get("exists") else ""
4196        old_column = self.sql(expression, "this")
4197        new_column = self.sql(expression, "to")
4198        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4199
4200    def alterset_sql(self, expression: exp.AlterSet) -> str:
4201        exprs = self.expressions(expression, flat=True)
4202        if self.ALTER_SET_WRAPPED:
4203            exprs = f"({exprs})"
4204
4205        return f"SET {exprs}"
4206
4207    def alter_sql(self, expression: exp.Alter) -> str:
4208        actions = expression.args["actions"]
4209
4210        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4211            actions[0], exp.ColumnDef
4212        ):
4213            actions_sql = self.expressions(expression, key="actions", flat=True)
4214            actions_sql = f"ADD {actions_sql}"
4215        else:
4216            actions_list = []
4217            for action in actions:
4218                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4219                    action_sql = self.add_column_sql(action)
4220                else:
4221                    action_sql = self.sql(action)
4222                    if isinstance(action, exp.Query):
4223                        action_sql = f"AS {action_sql}"
4224
4225                actions_list.append(action_sql)
4226
4227            actions_sql = self.format_args(*actions_list).lstrip("\n")
4228
4229        iceberg = (
4230            "ICEBERG "
4231            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4232            else ""
4233        )
4234        exists = " IF EXISTS" if expression.args.get("exists") else ""
4235        on_cluster = self.sql(expression, "cluster")
4236        on_cluster = f" {on_cluster}" if on_cluster else ""
4237        only = " ONLY" if expression.args.get("only") else ""
4238        options = self.expressions(expression, key="options")
4239        options = f", {options}" if options else ""
4240        kind = self.sql(expression, "kind")
4241        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4242        check = " WITH CHECK" if expression.args.get("check") else ""
4243        cascade = (
4244            " CASCADE"
4245            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4246            else ""
4247        )
4248        this = self.sql(expression, "this")
4249        this = f" {this}" if this else ""
4250
4251        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4252
4253    def altersession_sql(self, expression: exp.AlterSession) -> str:
4254        items_sql = self.expressions(expression, flat=True)
4255        keyword = "UNSET" if expression.args.get("unset") else "SET"
4256        return f"{keyword} {items_sql}"
4257
4258    def add_column_sql(self, expression: exp.Expr) -> str:
4259        sql = self.sql(expression)
4260        if isinstance(expression, exp.Schema):
4261            column_text = " COLUMNS"
4262        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4263            column_text = " COLUMN"
4264        else:
4265            column_text = ""
4266
4267        return f"ADD{column_text} {sql}"
4268
4269    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4270        expressions = self.expressions(expression)
4271        exists = " IF EXISTS " if expression.args.get("exists") else " "
4272        return f"DROP{exists}{expressions}"
4273
4274    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4275        return "DROP PRIMARY KEY"
4276
4277    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4278        return f"ADD {self.expressions(expression, indent=False)}"
4279
4280    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4281        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4282        location = self.sql(expression, "location")
4283        location = f" {location}" if location else ""
4284        return f"ADD {exists}{self.sql(expression.this)}{location}"
4285
4286    def distinct_sql(self, expression: exp.Distinct) -> str:
4287        this = self.expressions(expression, flat=True)
4288
4289        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4290            case = exp.case()
4291            for arg in expression.expressions:
4292                case = case.when(arg.is_(exp.null()), exp.null())
4293            this = self.sql(case.else_(f"({this})"))
4294
4295        this = f" {this}" if this else ""
4296
4297        on = self.sql(expression, "on")
4298        on = f" ON {on}" if on else ""
4299        return f"DISTINCT{this}{on}"
4300
4301    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4302        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4303
4304    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4305        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4306
4307    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4308        this_sql = self.sql(expression, "this")
4309        expression_sql = self.sql(expression, "expression")
4310        kind = "MAX" if expression.args.get("max") else "MIN"
4311        return f"{this_sql} HAVING {kind} {expression_sql}"
4312
4313    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4314        return self.sql(
4315            exp.Cast(
4316                this=exp.Div(this=expression.this, expression=expression.expression),
4317                to=exp.DataType(this=exp.DType.INT),
4318            )
4319        )
4320
4321    def dpipe_sql(self, expression: exp.DPipe) -> str:
4322        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4323            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4324        return self.binary(expression, "||")
4325
4326    def div_sql(self, expression: exp.Div) -> str:
4327        l, r = expression.left, expression.right
4328
4329        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4330            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4331
4332        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4333            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4334                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4335
4336        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4337            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4338                return self.sql(
4339                    exp.cast(
4340                        l / r,
4341                        to=exp.DType.BIGINT,
4342                    )
4343                )
4344
4345        return self.binary(expression, "/")
4346
4347    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4348        n = exp._wrap(expression.this, exp.Binary)
4349        d = exp._wrap(expression.expression, exp.Binary)
4350        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4351
4352    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4353        return self.binary(expression, "OVERLAPS")
4354
4355    def distance_sql(self, expression: exp.Distance) -> str:
4356        return self.binary(expression, "<->")
4357
4358    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4359        return self.binary(expression, "<<->>")
4360
4361    def dot_sql(self, expression: exp.Dot) -> str:
4362        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4363
4364    def eq_sql(self, expression: exp.EQ) -> str:
4365        return self.binary(expression, "=")
4366
4367    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4368        return self.binary(expression, ":=")
4369
4370    def escape_sql(self, expression: exp.Escape) -> str:
4371        this = expression.this
4372        if (
4373            isinstance(this, (exp.Like, exp.ILike))
4374            and isinstance(this.expression, (exp.All, exp.Any))
4375            and not self.SUPPORTS_LIKE_QUANTIFIERS
4376        ):
4377            return self._like_sql(this, escape=expression)
4378        return self.binary(expression, "ESCAPE")
4379
4380    def glob_sql(self, expression: exp.Glob) -> str:
4381        return self.binary(expression, "GLOB")
4382
4383    def gt_sql(self, expression: exp.GT) -> str:
4384        return self.binary(expression, ">")
4385
4386    def gte_sql(self, expression: exp.GTE) -> str:
4387        return self.binary(expression, ">=")
4388
4389    def is_sql(self, expression: exp.Is) -> str:
4390        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4391            return self.sql(
4392                expression.this if expression.expression.this else exp.not_(expression.this)
4393            )
4394        return self.binary(expression, "IS")
4395
4396    def _like_sql(
4397        self,
4398        expression: exp.Like | exp.ILike,
4399        escape: exp.Escape | None = None,
4400    ) -> str:
4401        this = expression.this
4402        rhs = expression.expression
4403
4404        if isinstance(expression, exp.Like):
4405            exp_class: type[exp.Like | exp.ILike] = exp.Like
4406            op = "LIKE"
4407        else:
4408            exp_class = exp.ILike
4409            op = "ILIKE"
4410
4411        if expression.args.get("negate"):
4412            op = f"NOT {op}"
4413
4414        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4415            exprs = rhs.this.unnest()
4416
4417            if isinstance(exprs, exp.Tuple):
4418                exprs = exprs.expressions
4419            else:
4420                exprs = [exprs]
4421
4422            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4423
4424            def _make_like(expr: exp.Expression) -> exp.Expression:
4425                like: exp.Expression = exp_class(
4426                    this=this, expression=expr, negate=expression.args.get("negate")
4427                )
4428                if escape:
4429                    like = exp.Escape(this=like, expression=escape.expression.copy())
4430                return like
4431
4432            like_expr: exp.Expr = _make_like(exprs[0])
4433            for expr in exprs[1:]:
4434                like_expr = connective(like_expr, _make_like(expr), copy=False)
4435
4436            parent = escape.parent if escape else expression.parent
4437            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4438                parent, exp.Condition
4439            ):
4440                like_expr = exp.paren(like_expr, copy=False)
4441
4442            return self.sql(like_expr)
4443
4444        return self.binary(expression, op)
4445
4446    def like_sql(self, expression: exp.Like) -> str:
4447        return self._like_sql(expression)
4448
4449    def ilike_sql(self, expression: exp.ILike) -> str:
4450        return self._like_sql(expression)
4451
4452    def match_sql(self, expression: exp.Match) -> str:
4453        return self.binary(expression, "MATCH")
4454
4455    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4456        return self.binary(expression, "SIMILAR TO")
4457
4458    def lt_sql(self, expression: exp.LT) -> str:
4459        return self.binary(expression, "<")
4460
4461    def lte_sql(self, expression: exp.LTE) -> str:
4462        return self.binary(expression, "<=")
4463
4464    def mod_sql(self, expression: exp.Mod) -> str:
4465        return self.binary(expression, "%")
4466
4467    def mul_sql(self, expression: exp.Mul) -> str:
4468        return self.binary(expression, "*")
4469
4470    def neq_sql(self, expression: exp.NEQ) -> str:
4471        return self.binary(expression, "<>")
4472
4473    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4474        return self.binary(expression, "IS NOT DISTINCT FROM")
4475
4476    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4477        return self.binary(expression, "IS DISTINCT FROM")
4478
4479    def sub_sql(self, expression: exp.Sub) -> str:
4480        return self.binary(expression, "-")
4481
4482    def trycast_sql(self, expression: exp.TryCast) -> str:
4483        return self.cast_sql(expression, safe_prefix="TRY_")
4484
4485    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4486        return self.cast_sql(expression)
4487
4488    def try_sql(self, expression: exp.Try) -> str:
4489        if not self.TRY_SUPPORTED:
4490            self.unsupported("Unsupported TRY function")
4491            return self.sql(expression, "this")
4492
4493        return self.func("TRY", expression.this)
4494
4495    def log_sql(self, expression: exp.Log) -> str:
4496        this = expression.this
4497        expr = expression.expression
4498
4499        if self.dialect.LOG_BASE_FIRST is False:
4500            this, expr = expr, this
4501        elif self.dialect.LOG_BASE_FIRST is None and expr:
4502            if this.name in ("2", "10"):
4503                return self.func(f"LOG{this.name}", expr)
4504
4505            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4506
4507        return self.func("LOG", this, expr)
4508
4509    def use_sql(self, expression: exp.Use) -> str:
4510        kind = self.sql(expression, "kind")
4511        kind = f" {kind}" if kind else ""
4512        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4513        this = f" {this}" if this else ""
4514        return f"USE{kind}{this}"
4515
4516    def binary(self, expression: exp.Binary, op: str) -> str:
4517        sqls: list[str] = []
4518        stack: list[None | str | exp.Expr] = [expression]
4519        binary_type = type(expression)
4520
4521        while stack:
4522            node = stack.pop()
4523
4524            if type(node) is binary_type:
4525                op_func = node.args.get("operator")
4526                if op_func:
4527                    op = f"OPERATOR({self.sql(op_func)})"
4528
4529                stack.append(node.args.get("expression"))
4530                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4531                stack.append(node.args.get("this"))
4532            else:
4533                sqls.append(self.sql(node))
4534
4535        return "".join(sqls)
4536
4537    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4538        to_clause = self.sql(expression, "to")
4539        if to_clause:
4540            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4541
4542        return self.function_fallback_sql(expression)
4543
4544    def function_fallback_sql(self, expression: exp.Func) -> str:
4545        args = []
4546
4547        for key in expression.arg_types:
4548            arg_value = expression.args.get(key)
4549
4550            if isinstance(arg_value, list):
4551                for value in arg_value:
4552                    args.append(value)
4553            elif arg_value is not None:
4554                args.append(arg_value)
4555
4556        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4557            name = expression.meta_get("name") or expression.sql_name()
4558        else:
4559            name = expression.sql_name()
4560
4561        return self.func(name, *args)
4562
4563    def func(
4564        self,
4565        name: str,
4566        *args: t.Any,
4567        prefix: str = "(",
4568        suffix: str = ")",
4569        normalize: bool = True,
4570    ) -> str:
4571        name = self.normalize_func(name) if normalize else name
4572        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4573
4574    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4575        arg_sqls = tuple(
4576            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4577        )
4578        if self.pretty and self.too_wide(arg_sqls):
4579            return self.indent(
4580                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4581            )
4582        return sep.join(arg_sqls)
4583
4584    def too_wide(self, args: t.Iterable) -> bool:
4585        return sum(len(arg) for arg in args) > self.max_text_width
4586
4587    def format_time(
4588        self,
4589        expression: exp.Expr,
4590        inverse_time_mapping: dict[str, str] | None = None,
4591        inverse_time_trie: dict | None = None,
4592    ) -> str | None:
4593        return format_time(
4594            self.sql(expression, "format"),
4595            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4596            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4597        )
4598
4599    def expressions(
4600        self,
4601        expression: exp.Expr | None = None,
4602        key: str | None = None,
4603        sqls: t.Collection[str | exp.Expr] | None = None,
4604        flat: bool = False,
4605        indent: bool = True,
4606        skip_first: bool = False,
4607        skip_last: bool = False,
4608        sep: str = ", ",
4609        prefix: str = "",
4610        dynamic: bool = False,
4611        new_line: bool = False,
4612    ) -> str:
4613        expressions = expression.args.get(key or "expressions") if expression else sqls
4614
4615        if not expressions:
4616            return ""
4617
4618        if flat:
4619            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4620
4621        num_sqls = len(expressions)
4622        result_sqls = []
4623
4624        for i, e in enumerate(expressions):
4625            sql = self.sql(e, comment=False)
4626            if not sql:
4627                continue
4628
4629            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4630
4631            if self.pretty:
4632                if self.leading_comma:
4633                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4634                else:
4635                    result_sqls.append(
4636                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4637                    )
4638            else:
4639                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4640
4641        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4642            if new_line:
4643                result_sqls.insert(0, "")
4644                result_sqls.append("")
4645            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4646        else:
4647            result_sql = "".join(result_sqls)
4648
4649        return (
4650            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4651            if indent
4652            else result_sql
4653        )
4654
4655    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4656        flat = flat or isinstance(expression.parent, exp.Properties)
4657        expressions_sql = self.expressions(expression, flat=flat)
4658        if flat:
4659            return f"{op} {expressions_sql}"
4660        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4661
4662    def naked_property(self, expression: exp.Property) -> str:
4663        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4664        if not property_name:
4665            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4666        return f"{property_name} {self.sql(expression, 'this')}"
4667
4668    def tag_sql(self, expression: exp.Tag) -> str:
4669        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4670
4671    def token_sql(self, token_type: TokenType) -> str:
4672        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4673
4674    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4675        this = self.sql(expression, "this")
4676        expressions = self.no_identify(self.expressions, expression)
4677        expressions = (
4678            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4679        )
4680        return f"{this}{expressions}" if expressions.strip() != "" else this
4681
4682    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4683        return self.expressions(expression, flat=True)
4684
4685    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4686        params = self.no_identify(self.expressions, expression, flat=True)
4687        body = self.sql(expression, "this")
4688        prefix = "TABLE " if expression.args.get("is_table") else ""
4689        return f"({params}) AS {prefix}{body}"
4690
4691    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4692        this = self.sql(expression, "this")
4693        expressions = self.expressions(expression, flat=True)
4694        return f"{this}({expressions})"
4695
4696    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4697        return self.binary(expression, "=>")
4698
4699    def when_sql(self, expression: exp.When) -> str:
4700        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4701        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4702        condition = self.sql(expression, "condition")
4703        condition = f" AND {condition}" if condition else ""
4704
4705        then_expression = expression.args.get("then")
4706        if isinstance(then_expression, exp.Insert):
4707            this = self.sql(then_expression, "this")
4708            this = f"INSERT {this}" if this else "INSERT"
4709            then = self.sql(then_expression, "expression")
4710            then = f"{this} VALUES {then}" if then else this
4711        elif isinstance(then_expression, exp.Update):
4712            if isinstance(then_expression.args.get("expressions"), exp.Star):
4713                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4714            else:
4715                expressions_sql = self.expressions(then_expression)
4716                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4717        else:
4718            then = self.sql(then_expression)
4719
4720        if isinstance(then_expression, (exp.Insert, exp.Update)):
4721            where = self.sql(then_expression, "where")
4722            if where and not self.SUPPORTS_MERGE_WHERE:
4723                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4724                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4725                where = ""
4726            then = f"{then}{where}"
4727        return f"WHEN {matched}{source}{condition} THEN {then}"
4728
4729    def whens_sql(self, expression: exp.Whens) -> str:
4730        return self.expressions(expression, sep=" ", indent=False)
4731
4732    def merge_sql(self, expression: exp.Merge) -> str:
4733        table = expression.this
4734        table_alias = ""
4735
4736        hints = table.args.get("hints")
4737        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4738            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4739            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4740
4741        this = self.sql(table)
4742        using = f"USING {self.sql(expression, 'using')}"
4743        whens = self.sql(expression, "whens")
4744
4745        on = self.sql(expression, "on")
4746        on = f"ON {on}" if on else ""
4747
4748        if not on:
4749            on = self.expressions(expression, key="using_cond")
4750            on = f"USING ({on})" if on else ""
4751
4752        returning = self.sql(expression, "returning")
4753        if returning:
4754            whens = f"{whens}{returning}"
4755
4756        sep = self.sep()
4757
4758        return self.prepend_ctes(
4759            expression,
4760            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4761        )
4762
4763    @unsupported_args("format")
4764    def tochar_sql(self, expression: exp.ToChar) -> str:
4765        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4766
4767    @unsupported_args("default")
4768    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4769        if not self.SUPPORTS_TO_NUMBER:
4770            self.unsupported("Unsupported TO_NUMBER function")
4771            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4772
4773        fmt = expression.args.get("format")
4774        if not fmt:
4775            self.unsupported("Conversion format is required for TO_NUMBER")
4776            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4777
4778        return self.func("TO_NUMBER", expression.this, fmt)
4779
4780    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4781        this = self.sql(expression, "this")
4782        kind = self.sql(expression, "kind")
4783        settings_sql = self.expressions(expression, key="settings", sep=" ")
4784        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4785        return f"{this}({kind}{args})"
4786
4787    def dictrange_sql(self, expression: exp.DictRange) -> str:
4788        this = self.sql(expression, "this")
4789        max = self.sql(expression, "max")
4790        min = self.sql(expression, "min")
4791        return f"{this}(MIN {min} MAX {max})"
4792
4793    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4794        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4795
4796    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4797        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4798
4799    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4800    def uniquekeyproperty_sql(
4801        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4802    ) -> str:
4803        return f"{prefix} ({self.expressions(expression, flat=True)})"
4804
4805    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4806    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4807        expressions = self.expressions(expression, flat=True)
4808        expressions = f" {self.wrap(expressions)}" if expressions else ""
4809        buckets = self.sql(expression, "buckets")
4810        kind = self.sql(expression, "kind")
4811        buckets = f" BUCKETS {buckets}" if buckets else ""
4812        order = self.sql(expression, "order")
4813        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4814
4815    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4816        return ""
4817
4818    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4819        expressions = self.expressions(expression, key="expressions", flat=True)
4820        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4821        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4822        buckets = self.sql(expression, "buckets")
4823        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4824
4825    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4826        this = self.sql(expression, "this")
4827        having = self.sql(expression, "having")
4828
4829        if having:
4830            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4831
4832        return self.func("ANY_VALUE", this)
4833
4834    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4835        transform = self.func("TRANSFORM", *expression.expressions)
4836        row_format_before = self.sql(expression, "row_format_before")
4837        row_format_before = f" {row_format_before}" if row_format_before else ""
4838        record_writer = self.sql(expression, "record_writer")
4839        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4840        using = f" USING {self.sql(expression, 'command_script')}"
4841        schema = self.sql(expression, "schema")
4842        schema = f" AS {schema}" if schema else ""
4843        row_format_after = self.sql(expression, "row_format_after")
4844        row_format_after = f" {row_format_after}" if row_format_after else ""
4845        record_reader = self.sql(expression, "record_reader")
4846        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4847        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4848
4849    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4850        key_block_size = self.sql(expression, "key_block_size")
4851        if key_block_size:
4852            return f"KEY_BLOCK_SIZE = {key_block_size}"
4853
4854        using = self.sql(expression, "using")
4855        if using:
4856            return f"USING {using}"
4857
4858        parser = self.sql(expression, "parser")
4859        if parser:
4860            return f"WITH PARSER {parser}"
4861
4862        comment = self.sql(expression, "comment")
4863        if comment:
4864            return f"COMMENT {comment}"
4865
4866        visible = expression.args.get("visible")
4867        if visible is not None:
4868            return "VISIBLE" if visible else "INVISIBLE"
4869
4870        engine_attr = self.sql(expression, "engine_attr")
4871        if engine_attr:
4872            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4873
4874        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4875        if secondary_engine_attr:
4876            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4877
4878        self.unsupported("Unsupported index constraint option.")
4879        return ""
4880
4881    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4882        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4883        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4884
4885    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4886        kind = self.sql(expression, "kind")
4887        kind = f"{kind} INDEX" if kind else "INDEX"
4888        this = self.sql(expression, "this")
4889        this = f" {this}" if this else ""
4890        index_type = self.sql(expression, "index_type")
4891        index_type = f" USING {index_type}" if index_type else ""
4892        expressions = self.expressions(expression, flat=True)
4893        expressions = f" ({expressions})" if expressions else ""
4894        options = self.expressions(expression, key="options", sep=" ")
4895        options = f" {options}" if options else ""
4896        return f"{kind}{this}{index_type}{expressions}{options}"
4897
4898    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4899        if self.NVL2_SUPPORTED:
4900            return self.function_fallback_sql(expression)
4901
4902        case = exp.Case().when(
4903            expression.this.is_(exp.null()).not_(copy=False),
4904            expression.args["true"],
4905            copy=False,
4906        )
4907        else_cond = expression.args.get("false")
4908        if else_cond:
4909            case.else_(else_cond, copy=False)
4910
4911        return self.sql(case)
4912
4913    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4914        this = self.sql(expression, "this")
4915        expr = self.sql(expression, "expression")
4916        position = self.sql(expression, "position")
4917        position = f", {position}" if position else ""
4918        iterator = self.sql(expression, "iterator")
4919        condition = self.sql(expression, "condition")
4920        condition = f" IF {condition}" if condition else ""
4921        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4922
4923    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4924        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4925
4926    def opclass_sql(self, expression: exp.Opclass) -> str:
4927        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4928
4929    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4930        model = self.sql(expression, "this")
4931        model = f"MODEL {model}"
4932        expr = expression.expression
4933        if expr:
4934            expr_sql = self.sql(expression, "expression")
4935            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
4936        else:
4937            expr_sql = None
4938
4939        parameters = self.sql(expression, "params_struct") or None
4940
4941        return self.func(name, model, expr_sql, parameters)
4942
4943    def predict_sql(self, expression: exp.Predict) -> str:
4944        return self._ml_sql(expression, "PREDICT")
4945
4946    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4947        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4948        return self._ml_sql(expression, name)
4949
4950    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4951        return self._ml_sql(expression, "GENERATE_TEXT")
4952
4953    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4954        return self._ml_sql(expression, "GENERATE_TABLE")
4955
4956    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4957        return self._ml_sql(expression, "GENERATE_BOOL")
4958
4959    def generateint_sql(self, expression: exp.GenerateInt) -> str:
4960        return self._ml_sql(expression, "GENERATE_INT")
4961
4962    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
4963        return self._ml_sql(expression, "GENERATE_DOUBLE")
4964
4965    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4966        return self._ml_sql(expression, "TRANSLATE")
4967
4968    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4969        return self._ml_sql(expression, "FORECAST")
4970
4971    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
4972        this_sql = self.sql(expression, "this")
4973        if isinstance(expression.this, exp.Table):
4974            this_sql = f"TABLE {this_sql}"
4975
4976        return self.func(
4977            "FORECAST",
4978            this_sql,
4979            expression.args.get("data_col"),
4980            expression.args.get("timestamp_col"),
4981            expression.args.get("model"),
4982            expression.args.get("id_cols"),
4983            expression.args.get("horizon"),
4984            expression.args.get("forecast_end_timestamp"),
4985            expression.args.get("confidence_level"),
4986            expression.args.get("output_historical_time_series"),
4987            expression.args.get("context_window"),
4988        )
4989
4990    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4991        this_sql = self.sql(expression, "this")
4992        if isinstance(expression.this, exp.Table):
4993            this_sql = f"TABLE {this_sql}"
4994
4995        return self.func(
4996            "FEATURES_AT_TIME",
4997            this_sql,
4998            expression.args.get("time"),
4999            expression.args.get("num_rows"),
5000            expression.args.get("ignore_feature_nulls"),
5001        )
5002
5003    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5004        this_sql = self.sql(expression, "this")
5005        if isinstance(expression.this, exp.Table):
5006            this_sql = f"TABLE {this_sql}"
5007
5008        query_table = self.sql(expression, "query_table")
5009        if isinstance(expression.args["query_table"], exp.Table):
5010            query_table = f"TABLE {query_table}"
5011
5012        return self.func(
5013            "VECTOR_SEARCH",
5014            this_sql,
5015            expression.args.get("column_to_search"),
5016            query_table,
5017            expression.args.get("query_column_to_search"),
5018            expression.args.get("top_k"),
5019            expression.args.get("distance_type"),
5020            expression.args.get("options"),
5021        )
5022
5023    def forin_sql(self, expression: exp.ForIn) -> str:
5024        this = self.sql(expression, "this")
5025        expression_sql = self.sql(expression, "expression")
5026        return f"FOR {this} DO {expression_sql}"
5027
5028    def refresh_sql(self, expression: exp.Refresh) -> str:
5029        this = self.sql(expression, "this")
5030        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5031        return f"REFRESH {kind}{this}"
5032
5033    def toarray_sql(self, expression: exp.ToArray) -> str:
5034        arg = expression.this
5035        if not arg.type:
5036            import sqlglot.optimizer.annotate_types
5037
5038            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5039
5040        if arg.is_type(exp.DType.ARRAY):
5041            return self.sql(arg)
5042
5043        cond_for_null = arg.is_(exp.null())
5044        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5045
5046    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5047        this = expression.this
5048        time_format = self.format_time(expression)
5049
5050        if time_format:
5051            return self.sql(
5052                exp.cast(
5053                    exp.StrToTime(this=this, format=expression.args["format"]),
5054                    exp.DType.TIME,
5055                )
5056            )
5057
5058        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5059            return self.sql(this)
5060
5061        return self.sql(exp.cast(this, exp.DType.TIME))
5062
5063    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5064        this = expression.this
5065        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5066            return self.sql(this)
5067
5068        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5069
5070    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5071        this = expression.this
5072        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5073            return self.sql(this)
5074
5075        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5076
5077    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5078        this = expression.this
5079        time_format = self.format_time(expression)
5080        safe = expression.args.get("safe")
5081        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5082            return self.sql(
5083                exp.cast(
5084                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5085                    exp.DType.DATE,
5086                )
5087            )
5088
5089        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5090            return self.sql(this)
5091
5092        if safe:
5093            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5094
5095        return self.sql(exp.cast(this, exp.DType.DATE))
5096
5097    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5098        return self.sql(
5099            exp.func(
5100                "DATEDIFF",
5101                expression.this,
5102                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5103                "day",
5104            )
5105        )
5106
5107    def lastday_sql(self, expression: exp.LastDay) -> str:
5108        if self.LAST_DAY_SUPPORTS_DATE_PART:
5109            return self.function_fallback_sql(expression)
5110
5111        unit = expression.text("unit")
5112        if unit and unit != "MONTH":
5113            self.unsupported("Date parts are not supported in LAST_DAY.")
5114
5115        return self.func("LAST_DAY", expression.this)
5116
5117    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5118        import sqlglot.dialects.dialect
5119
5120        return self.func(
5121            "DATE_ADD",
5122            expression.this,
5123            expression.expression,
5124            sqlglot.dialects.dialect.unit_to_str(expression),
5125        )
5126
5127    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5128        if self.CAN_IMPLEMENT_ARRAY_ANY:
5129            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5130            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5131            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5132            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5133
5134        import sqlglot.dialects.dialect
5135
5136        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5137        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5138            self.unsupported("ARRAY_ANY is unsupported")
5139
5140        return self.function_fallback_sql(expression)
5141
5142    def struct_sql(self, expression: exp.Struct) -> str:
5143        expression.set(
5144            "expressions",
5145            [
5146                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5147                if isinstance(e, exp.PropertyEQ)
5148                else e
5149                for e in expression.expressions
5150            ],
5151        )
5152
5153        return self.function_fallback_sql(expression)
5154
5155    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5156        low = self.sql(expression, "this")
5157        high = self.sql(expression, "expression")
5158
5159        return f"{low} TO {high}"
5160
5161    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5162        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5163        tables = f" {self.expressions(expression)}"
5164
5165        exists = " IF EXISTS" if expression.args.get("exists") else ""
5166
5167        on_cluster = self.sql(expression, "cluster")
5168        on_cluster = f" {on_cluster}" if on_cluster else ""
5169
5170        identity = self.sql(expression, "identity")
5171        identity = f" {identity} IDENTITY" if identity else ""
5172
5173        option = self.sql(expression, "option")
5174        option = f" {option}" if option else ""
5175
5176        partition = self.sql(expression, "partition")
5177        partition = f" {partition}" if partition else ""
5178
5179        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5180
5181    # This transpiles T-SQL's CONVERT function
5182    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5183    def convert_sql(self, expression: exp.Convert) -> str:
5184        to = expression.this
5185        value = expression.expression
5186        style = expression.args.get("style")
5187        safe = expression.args.get("safe")
5188        strict = expression.args.get("strict")
5189
5190        if not to or not value:
5191            return ""
5192
5193        # Retrieve length of datatype and override to default if not specified
5194        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5195            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5196
5197        transformed: exp.Expr | None = None
5198        cast = exp.Cast if strict else exp.TryCast
5199
5200        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5201        if isinstance(style, exp.Literal) and style.is_int:
5202            import sqlglot.dialects.tsql
5203
5204            style_value = style.name
5205            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5206            if not converted_style:
5207                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5208
5209            fmt = exp.Literal.string(converted_style)
5210
5211            if to.this == exp.DType.DATE:
5212                transformed = exp.StrToDate(this=value, format=fmt)
5213            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5214                transformed = exp.StrToTime(this=value, format=fmt)
5215            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5216                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5217            elif to.this == exp.DType.TEXT:
5218                transformed = exp.TimeToStr(this=value, format=fmt)
5219
5220        if not transformed:
5221            transformed = cast(this=value, to=to, safe=safe)
5222
5223        return self.sql(transformed)
5224
5225    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5226        this = expression.this
5227        if isinstance(this, exp.JSONPathWildcard):
5228            this = self.json_path_part(this)
5229            return f".{this}" if this else ""
5230
5231        if self.SAFE_JSON_PATH_KEY_RE.match(this):
5232            return f".{this}"
5233
5234        this = self.json_path_part(this)
5235        return (
5236            f"[{this}]"
5237            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5238            else f".{this}"
5239        )
5240
5241    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5242        this = self.json_path_part(expression.this)
5243        return f"[{this}]" if this else ""
5244
5245    def _simplify_unless_literal(self, expression: E) -> E:
5246        if not isinstance(expression, exp.Literal):
5247            import sqlglot.optimizer.simplify
5248
5249            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5250
5251        return expression
5252
5253    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5254        this = expression.this
5255        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5256            self.unsupported(
5257                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5258            )
5259            return self.sql(this)
5260
5261        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5262            if self.IGNORE_NULLS_BEFORE_ORDER:
5263                # The first modifier here will be the one closest to the AggFunc's arg
5264                mods = sorted(
5265                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5266                    key=lambda x: (
5267                        0
5268                        if isinstance(x, exp.HavingMax)
5269                        else (1 if isinstance(x, exp.Order) else 2)
5270                    ),
5271                )
5272
5273                if mods:
5274                    mod = mods[0]
5275                    this = expression.__class__(this=mod.this.copy())
5276                    this.meta["inline"] = True
5277                    mod.this.replace(this)
5278                    return self.sql(expression.this)
5279
5280            agg_func = expression.find(exp.AggFunc)
5281
5282            if agg_func:
5283                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5284                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5285
5286        return f"{self.sql(expression, 'this')} {text}"
5287
5288    def _replace_line_breaks(self, string: str) -> str:
5289        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5290        if self.pretty:
5291            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5292        return string
5293
5294    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5295        option = self.sql(expression, "this")
5296
5297        if expression.expressions:
5298            upper = option.upper()
5299
5300            # Snowflake FILE_FORMAT options are separated by whitespace
5301            sep = " " if upper == "FILE_FORMAT" else ", "
5302
5303            # Databricks copy/format options do not set their list of values with EQ
5304            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5305            values = self.expressions(expression, flat=True, sep=sep)
5306            return f"{option}{op}({values})"
5307
5308        value = self.sql(expression, "expression")
5309
5310        if not value:
5311            return option
5312
5313        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5314
5315        return f"{option}{op}{value}"
5316
5317    def credentials_sql(self, expression: exp.Credentials) -> str:
5318        cred_expr = expression.args.get("credentials")
5319        if isinstance(cred_expr, exp.Literal):
5320            # Redshift case: CREDENTIALS <string>
5321            credentials = self.sql(expression, "credentials")
5322            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5323        else:
5324            # Snowflake case: CREDENTIALS = (...)
5325            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5326            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5327
5328        storage = self.sql(expression, "storage")
5329        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5330
5331        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5332        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5333
5334        iam_role = self.sql(expression, "iam_role")
5335        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5336
5337        region = self.sql(expression, "region")
5338        region = f" REGION {region}" if region else ""
5339
5340        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5341
5342    def copy_sql(self, expression: exp.Copy) -> str:
5343        this = self.sql(expression, "this")
5344        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5345
5346        credentials = self.sql(expression, "credentials")
5347        credentials = self.seg(credentials) if credentials else ""
5348        files = self.expressions(expression, key="files", flat=True)
5349        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5350
5351        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5352        params = self.expressions(
5353            expression,
5354            key="params",
5355            sep=sep,
5356            new_line=True,
5357            skip_last=True,
5358            skip_first=True,
5359            indent=self.COPY_PARAMS_ARE_WRAPPED,
5360        )
5361
5362        if params:
5363            if self.COPY_PARAMS_ARE_WRAPPED:
5364                params = f" WITH ({params})"
5365            elif not self.pretty and (files or credentials):
5366                params = f" {params}"
5367
5368        return f"COPY{this}{kind} {files}{credentials}{params}"
5369
5370    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5371        return ""
5372
5373    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5374        on_sql = "ON" if expression.args.get("on") else "OFF"
5375        filter_col: str | None = self.sql(expression, "filter_column")
5376        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5377        retention_period: str | None = self.sql(expression, "retention_period")
5378        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5379
5380        if filter_col or retention_period:
5381            on_sql = self.func("ON", filter_col, retention_period)
5382
5383        return f"DATA_DELETION={on_sql}"
5384
5385    def maskingpolicycolumnconstraint_sql(
5386        self, expression: exp.MaskingPolicyColumnConstraint
5387    ) -> str:
5388        this = self.sql(expression, "this")
5389        expressions = self.expressions(expression, flat=True)
5390        expressions = f" USING ({expressions})" if expressions else ""
5391        return f"MASKING POLICY {this}{expressions}"
5392
5393    def gapfill_sql(self, expression: exp.GapFill) -> str:
5394        this = self.sql(expression, "this")
5395        this = f"TABLE {this}"
5396        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5397
5398    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5399        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5400
5401    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5402        this = self.sql(expression, "this")
5403        expr = expression.expression
5404
5405        if isinstance(expr, exp.Func):
5406            # T-SQL's CLR functions are case sensitive
5407            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5408        else:
5409            expr = self.sql(expression, "expression")
5410
5411        return self.scope_resolution(expr, this)
5412
5413    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5414        if self.PARSE_JSON_NAME is None:
5415            return self.sql(expression.this)
5416
5417        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5418
5419    def rand_sql(self, expression: exp.Rand) -> str:
5420        lower = self.sql(expression, "lower")
5421        upper = self.sql(expression, "upper")
5422
5423        if lower and upper:
5424            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5425        return self.func("RAND", expression.this)
5426
5427    def changes_sql(self, expression: exp.Changes) -> str:
5428        information = self.sql(expression, "information")
5429        information = f"INFORMATION => {information}"
5430        at_before = self.sql(expression, "at_before")
5431        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5432        end = self.sql(expression, "end")
5433        end = f"{self.seg('')}{end}" if end else ""
5434
5435        return f"CHANGES ({information}){at_before}{end}"
5436
5437    def pad_sql(self, expression: exp.Pad) -> str:
5438        prefix = "L" if expression.args.get("is_left") else "R"
5439
5440        fill_pattern = self.sql(expression, "fill_pattern") or None
5441        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5442            fill_pattern = "' '"
5443
5444        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5445
5446    def summarize_sql(self, expression: exp.Summarize) -> str:
5447        table = " TABLE" if expression.args.get("table") else ""
5448        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5449
5450    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5451        generate_series = exp.GenerateSeries(**expression.args)
5452
5453        parent = expression.parent
5454        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5455            parent = parent.parent
5456
5457        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5458            return self.sql(exp.Unnest(expressions=[generate_series]))
5459
5460        if isinstance(parent, exp.Select):
5461            self.unsupported("GenerateSeries projection unnesting is not supported.")
5462
5463        return self.sql(generate_series)
5464
5465    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5466        if self.SUPPORTS_CONVERT_TIMEZONE:
5467            return self.function_fallback_sql(expression)
5468
5469        source_tz = expression.args.get("source_tz")
5470        target_tz = expression.args.get("target_tz")
5471        timestamp = expression.args.get("timestamp")
5472
5473        if source_tz and timestamp:
5474            timestamp = exp.AtTimeZone(
5475                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5476            )
5477
5478        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5479
5480        return self.sql(expr)
5481
5482    def json_sql(self, expression: exp.JSON) -> str:
5483        this = self.sql(expression, "this")
5484        this = f" {this}" if this else ""
5485
5486        _with = expression.args.get("with_")
5487
5488        if _with is None:
5489            with_sql = ""
5490        elif not _with:
5491            with_sql = " WITHOUT"
5492        else:
5493            with_sql = " WITH"
5494
5495        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5496
5497        return f"JSON{this}{with_sql}{unique_sql}"
5498
5499    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5500        path = self.sql(expression, "path")
5501        returning = self.sql(expression, "returning")
5502        returning = f" RETURNING {returning}" if returning else ""
5503
5504        on_condition = self.sql(expression, "on_condition")
5505        on_condition = f" {on_condition}" if on_condition else ""
5506
5507        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5508
5509    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5510        regexp = " REGEXP" if expression.args.get("regexp") else ""
5511        return f"SKIP{regexp} {self.sql(expression.expression)}"
5512
5513    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5514        else_ = "ELSE " if expression.args.get("else_") else ""
5515        condition = self.sql(expression, "expression")
5516        condition = f"WHEN {condition} THEN " if condition else else_
5517        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5518        return f"{condition}{insert}"
5519
5520    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5521        kind = self.sql(expression, "kind")
5522        expressions = self.seg(self.expressions(expression, sep=" "))
5523        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5524        return res
5525
5526    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5527        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5528        empty = expression.args.get("empty")
5529        empty = (
5530            f"DEFAULT {empty} ON EMPTY"
5531            if isinstance(empty, exp.Expr)
5532            else self.sql(expression, "empty")
5533        )
5534
5535        error = expression.args.get("error")
5536        error = (
5537            f"DEFAULT {error} ON ERROR"
5538            if isinstance(error, exp.Expr)
5539            else self.sql(expression, "error")
5540        )
5541
5542        if error and empty:
5543            error = (
5544                f"{empty} {error}"
5545                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5546                else f"{error} {empty}"
5547            )
5548            empty = ""
5549
5550        null = self.sql(expression, "null")
5551
5552        return f"{empty}{error}{null}"
5553
5554    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5555        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5556        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5557
5558    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5559        this = self.sql(expression, "this")
5560        path = self.sql(expression, "path")
5561
5562        passing = self.expressions(expression, "passing")
5563        passing = f" PASSING {passing}" if passing else ""
5564
5565        on_condition = self.sql(expression, "on_condition")
5566        on_condition = f" {on_condition}" if on_condition else ""
5567
5568        path = f"{path}{passing}{on_condition}"
5569
5570        return self.func("JSON_EXISTS", this, path)
5571
5572    def _add_arrayagg_null_filter(
5573        self,
5574        array_agg_sql: str,
5575        array_agg_expr: exp.ArrayAgg,
5576        column_expr: exp.Expr,
5577    ) -> str:
5578        """
5579        Add NULL filter to ARRAY_AGG if dialect requires it.
5580
5581        Args:
5582            array_agg_sql: The generated ARRAY_AGG SQL string
5583            array_agg_expr: The ArrayAgg expression node
5584            column_expr: The column/expression to filter (before ORDER BY wrapping)
5585
5586        Returns:
5587            SQL string with FILTER clause added if needed
5588        """
5589        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5590        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5591        if not (
5592            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5593        ):
5594            return array_agg_sql
5595
5596        parent = array_agg_expr.parent
5597        if isinstance(parent, exp.Filter):
5598            parent_cond = parent.expression.this
5599            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5600        elif column_expr.find(exp.Column):
5601            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5602            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5603            this_sql = (
5604                self.expressions(column_expr)
5605                if isinstance(column_expr, exp.Distinct)
5606                else self.sql(column_expr)
5607            )
5608            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5609
5610        return array_agg_sql
5611
5612    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5613        array_agg = self.function_fallback_sql(expression)
5614        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5615
5616    def slice_sql(self, expression: exp.Slice) -> str:
5617        step = self.sql(expression, "step")
5618        end = self.sql(expression.expression)
5619        begin = self.sql(expression.this)
5620
5621        sql = f"{end}:{step}" if step else end
5622        return f"{begin}:{sql}" if sql else f"{begin}:"
5623
5624    def apply_sql(self, expression: exp.Apply) -> str:
5625        this = self.sql(expression, "this")
5626        expr = self.sql(expression, "expression")
5627
5628        return f"{this} APPLY({expr})"
5629
5630    def _grant_or_revoke_sql(
5631        self,
5632        expression: exp.Grant | exp.Revoke,
5633        keyword: str,
5634        preposition: str,
5635        grant_option_prefix: str = "",
5636        grant_option_suffix: str = "",
5637    ) -> str:
5638        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5639
5640        kind = self.sql(expression, "kind")
5641        kind = f" {kind}" if kind else ""
5642
5643        securable = self.sql(expression, "securable")
5644        securable = f" {securable}" if securable else ""
5645
5646        principals = self.expressions(expression, key="principals", flat=True)
5647
5648        if not expression.args.get("grant_option"):
5649            grant_option_prefix = grant_option_suffix = ""
5650
5651        # cascade for revoke only
5652        cascade = self.sql(expression, "cascade")
5653        cascade = f" {cascade}" if cascade else ""
5654
5655        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5656
5657    def grant_sql(self, expression: exp.Grant) -> str:
5658        return self._grant_or_revoke_sql(
5659            expression,
5660            keyword="GRANT",
5661            preposition="TO",
5662            grant_option_suffix=" WITH GRANT OPTION",
5663        )
5664
5665    def revoke_sql(self, expression: exp.Revoke) -> str:
5666        return self._grant_or_revoke_sql(
5667            expression,
5668            keyword="REVOKE",
5669            preposition="FROM",
5670            grant_option_prefix="GRANT OPTION FOR ",
5671        )
5672
5673    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5674        this = self.sql(expression, "this")
5675        columns = self.expressions(expression, flat=True)
5676        columns = f"({columns})" if columns else ""
5677
5678        return f"{this}{columns}"
5679
5680    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5681        this = self.sql(expression, "this")
5682
5683        kind = self.sql(expression, "kind")
5684        kind = f"{kind} " if kind else ""
5685
5686        return f"{kind}{this}"
5687
5688    def columns_sql(self, expression: exp.Columns) -> str:
5689        func = self.function_fallback_sql(expression)
5690        if expression.args.get("unpack"):
5691            func = f"*{func}"
5692
5693        return func
5694
5695    def overlay_sql(self, expression: exp.Overlay) -> str:
5696        this = self.sql(expression, "this")
5697        expr = self.sql(expression, "expression")
5698        from_sql = self.sql(expression, "from_")
5699        for_sql = self.sql(expression, "for_")
5700        for_sql = f" FOR {for_sql}" if for_sql else ""
5701
5702        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5703
5704    @unsupported_args("format")
5705    def todouble_sql(self, expression: exp.ToDouble) -> str:
5706        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5707        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5708
5709    def string_sql(self, expression: exp.String) -> str:
5710        this = expression.this
5711        zone = expression.args.get("zone")
5712
5713        if zone:
5714            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5715            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5716            # set for source_tz to transpile the time conversion before the STRING cast
5717            this = exp.ConvertTimezone(
5718                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5719            )
5720
5721        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5722
5723    def median_sql(self, expression: exp.Median) -> str:
5724        if not self.SUPPORTS_MEDIAN:
5725            return self.sql(
5726                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5727            )
5728
5729        return self.function_fallback_sql(expression)
5730
5731    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5732        filler = self.sql(expression, "this")
5733        filler = f" {filler}" if filler else ""
5734        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5735        return f"TRUNCATE{filler} {with_count}"
5736
5737    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5738        if self.SUPPORTS_UNIX_SECONDS:
5739            return self.function_fallback_sql(expression)
5740
5741        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5742
5743        return self.sql(
5744            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5745        )
5746
5747    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5748        dim = expression.expression
5749
5750        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5751        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5752            if not (dim.is_int and dim.name == "1"):
5753                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5754            dim = None
5755
5756        # If dimension is required but not specified, default initialize it
5757        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5758            dim = exp.Literal.number(1)
5759
5760        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5761
5762    def attach_sql(self, expression: exp.Attach) -> str:
5763        this = self.sql(expression, "this")
5764        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5765        expressions = self.expressions(expression)
5766        expressions = f" ({expressions})" if expressions else ""
5767
5768        return f"ATTACH{exists_sql} {this}{expressions}"
5769
5770    def detach_sql(self, expression: exp.Detach) -> str:
5771        kind = self.sql(expression, "kind")
5772        kind = f" {kind}" if kind else ""
5773        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5774        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5775        exists = " IF EXISTS" if expression.args.get("exists") else ""
5776        if exists:
5777            kind = kind or " DATABASE"
5778
5779        this = self.sql(expression, "this")
5780        this = f" {this}" if this else ""
5781        cluster = self.sql(expression, "cluster")
5782        cluster = f" {cluster}" if cluster else ""
5783        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5784        sync = " SYNC" if expression.args.get("sync") else ""
5785        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5786
5787    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5788        this = self.sql(expression, "this")
5789        value = self.sql(expression, "expression")
5790        value = f" {value}" if value else ""
5791        return f"{this}{value}"
5792
5793    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5794        return (
5795            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5796        )
5797
5798    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5799        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5800        encode = f"{encode} {self.sql(expression, 'this')}"
5801
5802        properties = expression.args.get("properties")
5803        if properties:
5804            encode = f"{encode} {self.properties(properties)}"
5805
5806        return encode
5807
5808    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5809        this = self.sql(expression, "this")
5810        include = f"INCLUDE {this}"
5811
5812        column_def = self.sql(expression, "column_def")
5813        if column_def:
5814            include = f"{include} {column_def}"
5815
5816        alias = self.sql(expression, "alias")
5817        if alias:
5818            include = f"{include} AS {alias}"
5819
5820        return include
5821
5822    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5823        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5824        name = f"{prefix} {self.sql(expression, 'this')}"
5825        return self.func("XMLELEMENT", name, *expression.expressions)
5826
5827    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5828        this = self.sql(expression, "this")
5829        expr = self.sql(expression, "expression")
5830        expr = f"({expr})" if expr else ""
5831        return f"{this}{expr}"
5832
5833    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5834        partitions = self.expressions(expression, "partition_expressions")
5835        create = self.expressions(expression, "create_expressions")
5836        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5837
5838    def partitionbyrangepropertydynamic_sql(
5839        self, expression: exp.PartitionByRangePropertyDynamic
5840    ) -> str:
5841        start = self.sql(expression, "start")
5842        end = self.sql(expression, "end")
5843
5844        every = expression.args["every"]
5845        if isinstance(every, exp.Interval) and every.this.is_string:
5846            every.this.replace(exp.Literal.number(every.name))
5847
5848        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5849
5850    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5851        name = self.sql(expression, "this")
5852        values = self.expressions(expression, flat=True)
5853
5854        return f"NAME {name} VALUE {values}"
5855
5856    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5857        kind = self.sql(expression, "kind")
5858        sample = self.sql(expression, "sample")
5859        return f"SAMPLE {sample} {kind}"
5860
5861    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5862        kind = self.sql(expression, "kind")
5863        option = self.sql(expression, "option")
5864        option = f" {option}" if option else ""
5865        this = self.sql(expression, "this")
5866        this = f" {this}" if this else ""
5867        columns = self.expressions(expression)
5868        columns = f" {columns}" if columns else ""
5869        return f"{kind}{option} STATISTICS{this}{columns}"
5870
5871    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5872        this = self.sql(expression, "this")
5873        columns = self.expressions(expression)
5874        inner_expression = self.sql(expression, "expression")
5875        inner_expression = f" {inner_expression}" if inner_expression else ""
5876        update_options = self.sql(expression, "update_options")
5877        update_options = f" {update_options} UPDATE" if update_options else ""
5878        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5879
5880    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5881        kind = self.sql(expression, "kind")
5882        kind = f" {kind}" if kind else ""
5883        return f"DELETE{kind} STATISTICS"
5884
5885    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5886        inner_expression = self.sql(expression, "expression")
5887        return f"LIST CHAINED ROWS{inner_expression}"
5888
5889    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5890        kind = self.sql(expression, "kind")
5891        this = self.sql(expression, "this")
5892        this = f" {this}" if this else ""
5893        inner_expression = self.sql(expression, "expression")
5894        return f"VALIDATE {kind}{this}{inner_expression}"
5895
5896    def analyze_sql(self, expression: exp.Analyze) -> str:
5897        options = self.expressions(expression, key="options", sep=" ")
5898        options = f" {options}" if options else ""
5899        kind = self.sql(expression, "kind")
5900        kind = f" {kind}" if kind else ""
5901        this = self.sql(expression, "this")
5902        this = f" {this}" if this else ""
5903        mode = self.sql(expression, "mode")
5904        mode = f" {mode}" if mode else ""
5905        properties = self.sql(expression, "properties")
5906        properties = f" {properties}" if properties else ""
5907        partition = self.sql(expression, "partition")
5908        partition = f" {partition}" if partition else ""
5909        inner_expression = self.sql(expression, "expression")
5910        inner_expression = f" {inner_expression}" if inner_expression else ""
5911        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5912
5913    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5914        this = self.sql(expression, "this")
5915        namespaces = self.expressions(expression, key="namespaces")
5916        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5917        passing = self.expressions(expression, key="passing")
5918        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5919        columns = self.expressions(expression, key="columns")
5920        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5921        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5922        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5923
5924    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5925        this = self.sql(expression, "this")
5926        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5927
5928    def export_sql(self, expression: exp.Export) -> str:
5929        this = self.sql(expression, "this")
5930        connection = self.sql(expression, "connection")
5931        connection = f"WITH CONNECTION {connection} " if connection else ""
5932        options = self.sql(expression, "options")
5933        return f"EXPORT DATA {connection}{options} AS {this}"
5934
5935    def declare_sql(self, expression: exp.Declare) -> str:
5936        replace = "OR REPLACE " if expression.args.get("replace") else ""
5937        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
5938
5939    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5940        variables = self.expressions(expression, "this")
5941        default = self.sql(expression, "default")
5942        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5943
5944        kind = self.sql(expression, "kind")
5945        if isinstance(expression.args.get("kind"), exp.Schema):
5946            kind = f"TABLE {kind}"
5947
5948        kind = f" {kind}" if kind else ""
5949
5950        return f"{variables}{kind}{default}"
5951
5952    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5953        kind = self.sql(expression, "kind")
5954        this = self.sql(expression, "this")
5955        set = self.sql(expression, "expression")
5956        using = self.sql(expression, "using")
5957        using = f" USING {using}" if using else ""
5958
5959        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5960
5961        return f"{kind_sql} {this} SET {set}{using}"
5962
5963    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5964        params = self.expressions(expression, key="params", flat=True)
5965        return self.func(expression.name, *expression.expressions) + f"({params})"
5966
5967    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5968        return self.func(expression.name, *expression.expressions)
5969
5970    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5971        return self.anonymousaggfunc_sql(expression)
5972
5973    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5974        return self.parameterizedagg_sql(expression)
5975
5976    def show_sql(self, expression: exp.Show) -> str:
5977        self.unsupported("Unsupported SHOW statement")
5978        return ""
5979
5980    def install_sql(self, expression: exp.Install) -> str:
5981        self.unsupported("Unsupported INSTALL statement")
5982        return ""
5983
5984    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5985        # Snowflake GET/PUT statements:
5986        #   PUT <file> <internalStage> <properties>
5987        #   GET <internalStage> <file> <properties>
5988        props = expression.args.get("properties")
5989        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5990        this = self.sql(expression, "this")
5991        target = self.sql(expression, "target")
5992
5993        if isinstance(expression, exp.Put):
5994            return f"PUT {this} {target}{props_sql}"
5995        else:
5996            return f"GET {target} {this}{props_sql}"
5997
5998    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
5999        this = self.sql(expression, "this")
6000        expr = self.sql(expression, "expression")
6001        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6002        return f"TRANSLATE({this} USING {expr}{with_error})"
6003
6004    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6005        if self.SUPPORTS_DECODE_CASE:
6006            return self.func("DECODE", *expression.expressions)
6007
6008        decode_expr, *expressions = expression.expressions
6009
6010        ifs = []
6011        for search, result in zip(expressions[::2], expressions[1::2]):
6012            if isinstance(search, exp.Literal):
6013                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6014            elif isinstance(search, exp.Null):
6015                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6016            else:
6017                if isinstance(search, exp.Binary):
6018                    search = exp.paren(search)
6019
6020                cond = exp.or_(
6021                    decode_expr.eq(search),
6022                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6023                    copy=False,
6024                )
6025                ifs.append(exp.If(this=cond, true=result))
6026
6027        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6028        return self.sql(case)
6029
6030    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6031        this = self.sql(expression, "this")
6032        this = self.seg(this, sep="")
6033        dimensions = self.expressions(
6034            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6035        )
6036        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6037        metrics = self.expressions(
6038            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6039        )
6040        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6041        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6042        facts = self.seg(f"FACTS {facts}") if facts else ""
6043        where = self.sql(expression, "where")
6044        where = self.seg(f"WHERE {where}") if where else ""
6045        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6046        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6047
6048    def getextract_sql(self, expression: exp.GetExtract) -> str:
6049        this = expression.this
6050        expr = expression.expression
6051
6052        if not this.type or not expression.type:
6053            import sqlglot.optimizer.annotate_types
6054
6055            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6056
6057        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6058            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6059
6060        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6061
6062    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6063        return self.sql(
6064            exp.DateAdd(
6065                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6066                expression=expression.this,
6067                unit=exp.var("DAY"),
6068            )
6069        )
6070
6071    def space_sql(self: Generator, expression: exp.Space) -> str:
6072        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6073
6074    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6075        return f"BUILD {self.sql(expression, 'this')}"
6076
6077    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6078        method = self.sql(expression, "method")
6079        kind = expression.args.get("kind")
6080        if not kind:
6081            return f"REFRESH {method}"
6082
6083        every = self.sql(expression, "every")
6084        unit = self.sql(expression, "unit")
6085        every = f" EVERY {every} {unit}" if every else ""
6086        starts = self.sql(expression, "starts")
6087        starts = f" STARTS {starts}" if starts else ""
6088
6089        return f"REFRESH {method} ON {kind}{every}{starts}"
6090
6091    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6092        self.unsupported("The model!attribute syntax is not supported")
6093        return ""
6094
6095    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6096        return self.func("DIRECTORY", expression.this)
6097
6098    def uuid_sql(self, expression: exp.Uuid) -> str:
6099        is_string = expression.args.get("is_string", False)
6100        uuid_func_sql = self.func("UUID")
6101
6102        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6103            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6104
6105        return uuid_func_sql
6106
6107    def initcap_sql(self, expression: exp.Initcap) -> str:
6108        delimiters = expression.expression
6109
6110        if delimiters:
6111            # do not generate delimiters arg if we are round-tripping from default delimiters
6112            if (
6113                delimiters.is_string
6114                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6115            ):
6116                delimiters = None
6117            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6118                self.unsupported("INITCAP does not support custom delimiters")
6119                delimiters = None
6120
6121        return self.func("INITCAP", expression.this, delimiters)
6122
6123    def localtime_sql(self, expression: exp.Localtime) -> str:
6124        this = expression.this
6125        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6126
6127    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6128        this = expression.this
6129        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6130
6131    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6132        this = expression.this.name.upper()
6133        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
6134            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
6135            return "WEEK"
6136
6137        return self.func("WEEK", expression.this)
6138
6139    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6140        this = self.expressions(expression)
6141        charset = self.sql(expression, "charset")
6142        using = f" USING {charset}" if charset else ""
6143        return self.func(name, this + using)
6144
6145    def block_sql(self, expression: exp.Block) -> str:
6146        expressions = self.expressions(expression, sep="; ", flat=True)
6147        return f"{expressions}" if expressions else ""
6148
6149    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6150        self.unsupported("Unsupported Stored Procedure syntax")
6151        return ""
6152
6153    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6154        self.unsupported("Unsupported If block syntax")
6155        return ""
6156
6157    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6158        self.unsupported("Unsupported While block syntax")
6159        return ""
6160
6161    def execute_sql(self, expression: exp.Execute) -> str:
6162        self.unsupported("Unsupported Execute syntax")
6163        return ""
6164
6165    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6166        self.unsupported("Unsupported Execute syntax")
6167        return ""
6168
6169    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6170        props = self.expressions(expression, sep=" ")
6171        return f"MODIFY {props}"
6172
6173    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6174        kind = expression.args.get("kind")
6175        return f"USING {kind} {self.sql(expression, 'this')}"
6176
6177    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6178        this = self.sql(expression, "this")
6179        to = self.sql(expression, "to")
6180        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]]:
31def unsupported_args(
32    *args: str | tuple[str, str],
33) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
34    """
35    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
36    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
37    """
38    diagnostic_by_arg: dict[str, str | None] = {}
39    for arg in args:
40        if isinstance(arg, str):
41            diagnostic_by_arg[arg] = None
42        else:
43            diagnostic_by_arg[arg[0]] = arg[1]
44
45    def decorator(func: GeneratorMethod) -> GeneratorMethod:
46        @wraps(func)
47        def _func(generator: G, expression: E) -> str:
48            expression_name = expression.__class__.__name__
49            dialect_name = generator.dialect.__class__.__name__
50
51            for arg_name, diagnostic in diagnostic_by_arg.items():
52                if expression.args.get(arg_name):
53                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
54                        arg_name, expression_name, dialect_name
55                    )
56                    generator.unsupported(diagnostic)
57
58            return func(generator, expression)
59
60        return _func
61
62    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:
  96class Generator:
  97    """
  98    Generator converts a given syntax tree to the corresponding SQL string.
  99
 100    Args:
 101        pretty: Whether to format the produced SQL string.
 102            Default: False.
 103        identify: Determines when an identifier should be quoted. Possible values are:
 104            False (default): Never quote, except in cases where it's mandatory by the dialect.
 105            True: Always quote except for specials cases.
 106            'safe': Only quote identifiers that are case insensitive.
 107        normalize: Whether to normalize identifiers to lowercase.
 108            Default: False.
 109        pad: The pad size in a formatted string. For example, this affects the indentation of
 110            a projection in a query, relative to its nesting level.
 111            Default: 2.
 112        indent: The indentation size in a formatted string. For example, this affects the
 113            indentation of subqueries and filters under a `WHERE` clause.
 114            Default: 2.
 115        normalize_functions: How to normalize function names. Possible values are:
 116            "upper" or True (default): Convert names to uppercase.
 117            "lower": Convert names to lowercase.
 118            False: Disables function name normalization.
 119        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 120            Default ErrorLevel.WARN.
 121        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 122            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 123            Default: 3
 124        leading_comma: Whether the comma is leading or trailing in select expressions.
 125            This is only relevant when generating in pretty mode.
 126            Default: False
 127        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 128            The default is on the smaller end because the length only represents a segment and not the true
 129            line length.
 130            Default: 80
 131        comments: Whether to preserve comments in the output SQL code.
 132            Default: True
 133    """
 134
 135    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 136        **JSON_PATH_PART_TRANSFORMS,
 137        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 138        exp.AllowedValuesProperty: lambda self, e: (
 139            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 140        ),
 141        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 142        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 143        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 144        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 145        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 146        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 147        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 148        exp.CaseSpecificColumnConstraint: lambda _, e: (
 149            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 150        ),
 151        exp.Ceil: lambda self, e: self.ceil_floor(e),
 152        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 153        exp.CharacterSetProperty: lambda self, e: (
 154            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 155        ),
 156        exp.ClusteredColumnConstraint: lambda self, e: (
 157            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 158        ),
 159        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 160        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 161        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 162        exp.ConvertToCharset: lambda self, e: self.func(
 163            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 164        ),
 165        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 166        exp.CredentialsProperty: lambda self, e: (
 167            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 168        ),
 169        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 170        exp.SessionUser: lambda *_: "SESSION_USER",
 171        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 172        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 173        exp.ApiProperty: lambda *_: "API",
 174        exp.ApplicationProperty: lambda *_: "APPLICATION",
 175        exp.CatalogProperty: lambda *_: "CATALOG",
 176        exp.ComputeProperty: lambda *_: "COMPUTE",
 177        exp.DatabaseProperty: lambda *_: "DATABASE",
 178        exp.DynamicProperty: lambda *_: "DYNAMIC",
 179        exp.EmptyProperty: lambda *_: "EMPTY",
 180        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 181        exp.EndStatement: lambda *_: "END",
 182        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 183        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 184        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 185        exp.EphemeralColumnConstraint: lambda self, e: (
 186            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 187        ),
 188        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 189        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 190        exp.Except: lambda self, e: self.set_operations(e),
 191        exp.ExternalProperty: lambda *_: "EXTERNAL",
 192        exp.Floor: lambda self, e: self.ceil_floor(e),
 193        exp.Get: lambda self, e: self.get_put_sql(e),
 194        exp.GlobalProperty: lambda *_: "GLOBAL",
 195        exp.HeapProperty: lambda *_: "HEAP",
 196        exp.HybridProperty: lambda *_: "HYBRID",
 197        exp.IcebergProperty: lambda *_: "ICEBERG",
 198        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 199        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 200        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 201        exp.Intersect: lambda self, e: self.set_operations(e),
 202        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 203        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 204        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 205        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 206        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 207        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 208        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 209        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 210        exp.LocationProperty: lambda self, e: self.naked_property(e),
 211        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 212        exp.MaskingProperty: lambda *_: "MASKING",
 213        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 214        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 215        exp.NetworkProperty: lambda *_: "NETWORK",
 216        exp.NonClusteredColumnConstraint: lambda self, e: (
 217            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 218        ),
 219        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 220        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 221        exp.OnCommitProperty: lambda _, e: (
 222            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 223        ),
 224        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 225        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 226        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 227        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 228        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 229        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 230        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 231        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 232        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 233        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 234        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 235        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 236            f"PROJECTION POLICY {self.sql(e, 'this')}"
 237        ),
 238        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 239        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 240        exp.Put: lambda self, e: self.get_put_sql(e),
 241        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 242            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 243        ),
 244        exp.ReturnsProperty: lambda self, e: (
 245            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 246        ),
 247        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 248        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 249        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 250        exp.SecureProperty: lambda *_: "SECURE",
 251        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 252        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 253        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 254        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 255        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 256        exp.SqlReadWriteProperty: lambda _, e: e.name,
 257        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 258        exp.StabilityProperty: lambda _, e: e.name,
 259        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 260        exp.StreamingTableProperty: lambda *_: "STREAMING",
 261        exp.StrictProperty: lambda *_: "STRICT",
 262        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 263        exp.TableColumn: lambda self, e: self.sql(e.this),
 264        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 265        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 266        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 267        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 268        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 269        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 270        exp.TransientProperty: lambda *_: "TRANSIENT",
 271        exp.VirtualProperty: lambda *_: "VIRTUAL",
 272        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 273        exp.Union: lambda self, e: self.set_operations(e),
 274        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 275        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 276        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 277        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 278        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 279        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 280        exp.UtcTimestamp: lambda self, e: self.sql(
 281            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 282        ),
 283        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 284        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 285        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 286        exp.VolatileProperty: lambda *_: "VOLATILE",
 287        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 288        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 289        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 290        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 291        exp.ForceProperty: lambda *_: "FORCE",
 292    }
 293
 294    # Whether null ordering is supported in order by
 295    # True: Full Support, None: No support, False: No support for certain cases
 296    # such as window specifications, aggregate functions etc
 297    NULL_ORDERING_SUPPORTED: bool | None = True
 298
 299    # Window functions that support NULLS FIRST/LAST
 300    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 301
 302    # Whether ignore nulls is inside the agg or outside.
 303    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 304    IGNORE_NULLS_IN_FUNC = False
 305
 306    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 307    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 308    IGNORE_NULLS_BEFORE_ORDER = True
 309
 310    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 311    LOCKING_READS_SUPPORTED = False
 312
 313    # Whether the EXCEPT and INTERSECT operations can return duplicates
 314    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 315
 316    # Wrap derived values in parens, usually standard but spark doesn't support it
 317    WRAP_DERIVED_VALUES = True
 318
 319    # Whether create function uses an AS before the RETURN
 320    CREATE_FUNCTION_RETURN_AS = True
 321
 322    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 323    MATCHED_BY_SOURCE = True
 324
 325    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 326    SUPPORTS_MERGE_WHERE = False
 327
 328    # Whether the INTERVAL expression works only with values like '1 day'
 329    SINGLE_STRING_INTERVAL = False
 330
 331    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 332    INTERVAL_ALLOWS_PLURAL_FORM = True
 333
 334    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 335    LIMIT_FETCH = "ALL"
 336
 337    # Whether limit and fetch allows expresions or just limits
 338    LIMIT_ONLY_LITERALS = False
 339
 340    # Whether a table is allowed to be renamed with a db
 341    RENAME_TABLE_WITH_DB = True
 342
 343    # The separator for grouping sets and rollups
 344    GROUPINGS_SEP = ","
 345
 346    # The string used for creating an index on a table
 347    INDEX_ON = "ON"
 348
 349    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 350    INOUT_SEPARATOR = " "
 351
 352    # Whether join hints should be generated
 353    JOIN_HINTS = True
 354
 355    # Whether directed joins are supported
 356    DIRECTED_JOINS = False
 357
 358    # Whether table hints should be generated
 359    TABLE_HINTS = True
 360
 361    # Whether query hints should be generated
 362    QUERY_HINTS = True
 363
 364    # What kind of separator to use for query hints
 365    QUERY_HINT_SEP = ", "
 366
 367    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 368    IS_BOOL_ALLOWED = True
 369
 370    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 371    DUPLICATE_KEY_UPDATE_WITH_SET = True
 372
 373    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 374    LIMIT_IS_TOP = False
 375
 376    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 377    RETURNING_END = True
 378
 379    # Whether to generate an unquoted value for EXTRACT's date part argument
 380    EXTRACT_ALLOWS_QUOTES = True
 381
 382    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 383    TZ_TO_WITH_TIME_ZONE = False
 384
 385    # Whether the NVL2 function is supported
 386    NVL2_SUPPORTED = True
 387
 388    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 389    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 390
 391    # Whether VALUES statements can be used as derived tables.
 392    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 393    # SELECT * VALUES into SELECT UNION
 394    VALUES_AS_TABLE = True
 395
 396    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 397    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 398
 399    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 400    UNNEST_WITH_ORDINALITY = True
 401
 402    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 403    AGGREGATE_FILTER_SUPPORTED = True
 404
 405    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 406    SEMI_ANTI_JOIN_WITH_SIDE = True
 407
 408    # Whether to include the type of a computed column in the CREATE DDL
 409    COMPUTED_COLUMN_WITH_TYPE = True
 410
 411    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 412    SUPPORTS_TABLE_COPY = True
 413
 414    # Whether parentheses are required around the table sample's expression
 415    TABLESAMPLE_REQUIRES_PARENS = True
 416
 417    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 418    TABLESAMPLE_SIZE_IS_ROWS = True
 419
 420    # The keyword(s) to use when generating a sample clause
 421    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 422
 423    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 424    TABLESAMPLE_WITH_METHOD = True
 425
 426    # The keyword to use when specifying the seed of a sample clause
 427    TABLESAMPLE_SEED_KEYWORD = "SEED"
 428
 429    # Whether COLLATE is a function instead of a binary operator
 430    COLLATE_IS_FUNC = False
 431
 432    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 433    DATA_TYPE_SPECIFIERS_ALLOWED = False
 434
 435    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 436    ENSURE_BOOLS = False
 437
 438    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 439    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 440
 441    # Whether CONCAT requires >1 arguments
 442    SUPPORTS_SINGLE_ARG_CONCAT = True
 443
 444    # Whether LAST_DAY function supports a date part argument
 445    LAST_DAY_SUPPORTS_DATE_PART = True
 446
 447    # Whether named columns are allowed in table aliases
 448    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 449
 450    # Whether named columns are allowed in CTE definitions
 451    SUPPORTS_NAMED_CTE_COLUMNS = True
 452
 453    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 454    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 455
 456    # What delimiter to use for separating JSON key/value pairs
 457    JSON_KEY_VALUE_PAIR_SEP = ":"
 458
 459    # INSERT OVERWRITE TABLE x override
 460    INSERT_OVERWRITE = " OVERWRITE TABLE"
 461
 462    # Whether the SELECT .. INTO syntax is used instead of CTAS
 463    SUPPORTS_SELECT_INTO = False
 464
 465    # Whether UNLOGGED tables can be created
 466    SUPPORTS_UNLOGGED_TABLES = False
 467
 468    # Whether the CREATE TABLE LIKE statement is supported
 469    SUPPORTS_CREATE_TABLE_LIKE = True
 470
 471    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 472    SUPPORTS_MODIFY_COLUMN = False
 473
 474    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 475    SUPPORTS_CHANGE_COLUMN = False
 476
 477    # Whether the LikeProperty needs to be specified inside of the schema clause
 478    LIKE_PROPERTY_INSIDE_SCHEMA = False
 479
 480    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 481    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 482    MULTI_ARG_DISTINCT = True
 483
 484    # Whether the JSON extraction operators expect a value of type JSON
 485    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 486
 487    # Whether bracketed keys like ["foo"] are supported in JSON paths
 488    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 489
 490    # Whether to escape keys using single quotes in JSON paths
 491    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 492
 493    # The JSONPathPart expressions supported by this dialect
 494    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 495
 496    # Whether any(f(x) for x in array) can be implemented by this dialect
 497    CAN_IMPLEMENT_ARRAY_ANY = False
 498
 499    # Whether the function TO_NUMBER is supported
 500    SUPPORTS_TO_NUMBER = True
 501
 502    # Whether EXCLUDE in window specification is supported
 503    SUPPORTS_WINDOW_EXCLUDE = False
 504
 505    # Whether or not set op modifiers apply to the outer set op or select.
 506    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 507    # True means limit 1 happens after the set op, False means it it happens on y.
 508    SET_OP_MODIFIERS = True
 509
 510    # Whether parameters from COPY statement are wrapped in parentheses
 511    COPY_PARAMS_ARE_WRAPPED = True
 512
 513    # Whether values of params are set with "=" token or empty space
 514    COPY_PARAMS_EQ_REQUIRED = False
 515
 516    # Whether COPY statement has INTO keyword
 517    COPY_HAS_INTO_KEYWORD = True
 518
 519    # Whether the conditional TRY(expression) function is supported
 520    TRY_SUPPORTED = True
 521
 522    # Whether the UESCAPE syntax in unicode strings is supported
 523    SUPPORTS_UESCAPE = True
 524
 525    # Function used to replace escaped unicode codes in unicode strings
 526    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 527
 528    # The keyword to use when generating a star projection with excluded columns
 529    STAR_EXCEPT = "EXCEPT"
 530
 531    # The HEX function name
 532    HEX_FUNC = "HEX"
 533
 534    # The keywords to use when prefixing & separating WITH based properties
 535    WITH_PROPERTIES_PREFIX = "WITH"
 536
 537    # Whether to quote the generated expression of exp.JsonPath
 538    QUOTE_JSON_PATH = True
 539
 540    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 541    PAD_FILL_PATTERN_IS_REQUIRED = False
 542
 543    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 544    SUPPORTS_EXPLODING_PROJECTIONS = True
 545
 546    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 547    ARRAY_CONCAT_IS_VAR_LEN = True
 548
 549    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 550    SUPPORTS_CONVERT_TIMEZONE = False
 551
 552    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 553    SUPPORTS_MEDIAN = True
 554
 555    # Whether UNIX_SECONDS(timestamp) is supported
 556    SUPPORTS_UNIX_SECONDS = False
 557
 558    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 559    ALTER_SET_WRAPPED = False
 560
 561    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 562    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 563    # TODO: The normalization should be done by default once we've tested it across all dialects.
 564    NORMALIZE_EXTRACT_DATE_PARTS = False
 565
 566    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 567    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 568
 569    # The function name of the exp.ArraySize expression
 570    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 571
 572    # The syntax to use when altering the type of a column
 573    ALTER_SET_TYPE = "SET DATA TYPE"
 574
 575    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 576    # None -> Doesn't support it at all
 577    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 578    # True (Postgres) -> Explicitly requires it
 579    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 580
 581    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 582    SUPPORTS_DECODE_CASE = True
 583
 584    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 585    SUPPORTS_BETWEEN_FLAGS = False
 586
 587    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 588    SUPPORTS_LIKE_QUANTIFIERS = True
 589
 590    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 591    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 592
 593    # Whether to include the VARIABLE keyword for SET assignments
 594    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 595
 596    # The keyword to use for default value assignment in DECLARE statements
 597    DECLARE_DEFAULT_ASSIGNMENT = "="
 598
 599    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 600    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 601    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 602    UPDATE_STATEMENT_SUPPORTS_FROM = True
 603
 604    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 605    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 606
 607    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 608    # - Snowflake: DROP ICEBERG TABLE a.b;
 609    # - DuckDB:    DROP TABLE a.b;
 610    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 611
 612    TYPE_MAPPING: t.ClassVar = {
 613        exp.DType.DATETIME2: "TIMESTAMP",
 614        exp.DType.NCHAR: "CHAR",
 615        exp.DType.NVARCHAR: "VARCHAR",
 616        exp.DType.MEDIUMTEXT: "TEXT",
 617        exp.DType.LONGTEXT: "TEXT",
 618        exp.DType.TINYTEXT: "TEXT",
 619        exp.DType.BLOB: "VARBINARY",
 620        exp.DType.MEDIUMBLOB: "BLOB",
 621        exp.DType.LONGBLOB: "BLOB",
 622        exp.DType.TINYBLOB: "BLOB",
 623        exp.DType.INET: "INET",
 624        exp.DType.ROWVERSION: "VARBINARY",
 625        exp.DType.SMALLDATETIME: "TIMESTAMP",
 626    }
 627
 628    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 629
 630    # mapping of DType to its default parameters, bounds
 631    TYPE_PARAM_SETTINGS: t.ClassVar[
 632        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 633    ] = {}
 634
 635    TIME_PART_SINGULARS: t.ClassVar = {
 636        "MICROSECONDS": "MICROSECOND",
 637        "SECONDS": "SECOND",
 638        "MINUTES": "MINUTE",
 639        "HOURS": "HOUR",
 640        "DAYS": "DAY",
 641        "WEEKS": "WEEK",
 642        "MONTHS": "MONTH",
 643        "QUARTERS": "QUARTER",
 644        "YEARS": "YEAR",
 645    }
 646
 647    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 648        "cluster": lambda self, e: self.sql(e, "cluster"),
 649        "distribute": lambda self, e: self.sql(e, "distribute"),
 650        "sort": lambda self, e: self.sql(e, "sort"),
 651        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 652    }
 653
 654    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 655
 656    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 657
 658    PARAMETER_TOKEN = "@"
 659    NAMED_PLACEHOLDER_TOKEN = ":"
 660
 661    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 662
 663    PROPERTIES_LOCATION: t.ClassVar = {
 664        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 665        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 666        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 667        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 668        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 669        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 671        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 672        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 673        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 674        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 675        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 676        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 677        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 678        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 679        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 681        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 682        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 683        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 684        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 685        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 686        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 687        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 688        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 689        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 690        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 691        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 692        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 693        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 694        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 695        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 696        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 697        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 698        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 699        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 700        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 701        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 702        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 703        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 704        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 705        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 706        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 707        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 708        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 709        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 710        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 711        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 712        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 713        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 714        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 717        exp.LogProperty: exp.Properties.Location.POST_NAME,
 718        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 719        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 720        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 721        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 723        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 724        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 726        exp.Order: exp.Properties.Location.POST_SCHEMA,
 727        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 729        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 731        exp.Property: exp.Properties.Location.POST_WITH,
 732        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 735        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 736        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 737        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 743        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 744        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 745        exp.Set: exp.Properties.Location.POST_SCHEMA,
 746        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 747        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 748        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 750        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 751        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 752        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 753        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 754        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 758        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 759        exp.Tags: exp.Properties.Location.POST_WITH,
 760        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 761        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 762        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 763        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 764        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 765        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 766        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 767        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 770        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 771        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 772        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 773        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 774        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 776        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 777    }
 778
 779    # Keywords that can't be used as unquoted identifier names
 780    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 781
 782    # Exprs whose comments are separated from them for better formatting
 783    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 784        exp.Command,
 785        exp.Create,
 786        exp.Describe,
 787        exp.Delete,
 788        exp.Drop,
 789        exp.From,
 790        exp.Insert,
 791        exp.Join,
 792        exp.MultitableInserts,
 793        exp.Order,
 794        exp.Group,
 795        exp.Having,
 796        exp.Select,
 797        exp.SetOperation,
 798        exp.Update,
 799        exp.Where,
 800        exp.With,
 801    )
 802
 803    # Exprs that should not have their comments generated in maybe_comment
 804    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 805        exp.Binary,
 806        exp.SetOperation,
 807    )
 808
 809    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 810    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 811        exp.Column,
 812        exp.Literal,
 813        exp.Neg,
 814        exp.Paren,
 815    )
 816
 817    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 818        exp.DType.NVARCHAR,
 819        exp.DType.VARCHAR,
 820        exp.DType.CHAR,
 821        exp.DType.NCHAR,
 822    }
 823
 824    # Exprs that need to have all CTEs under them bubbled up to them
 825    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 826
 827    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 828
 829    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 830
 831    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 832
 833    __slots__ = (
 834        "pretty",
 835        "identify",
 836        "normalize",
 837        "pad",
 838        "_indent",
 839        "normalize_functions",
 840        "unsupported_level",
 841        "max_unsupported",
 842        "leading_comma",
 843        "max_text_width",
 844        "comments",
 845        "dialect",
 846        "unsupported_messages",
 847        "_escaped_quote_end",
 848        "_escaped_byte_quote_end",
 849        "_escaped_identifier_end",
 850        "_next_name",
 851        "_identifier_start",
 852        "_identifier_end",
 853        "_quote_json_path_key_using_brackets",
 854        "_dispatch",
 855    )
 856
 857    def __init__(
 858        self,
 859        pretty: bool | int | None = None,
 860        identify: str | bool = False,
 861        normalize: bool = False,
 862        pad: int = 2,
 863        indent: int = 2,
 864        normalize_functions: str | bool | None = None,
 865        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 866        max_unsupported: int = 3,
 867        leading_comma: bool = False,
 868        max_text_width: int = 80,
 869        comments: bool = True,
 870        dialect: DialectType = None,
 871    ):
 872        import sqlglot
 873        import sqlglot.dialects.dialect
 874
 875        self.pretty = pretty if pretty is not None else sqlglot.pretty
 876        self.identify = identify
 877        self.normalize = normalize
 878        self.pad = pad
 879        self._indent = indent
 880        self.unsupported_level = unsupported_level
 881        self.max_unsupported = max_unsupported
 882        self.leading_comma = leading_comma
 883        self.max_text_width = max_text_width
 884        self.comments = comments
 885        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 886
 887        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 888        self.normalize_functions = (
 889            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 890        )
 891
 892        self.unsupported_messages: list[str] = []
 893        self._escaped_quote_end: str = (
 894            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 895        )
 896        self._escaped_byte_quote_end: str = (
 897            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 898            if self.dialect.BYTE_END
 899            else ""
 900        )
 901        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 902
 903        self._next_name = name_sequence("_t")
 904
 905        self._identifier_start = self.dialect.IDENTIFIER_START
 906        self._identifier_end = self.dialect.IDENTIFIER_END
 907
 908        self._quote_json_path_key_using_brackets = True
 909
 910        cls = type(self)
 911        dispatch = _DISPATCH_CACHE.get(cls)
 912        if dispatch is None:
 913            dispatch = _build_dispatch(cls)
 914            _DISPATCH_CACHE[cls] = dispatch
 915        self._dispatch = dispatch
 916
 917    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 918        """
 919        Generates the SQL string corresponding to the given syntax tree.
 920
 921        Args:
 922            expression: The syntax tree.
 923            copy: Whether to copy the expression. The generator performs mutations so
 924                it is safer to copy.
 925
 926        Returns:
 927            The SQL string corresponding to `expression`.
 928        """
 929        if copy:
 930            expression = expression.copy()
 931
 932        expression = self.preprocess(expression)
 933
 934        self.unsupported_messages = []
 935        sql = self.sql(expression).strip()
 936
 937        if self.pretty:
 938            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 939
 940        if self.unsupported_level == ErrorLevel.IGNORE:
 941            return sql
 942
 943        if self.unsupported_level == ErrorLevel.WARN:
 944            for msg in self.unsupported_messages:
 945                logger.warning(msg)
 946        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 947            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 948
 949        return sql
 950
 951    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 952        """Apply generic preprocessing transformations to a given expression."""
 953        expression = self._move_ctes_to_top_level(expression)
 954
 955        if self.ENSURE_BOOLS:
 956            import sqlglot.transforms
 957
 958            expression = sqlglot.transforms.ensure_bools(expression)
 959
 960        return expression
 961
 962    def _move_ctes_to_top_level(self, expression: E) -> E:
 963        if (
 964            not expression.parent
 965            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 966            and any(node.parent is not expression for node in expression.find_all(exp.With))
 967        ):
 968            import sqlglot.transforms
 969
 970            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
 971        return expression
 972
 973    def unsupported(self, message: str) -> None:
 974        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 975            raise UnsupportedError(message)
 976        self.unsupported_messages.append(message)
 977
 978    def sep(self, sep: str = " ") -> str:
 979        return f"{sep.strip()}\n" if self.pretty else sep
 980
 981    def seg(self, sql: str, sep: str = " ") -> str:
 982        return f"{self.sep(sep)}{sql}"
 983
 984    def sanitize_comment(self, comment: str) -> str:
 985        comment = " " + comment if comment[0].strip() else comment
 986        comment = comment + " " if comment[-1].strip() else comment
 987
 988        # Escape block comment markers to prevent premature closure or unintended nesting.
 989        # This is necessary because single-line comments (--) are converted to block comments
 990        # (/* */) on output, and any */ in the original text would close the comment early.
 991        comment = comment.replace("*/", "* /").replace("/*", "/ *")
 992
 993        return comment
 994
 995    def maybe_comment(
 996        self,
 997        sql: str,
 998        expression: exp.Expr | None = None,
 999        comments: list[str] | None = None,
1000        separated: bool = False,
1001    ) -> str:
1002        comments = (
1003            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1004            if self.comments
1005            else None
1006        )
1007
1008        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1009            return sql
1010
1011        comments_sql = " ".join(
1012            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1013        )
1014
1015        if not comments_sql:
1016            return sql
1017
1018        comments_sql = self._replace_line_breaks(comments_sql)
1019
1020        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1021            return (
1022                f"{self.sep()}{comments_sql}{sql}"
1023                if not sql or sql[0].isspace()
1024                else f"{comments_sql}{self.sep()}{sql}"
1025            )
1026
1027        return f"{sql} {comments_sql}"
1028
1029    def wrap(self, expression: exp.Expr | str) -> str:
1030        this_sql = (
1031            self.sql(expression)
1032            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1033            else self.sql(expression, "this")
1034        )
1035        if not this_sql:
1036            return "()"
1037
1038        this_sql = self.indent(this_sql, level=1, pad=0)
1039        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1040
1041    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1042        original = self.identify
1043        self.identify = False
1044        result = func(*args, **kwargs)
1045        self.identify = original
1046        return result
1047
1048    def normalize_func(self, name: str) -> str:
1049        if self.normalize_functions == "upper" or self.normalize_functions is True:
1050            return name.upper()
1051        if self.normalize_functions == "lower":
1052            return name.lower()
1053        return name
1054
1055    def indent(
1056        self,
1057        sql: str,
1058        level: int = 0,
1059        pad: int | None = None,
1060        skip_first: bool = False,
1061        skip_last: bool = False,
1062    ) -> str:
1063        if not self.pretty or not sql:
1064            return sql
1065
1066        pad = self.pad if pad is None else pad
1067        lines = sql.split("\n")
1068
1069        return "\n".join(
1070            (
1071                line
1072                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1073                else f"{' ' * (level * self._indent + pad)}{line}"
1074            )
1075            for i, line in enumerate(lines)
1076        )
1077
1078    def sql(
1079        self,
1080        expression: str | exp.Expr | None,
1081        key: str | None = None,
1082        comment: bool = True,
1083    ) -> str:
1084        if not expression:
1085            return ""
1086
1087        if isinstance(expression, str):
1088            return expression
1089
1090        if key:
1091            value = expression.args.get(key)
1092            if value:
1093                return self.sql(value)
1094            return ""
1095
1096        handler = self._dispatch.get(expression.__class__)
1097
1098        if handler:
1099            sql = handler(self, expression)
1100        elif isinstance(expression, exp.Func):
1101            sql = self.function_fallback_sql(expression)
1102        elif isinstance(expression, exp.Property):
1103            sql = self.property_sql(expression)
1104        else:
1105            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1106
1107        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1108
1109    def uncache_sql(self, expression: exp.Uncache) -> str:
1110        table = self.sql(expression, "this")
1111        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1112        return f"UNCACHE TABLE{exists_sql} {table}"
1113
1114    def cache_sql(self, expression: exp.Cache) -> str:
1115        lazy = " LAZY" if expression.args.get("lazy") else ""
1116        table = self.sql(expression, "this")
1117        options = expression.args.get("options")
1118        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1119        sql = self.sql(expression, "expression")
1120        sql = f" AS{self.sep()}{sql}" if sql else ""
1121        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1122        return self.prepend_ctes(expression, sql)
1123
1124    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1125        default = "DEFAULT " if expression.args.get("default") else ""
1126        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1127
1128    def column_parts(self, expression: exp.Column) -> str:
1129        return ".".join(
1130            self.sql(part)
1131            for part in (
1132                expression.args.get("catalog"),
1133                expression.args.get("db"),
1134                expression.args.get("table"),
1135                expression.args.get("this"),
1136            )
1137            if part
1138        )
1139
1140    def column_sql(self, expression: exp.Column) -> str:
1141        join_mark = " (+)" if expression.args.get("join_mark") else ""
1142
1143        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1144            join_mark = ""
1145            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1146
1147        return f"{self.column_parts(expression)}{join_mark}"
1148
1149    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1150        return self.column_sql(expression)
1151
1152    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1153        this = self.sql(expression, "this")
1154        this = f" {this}" if this else ""
1155        position = self.sql(expression, "position")
1156        return f"{position}{this}"
1157
1158    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1159        column = self.sql(expression, "this")
1160        kind = self.sql(expression, "kind")
1161        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1162        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1163        kind = f"{sep}{kind}" if kind else ""
1164        constraints = f" {constraints}" if constraints else ""
1165        position = self.sql(expression, "position")
1166        position = f" {position}" if position else ""
1167
1168        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1169            kind = ""
1170
1171        return f"{exists}{column}{kind}{constraints}{position}"
1172
1173    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1174        this = self.sql(expression, "this")
1175        kind_sql = self.sql(expression, "kind").strip()
1176        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1177
1178    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1179        this = self.sql(expression, "this")
1180        if expression.args.get("not_null"):
1181            persisted = " PERSISTED NOT NULL"
1182        elif expression.args.get("persisted"):
1183            persisted = " PERSISTED"
1184        else:
1185            persisted = ""
1186
1187        return f"AS {this}{persisted}"
1188
1189    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1190        return self.token_sql(TokenType.AUTO_INCREMENT)
1191
1192    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1193        if isinstance(expression.this, list):
1194            this = self.wrap(self.expressions(expression, key="this", flat=True))
1195        else:
1196            this = self.sql(expression, "this")
1197
1198        return f"COMPRESS {this}"
1199
1200    def generatedasidentitycolumnconstraint_sql(
1201        self, expression: exp.GeneratedAsIdentityColumnConstraint
1202    ) -> str:
1203        this = ""
1204        if expression.this is not None:
1205            on_null = " ON NULL" if expression.args.get("on_null") else ""
1206            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1207
1208        start = expression.args.get("start")
1209        start = f"START WITH {start}" if start else ""
1210        increment = expression.args.get("increment")
1211        increment = f" INCREMENT BY {increment}" if increment else ""
1212        minvalue = expression.args.get("minvalue")
1213        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1214        maxvalue = expression.args.get("maxvalue")
1215        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1216        cycle = expression.args.get("cycle")
1217        cycle_sql = ""
1218
1219        if cycle is not None:
1220            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1221            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1222
1223        sequence_opts = ""
1224        if start or increment or cycle_sql:
1225            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1226            sequence_opts = f" ({sequence_opts.strip()})"
1227
1228        expr = self.sql(expression, "expression")
1229        expr = f"({expr})" if expr else "IDENTITY"
1230
1231        return f"GENERATED{this} AS {expr}{sequence_opts}"
1232
1233    def generatedasrowcolumnconstraint_sql(
1234        self, expression: exp.GeneratedAsRowColumnConstraint
1235    ) -> str:
1236        start = "START" if expression.args.get("start") else "END"
1237        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1238        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1239
1240    def periodforsystemtimeconstraint_sql(
1241        self, expression: exp.PeriodForSystemTimeConstraint
1242    ) -> str:
1243        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1244
1245    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1246        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1247
1248    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1249        desc = expression.args.get("desc")
1250        if desc is not None:
1251            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1252        options = self.expressions(expression, key="options", flat=True, sep=" ")
1253        options = f" {options}" if options else ""
1254        return f"PRIMARY KEY{options}"
1255
1256    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1257        this = self.sql(expression, "this")
1258        this = f" {this}" if this else ""
1259        index_type = expression.args.get("index_type")
1260        index_type = f" USING {index_type}" if index_type else ""
1261        on_conflict = self.sql(expression, "on_conflict")
1262        on_conflict = f" {on_conflict}" if on_conflict else ""
1263        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1264        options = self.expressions(expression, key="options", flat=True, sep=" ")
1265        options = f" {options}" if options else ""
1266        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1267
1268    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1269        input_ = expression.args.get("input_")
1270        output = expression.args.get("output")
1271        variadic = expression.args.get("variadic")
1272
1273        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1274        if variadic:
1275            return "VARIADIC"
1276
1277        if input_ and output:
1278            return f"IN{self.INOUT_SEPARATOR}OUT"
1279        if input_:
1280            return "IN"
1281        if output:
1282            return "OUT"
1283
1284        return ""
1285
1286    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1287        return self.sql(expression, "this")
1288
1289    def create_sql(self, expression: exp.Create) -> str:
1290        kind = self.sql(expression, "kind")
1291        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1292
1293        properties = expression.args.get("properties")
1294
1295        if (
1296            kind == "TRIGGER"
1297            and properties
1298            and properties.expressions
1299            and isinstance(properties.expressions[0], exp.TriggerProperties)
1300            and properties.expressions[0].args.get("constraint")
1301        ):
1302            kind = f"CONSTRAINT {kind}"
1303
1304        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1305
1306        this = self.createable_sql(expression, properties_locs)
1307
1308        properties_sql = ""
1309        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1310            exp.Properties.Location.POST_WITH
1311        ):
1312            props_ast = exp.Properties(
1313                expressions=[
1314                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1315                    *properties_locs[exp.Properties.Location.POST_WITH],
1316                ]
1317            )
1318            props_ast.parent = expression
1319            properties_sql = self.sql(props_ast)
1320
1321            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1322                properties_sql = self.sep() + properties_sql
1323            elif not self.pretty:
1324                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1325                properties_sql = f" {properties_sql}"
1326
1327        begin = " BEGIN" if expression.args.get("begin") else ""
1328
1329        expression_sql = self.sql(expression, "expression")
1330        if expression_sql:
1331            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1332
1333            if not isinstance(expression.expression, exp.MacroOverloads) and (
1334                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1335            ):
1336                postalias_props_sql = ""
1337                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1338                    postalias_props_sql = self.properties(
1339                        exp.Properties(
1340                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1341                        ),
1342                        wrapped=False,
1343                    )
1344                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1345                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1346
1347        postindex_props_sql = ""
1348        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1349            postindex_props_sql = self.properties(
1350                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1351                wrapped=False,
1352                prefix=" ",
1353            )
1354
1355        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1356        indexes = f" {indexes}" if indexes else ""
1357        index_sql = indexes + postindex_props_sql
1358
1359        replace = " OR REPLACE" if expression.args.get("replace") else ""
1360        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1361        unique = " UNIQUE" if expression.args.get("unique") else ""
1362
1363        clustered = expression.args.get("clustered")
1364        if clustered is None:
1365            clustered_sql = ""
1366        elif clustered:
1367            clustered_sql = " CLUSTERED COLUMNSTORE"
1368        else:
1369            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1370
1371        postcreate_props_sql = ""
1372        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1373            postcreate_props_sql = self.properties(
1374                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1375                sep=" ",
1376                prefix=" ",
1377                wrapped=False,
1378            )
1379
1380        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1381
1382        postexpression_props_sql = ""
1383        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1384            postexpression_props_sql = self.properties(
1385                exp.Properties(
1386                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1387                ),
1388                sep=" ",
1389                prefix=" ",
1390                wrapped=False,
1391            )
1392
1393        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1394        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1395        no_schema_binding = (
1396            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1397        )
1398
1399        clone = self.sql(expression, "clone")
1400        clone = f" {clone}" if clone else ""
1401
1402        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1403            properties_expression = f"{expression_sql}{properties_sql}"
1404        else:
1405            properties_expression = f"{properties_sql}{expression_sql}"
1406
1407        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1408        return self.prepend_ctes(expression, expression_sql)
1409
1410    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1411        start = self.sql(expression, "start")
1412        start = f"START WITH {start}" if start else ""
1413        increment = self.sql(expression, "increment")
1414        increment = f" INCREMENT BY {increment}" if increment else ""
1415        minvalue = self.sql(expression, "minvalue")
1416        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1417        maxvalue = self.sql(expression, "maxvalue")
1418        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1419        owned = self.sql(expression, "owned")
1420        owned = f" OWNED BY {owned}" if owned else ""
1421
1422        cache = expression.args.get("cache")
1423        if cache is None:
1424            cache_str = ""
1425        elif cache is True:
1426            cache_str = " CACHE"
1427        else:
1428            cache_str = f" CACHE {cache}"
1429
1430        options = self.expressions(expression, key="options", flat=True, sep=" ")
1431        options = f" {options}" if options else ""
1432
1433        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1434
1435    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1436        timing = expression.args.get("timing", "")
1437        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1438        timing_events = f"{timing} {events}".strip() if timing or events else ""
1439
1440        parts = [timing_events, "ON", self.sql(expression, "table")]
1441
1442        if referenced_table := expression.args.get("referenced_table"):
1443            parts.extend(["FROM", self.sql(referenced_table)])
1444
1445        if deferrable := expression.args.get("deferrable"):
1446            parts.append(deferrable)
1447
1448        if initially := expression.args.get("initially"):
1449            parts.append(f"INITIALLY {initially}")
1450
1451        if referencing := expression.args.get("referencing"):
1452            parts.append(self.sql(referencing))
1453
1454        if for_each := expression.args.get("for_each"):
1455            parts.append(f"FOR EACH {for_each}")
1456
1457        if when := expression.args.get("when"):
1458            parts.append(f"WHEN ({self.sql(when)})")
1459
1460        parts.append(self.sql(expression, "execute"))
1461
1462        return self.sep().join(parts)
1463
1464    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1465        parts = []
1466
1467        if old_alias := expression.args.get("old"):
1468            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1469
1470        if new_alias := expression.args.get("new"):
1471            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1472
1473        return f"REFERENCING {' '.join(parts)}"
1474
1475    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1476        columns = expression.args.get("columns")
1477        if columns:
1478            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1479
1480        return self.sql(expression, "this")
1481
1482    def clone_sql(self, expression: exp.Clone) -> str:
1483        this = self.sql(expression, "this")
1484        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1485        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1486        return f"{shallow}{keyword} {this}"
1487
1488    def describe_sql(self, expression: exp.Describe) -> str:
1489        style = expression.args.get("style")
1490        style = f" {style}" if style else ""
1491        partition = self.sql(expression, "partition")
1492        partition = f" {partition}" if partition else ""
1493        format = self.sql(expression, "format")
1494        format = f" {format}" if format else ""
1495        as_json = " AS JSON" if expression.args.get("as_json") else ""
1496
1497        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1498
1499    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1500        tag = self.sql(expression, "tag")
1501        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1502
1503    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1504        with_ = self.sql(expression, "with_")
1505        if with_:
1506            sql = f"{with_}{self.sep()}{sql}"
1507        return sql
1508
1509    def with_sql(self, expression: exp.With) -> str:
1510        sql = self.expressions(expression, flat=True)
1511        recursive = (
1512            "RECURSIVE "
1513            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1514            else ""
1515        )
1516        search = self.sql(expression, "search")
1517        search = f" {search}" if search else ""
1518
1519        return f"WITH {recursive}{sql}{search}"
1520
1521    def cte_sql(self, expression: exp.CTE) -> str:
1522        alias = expression.args.get("alias")
1523        if alias:
1524            alias.add_comments(expression.pop_comments())
1525
1526        alias_sql = self.sql(expression, "alias")
1527
1528        materialized = expression.args.get("materialized")
1529        if materialized is False:
1530            materialized = "NOT MATERIALIZED "
1531        elif materialized:
1532            materialized = "MATERIALIZED "
1533
1534        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1535        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1536
1537        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1538
1539    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1540        alias = self.sql(expression, "this")
1541        columns = self.expressions(expression, key="columns", flat=True)
1542        columns = f"({columns})" if columns else ""
1543
1544        if (
1545            columns
1546            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1547            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1548        ):
1549            columns = ""
1550            self.unsupported("Named columns are not supported in table alias.")
1551
1552        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1553            alias = self._next_name()
1554
1555        return f"{alias}{columns}"
1556
1557    def bitstring_sql(self, expression: exp.BitString) -> str:
1558        this = self.sql(expression, "this")
1559        if self.dialect.BIT_START:
1560            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1561        return f"{int(this, 2)}"
1562
1563    def hexstring_sql(
1564        self, expression: exp.HexString, binary_function_repr: str | None = None
1565    ) -> str:
1566        this = self.sql(expression, "this")
1567        is_integer_type = expression.args.get("is_integer")
1568
1569        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1570            not self.dialect.HEX_START and not binary_function_repr
1571        ):
1572            # Integer representation will be returned if:
1573            # - The read dialect treats the hex value as integer literal but not the write
1574            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1575            return f"{int(this, 16)}"
1576
1577        if not is_integer_type:
1578            # Read dialect treats the hex value as BINARY/BLOB
1579            if binary_function_repr:
1580                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1581                return self.func(binary_function_repr, exp.Literal.string(this))
1582            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1583                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1584                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1585
1586        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1587
1588    def bytestring_sql(self, expression: exp.ByteString) -> str:
1589        this = self.sql(expression, "this")
1590        if self.dialect.BYTE_START:
1591            escaped_byte_string = self.escape_str(
1592                this,
1593                escape_backslash=False,
1594                delimiter=self.dialect.BYTE_END,
1595                escaped_delimiter=self._escaped_byte_quote_end,
1596                is_byte_string=True,
1597            )
1598            is_bytes = expression.args.get("is_bytes", False)
1599            delimited_byte_string = (
1600                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1601            )
1602            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1603                return self.sql(
1604                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1605                )
1606            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1607                return self.sql(
1608                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1609                )
1610
1611            return delimited_byte_string
1612
1613        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1614            return self.sql(exp.Literal.string(this))
1615
1616        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1617        return ""
1618
1619    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1620        this = self.sql(expression, "this")
1621        escape = expression.args.get("escape")
1622
1623        if self.dialect.UNICODE_START:
1624            escape_substitute = r"\\\1"
1625            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1626        else:
1627            escape_substitute = r"\\u\1"
1628            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1629
1630        if escape:
1631            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1632            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1633        else:
1634            escape_pattern = ESCAPED_UNICODE_RE
1635            escape_sql = ""
1636
1637        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1638            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1639
1640        return f"{left_quote}{this}{right_quote}{escape_sql}"
1641
1642    def rawstring_sql(self, expression: exp.RawString) -> str:
1643        string = expression.this
1644        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1645            string = string.replace("\\", "\\\\")
1646
1647        string = self.escape_str(string, escape_backslash=False)
1648        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1649
1650    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1651        this = self.sql(expression, "this")
1652        specifier = self.sql(expression, "expression")
1653        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1654        return f"{this}{specifier}"
1655
1656    def datatype_param_bound_limiter(
1657        self,
1658        expression: exp.DataType,
1659        type_value: exp.DType,
1660        defaults: tuple[int, ...],
1661        bounds: tuple[int | None, ...],
1662    ) -> exp.DataType:
1663        params = expression.expressions
1664
1665        if not params:
1666            if defaults:
1667                expression.set(
1668                    "expressions",
1669                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1670                )
1671            return expression
1672
1673        if not bounds:
1674            return expression
1675
1676        for i, param in enumerate(params):
1677            bound = bounds[i] if i < len(bounds) else None
1678            if bound is None:
1679                continue
1680
1681            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1682            if (
1683                isinstance(param_value, exp.Literal)
1684                and param_value.is_number
1685                and int(param_value.to_py()) > bound
1686            ):
1687                self.unsupported(
1688                    f"{type_value.value} parameter {param_value.name} exceeds "
1689                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1690                )
1691                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1692
1693        return expression
1694
1695    def datatype_sql(self, expression: exp.DataType) -> str:
1696        nested = ""
1697        values = ""
1698
1699        expr_nested = expression.args.get("nested")
1700        type_value = expression.this
1701
1702        if (
1703            not expr_nested
1704            and isinstance(type_value, exp.DType)
1705            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1706        ):
1707            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1708
1709        interior = (
1710            self.expressions(
1711                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1712            )
1713            if expr_nested and self.pretty
1714            else self.expressions(expression, flat=True)
1715        )
1716
1717        if type_value in self.UNSUPPORTED_TYPES:
1718            self.unsupported(
1719                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1720            )
1721
1722        type_sql: t.Any = ""
1723        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1724            type_sql = self.sql(expression, "kind")
1725        elif type_value == exp.DType.CHARACTER_SET:
1726            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1727        else:
1728            type_sql = (
1729                self.TYPE_MAPPING.get(type_value, type_value.value)
1730                if isinstance(type_value, exp.DType)
1731                else type_value
1732            )
1733
1734        if interior:
1735            if expr_nested:
1736                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1737                if expression.args.get("values") is not None:
1738                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1739                    values = self.expressions(expression, key="values", flat=True)
1740                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1741            elif type_value == exp.DType.INTERVAL:
1742                nested = f" {interior}"
1743            else:
1744                nested = f"({interior})"
1745
1746        type_sql = f"{type_sql}{nested}{values}"
1747        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1748            exp.DType.TIMETZ,
1749            exp.DType.TIMESTAMPTZ,
1750        ):
1751            type_sql = f"{type_sql} WITH TIME ZONE"
1752
1753        collate = self.sql(expression, "collate")
1754        if collate:
1755            type_sql = f"{type_sql} COLLATE {collate}"
1756
1757        return type_sql
1758
1759    def directory_sql(self, expression: exp.Directory) -> str:
1760        local = "LOCAL " if expression.args.get("local") else ""
1761        row_format = self.sql(expression, "row_format")
1762        row_format = f" {row_format}" if row_format else ""
1763        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1764
1765    def delete_sql(self, expression: exp.Delete) -> str:
1766        hint = self.sql(expression, "hint")
1767        this = self.sql(expression, "this")
1768        this = f" FROM {this}" if this else ""
1769        using = self.expressions(expression, key="using")
1770        using = f" USING {using}" if using else ""
1771        cluster = self.sql(expression, "cluster")
1772        cluster = f" {cluster}" if cluster else ""
1773        where = self.sql(expression, "where")
1774        returning = self.sql(expression, "returning")
1775        order = self.sql(expression, "order")
1776        limit = self.sql(expression, "limit")
1777        tables = self.expressions(expression, key="tables")
1778        tables = f" {tables}" if tables else ""
1779        if self.RETURNING_END:
1780            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1781        else:
1782            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1783        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1784
1785    def drop_sql(self, expression: exp.Drop) -> str:
1786        this = self.sql(expression, "this")
1787        expressions = self.expressions(expression, flat=True)
1788        expressions = f" ({expressions})" if expressions else ""
1789        kind = expression.args["kind"]
1790        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1791        iceberg = (
1792            " ICEBERG"
1793            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1794            else ""
1795        )
1796        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1797        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1798        on_cluster = self.sql(expression, "cluster")
1799        on_cluster = f" {on_cluster}" if on_cluster else ""
1800        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1801        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1802        cascade = " CASCADE" if expression.args.get("cascade") else ""
1803        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1804        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1805        purge = " PURGE" if expression.args.get("purge") else ""
1806        sync = " SYNC" if expression.args.get("sync") else ""
1807        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1808
1809    def set_operation(self, expression: exp.SetOperation) -> str:
1810        op_type = type(expression)
1811        op_name = op_type.key.upper()
1812
1813        distinct = expression.args.get("distinct")
1814        if (
1815            distinct is False
1816            and op_type in (exp.Except, exp.Intersect)
1817            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1818        ):
1819            self.unsupported(f"{op_name} ALL is not supported")
1820
1821        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1822
1823        if distinct is None:
1824            distinct = default_distinct
1825            if distinct is None:
1826                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1827
1828        if distinct is default_distinct:
1829            distinct_or_all = ""
1830        else:
1831            distinct_or_all = " DISTINCT" if distinct else " ALL"
1832
1833        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1834        side_kind = f"{side_kind} " if side_kind else ""
1835
1836        by_name = " BY NAME" if expression.args.get("by_name") else ""
1837        on = self.expressions(expression, key="on", flat=True)
1838        on = f" ON ({on})" if on else ""
1839
1840        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1841
1842    def set_operations(self, expression: exp.SetOperation) -> str:
1843        if not self.SET_OP_MODIFIERS:
1844            limit = expression.args.get("limit")
1845            order = expression.args.get("order")
1846
1847            if limit or order:
1848                select = self._move_ctes_to_top_level(
1849                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1850                )
1851
1852                if limit:
1853                    select = select.limit(limit.pop(), copy=False)
1854                if order:
1855                    select = select.order_by(order.pop(), copy=False)
1856                return self.sql(select)
1857
1858        sqls: list[str] = []
1859        stack: list[str | exp.Expr] = [expression]
1860
1861        while stack:
1862            node = stack.pop()
1863
1864            if isinstance(node, exp.SetOperation):
1865                stack.append(node.expression)
1866                stack.append(
1867                    self.maybe_comment(
1868                        self.set_operation(node), comments=node.comments, separated=True
1869                    )
1870                )
1871                stack.append(node.this)
1872            else:
1873                sqls.append(self.sql(node))
1874
1875        this = self.sep().join(sqls)
1876        this = self.query_modifiers(expression, this)
1877        return self.prepend_ctes(expression, this)
1878
1879    def fetch_sql(self, expression: exp.Fetch) -> str:
1880        direction = expression.args.get("direction")
1881        direction = f" {direction}" if direction else ""
1882        count = self.sql(expression, "count")
1883        count = f" {count}" if count else ""
1884        limit_options = self.sql(expression, "limit_options")
1885        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1886        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1887
1888    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1889        percent = " PERCENT" if expression.args.get("percent") else ""
1890        rows = " ROWS" if expression.args.get("rows") else ""
1891        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1892        if not with_ties and rows:
1893            with_ties = " ONLY"
1894        return f"{percent}{rows}{with_ties}"
1895
1896    def filter_sql(self, expression: exp.Filter) -> str:
1897        if self.AGGREGATE_FILTER_SUPPORTED:
1898            this = self.sql(expression, "this")
1899            where = self.sql(expression, "expression").strip()
1900            return f"{this} FILTER({where})"
1901
1902        agg = expression.this
1903        agg_arg = agg.this
1904        cond = expression.expression.this
1905        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1906        return self.sql(agg)
1907
1908    def hint_sql(self, expression: exp.Hint) -> str:
1909        if not self.QUERY_HINTS:
1910            self.unsupported("Hints are not supported")
1911            return ""
1912
1913        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1914
1915    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1916        using = self.sql(expression, "using")
1917        using = f" USING {using}" if using else ""
1918        columns = self.expressions(expression, key="columns", flat=True)
1919        columns = f"({columns})" if columns else ""
1920        partition_by = self.expressions(expression, key="partition_by", flat=True)
1921        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1922        where = self.sql(expression, "where")
1923        include = self.expressions(expression, key="include", flat=True)
1924        if include:
1925            include = f" INCLUDE ({include})"
1926        with_storage = self.expressions(expression, key="with_storage", flat=True)
1927        with_storage = f" WITH ({with_storage})" if with_storage else ""
1928        tablespace = self.sql(expression, "tablespace")
1929        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1930        on = self.sql(expression, "on")
1931        on = f" ON {on}" if on else ""
1932
1933        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1934
1935    def index_sql(self, expression: exp.Index) -> str:
1936        unique = "UNIQUE " if expression.args.get("unique") else ""
1937        primary = "PRIMARY " if expression.args.get("primary") else ""
1938        amp = "AMP " if expression.args.get("amp") else ""
1939        name = self.sql(expression, "this")
1940        name = f"{name} " if name else ""
1941        table = self.sql(expression, "table")
1942        table = f"{self.INDEX_ON} {table}" if table else ""
1943
1944        index = "INDEX " if not table else ""
1945
1946        params = self.sql(expression, "params")
1947        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1948
1949    def identifier_sql(self, expression: exp.Identifier) -> str:
1950        text = expression.name
1951        lower = text.lower()
1952        quoted = expression.quoted
1953        text = lower if self.normalize and not quoted else text
1954        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1955        if (
1956            quoted
1957            or self.dialect.can_quote(expression, self.identify)
1958            or lower in self.RESERVED_KEYWORDS
1959            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1960        ):
1961            text = (
1962                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1963            )
1964        return text
1965
1966    def hex_sql(self, expression: exp.Hex) -> str:
1967        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1968        if self.dialect.HEX_LOWERCASE:
1969            text = self.func("LOWER", text)
1970
1971        return text
1972
1973    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1974        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1975        if not self.dialect.HEX_LOWERCASE:
1976            text = self.func("LOWER", text)
1977        return text
1978
1979    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1980        input_format = self.sql(expression, "input_format")
1981        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1982        output_format = self.sql(expression, "output_format")
1983        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1984        return self.sep().join((input_format, output_format))
1985
1986    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1987        string = self.sql(exp.Literal.string(expression.name))
1988        return f"{prefix}{string}"
1989
1990    def partition_sql(self, expression: exp.Partition) -> str:
1991        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1992        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1993
1994    def properties_sql(self, expression: exp.Properties) -> str:
1995        root_properties = []
1996        with_properties = []
1997
1998        for p in expression.expressions:
1999            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2000            if p_loc == exp.Properties.Location.POST_WITH:
2001                with_properties.append(p)
2002            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2003                root_properties.append(p)
2004
2005        root_props_ast = exp.Properties(expressions=root_properties)
2006        root_props_ast.parent = expression.parent
2007
2008        with_props_ast = exp.Properties(expressions=with_properties)
2009        with_props_ast.parent = expression.parent
2010
2011        root_props = self.root_properties(root_props_ast)
2012        with_props = self.with_properties(with_props_ast)
2013
2014        if root_props and with_props and not self.pretty:
2015            with_props = " " + with_props
2016
2017        return root_props + with_props
2018
2019    def root_properties(self, properties: exp.Properties) -> str:
2020        if properties.expressions:
2021            return self.expressions(properties, indent=False, sep=" ")
2022        return ""
2023
2024    def properties(
2025        self,
2026        properties: exp.Properties,
2027        prefix: str = "",
2028        sep: str = ", ",
2029        suffix: str = "",
2030        wrapped: bool = True,
2031    ) -> str:
2032        if properties.expressions:
2033            expressions = self.expressions(properties, sep=sep, indent=False)
2034            if expressions:
2035                expressions = self.wrap(expressions) if wrapped else expressions
2036                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2037        return ""
2038
2039    def with_properties(self, properties: exp.Properties) -> str:
2040        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2041
2042    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2043        properties_locs = defaultdict(list)
2044        for p in properties.expressions:
2045            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2046            if p_loc != exp.Properties.Location.UNSUPPORTED:
2047                properties_locs[p_loc].append(p)
2048            else:
2049                self.unsupported(f"Unsupported property {p.key}")
2050
2051        return properties_locs
2052
2053    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2054        if isinstance(expression.this, exp.Dot):
2055            return self.sql(expression, "this")
2056        return f"'{expression.name}'" if string_key else expression.name
2057
2058    def property_sql(self, expression: exp.Property) -> str:
2059        property_cls = expression.__class__
2060        if property_cls == exp.Property:
2061            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2062
2063        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2064        if not property_name:
2065            self.unsupported(f"Unsupported property {expression.key}")
2066
2067        return f"{property_name}={self.sql(expression, 'this')}"
2068
2069    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2070        return f"UUID {self.sql(expression, 'this')}"
2071
2072    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2073        if self.SUPPORTS_CREATE_TABLE_LIKE:
2074            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2075            options = f" {options}" if options else ""
2076
2077            like = f"LIKE {self.sql(expression, 'this')}{options}"
2078            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2079                like = f"({like})"
2080
2081            return like
2082
2083        if expression.expressions:
2084            self.unsupported("Transpilation of LIKE property options is unsupported")
2085
2086        select = exp.select("*").from_(expression.this).limit(0)
2087        return f"AS {self.sql(select)}"
2088
2089    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2090        no = "NO " if expression.args.get("no") else ""
2091        protection = " PROTECTION" if expression.args.get("protection") else ""
2092        return f"{no}FALLBACK{protection}"
2093
2094    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2095        no = "NO " if expression.args.get("no") else ""
2096        local = expression.args.get("local")
2097        local = f"{local} " if local else ""
2098        dual = "DUAL " if expression.args.get("dual") else ""
2099        before = "BEFORE " if expression.args.get("before") else ""
2100        after = "AFTER " if expression.args.get("after") else ""
2101        return f"{no}{local}{dual}{before}{after}JOURNAL"
2102
2103    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2104        freespace = self.sql(expression, "this")
2105        percent = " PERCENT" if expression.args.get("percent") else ""
2106        return f"FREESPACE={freespace}{percent}"
2107
2108    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2109        if expression.args.get("default"):
2110            property = "DEFAULT"
2111        elif expression.args.get("on"):
2112            property = "ON"
2113        else:
2114            property = "OFF"
2115        return f"CHECKSUM={property}"
2116
2117    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2118        if expression.args.get("no"):
2119            return "NO MERGEBLOCKRATIO"
2120        if expression.args.get("default"):
2121            return "DEFAULT MERGEBLOCKRATIO"
2122
2123        percent = " PERCENT" if expression.args.get("percent") else ""
2124        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2125
2126    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2127        expressions = self.expressions(expression, flat=True)
2128        expressions = f"({expressions})" if expressions else ""
2129        return f"USING {self.sql(expression, 'this')}{expressions}"
2130
2131    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2132        default = expression.args.get("default")
2133        minimum = expression.args.get("minimum")
2134        maximum = expression.args.get("maximum")
2135        if default or minimum or maximum:
2136            if default:
2137                prop = "DEFAULT"
2138            elif minimum:
2139                prop = "MINIMUM"
2140            else:
2141                prop = "MAXIMUM"
2142            return f"{prop} DATABLOCKSIZE"
2143        units = expression.args.get("units")
2144        units = f" {units}" if units else ""
2145        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2146
2147    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2148        autotemp = expression.args.get("autotemp")
2149        always = expression.args.get("always")
2150        default = expression.args.get("default")
2151        manual = expression.args.get("manual")
2152        never = expression.args.get("never")
2153
2154        if autotemp is not None:
2155            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2156        elif always:
2157            prop = "ALWAYS"
2158        elif default:
2159            prop = "DEFAULT"
2160        elif manual:
2161            prop = "MANUAL"
2162        elif never:
2163            prop = "NEVER"
2164        return f"BLOCKCOMPRESSION={prop}"
2165
2166    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2167        no = expression.args.get("no")
2168        no = " NO" if no else ""
2169        concurrent = expression.args.get("concurrent")
2170        concurrent = " CONCURRENT" if concurrent else ""
2171        target = self.sql(expression, "target")
2172        target = f" {target}" if target else ""
2173        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2174
2175    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2176        if isinstance(expression.this, list):
2177            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2178        if expression.this:
2179            modulus = self.sql(expression, "this")
2180            remainder = self.sql(expression, "expression")
2181            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2182
2183        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2184        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2185        return f"FROM ({from_expressions}) TO ({to_expressions})"
2186
2187    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2188        this = self.sql(expression, "this")
2189
2190        for_values_or_default = expression.expression
2191        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2192            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2193        else:
2194            for_values_or_default = " DEFAULT"
2195
2196        return f"PARTITION OF {this}{for_values_or_default}"
2197
2198    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2199        kind = expression.args.get("kind")
2200        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2201        for_or_in = expression.args.get("for_or_in")
2202        for_or_in = f" {for_or_in}" if for_or_in else ""
2203        lock_type = expression.args.get("lock_type")
2204        override = " OVERRIDE" if expression.args.get("override") else ""
2205        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2206
2207    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2208        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2209        statistics = expression.args.get("statistics")
2210        statistics_sql = ""
2211        if statistics is not None:
2212            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2213        return f"{data_sql}{statistics_sql}"
2214
2215    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2216        this = self.sql(expression, "this")
2217        this = f"HISTORY_TABLE={this}" if this else ""
2218        data_consistency: str | None = self.sql(expression, "data_consistency")
2219        data_consistency = (
2220            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2221        )
2222        retention_period: str | None = self.sql(expression, "retention_period")
2223        retention_period = (
2224            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2225        )
2226
2227        if this:
2228            on_sql = self.func("ON", this, data_consistency, retention_period)
2229        else:
2230            on_sql = "ON" if expression.args.get("on") else "OFF"
2231
2232        sql = f"SYSTEM_VERSIONING={on_sql}"
2233
2234        return f"WITH({sql})" if expression.args.get("with_") else sql
2235
2236    def insert_sql(self, expression: exp.Insert) -> str:
2237        hint = self.sql(expression, "hint")
2238        overwrite = expression.args.get("overwrite")
2239
2240        if isinstance(expression.this, exp.Directory):
2241            this = " OVERWRITE" if overwrite else " INTO"
2242        else:
2243            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2244
2245        stored = self.sql(expression, "stored")
2246        stored = f" {stored}" if stored else ""
2247        alternative = expression.args.get("alternative")
2248        alternative = f" OR {alternative}" if alternative else ""
2249        ignore = " IGNORE" if expression.args.get("ignore") else ""
2250        is_function = expression.args.get("is_function")
2251        if is_function:
2252            this = f"{this} FUNCTION"
2253        this = f"{this} {self.sql(expression, 'this')}"
2254
2255        exists = " IF EXISTS" if expression.args.get("exists") else ""
2256        where = self.sql(expression, "where")
2257        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2258        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2259        on_conflict = self.sql(expression, "conflict")
2260        on_conflict = f" {on_conflict}" if on_conflict else ""
2261        by_name = " BY NAME" if expression.args.get("by_name") else ""
2262        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2263        returning = self.sql(expression, "returning")
2264
2265        if self.RETURNING_END:
2266            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2267        else:
2268            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2269
2270        partition_by = self.sql(expression, "partition")
2271        partition_by = f" {partition_by}" if partition_by else ""
2272        settings = self.sql(expression, "settings")
2273        settings = f" {settings}" if settings else ""
2274
2275        source = self.sql(expression, "source")
2276        source = f"TABLE {source}" if source else ""
2277
2278        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2279        return self.prepend_ctes(expression, sql)
2280
2281    def introducer_sql(self, expression: exp.Introducer) -> str:
2282        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2283
2284    def kill_sql(self, expression: exp.Kill) -> str:
2285        kind = self.sql(expression, "kind")
2286        kind = f" {kind}" if kind else ""
2287        this = self.sql(expression, "this")
2288        this = f" {this}" if this else ""
2289        return f"KILL{kind}{this}"
2290
2291    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2292        return expression.name
2293
2294    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2295        return expression.name
2296
2297    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2298        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2299
2300        constraint = self.sql(expression, "constraint")
2301        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2302
2303        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2304        if conflict_keys:
2305            conflict_keys = f"({conflict_keys})"
2306
2307        index_predicate = self.sql(expression, "index_predicate")
2308        conflict_keys = f"{conflict_keys}{index_predicate} "
2309
2310        action = self.sql(expression, "action")
2311
2312        expressions = self.expressions(expression, flat=True)
2313        if expressions:
2314            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2315            expressions = f" {set_keyword}{expressions}"
2316
2317        where = self.sql(expression, "where")
2318        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2319
2320    def returning_sql(self, expression: exp.Returning) -> str:
2321        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2322
2323    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2324        fields = self.sql(expression, "fields")
2325        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2326        escaped = self.sql(expression, "escaped")
2327        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2328        items = self.sql(expression, "collection_items")
2329        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2330        keys = self.sql(expression, "map_keys")
2331        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2332        lines = self.sql(expression, "lines")
2333        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2334        null = self.sql(expression, "null")
2335        null = f" NULL DEFINED AS {null}" if null else ""
2336        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2337
2338    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2339        return f"WITH ({self.expressions(expression, flat=True)})"
2340
2341    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2342        this = f"{self.sql(expression, 'this')} INDEX"
2343        target = self.sql(expression, "target")
2344        target = f" FOR {target}" if target else ""
2345        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2346
2347    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2348        this = self.sql(expression, "this")
2349        kind = self.sql(expression, "kind")
2350        expr = self.sql(expression, "expression")
2351        return f"{this} ({kind} => {expr})"
2352
2353    def table_parts(self, expression: exp.Table) -> str:
2354        return ".".join(
2355            self.sql(part)
2356            for part in (
2357                expression.args.get("catalog"),
2358                expression.args.get("db"),
2359                expression.args.get("this"),
2360            )
2361            if part is not None
2362        )
2363
2364    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2365        table = self.table_parts(expression)
2366        only = "ONLY " if expression.args.get("only") else ""
2367        partition = self.sql(expression, "partition")
2368        partition = f" {partition}" if partition else ""
2369        version = self.sql(expression, "version")
2370        version = f" {version}" if version else ""
2371        alias = self.sql(expression, "alias")
2372        alias = f"{sep}{alias}" if alias else ""
2373
2374        sample = self.sql(expression, "sample")
2375        post_alias = ""
2376        pre_alias = ""
2377
2378        if self.dialect.ALIAS_POST_TABLESAMPLE:
2379            pre_alias = sample
2380        else:
2381            post_alias = sample
2382
2383        if self.dialect.ALIAS_POST_VERSION:
2384            pre_alias = f"{pre_alias}{version}"
2385        else:
2386            post_alias = f"{post_alias}{version}"
2387
2388        hints = self.expressions(expression, key="hints", sep=" ")
2389        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2390        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2391        joins = self.indent(
2392            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2393        )
2394        laterals = self.expressions(expression, key="laterals", sep="")
2395
2396        file_format = self.sql(expression, "format")
2397        if file_format:
2398            pattern = self.sql(expression, "pattern")
2399            pattern = f", PATTERN => {pattern}" if pattern else ""
2400            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2401
2402        ordinality = expression.args.get("ordinality") or ""
2403        if ordinality:
2404            ordinality = f" WITH ORDINALITY{alias}"
2405            alias = ""
2406
2407        when = self.sql(expression, "when")
2408        if when:
2409            table = f"{table} {when}"
2410
2411        changes = self.sql(expression, "changes")
2412        changes = f" {changes}" if changes else ""
2413
2414        rows_from = self.expressions(expression, key="rows_from")
2415        if rows_from:
2416            table = f"ROWS FROM {self.wrap(rows_from)}"
2417
2418        indexed = expression.args.get("indexed")
2419        if indexed is not None:
2420            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2421        else:
2422            indexed = ""
2423
2424        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2425
2426    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2427        table = self.func("TABLE", expression.this)
2428        alias = self.sql(expression, "alias")
2429        alias = f" AS {alias}" if alias else ""
2430        sample = self.sql(expression, "sample")
2431        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2432        joins = self.indent(
2433            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2434        )
2435        return f"{table}{alias}{pivots}{sample}{joins}"
2436
2437    def tablesample_sql(
2438        self,
2439        expression: exp.TableSample,
2440        tablesample_keyword: str | None = None,
2441    ) -> str:
2442        method = self.sql(expression, "method")
2443        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2444        numerator = self.sql(expression, "bucket_numerator")
2445        denominator = self.sql(expression, "bucket_denominator")
2446        field = self.sql(expression, "bucket_field")
2447        field = f" ON {field}" if field else ""
2448        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2449        seed = self.sql(expression, "seed")
2450        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2451
2452        size = self.sql(expression, "size")
2453        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2454            size = f"{size} ROWS"
2455
2456        percent = self.sql(expression, "percent")
2457        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2458            percent = f"{percent} PERCENT"
2459
2460        expr = f"{bucket}{percent}{size}"
2461        if self.TABLESAMPLE_REQUIRES_PARENS:
2462            expr = f"({expr})"
2463
2464        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2465
2466    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2467        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2468        # the stored column name differs from the target dialect's natural output.
2469        columns = expression.args.get("columns")
2470        if not columns or len(expression.fields) != 1:
2471            return None
2472
2473        args = expression.args
2474        parser_cls = self.dialect.parser_class
2475
2476        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2477        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2478        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2479
2480        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2481        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2482        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2483
2484        if (
2485            src_identify_pivot_strings == tgt_identify_pivot_strings
2486            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2487            and src_pivot_column_naming == tgt_pivot_column_naming
2488        ):
2489            return None
2490
2491        in_exprs = expression.fields[0].expressions
2492        step = len(columns) // len(in_exprs)
2493
2494        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2495        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2496        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2497        first_stored = columns[0].name
2498
2499        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2500        if not first_stored.startswith(first_base):
2501            return None
2502
2503        suffix = first_stored[len(first_base) :]
2504
2505        # Whether the target dialect would append an agg-name suffix for this pivot.
2506        # Spark single-agg uniquely drops the agg alias entirely.
2507        target_has_suffix = (
2508            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2509        ) and any(a.alias for a in expression.expressions)
2510        source_has_suffix = suffix != ""
2511
2512        new_exprs: list[exp.Expression] = []
2513        modified = False
2514        for val_idx, e in enumerate(in_exprs):
2515            if isinstance(e, exp.PivotAlias):
2516                new_exprs.append(e)
2517                continue
2518
2519            i = val_idx * step
2520            stored_full = columns[i].name
2521            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2522            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2523
2524            # Source had a suffix, but target won't apply one
2525            if source_has_suffix and not target_has_suffix:
2526                new_exprs.append(
2527                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2528                )
2529                modified = True
2530            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2531            elif stored_value != target_value:
2532                new_exprs.append(
2533                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2534                )
2535                modified = True
2536            else:
2537                new_exprs.append(e)
2538
2539        return new_exprs if modified else None
2540
2541    def pivot_sql(self, expression: exp.Pivot) -> str:
2542        expressions = self.expressions(expression, flat=True)
2543        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2544
2545        group = self.sql(expression, "group")
2546
2547        if expression.this:
2548            this = self.sql(expression, "this")
2549            if not expressions:
2550                sql = f"UNPIVOT {this}"
2551            else:
2552                on = f"{self.seg('ON')} {expressions}"
2553                into = self.sql(expression, "into")
2554                into = f"{self.seg('INTO')} {into}" if into else ""
2555                using = self.expressions(expression, key="using", flat=True)
2556                using = f"{self.seg('USING')} {using}" if using else ""
2557                sql = f"{direction} {this}{on}{into}{using}{group}"
2558            return self.prepend_ctes(expression, sql)
2559
2560        if not expression.unpivot:
2561            # Wrap IN-list values with explicit aliases where the target dialect would differ
2562            new_field_exprs = self._pivot_in_value_aliases(expression)
2563            if new_field_exprs is not None:
2564                expression.fields[0].set("expressions", new_field_exprs)
2565
2566        alias = self.sql(expression, "alias")
2567        alias = f" AS {alias}" if alias else ""
2568
2569        fields = self.expressions(
2570            expression,
2571            "fields",
2572            sep=" ",
2573            dynamic=True,
2574            new_line=True,
2575            skip_first=True,
2576            skip_last=True,
2577        )
2578
2579        include_nulls = expression.args.get("include_nulls")
2580        if include_nulls is not None:
2581            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2582        else:
2583            nulls = ""
2584
2585        default_on_null = self.sql(expression, "default_on_null")
2586        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2587        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2588        return self.prepend_ctes(expression, sql)
2589
2590    def version_sql(self, expression: exp.Version) -> str:
2591        this = f"FOR {expression.name}"
2592        kind = expression.text("kind")
2593        expr = self.sql(expression, "expression")
2594        return f"{this} {kind} {expr}"
2595
2596    def tuple_sql(self, expression: exp.Tuple) -> str:
2597        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2598
2599    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2600        """
2601        Returns (join_sql, from_sql) for UPDATE statements.
2602        - join_sql: placed after UPDATE table, before SET
2603        - from_sql: placed after SET clause (standard position)
2604        Dialects like MySQL need to convert FROM to JOIN syntax.
2605        """
2606        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2607            return ("", self.sql(expression, "from_"))
2608
2609        # Qualify unqualified columns in SET clause with the target table
2610        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2611        target_table = expression.this
2612        if isinstance(target_table, exp.Table):
2613            target_name = exp.to_identifier(target_table.alias_or_name)
2614            for eq in expression.expressions:
2615                col = eq.this
2616                if isinstance(col, exp.Column) and not col.table:
2617                    col.set("table", target_name)
2618
2619        table = from_expr.this
2620        if nested_joins := table.args.get("joins", []):
2621            table.set("joins", None)
2622
2623        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2624        for nested in nested_joins:
2625            if not nested.args.get("on") and not nested.args.get("using"):
2626                nested.set("on", exp.true())
2627            join_sql += self.sql(nested)
2628
2629        return (join_sql, "")
2630
2631    def update_sql(self, expression: exp.Update) -> str:
2632        hint = self.sql(expression, "hint")
2633        this = self.sql(expression, "this")
2634        join_sql, from_sql = self._update_from_joins_sql(expression)
2635        set_sql = self.expressions(expression, flat=True)
2636        where_sql = self.sql(expression, "where")
2637        returning = self.sql(expression, "returning")
2638        order = self.sql(expression, "order")
2639        limit = self.sql(expression, "limit")
2640        if self.RETURNING_END:
2641            expression_sql = f"{from_sql}{where_sql}{returning}"
2642        else:
2643            expression_sql = f"{returning}{from_sql}{where_sql}"
2644        options = self.expressions(expression, key="options")
2645        options = f" OPTION({options})" if options else ""
2646        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2647        return self.prepend_ctes(expression, sql)
2648
2649    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2650        values_as_table = values_as_table and self.VALUES_AS_TABLE
2651
2652        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2653        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2654            args = self.expressions(expression)
2655            alias = self.sql(expression, "alias")
2656            values = f"VALUES{self.seg('')}{args}"
2657            values = (
2658                f"({values})"
2659                if self.WRAP_DERIVED_VALUES
2660                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2661                else values
2662            )
2663            values = self.query_modifiers(expression, values)
2664            return f"{values} AS {alias}" if alias else values
2665
2666        # Converts `VALUES...` expression into a series of select unions.
2667        alias_node = expression.args.get("alias")
2668        column_names = alias_node and alias_node.columns
2669
2670        selects: list[exp.Query] = []
2671
2672        for i, tup in enumerate(expression.expressions):
2673            row = tup.expressions
2674
2675            if i == 0 and column_names:
2676                row = [
2677                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2678                ]
2679
2680            selects.append(exp.Select(expressions=row))
2681
2682        if self.pretty:
2683            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2684            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2685            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2686            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2687            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2688
2689        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2690        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2691        return f"({unions}){alias}"
2692
2693    def var_sql(self, expression: exp.Var) -> str:
2694        return self.sql(expression, "this")
2695
2696    @unsupported_args("expressions")
2697    def into_sql(self, expression: exp.Into) -> str:
2698        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2699        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2700        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2701
2702    def from_sql(self, expression: exp.From) -> str:
2703        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2704
2705    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2706        grouping_sets = self.expressions(expression, indent=False)
2707        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2708
2709    def rollup_sql(self, expression: exp.Rollup) -> str:
2710        expressions = self.expressions(expression, indent=False)
2711        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2712
2713    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2714        this = self.sql(expression, "this")
2715
2716        columns = self.expressions(expression, flat=True)
2717
2718        from_sql = self.sql(expression, "from_index")
2719        from_sql = f" FROM {from_sql}" if from_sql else ""
2720
2721        properties = expression.args.get("properties")
2722        properties_sql = (
2723            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2724        )
2725
2726        return f"{this}({columns}){from_sql}{properties_sql}"
2727
2728    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2729        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2730
2731    def cube_sql(self, expression: exp.Cube) -> str:
2732        expressions = self.expressions(expression, indent=False)
2733        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2734
2735    def group_sql(self, expression: exp.Group) -> str:
2736        group_by_all = expression.args.get("all")
2737        if group_by_all is True:
2738            modifier = " ALL"
2739        elif group_by_all is False:
2740            modifier = " DISTINCT"
2741        else:
2742            modifier = ""
2743
2744        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2745
2746        grouping_sets = self.expressions(expression, key="grouping_sets")
2747        cube = self.expressions(expression, key="cube")
2748        rollup = self.expressions(expression, key="rollup")
2749
2750        groupings = csv(
2751            self.seg(grouping_sets) if grouping_sets else "",
2752            self.seg(cube) if cube else "",
2753            self.seg(rollup) if rollup else "",
2754            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2755            sep=self.GROUPINGS_SEP,
2756        )
2757
2758        if (
2759            expression.expressions
2760            and groupings
2761            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2762        ):
2763            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2764
2765        return f"{group_by}{groupings}"
2766
2767    def having_sql(self, expression: exp.Having) -> str:
2768        this = self.indent(self.sql(expression, "this"))
2769        return f"{self.seg('HAVING')}{self.sep()}{this}"
2770
2771    def connect_sql(self, expression: exp.Connect) -> str:
2772        start = self.sql(expression, "start")
2773        start = self.seg(f"START WITH {start}") if start else ""
2774        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2775        connect = self.sql(expression, "connect")
2776        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2777        return start + connect
2778
2779    def prior_sql(self, expression: exp.Prior) -> str:
2780        return f"PRIOR {self.sql(expression, 'this')}"
2781
2782    def join_sql(self, expression: exp.Join) -> str:
2783        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2784            side = None
2785        else:
2786            side = expression.side
2787
2788        op_sql = " ".join(
2789            op
2790            for op in (
2791                expression.method,
2792                "GLOBAL" if expression.args.get("global_") else None,
2793                side,
2794                expression.kind,
2795                expression.hint if self.JOIN_HINTS else None,
2796                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2797            )
2798            if op
2799        )
2800        match_cond = self.sql(expression, "match_condition")
2801        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2802        on_sql = self.sql(expression, "on")
2803        using = expression.args.get("using")
2804
2805        if not on_sql and using:
2806            on_sql = csv(*(self.sql(column) for column in using))
2807
2808        this = expression.this
2809        this_sql = self.sql(this)
2810
2811        exprs = self.expressions(expression)
2812        if exprs:
2813            this_sql = f"{this_sql},{self.seg(exprs)}"
2814
2815        if on_sql:
2816            on_sql = self.indent(on_sql, skip_first=True)
2817            space = self.seg(" " * self.pad) if self.pretty else " "
2818            if using:
2819                on_sql = f"{space}USING ({on_sql})"
2820            else:
2821                on_sql = f"{space}ON {on_sql}"
2822        elif not op_sql:
2823            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2824                return f" {this_sql}"
2825
2826            return f", {this_sql}"
2827
2828        if op_sql != "STRAIGHT_JOIN":
2829            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2830
2831        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2832        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2833
2834    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2835        args = self.expressions(expression, flat=True)
2836        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2837        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2838
2839    def lateral_op(self, expression: exp.Lateral) -> str:
2840        cross_apply = expression.args.get("cross_apply")
2841
2842        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2843        if cross_apply is True:
2844            op = "INNER JOIN "
2845        elif cross_apply is False:
2846            op = "LEFT JOIN "
2847        else:
2848            op = ""
2849
2850        return f"{op}LATERAL"
2851
2852    def lateral_sql(self, expression: exp.Lateral) -> str:
2853        this = self.sql(expression, "this")
2854
2855        if expression.args.get("view"):
2856            alias = expression.args["alias"]
2857            columns = self.expressions(alias, key="columns", flat=True)
2858            table = f" {alias.name}" if alias.name else ""
2859            columns = f" AS {columns}" if columns else ""
2860            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2861            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2862
2863        alias = self.sql(expression, "alias")
2864        alias = f" AS {alias}" if alias else ""
2865
2866        ordinality = expression.args.get("ordinality") or ""
2867        if ordinality:
2868            ordinality = f" WITH ORDINALITY{alias}"
2869            alias = ""
2870
2871        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2872
2873    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2874        this = self.sql(expression, "this")
2875
2876        args = [
2877            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2878            for e in (expression.args.get(k) for k in ("offset", "expression"))
2879            if e
2880        ]
2881
2882        args_sql = ", ".join(self.sql(e) for e in args)
2883        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2884        expressions = self.expressions(expression, flat=True)
2885        limit_options = self.sql(expression, "limit_options")
2886        expressions = f" BY {expressions}" if expressions else ""
2887
2888        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2889
2890    def offset_sql(self, expression: exp.Offset) -> str:
2891        this = self.sql(expression, "this")
2892        value = expression.expression
2893        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2894        expressions = self.expressions(expression, flat=True)
2895        expressions = f" BY {expressions}" if expressions else ""
2896        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2897
2898    def setitem_sql(self, expression: exp.SetItem) -> str:
2899        kind = self.sql(expression, "kind")
2900        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2901            kind = ""
2902        else:
2903            kind = f"{kind} " if kind else ""
2904        this = self.sql(expression, "this")
2905        expressions = self.expressions(expression)
2906        collate = self.sql(expression, "collate")
2907        collate = f" COLLATE {collate}" if collate else ""
2908        global_ = "GLOBAL " if expression.args.get("global_") else ""
2909        return f"{global_}{kind}{this}{expressions}{collate}"
2910
2911    def set_sql(self, expression: exp.Set) -> str:
2912        expressions = f" {self.expressions(expression, flat=True)}"
2913        tag = " TAG" if expression.args.get("tag") else ""
2914        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2915
2916    def queryband_sql(self, expression: exp.QueryBand) -> str:
2917        this = self.sql(expression, "this")
2918        update = " UPDATE" if expression.args.get("update") else ""
2919        scope = self.sql(expression, "scope")
2920        scope = f" FOR {scope}" if scope else ""
2921
2922        return f"QUERY_BAND = {this}{update}{scope}"
2923
2924    def pragma_sql(self, expression: exp.Pragma) -> str:
2925        return f"PRAGMA {self.sql(expression, 'this')}"
2926
2927    def lock_sql(self, expression: exp.Lock) -> str:
2928        if not self.LOCKING_READS_SUPPORTED:
2929            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2930            return ""
2931
2932        update = expression.args["update"]
2933        key = expression.args.get("key")
2934        if update:
2935            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2936        else:
2937            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2938        expressions = self.expressions(expression, flat=True)
2939        expressions = f" OF {expressions}" if expressions else ""
2940        wait = expression.args.get("wait")
2941
2942        if wait is not None:
2943            if isinstance(wait, exp.Literal):
2944                wait = f" WAIT {self.sql(wait)}"
2945            else:
2946                wait = " NOWAIT" if wait else " SKIP LOCKED"
2947
2948        return f"{lock_type}{expressions}{wait or ''}"
2949
2950    def literal_sql(self, expression: exp.Literal) -> str:
2951        text = expression.this or ""
2952        if expression.is_string:
2953            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2954        return text
2955
2956    def escape_str(
2957        self,
2958        text: str,
2959        escape_backslash: bool = True,
2960        delimiter: str | None = None,
2961        escaped_delimiter: str | None = None,
2962        is_byte_string: bool = False,
2963    ) -> str:
2964        if is_byte_string:
2965            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2966        else:
2967            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2968
2969        if supports_escape_sequences:
2970            text = "".join(
2971                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2972                for ch in text
2973            )
2974
2975        delimiter = delimiter or self.dialect.QUOTE_END
2976        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2977
2978        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2979
2980    def loaddata_sql(self, expression: exp.LoadData) -> str:
2981        is_overwrite = expression.args.get("overwrite")
2982        overwrite = " OVERWRITE" if is_overwrite else ""
2983        this = self.sql(expression, "this")
2984
2985        files = expression.args.get("files")
2986        if files:
2987            files_sql = self.expressions(files, flat=True)
2988            files_sql = f"FILES{self.wrap(files_sql)}"
2989            if is_overwrite:
2990                this = f" {this}"
2991            elif expression.args.get("temp"):
2992                this = f" INTO TEMP TABLE {this}"
2993            else:
2994                this = f" INTO TABLE {this}"
2995            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2996
2997        local = " LOCAL" if expression.args.get("local") else ""
2998        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2999        this = f" INTO TABLE {this}"
3000        partition = self.sql(expression, "partition")
3001        partition = f" {partition}" if partition else ""
3002        input_format = self.sql(expression, "input_format")
3003        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3004        serde = self.sql(expression, "serde")
3005        serde = f" SERDE {serde}" if serde else ""
3006        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3007
3008    def null_sql(self, *_) -> str:
3009        return "NULL"
3010
3011    def boolean_sql(self, expression: exp.Boolean) -> str:
3012        return "TRUE" if expression.this else "FALSE"
3013
3014    def booland_sql(self, expression: exp.Booland) -> str:
3015        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3016
3017    def boolor_sql(self, expression: exp.Boolor) -> str:
3018        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3019
3020    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3021        this = self.sql(expression, "this")
3022        this = f"{this} " if this else this
3023        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3024        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3025
3026    def withfill_sql(self, expression: exp.WithFill) -> str:
3027        from_sql = self.sql(expression, "from_")
3028        from_sql = f" FROM {from_sql}" if from_sql else ""
3029        to_sql = self.sql(expression, "to")
3030        to_sql = f" TO {to_sql}" if to_sql else ""
3031        step_sql = self.sql(expression, "step")
3032        step_sql = f" STEP {step_sql}" if step_sql else ""
3033        interpolated_values = [
3034            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3035            if isinstance(e, exp.Alias)
3036            else self.sql(e, "this")
3037            for e in expression.args.get("interpolate") or []
3038        ]
3039        interpolate = (
3040            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3041        )
3042        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3043
3044    def cluster_sql(self, expression: exp.Cluster) -> str:
3045        return self.op_expressions("CLUSTER BY", expression)
3046
3047    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3048        if expression.this:
3049            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3050            return ""
3051        expressions = self.expressions(expression, flat=True)
3052        return f"CLUSTER BY ({expressions})"
3053
3054    def distribute_sql(self, expression: exp.Distribute) -> str:
3055        return self.op_expressions("DISTRIBUTE BY", expression)
3056
3057    def sort_sql(self, expression: exp.Sort) -> str:
3058        return self.op_expressions("SORT BY", expression)
3059
3060    def _resolve_ordered_for_null_ordering_simulation(
3061        self, expression: exp.Ordered
3062    ) -> exp.Expr | None:
3063        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3064
3065        Returns the underlying expression of the uniquely-matching projection
3066        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3067        simulation, since the CASE is evaluated in FROM-clause scope rather
3068        than alias scope (MySQL error 1052). Returns None if no safe
3069        substitution applies, leaving the original behaviour unchanged.
3070        """
3071        this = expression.this
3072        if not (isinstance(this, exp.Column) and not this.table):
3073            return None
3074
3075        ancestor = expression.find_ancestor(exp.Select, exp.Window)
3076        if not isinstance(ancestor, exp.Select):
3077            return None
3078
3079        column_name = this.name
3080        matched: list[exp.Expr] = [
3081            p.this if isinstance(p, exp.Alias) else p
3082            for p in ancestor.selects
3083            if p.output_name == column_name
3084        ]
3085        match = matched[0] if len(matched) == 1 else None
3086
3087        # Skip the substitution when it would be identical to the existing
3088        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3089        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3090            return None
3091
3092        return match
3093
3094    def ordered_sql(self, expression: exp.Ordered) -> str:
3095        desc = expression.args.get("desc")
3096        asc = not desc
3097
3098        nulls_first = expression.args.get("nulls_first")
3099        nulls_last = not nulls_first
3100        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3101        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3102        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3103
3104        this = self.sql(expression, "this")
3105
3106        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3107        nulls_sort_change = ""
3108        if nulls_first and (
3109            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3110        ):
3111            nulls_sort_change = " NULLS FIRST"
3112        elif (
3113            nulls_last
3114            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3115            and not nulls_are_last
3116        ):
3117            nulls_sort_change = " NULLS LAST"
3118
3119        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3120        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3121            window = expression.find_ancestor(exp.Window, exp.Select)
3122
3123            if isinstance(window, exp.Window):
3124                window_this = window.this
3125                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3126                    window_this = window_this.this
3127                spec = window.args.get("spec")
3128            else:
3129                window_this = None
3130                spec = None
3131
3132            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3133            # without a spec or with a ROWS spec, but not with RANGE
3134            if not (
3135                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3136                and (not spec or spec.text("kind").upper() == "ROWS")
3137            ):
3138                if window_this and spec:
3139                    self.unsupported(
3140                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3141                    )
3142                    nulls_sort_change = ""
3143                elif self.NULL_ORDERING_SUPPORTED is False and (
3144                    (asc and nulls_sort_change == " NULLS LAST")
3145                    or (desc and nulls_sort_change == " NULLS FIRST")
3146                ):
3147                    # BigQuery does not allow these ordering/nulls combinations when used under
3148                    # an aggregation func or under a window containing one
3149                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3150
3151                    if isinstance(ancestor, exp.Window):
3152                        ancestor = ancestor.this
3153                    if isinstance(ancestor, exp.AggFunc):
3154                        self.unsupported(
3155                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3156                        )
3157                        nulls_sort_change = ""
3158                elif self.NULL_ORDERING_SUPPORTED is None:
3159                    if expression.this.is_int:
3160                        self.unsupported(
3161                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3162                        )
3163                    elif not isinstance(expression.this, exp.Rand):
3164                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3165                        target = self.sql(resolved) if resolved is not None else this
3166                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3167                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3168                    nulls_sort_change = ""
3169
3170        with_fill = self.sql(expression, "with_fill")
3171        with_fill = f" {with_fill}" if with_fill else ""
3172
3173        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3174
3175    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3176        window_frame = self.sql(expression, "window_frame")
3177        window_frame = f"{window_frame} " if window_frame else ""
3178
3179        this = self.sql(expression, "this")
3180
3181        return f"{window_frame}{this}"
3182
3183    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3184        partition = self.partition_by_sql(expression)
3185        order = self.sql(expression, "order")
3186        measures = self.expressions(expression, key="measures")
3187        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3188        rows = self.sql(expression, "rows")
3189        rows = self.seg(rows) if rows else ""
3190        after = self.sql(expression, "after")
3191        after = self.seg(after) if after else ""
3192        pattern = self.sql(expression, "pattern")
3193        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3194        definition_sqls = [
3195            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3196            for definition in expression.args.get("define", [])
3197        ]
3198        definitions = self.expressions(sqls=definition_sqls)
3199        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3200        body = "".join(
3201            (
3202                partition,
3203                order,
3204                measures,
3205                rows,
3206                after,
3207                pattern,
3208                define,
3209            )
3210        )
3211        alias = self.sql(expression, "alias")
3212        alias = f" {alias}" if alias else ""
3213        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3214
3215    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3216        limit = expression.args.get("limit")
3217
3218        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3219            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3220        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3221            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3222
3223        return csv(
3224            *sqls,
3225            *[self.sql(join) for join in expression.args.get("joins") or []],
3226            self.sql(expression, "match"),
3227            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3228            self.sql(expression, "prewhere"),
3229            self.sql(expression, "where"),
3230            self.sql(expression, "connect"),
3231            self.sql(expression, "group"),
3232            self.sql(expression, "having"),
3233            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3234            self.sql(expression, "order"),
3235            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3236            *self.after_limit_modifiers(expression),
3237            self.options_modifier(expression),
3238            self.sql(expression, "for_"),
3239            sep="",
3240        )
3241
3242    def options_modifier(self, expression: exp.Expr) -> str:
3243        options = self.expressions(expression, key="options")
3244        return f" {options}" if options else ""
3245
3246    def forclause_sql(self, expression: exp.ForClause) -> str:
3247        kind = expression.args["kind"]
3248        if kind == "BROWSE":
3249            return f"{self.sep()}FOR BROWSE"
3250        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3251        # the target dialect doesn't support QueryOption, so we drop the clause.
3252        options = self.expressions(expression, key="expressions")
3253        if not options:
3254            return ""
3255        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3256
3257    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3258        self.unsupported("Unsupported query option.")
3259        return ""
3260
3261    def offset_limit_modifiers(
3262        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3263    ) -> list[str]:
3264        return [
3265            self.sql(expression, "offset") if fetch else self.sql(limit),
3266            self.sql(limit) if fetch else self.sql(expression, "offset"),
3267        ]
3268
3269    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3270        locks = self.expressions(expression, key="locks", sep=" ")
3271        locks = f" {locks}" if locks else ""
3272        return [locks, self.sql(expression, "sample")]
3273
3274    def select_sql(self, expression: exp.Select) -> str:
3275        into = expression.args.get("into")
3276        if not self.SUPPORTS_SELECT_INTO and into:
3277            into.pop()
3278
3279        hint = self.sql(expression, "hint")
3280        distinct = self.sql(expression, "distinct")
3281        distinct = f" {distinct}" if distinct else ""
3282        kind = self.sql(expression, "kind")
3283
3284        limit = expression.args.get("limit")
3285        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3286            top = self.limit_sql(limit, top=True)
3287            limit.pop()
3288        else:
3289            top = ""
3290
3291        expressions = self.expressions(expression)
3292
3293        if kind:
3294            if kind in self.SELECT_KINDS:
3295                kind = f" AS {kind}"
3296            else:
3297                if kind == "STRUCT":
3298                    expressions = self.expressions(
3299                        sqls=[
3300                            self.sql(
3301                                exp.Struct(
3302                                    expressions=[
3303                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3304                                        if isinstance(e, exp.Alias)
3305                                        else e
3306                                        for e in expression.expressions
3307                                    ]
3308                                )
3309                            )
3310                        ]
3311                    )
3312                kind = ""
3313
3314        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3315        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3316
3317        exclude = expression.args.get("exclude")
3318
3319        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3320            exclude_sql = self.expressions(sqls=exclude, flat=True)
3321            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3322
3323        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3324        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3325        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3326        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3327        sql = self.query_modifiers(
3328            expression,
3329            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3330            self.sql(expression, "into", comment=False),
3331            self.sql(expression, "from_", comment=False),
3332        )
3333
3334        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3335        if expression.args.get("with_"):
3336            sql = self.maybe_comment(sql, expression)
3337            expression.pop_comments()
3338
3339        sql = self.prepend_ctes(expression, sql)
3340
3341        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3342            expression.set("exclude", None)
3343            subquery = expression.subquery(copy=False)
3344            star = exp.Star(except_=exclude)
3345            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3346
3347        if not self.SUPPORTS_SELECT_INTO and into:
3348            if into.args.get("temporary"):
3349                table_kind = " TEMPORARY"
3350            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3351                table_kind = " UNLOGGED"
3352            else:
3353                table_kind = ""
3354            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3355
3356        return sql
3357
3358    def schema_sql(self, expression: exp.Schema) -> str:
3359        this = self.sql(expression, "this")
3360        sql = self.schema_columns_sql(expression)
3361        return f"{this} {sql}" if this and sql else this or sql
3362
3363    def schema_columns_sql(self, expression: exp.Expr) -> str:
3364        if expression.expressions:
3365            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3366        return ""
3367
3368    def star_sql(self, expression: exp.Star) -> str:
3369        except_ = self.expressions(expression, key="except_", flat=True)
3370        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3371        replace = self.expressions(expression, key="replace", flat=True)
3372        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3373        rename = self.expressions(expression, key="rename", flat=True)
3374        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3375        return f"*{except_}{replace}{rename}"
3376
3377    def parameter_sql(self, expression: exp.Parameter) -> str:
3378        this = self.sql(expression, "this")
3379        return f"{self.PARAMETER_TOKEN}{this}"
3380
3381    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3382        this = self.sql(expression, "this")
3383        kind = expression.text("kind")
3384        if kind:
3385            kind = f"{kind}."
3386        return f"@@{kind}{this}"
3387
3388    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3389        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3390
3391    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3392        alias = self.sql(expression, "alias")
3393        alias = f"{sep}{alias}" if alias else ""
3394        sample = self.sql(expression, "sample")
3395        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3396            alias = f"{sample}{alias}"
3397
3398            # Set to None so it's not generated again by self.query_modifiers()
3399            expression.set("sample", None)
3400
3401        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3402        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3403        return self.prepend_ctes(expression, sql)
3404
3405    def qualify_sql(self, expression: exp.Qualify) -> str:
3406        this = self.indent(self.sql(expression, "this"))
3407        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3408
3409    def unnest_sql(self, expression: exp.Unnest) -> str:
3410        args = self.expressions(expression, flat=True)
3411
3412        alias = expression.args.get("alias")
3413        offset = expression.args.get("offset")
3414
3415        if self.UNNEST_WITH_ORDINALITY:
3416            if alias and isinstance(offset, exp.Expr):
3417                alias.append("columns", offset)
3418                expression.set("offset", None)
3419
3420        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3421            columns = alias.columns
3422            alias = self.sql(columns[0]) if columns else ""
3423        else:
3424            alias = self.sql(alias)
3425
3426        alias = f" AS {alias}" if alias else alias
3427        if self.UNNEST_WITH_ORDINALITY:
3428            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3429        else:
3430            if isinstance(offset, exp.Expr):
3431                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3432            elif offset:
3433                suffix = f"{alias} WITH OFFSET"
3434            else:
3435                suffix = alias
3436
3437        return f"UNNEST({args}){suffix}"
3438
3439    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3440        return ""
3441
3442    def where_sql(self, expression: exp.Where) -> str:
3443        this = self.indent(self.sql(expression, "this"))
3444        return f"{self.seg('WHERE')}{self.sep()}{this}"
3445
3446    def window_sql(self, expression: exp.Window) -> str:
3447        this = self.sql(expression, "this")
3448        partition = self.partition_by_sql(expression)
3449        order = expression.args.get("order")
3450        order = self.order_sql(order, flat=True) if order else ""
3451        spec = self.sql(expression, "spec")
3452        alias = self.sql(expression, "alias")
3453        over = self.sql(expression, "over") or "OVER"
3454
3455        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3456
3457        first = expression.args.get("first")
3458        if first is None:
3459            first = ""
3460        else:
3461            first = "FIRST" if first else "LAST"
3462
3463        if not partition and not order and not spec and alias:
3464            return f"{this} {alias}"
3465
3466        args = self.format_args(
3467            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3468        )
3469        return f"{this} ({args})"
3470
3471    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3472        partition = self.expressions(expression, key="partition_by", flat=True)
3473        return f"PARTITION BY {partition}" if partition else ""
3474
3475    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3476        kind = self.sql(expression, "kind")
3477        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3478        end = (
3479            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3480            or "CURRENT ROW"
3481        )
3482
3483        window_spec = f"{kind} BETWEEN {start} AND {end}"
3484
3485        exclude = self.sql(expression, "exclude")
3486        if exclude:
3487            if self.SUPPORTS_WINDOW_EXCLUDE:
3488                window_spec += f" EXCLUDE {exclude}"
3489            else:
3490                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3491
3492        return window_spec
3493
3494    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3495        this = self.sql(expression, "this")
3496        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3497        return f"{this} WITHIN GROUP ({expression_sql})"
3498
3499    def between_sql(self, expression: exp.Between) -> str:
3500        this = self.sql(expression, "this")
3501        low = self.sql(expression, "low")
3502        high = self.sql(expression, "high")
3503        symmetric = expression.args.get("symmetric")
3504
3505        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3506            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3507
3508        flag = (
3509            " SYMMETRIC"
3510            if symmetric
3511            else " ASYMMETRIC"
3512            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3513            else ""  # silently drop ASYMMETRIC – semantics identical
3514        )
3515        return f"{this} BETWEEN{flag} {low} AND {high}"
3516
3517    def bracket_offset_expressions(
3518        self, expression: exp.Bracket, index_offset: int | None = None
3519    ) -> list[exp.Expr]:
3520        if expression.args.get("json_access"):
3521            return expression.expressions
3522
3523        return apply_index_offset(
3524            expression.this,
3525            expression.expressions,
3526            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3527            dialect=self.dialect,
3528        )
3529
3530    def bracket_sql(self, expression: exp.Bracket) -> str:
3531        expressions = self.bracket_offset_expressions(expression)
3532        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3533        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3534
3535    def all_sql(self, expression: exp.All) -> str:
3536        this = self.sql(expression, "this")
3537        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3538            this = self.wrap(this)
3539        return f"ALL {this}"
3540
3541    def any_sql(self, expression: exp.Any) -> str:
3542        this = self.sql(expression, "this")
3543        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3544            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3545                this = self.wrap(this)
3546            return f"ANY{this}"
3547        return f"ANY {this}"
3548
3549    def exists_sql(self, expression: exp.Exists) -> str:
3550        return f"EXISTS{self.wrap(expression)}"
3551
3552    def case_sql(self, expression: exp.Case) -> str:
3553        this = self.sql(expression, "this")
3554        statements = [f"CASE {this}" if this else "CASE"]
3555
3556        for e in expression.args["ifs"]:
3557            statements.append(f"WHEN {self.sql(e, 'this')}")
3558            statements.append(f"THEN {self.sql(e, 'true')}")
3559
3560        default = self.sql(expression, "default")
3561
3562        if default:
3563            statements.append(f"ELSE {default}")
3564
3565        statements.append("END")
3566
3567        if self.pretty and self.too_wide(statements):
3568            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3569
3570        return " ".join(statements)
3571
3572    def constraint_sql(self, expression: exp.Constraint) -> str:
3573        this = self.sql(expression, "this")
3574        expressions = self.expressions(expression, flat=True)
3575        return f"CONSTRAINT {this} {expressions}"
3576
3577    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3578        order = expression.args.get("order")
3579        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3580        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3581
3582    def extract_sql(self, expression: exp.Extract) -> str:
3583        import sqlglot.dialects.dialect
3584
3585        this = (
3586            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3587            if self.NORMALIZE_EXTRACT_DATE_PARTS
3588            else expression.this
3589        )
3590        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3591        expression_sql = self.sql(expression, "expression")
3592
3593        return f"EXTRACT({this_sql} FROM {expression_sql})"
3594
3595    def trim_sql(self, expression: exp.Trim) -> str:
3596        trim_type = self.sql(expression, "position")
3597
3598        if trim_type == "LEADING":
3599            func_name = "LTRIM"
3600        elif trim_type == "TRAILING":
3601            func_name = "RTRIM"
3602        else:
3603            func_name = "TRIM"
3604
3605        return self.func(func_name, expression.this, expression.expression)
3606
3607    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3608        args = expression.expressions
3609        if isinstance(expression, exp.ConcatWs):
3610            args = args[1:]  # Skip the delimiter
3611
3612        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3613            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3614
3615        concat_coalesce = (
3616            self.dialect.CONCAT_WS_COALESCE
3617            if isinstance(expression, exp.ConcatWs)
3618            else self.dialect.CONCAT_COALESCE
3619        )
3620
3621        if not concat_coalesce and expression.args.get("coalesce"):
3622
3623            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3624                if not e.type:
3625                    import sqlglot.optimizer.annotate_types
3626
3627                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3628
3629                if e.is_string or e.is_type(exp.DType.ARRAY):
3630                    return e
3631
3632                return exp.func("coalesce", e, exp.Literal.string(""))
3633
3634            args = [_wrap_with_coalesce(e) for e in args]
3635
3636        return args
3637
3638    def concat_sql(self, expression: exp.Concat) -> str:
3639        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3640            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3641            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3642            # instead of coalescing them to empty string.
3643            import sqlglot.dialects.dialect
3644
3645            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3646
3647        expressions = self.convert_concat_args(expression)
3648
3649        # Some dialects don't allow a single-argument CONCAT call
3650        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3651            return self.sql(expressions[0])
3652
3653        return self.func("CONCAT", *expressions)
3654
3655    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3656        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3657            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3658            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3659            all_args = expression.expressions
3660            expression.set("coalesce", True)
3661            return self.sql(
3662                exp.case()
3663                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3664                .else_(expression)
3665            )
3666
3667        return self.func(
3668            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3669        )
3670
3671    def check_sql(self, expression: exp.Check) -> str:
3672        this = self.sql(expression, key="this")
3673        return f"CHECK ({this})"
3674
3675    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3676        expressions = self.expressions(expression, flat=True)
3677        expressions = f" ({expressions})" if expressions else ""
3678        reference = self.sql(expression, "reference")
3679        reference = f" {reference}" if reference else ""
3680        delete = self.sql(expression, "delete")
3681        delete = f" ON DELETE {delete}" if delete else ""
3682        update = self.sql(expression, "update")
3683        update = f" ON UPDATE {update}" if update else ""
3684        options = self.expressions(expression, key="options", flat=True, sep=" ")
3685        options = f" {options}" if options else ""
3686        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3687
3688    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3689        this = self.sql(expression, "this")
3690        this = f" {this}" if this else ""
3691        expressions = self.expressions(expression, flat=True)
3692        include = self.sql(expression, "include")
3693        options = self.expressions(expression, key="options", flat=True, sep=" ")
3694        options = f" {options}" if options else ""
3695        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3696
3697    def if_sql(self, expression: exp.If) -> str:
3698        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3699
3700    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3701        if self.MATCH_AGAINST_TABLE_PREFIX:
3702            expressions = []
3703            for expr in expression.expressions:
3704                if isinstance(expr, exp.Table):
3705                    expressions.append(f"TABLE {self.sql(expr)}")
3706                else:
3707                    expressions.append(expr)
3708        else:
3709            expressions = expression.expressions
3710
3711        modifier = expression.args.get("modifier")
3712        modifier = f" {modifier}" if modifier else ""
3713        return (
3714            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3715        )
3716
3717    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3718        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3719
3720    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3721        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3722
3723        if expression.args.get("escape"):
3724            path = self.escape_str(path)
3725
3726        if self.QUOTE_JSON_PATH:
3727            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3728
3729        return path
3730
3731    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3732        if isinstance(expression, exp.JSONPathPart):
3733            transform = self.TRANSFORMS.get(expression.__class__)
3734            if not callable(transform):
3735                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3736                return ""
3737
3738            return transform(self, expression)
3739
3740        if isinstance(expression, int):
3741            return str(expression)
3742
3743        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3744            escaped = expression.replace("'", "\\'")
3745            escaped = f"\\'{expression}\\'"
3746        else:
3747            escaped = expression.replace('"', '\\"')
3748            escaped = f'"{escaped}"'
3749
3750        return escaped
3751
3752    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3753        return f"{self.sql(expression, 'this')} FORMAT JSON"
3754
3755    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3756        # Output the Teradata column FORMAT override.
3757        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3758        this = self.sql(expression, "this")
3759        fmt = self.sql(expression, "format")
3760        return f"{this} (FORMAT {fmt})"
3761
3762    def _jsonobject_sql(
3763        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3764    ) -> str:
3765        null_handling = expression.args.get("null_handling")
3766        null_handling = f" {null_handling}" if null_handling else ""
3767
3768        unique_keys = expression.args.get("unique_keys")
3769        if unique_keys is not None:
3770            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3771        else:
3772            unique_keys = ""
3773
3774        return_type = self.sql(expression, "return_type")
3775        return_type = f" RETURNING {return_type}" if return_type else ""
3776        encoding = self.sql(expression, "encoding")
3777        encoding = f" ENCODING {encoding}" if encoding else ""
3778
3779        if not name:
3780            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3781
3782        return self.func(
3783            name,
3784            *expression.expressions,
3785            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3786        )
3787
3788    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3789        null_handling = expression.args.get("null_handling")
3790        null_handling = f" {null_handling}" if null_handling else ""
3791        return_type = self.sql(expression, "return_type")
3792        return_type = f" RETURNING {return_type}" if return_type else ""
3793        strict = " STRICT" if expression.args.get("strict") else ""
3794        return self.func(
3795            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3796        )
3797
3798    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3799        this = self.sql(expression, "this")
3800        order = self.sql(expression, "order")
3801        null_handling = expression.args.get("null_handling")
3802        null_handling = f" {null_handling}" if null_handling else ""
3803        return_type = self.sql(expression, "return_type")
3804        return_type = f" RETURNING {return_type}" if return_type else ""
3805        strict = " STRICT" if expression.args.get("strict") else ""
3806        return self.func(
3807            "JSON_ARRAYAGG",
3808            this,
3809            suffix=f"{order}{null_handling}{return_type}{strict})",
3810        )
3811
3812    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3813        path = self.sql(expression, "path")
3814        path = f" PATH {path}" if path else ""
3815        nested_schema = self.sql(expression, "nested_schema")
3816
3817        if nested_schema:
3818            return f"NESTED{path} {nested_schema}"
3819
3820        this = self.sql(expression, "this")
3821        kind = self.sql(expression, "kind")
3822        kind = f" {kind}" if kind else ""
3823        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3824
3825        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3826        return f"{this}{kind}{format_json}{path}{ordinality}"
3827
3828    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3829        return self.func("COLUMNS", *expression.expressions)
3830
3831    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3832        this = self.sql(expression, "this")
3833        path = self.sql(expression, "path")
3834        path = f", {path}" if path else ""
3835        error_handling = expression.args.get("error_handling")
3836        error_handling = f" {error_handling}" if error_handling else ""
3837        empty_handling = expression.args.get("empty_handling")
3838        empty_handling = f" {empty_handling}" if empty_handling else ""
3839        schema = self.sql(expression, "schema")
3840        return self.func(
3841            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3842        )
3843
3844    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3845        this = self.sql(expression, "this")
3846        kind = self.sql(expression, "kind")
3847        path = self.sql(expression, "path")
3848        path = f" {path}" if path else ""
3849        as_json = " AS JSON" if expression.args.get("as_json") else ""
3850        return f"{this} {kind}{path}{as_json}"
3851
3852    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3853        this = self.sql(expression, "this")
3854        path = self.sql(expression, "path")
3855        path = f", {path}" if path else ""
3856        expressions = self.expressions(expression)
3857        with_ = (
3858            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3859            if expressions
3860            else ""
3861        )
3862        return f"OPENJSON({this}{path}){with_}"
3863
3864    def in_sql(self, expression: exp.In) -> str:
3865        query = expression.args.get("query")
3866        unnest = expression.args.get("unnest")
3867        field = expression.args.get("field")
3868        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3869
3870        if query:
3871            in_sql = self.sql(query)
3872        elif unnest:
3873            in_sql = self.in_unnest_op(unnest)
3874        elif field:
3875            in_sql = self.sql(field)
3876        else:
3877            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3878
3879        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3880
3881    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3882        return f"(SELECT {self.sql(unnest)})"
3883
3884    def interval_sql(self, expression: exp.Interval) -> str:
3885        unit_expression = expression.args.get("unit")
3886        unit = self.sql(unit_expression) if unit_expression else ""
3887        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3888            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3889        unit = f" {unit}" if unit else ""
3890
3891        if self.SINGLE_STRING_INTERVAL:
3892            this = expression.this.name if expression.this else ""
3893            if this:
3894                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3895                    return f"INTERVAL '{this}'{unit}"
3896                return f"INTERVAL '{this}{unit}'"
3897            return f"INTERVAL{unit}"
3898
3899        this = self.sql(expression, "this")
3900        if this:
3901            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3902            this = f" {this}" if unwrapped else f" ({this})"
3903
3904        return f"INTERVAL{this}{unit}"
3905
3906    def return_sql(self, expression: exp.Return) -> str:
3907        return f"RETURN {self.sql(expression, 'this')}"
3908
3909    def reference_sql(self, expression: exp.Reference) -> str:
3910        this = self.sql(expression, "this")
3911        expressions = self.expressions(expression, flat=True)
3912        expressions = f"({expressions})" if expressions else ""
3913        options = self.expressions(expression, key="options", flat=True, sep=" ")
3914        options = f" {options}" if options else ""
3915        return f"REFERENCES {this}{expressions}{options}"
3916
3917    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3918        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3919        parent = expression.parent
3920        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3921
3922        return self.func(
3923            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3924        )
3925
3926    def paren_sql(self, expression: exp.Paren) -> str:
3927        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3928        return f"({sql}{self.seg(')', sep='')}"
3929
3930    def neg_sql(self, expression: exp.Neg) -> str:
3931        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3932        this_sql = self.sql(expression, "this")
3933        sep = " " if this_sql[0] == "-" else ""
3934        return f"-{sep}{this_sql}"
3935
3936    def not_sql(self, expression: exp.Not) -> str:
3937        return f"NOT {self.sql(expression, 'this')}"
3938
3939    def alias_sql(self, expression: exp.Alias) -> str:
3940        alias = self.sql(expression, "alias")
3941        alias = f" AS {alias}" if alias else ""
3942        return f"{self.sql(expression, 'this')}{alias}"
3943
3944    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3945        alias = expression.args["alias"]
3946
3947        parent = expression.parent
3948        pivot = parent and parent.parent
3949
3950        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3951            identifier_alias = isinstance(alias, exp.Identifier)
3952            literal_alias = isinstance(alias, exp.Literal)
3953
3954            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3955                alias.replace(exp.Literal.string(alias.output_name))
3956            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3957                alias.replace(exp.to_identifier(alias.output_name))
3958
3959        return self.alias_sql(expression)
3960
3961    def aliases_sql(self, expression: exp.Aliases) -> str:
3962        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3963
3964    def atindex_sql(self, expression: exp.AtIndex) -> str:
3965        this = self.sql(expression, "this")
3966        index = self.sql(expression, "expression")
3967        return f"{this} AT {index}"
3968
3969    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3970        this = self.sql(expression, "this")
3971        zone = self.sql(expression, "zone")
3972        return f"{this} AT TIME ZONE {zone}"
3973
3974    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3975        this = self.sql(expression, "this")
3976        zone = self.sql(expression, "zone")
3977        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3978
3979    def add_sql(self, expression: exp.Add) -> str:
3980        return self.binary(expression, "+")
3981
3982    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
3983        return self.connector_sql(expression, "AND", stack)
3984
3985    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
3986        return self.connector_sql(expression, "OR", stack)
3987
3988    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
3989        return self.connector_sql(expression, "XOR", stack)
3990
3991    def connector_sql(
3992        self,
3993        expression: exp.Connector,
3994        op: str,
3995        stack: list[str | exp.Expr] | None = None,
3996    ) -> str:
3997        if stack is not None:
3998            if expression.expressions:
3999                stack.append(self.expressions(expression, sep=f" {op} "))
4000            else:
4001                stack.append(expression.right)
4002                if expression.comments and self.comments:
4003                    op = self.maybe_comment(op, comments=expression.comments)
4004                stack.extend((op, expression.left))
4005            return op
4006
4007        stack = [expression]
4008        sqls: list[str] = []
4009        ops = set()
4010
4011        while stack:
4012            node = stack.pop()
4013            if isinstance(node, exp.Connector):
4014                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4015            else:
4016                sql = self.sql(node)
4017                if sqls and sqls[-1] in ops:
4018                    sqls[-1] += f" {sql}"
4019                else:
4020                    sqls.append(sql)
4021
4022        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4023        return sep.join(sqls)
4024
4025    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4026        return self.binary(expression, "&")
4027
4028    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4029        return self.binary(expression, "<<")
4030
4031    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4032        return f"~{self.sql(expression, 'this')}"
4033
4034    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4035        return self.binary(expression, "|")
4036
4037    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4038        return self.binary(expression, ">>")
4039
4040    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4041        return self.binary(expression, "^")
4042
4043    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4044        format_sql = self.sql(expression, "format")
4045        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4046        to_sql = self.sql(expression, "to")
4047        to_sql = f" {to_sql}" if to_sql else ""
4048        action = self.sql(expression, "action")
4049        action = f" {action}" if action else ""
4050        default = self.sql(expression, "default")
4051        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4052        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4053
4054    # Base implementation that excludes safe, zone, and target_type metadata args
4055    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4056        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4057
4058    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4059        zone = self.sql(expression, "this")
4060        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4061
4062    def collate_sql(self, expression: exp.Collate) -> str:
4063        if self.COLLATE_IS_FUNC:
4064            return self.function_fallback_sql(expression)
4065        return self.binary(expression, "COLLATE")
4066
4067    def command_sql(self, expression: exp.Command) -> str:
4068        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4069
4070    def comment_sql(self, expression: exp.Comment) -> str:
4071        this = self.sql(expression, "this")
4072        kind = expression.args["kind"]
4073        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4074        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4075        expression_sql = self.sql(expression, "expression")
4076        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4077
4078    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4079        this = self.sql(expression, "this")
4080        delete = " DELETE" if expression.args.get("delete") else ""
4081        recompress = self.sql(expression, "recompress")
4082        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4083        to_disk = self.sql(expression, "to_disk")
4084        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4085        to_volume = self.sql(expression, "to_volume")
4086        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4087        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4088
4089    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4090        where = self.sql(expression, "where")
4091        group = self.sql(expression, "group")
4092        aggregates = self.expressions(expression, key="aggregates")
4093        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4094
4095        if not (where or group or aggregates) and len(expression.expressions) == 1:
4096            return f"TTL {self.expressions(expression, flat=True)}"
4097
4098        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4099
4100    def transaction_sql(self, expression: exp.Transaction) -> str:
4101        modes = self.expressions(expression, key="modes")
4102        modes = f" {modes}" if modes else ""
4103        return f"BEGIN{modes}"
4104
4105    def commit_sql(self, expression: exp.Commit) -> str:
4106        chain = expression.args.get("chain")
4107        if chain is not None:
4108            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4109
4110        return f"COMMIT{chain or ''}"
4111
4112    def rollback_sql(self, expression: exp.Rollback) -> str:
4113        savepoint = expression.args.get("savepoint")
4114        savepoint = f" TO {savepoint}" if savepoint else ""
4115        return f"ROLLBACK{savepoint}"
4116
4117    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4118        this = self.sql(expression, "this")
4119
4120        dtype = self.sql(expression, "dtype")
4121        if dtype:
4122            collate = self.sql(expression, "collate")
4123            collate = f" COLLATE {collate}" if collate else ""
4124            using = self.sql(expression, "using")
4125            using = f" USING {using}" if using else ""
4126            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4127            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
4128
4129        default = self.sql(expression, "default")
4130        if default:
4131            return f"ALTER COLUMN {this} SET DEFAULT {default}"
4132
4133        comment = self.sql(expression, "comment")
4134        if comment:
4135            return f"ALTER COLUMN {this} COMMENT {comment}"
4136
4137        visible = expression.args.get("visible")
4138        if visible:
4139            return f"ALTER COLUMN {this} SET {visible}"
4140
4141        allow_null = expression.args.get("allow_null")
4142        drop = expression.args.get("drop")
4143
4144        if not drop and not allow_null:
4145            self.unsupported("Unsupported ALTER COLUMN syntax")
4146
4147        if allow_null is not None:
4148            keyword = "DROP" if drop else "SET"
4149            return f"ALTER COLUMN {this} {keyword} NOT NULL"
4150
4151        return f"ALTER COLUMN {this} DROP DEFAULT"
4152
4153    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4154        this = self.sql(expression, "this")
4155        rename_from = self.sql(expression, "rename_from")
4156        if rename_from:
4157            if not self.SUPPORTS_CHANGE_COLUMN:
4158                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4159            return f"CHANGE COLUMN {rename_from} {this}"
4160        if not self.SUPPORTS_MODIFY_COLUMN:
4161            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4162        return f"MODIFY COLUMN {this}"
4163
4164    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4165        this = self.sql(expression, "this")
4166
4167        visible = expression.args.get("visible")
4168        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4169
4170        return f"ALTER INDEX {this} {visible_sql}"
4171
4172    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4173        this = self.sql(expression, "this")
4174        if not isinstance(expression.this, exp.Var):
4175            this = f"KEY DISTKEY {this}"
4176        return f"ALTER DISTSTYLE {this}"
4177
4178    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4179        compound = " COMPOUND" if expression.args.get("compound") else ""
4180        this = self.sql(expression, "this")
4181        expressions = self.expressions(expression, flat=True)
4182        expressions = f"({expressions})" if expressions else ""
4183        return f"ALTER{compound} SORTKEY {this or expressions}"
4184
4185    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4186        if not self.RENAME_TABLE_WITH_DB:
4187            # Remove db from tables
4188            expression = expression.transform(
4189                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4190            ).assert_is(exp.AlterRename)
4191        this = self.sql(expression, "this")
4192        to_kw = " TO" if include_to else ""
4193        return f"RENAME{to_kw} {this}"
4194
4195    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4196        exists = " IF EXISTS" if expression.args.get("exists") else ""
4197        old_column = self.sql(expression, "this")
4198        new_column = self.sql(expression, "to")
4199        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4200
4201    def alterset_sql(self, expression: exp.AlterSet) -> str:
4202        exprs = self.expressions(expression, flat=True)
4203        if self.ALTER_SET_WRAPPED:
4204            exprs = f"({exprs})"
4205
4206        return f"SET {exprs}"
4207
4208    def alter_sql(self, expression: exp.Alter) -> str:
4209        actions = expression.args["actions"]
4210
4211        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4212            actions[0], exp.ColumnDef
4213        ):
4214            actions_sql = self.expressions(expression, key="actions", flat=True)
4215            actions_sql = f"ADD {actions_sql}"
4216        else:
4217            actions_list = []
4218            for action in actions:
4219                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4220                    action_sql = self.add_column_sql(action)
4221                else:
4222                    action_sql = self.sql(action)
4223                    if isinstance(action, exp.Query):
4224                        action_sql = f"AS {action_sql}"
4225
4226                actions_list.append(action_sql)
4227
4228            actions_sql = self.format_args(*actions_list).lstrip("\n")
4229
4230        iceberg = (
4231            "ICEBERG "
4232            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4233            else ""
4234        )
4235        exists = " IF EXISTS" if expression.args.get("exists") else ""
4236        on_cluster = self.sql(expression, "cluster")
4237        on_cluster = f" {on_cluster}" if on_cluster else ""
4238        only = " ONLY" if expression.args.get("only") else ""
4239        options = self.expressions(expression, key="options")
4240        options = f", {options}" if options else ""
4241        kind = self.sql(expression, "kind")
4242        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4243        check = " WITH CHECK" if expression.args.get("check") else ""
4244        cascade = (
4245            " CASCADE"
4246            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4247            else ""
4248        )
4249        this = self.sql(expression, "this")
4250        this = f" {this}" if this else ""
4251
4252        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4253
4254    def altersession_sql(self, expression: exp.AlterSession) -> str:
4255        items_sql = self.expressions(expression, flat=True)
4256        keyword = "UNSET" if expression.args.get("unset") else "SET"
4257        return f"{keyword} {items_sql}"
4258
4259    def add_column_sql(self, expression: exp.Expr) -> str:
4260        sql = self.sql(expression)
4261        if isinstance(expression, exp.Schema):
4262            column_text = " COLUMNS"
4263        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4264            column_text = " COLUMN"
4265        else:
4266            column_text = ""
4267
4268        return f"ADD{column_text} {sql}"
4269
4270    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4271        expressions = self.expressions(expression)
4272        exists = " IF EXISTS " if expression.args.get("exists") else " "
4273        return f"DROP{exists}{expressions}"
4274
4275    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4276        return "DROP PRIMARY KEY"
4277
4278    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4279        return f"ADD {self.expressions(expression, indent=False)}"
4280
4281    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4282        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4283        location = self.sql(expression, "location")
4284        location = f" {location}" if location else ""
4285        return f"ADD {exists}{self.sql(expression.this)}{location}"
4286
4287    def distinct_sql(self, expression: exp.Distinct) -> str:
4288        this = self.expressions(expression, flat=True)
4289
4290        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4291            case = exp.case()
4292            for arg in expression.expressions:
4293                case = case.when(arg.is_(exp.null()), exp.null())
4294            this = self.sql(case.else_(f"({this})"))
4295
4296        this = f" {this}" if this else ""
4297
4298        on = self.sql(expression, "on")
4299        on = f" ON {on}" if on else ""
4300        return f"DISTINCT{this}{on}"
4301
4302    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4303        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4304
4305    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4306        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4307
4308    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4309        this_sql = self.sql(expression, "this")
4310        expression_sql = self.sql(expression, "expression")
4311        kind = "MAX" if expression.args.get("max") else "MIN"
4312        return f"{this_sql} HAVING {kind} {expression_sql}"
4313
4314    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4315        return self.sql(
4316            exp.Cast(
4317                this=exp.Div(this=expression.this, expression=expression.expression),
4318                to=exp.DataType(this=exp.DType.INT),
4319            )
4320        )
4321
4322    def dpipe_sql(self, expression: exp.DPipe) -> str:
4323        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4324            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4325        return self.binary(expression, "||")
4326
4327    def div_sql(self, expression: exp.Div) -> str:
4328        l, r = expression.left, expression.right
4329
4330        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4331            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4332
4333        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4334            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4335                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4336
4337        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4338            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4339                return self.sql(
4340                    exp.cast(
4341                        l / r,
4342                        to=exp.DType.BIGINT,
4343                    )
4344                )
4345
4346        return self.binary(expression, "/")
4347
4348    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4349        n = exp._wrap(expression.this, exp.Binary)
4350        d = exp._wrap(expression.expression, exp.Binary)
4351        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4352
4353    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4354        return self.binary(expression, "OVERLAPS")
4355
4356    def distance_sql(self, expression: exp.Distance) -> str:
4357        return self.binary(expression, "<->")
4358
4359    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4360        return self.binary(expression, "<<->>")
4361
4362    def dot_sql(self, expression: exp.Dot) -> str:
4363        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4364
4365    def eq_sql(self, expression: exp.EQ) -> str:
4366        return self.binary(expression, "=")
4367
4368    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4369        return self.binary(expression, ":=")
4370
4371    def escape_sql(self, expression: exp.Escape) -> str:
4372        this = expression.this
4373        if (
4374            isinstance(this, (exp.Like, exp.ILike))
4375            and isinstance(this.expression, (exp.All, exp.Any))
4376            and not self.SUPPORTS_LIKE_QUANTIFIERS
4377        ):
4378            return self._like_sql(this, escape=expression)
4379        return self.binary(expression, "ESCAPE")
4380
4381    def glob_sql(self, expression: exp.Glob) -> str:
4382        return self.binary(expression, "GLOB")
4383
4384    def gt_sql(self, expression: exp.GT) -> str:
4385        return self.binary(expression, ">")
4386
4387    def gte_sql(self, expression: exp.GTE) -> str:
4388        return self.binary(expression, ">=")
4389
4390    def is_sql(self, expression: exp.Is) -> str:
4391        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4392            return self.sql(
4393                expression.this if expression.expression.this else exp.not_(expression.this)
4394            )
4395        return self.binary(expression, "IS")
4396
4397    def _like_sql(
4398        self,
4399        expression: exp.Like | exp.ILike,
4400        escape: exp.Escape | None = None,
4401    ) -> str:
4402        this = expression.this
4403        rhs = expression.expression
4404
4405        if isinstance(expression, exp.Like):
4406            exp_class: type[exp.Like | exp.ILike] = exp.Like
4407            op = "LIKE"
4408        else:
4409            exp_class = exp.ILike
4410            op = "ILIKE"
4411
4412        if expression.args.get("negate"):
4413            op = f"NOT {op}"
4414
4415        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4416            exprs = rhs.this.unnest()
4417
4418            if isinstance(exprs, exp.Tuple):
4419                exprs = exprs.expressions
4420            else:
4421                exprs = [exprs]
4422
4423            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4424
4425            def _make_like(expr: exp.Expression) -> exp.Expression:
4426                like: exp.Expression = exp_class(
4427                    this=this, expression=expr, negate=expression.args.get("negate")
4428                )
4429                if escape:
4430                    like = exp.Escape(this=like, expression=escape.expression.copy())
4431                return like
4432
4433            like_expr: exp.Expr = _make_like(exprs[0])
4434            for expr in exprs[1:]:
4435                like_expr = connective(like_expr, _make_like(expr), copy=False)
4436
4437            parent = escape.parent if escape else expression.parent
4438            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4439                parent, exp.Condition
4440            ):
4441                like_expr = exp.paren(like_expr, copy=False)
4442
4443            return self.sql(like_expr)
4444
4445        return self.binary(expression, op)
4446
4447    def like_sql(self, expression: exp.Like) -> str:
4448        return self._like_sql(expression)
4449
4450    def ilike_sql(self, expression: exp.ILike) -> str:
4451        return self._like_sql(expression)
4452
4453    def match_sql(self, expression: exp.Match) -> str:
4454        return self.binary(expression, "MATCH")
4455
4456    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4457        return self.binary(expression, "SIMILAR TO")
4458
4459    def lt_sql(self, expression: exp.LT) -> str:
4460        return self.binary(expression, "<")
4461
4462    def lte_sql(self, expression: exp.LTE) -> str:
4463        return self.binary(expression, "<=")
4464
4465    def mod_sql(self, expression: exp.Mod) -> str:
4466        return self.binary(expression, "%")
4467
4468    def mul_sql(self, expression: exp.Mul) -> str:
4469        return self.binary(expression, "*")
4470
4471    def neq_sql(self, expression: exp.NEQ) -> str:
4472        return self.binary(expression, "<>")
4473
4474    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4475        return self.binary(expression, "IS NOT DISTINCT FROM")
4476
4477    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4478        return self.binary(expression, "IS DISTINCT FROM")
4479
4480    def sub_sql(self, expression: exp.Sub) -> str:
4481        return self.binary(expression, "-")
4482
4483    def trycast_sql(self, expression: exp.TryCast) -> str:
4484        return self.cast_sql(expression, safe_prefix="TRY_")
4485
4486    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4487        return self.cast_sql(expression)
4488
4489    def try_sql(self, expression: exp.Try) -> str:
4490        if not self.TRY_SUPPORTED:
4491            self.unsupported("Unsupported TRY function")
4492            return self.sql(expression, "this")
4493
4494        return self.func("TRY", expression.this)
4495
4496    def log_sql(self, expression: exp.Log) -> str:
4497        this = expression.this
4498        expr = expression.expression
4499
4500        if self.dialect.LOG_BASE_FIRST is False:
4501            this, expr = expr, this
4502        elif self.dialect.LOG_BASE_FIRST is None and expr:
4503            if this.name in ("2", "10"):
4504                return self.func(f"LOG{this.name}", expr)
4505
4506            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4507
4508        return self.func("LOG", this, expr)
4509
4510    def use_sql(self, expression: exp.Use) -> str:
4511        kind = self.sql(expression, "kind")
4512        kind = f" {kind}" if kind else ""
4513        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4514        this = f" {this}" if this else ""
4515        return f"USE{kind}{this}"
4516
4517    def binary(self, expression: exp.Binary, op: str) -> str:
4518        sqls: list[str] = []
4519        stack: list[None | str | exp.Expr] = [expression]
4520        binary_type = type(expression)
4521
4522        while stack:
4523            node = stack.pop()
4524
4525            if type(node) is binary_type:
4526                op_func = node.args.get("operator")
4527                if op_func:
4528                    op = f"OPERATOR({self.sql(op_func)})"
4529
4530                stack.append(node.args.get("expression"))
4531                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4532                stack.append(node.args.get("this"))
4533            else:
4534                sqls.append(self.sql(node))
4535
4536        return "".join(sqls)
4537
4538    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4539        to_clause = self.sql(expression, "to")
4540        if to_clause:
4541            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4542
4543        return self.function_fallback_sql(expression)
4544
4545    def function_fallback_sql(self, expression: exp.Func) -> str:
4546        args = []
4547
4548        for key in expression.arg_types:
4549            arg_value = expression.args.get(key)
4550
4551            if isinstance(arg_value, list):
4552                for value in arg_value:
4553                    args.append(value)
4554            elif arg_value is not None:
4555                args.append(arg_value)
4556
4557        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4558            name = expression.meta_get("name") or expression.sql_name()
4559        else:
4560            name = expression.sql_name()
4561
4562        return self.func(name, *args)
4563
4564    def func(
4565        self,
4566        name: str,
4567        *args: t.Any,
4568        prefix: str = "(",
4569        suffix: str = ")",
4570        normalize: bool = True,
4571    ) -> str:
4572        name = self.normalize_func(name) if normalize else name
4573        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4574
4575    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4576        arg_sqls = tuple(
4577            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4578        )
4579        if self.pretty and self.too_wide(arg_sqls):
4580            return self.indent(
4581                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4582            )
4583        return sep.join(arg_sqls)
4584
4585    def too_wide(self, args: t.Iterable) -> bool:
4586        return sum(len(arg) for arg in args) > self.max_text_width
4587
4588    def format_time(
4589        self,
4590        expression: exp.Expr,
4591        inverse_time_mapping: dict[str, str] | None = None,
4592        inverse_time_trie: dict | None = None,
4593    ) -> str | None:
4594        return format_time(
4595            self.sql(expression, "format"),
4596            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4597            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4598        )
4599
4600    def expressions(
4601        self,
4602        expression: exp.Expr | None = None,
4603        key: str | None = None,
4604        sqls: t.Collection[str | exp.Expr] | None = None,
4605        flat: bool = False,
4606        indent: bool = True,
4607        skip_first: bool = False,
4608        skip_last: bool = False,
4609        sep: str = ", ",
4610        prefix: str = "",
4611        dynamic: bool = False,
4612        new_line: bool = False,
4613    ) -> str:
4614        expressions = expression.args.get(key or "expressions") if expression else sqls
4615
4616        if not expressions:
4617            return ""
4618
4619        if flat:
4620            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4621
4622        num_sqls = len(expressions)
4623        result_sqls = []
4624
4625        for i, e in enumerate(expressions):
4626            sql = self.sql(e, comment=False)
4627            if not sql:
4628                continue
4629
4630            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4631
4632            if self.pretty:
4633                if self.leading_comma:
4634                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4635                else:
4636                    result_sqls.append(
4637                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4638                    )
4639            else:
4640                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4641
4642        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4643            if new_line:
4644                result_sqls.insert(0, "")
4645                result_sqls.append("")
4646            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4647        else:
4648            result_sql = "".join(result_sqls)
4649
4650        return (
4651            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4652            if indent
4653            else result_sql
4654        )
4655
4656    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4657        flat = flat or isinstance(expression.parent, exp.Properties)
4658        expressions_sql = self.expressions(expression, flat=flat)
4659        if flat:
4660            return f"{op} {expressions_sql}"
4661        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4662
4663    def naked_property(self, expression: exp.Property) -> str:
4664        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4665        if not property_name:
4666            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4667        return f"{property_name} {self.sql(expression, 'this')}"
4668
4669    def tag_sql(self, expression: exp.Tag) -> str:
4670        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4671
4672    def token_sql(self, token_type: TokenType) -> str:
4673        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4674
4675    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4676        this = self.sql(expression, "this")
4677        expressions = self.no_identify(self.expressions, expression)
4678        expressions = (
4679            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4680        )
4681        return f"{this}{expressions}" if expressions.strip() != "" else this
4682
4683    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4684        return self.expressions(expression, flat=True)
4685
4686    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4687        params = self.no_identify(self.expressions, expression, flat=True)
4688        body = self.sql(expression, "this")
4689        prefix = "TABLE " if expression.args.get("is_table") else ""
4690        return f"({params}) AS {prefix}{body}"
4691
4692    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4693        this = self.sql(expression, "this")
4694        expressions = self.expressions(expression, flat=True)
4695        return f"{this}({expressions})"
4696
4697    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4698        return self.binary(expression, "=>")
4699
4700    def when_sql(self, expression: exp.When) -> str:
4701        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4702        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4703        condition = self.sql(expression, "condition")
4704        condition = f" AND {condition}" if condition else ""
4705
4706        then_expression = expression.args.get("then")
4707        if isinstance(then_expression, exp.Insert):
4708            this = self.sql(then_expression, "this")
4709            this = f"INSERT {this}" if this else "INSERT"
4710            then = self.sql(then_expression, "expression")
4711            then = f"{this} VALUES {then}" if then else this
4712        elif isinstance(then_expression, exp.Update):
4713            if isinstance(then_expression.args.get("expressions"), exp.Star):
4714                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4715            else:
4716                expressions_sql = self.expressions(then_expression)
4717                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4718        else:
4719            then = self.sql(then_expression)
4720
4721        if isinstance(then_expression, (exp.Insert, exp.Update)):
4722            where = self.sql(then_expression, "where")
4723            if where and not self.SUPPORTS_MERGE_WHERE:
4724                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4725                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4726                where = ""
4727            then = f"{then}{where}"
4728        return f"WHEN {matched}{source}{condition} THEN {then}"
4729
4730    def whens_sql(self, expression: exp.Whens) -> str:
4731        return self.expressions(expression, sep=" ", indent=False)
4732
4733    def merge_sql(self, expression: exp.Merge) -> str:
4734        table = expression.this
4735        table_alias = ""
4736
4737        hints = table.args.get("hints")
4738        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4739            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4740            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4741
4742        this = self.sql(table)
4743        using = f"USING {self.sql(expression, 'using')}"
4744        whens = self.sql(expression, "whens")
4745
4746        on = self.sql(expression, "on")
4747        on = f"ON {on}" if on else ""
4748
4749        if not on:
4750            on = self.expressions(expression, key="using_cond")
4751            on = f"USING ({on})" if on else ""
4752
4753        returning = self.sql(expression, "returning")
4754        if returning:
4755            whens = f"{whens}{returning}"
4756
4757        sep = self.sep()
4758
4759        return self.prepend_ctes(
4760            expression,
4761            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4762        )
4763
4764    @unsupported_args("format")
4765    def tochar_sql(self, expression: exp.ToChar) -> str:
4766        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4767
4768    @unsupported_args("default")
4769    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4770        if not self.SUPPORTS_TO_NUMBER:
4771            self.unsupported("Unsupported TO_NUMBER function")
4772            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4773
4774        fmt = expression.args.get("format")
4775        if not fmt:
4776            self.unsupported("Conversion format is required for TO_NUMBER")
4777            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4778
4779        return self.func("TO_NUMBER", expression.this, fmt)
4780
4781    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4782        this = self.sql(expression, "this")
4783        kind = self.sql(expression, "kind")
4784        settings_sql = self.expressions(expression, key="settings", sep=" ")
4785        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4786        return f"{this}({kind}{args})"
4787
4788    def dictrange_sql(self, expression: exp.DictRange) -> str:
4789        this = self.sql(expression, "this")
4790        max = self.sql(expression, "max")
4791        min = self.sql(expression, "min")
4792        return f"{this}(MIN {min} MAX {max})"
4793
4794    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4795        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4796
4797    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4798        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4799
4800    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4801    def uniquekeyproperty_sql(
4802        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4803    ) -> str:
4804        return f"{prefix} ({self.expressions(expression, flat=True)})"
4805
4806    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4807    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4808        expressions = self.expressions(expression, flat=True)
4809        expressions = f" {self.wrap(expressions)}" if expressions else ""
4810        buckets = self.sql(expression, "buckets")
4811        kind = self.sql(expression, "kind")
4812        buckets = f" BUCKETS {buckets}" if buckets else ""
4813        order = self.sql(expression, "order")
4814        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4815
4816    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4817        return ""
4818
4819    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4820        expressions = self.expressions(expression, key="expressions", flat=True)
4821        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4822        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4823        buckets = self.sql(expression, "buckets")
4824        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4825
4826    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4827        this = self.sql(expression, "this")
4828        having = self.sql(expression, "having")
4829
4830        if having:
4831            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4832
4833        return self.func("ANY_VALUE", this)
4834
4835    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4836        transform = self.func("TRANSFORM", *expression.expressions)
4837        row_format_before = self.sql(expression, "row_format_before")
4838        row_format_before = f" {row_format_before}" if row_format_before else ""
4839        record_writer = self.sql(expression, "record_writer")
4840        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4841        using = f" USING {self.sql(expression, 'command_script')}"
4842        schema = self.sql(expression, "schema")
4843        schema = f" AS {schema}" if schema else ""
4844        row_format_after = self.sql(expression, "row_format_after")
4845        row_format_after = f" {row_format_after}" if row_format_after else ""
4846        record_reader = self.sql(expression, "record_reader")
4847        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4848        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4849
4850    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4851        key_block_size = self.sql(expression, "key_block_size")
4852        if key_block_size:
4853            return f"KEY_BLOCK_SIZE = {key_block_size}"
4854
4855        using = self.sql(expression, "using")
4856        if using:
4857            return f"USING {using}"
4858
4859        parser = self.sql(expression, "parser")
4860        if parser:
4861            return f"WITH PARSER {parser}"
4862
4863        comment = self.sql(expression, "comment")
4864        if comment:
4865            return f"COMMENT {comment}"
4866
4867        visible = expression.args.get("visible")
4868        if visible is not None:
4869            return "VISIBLE" if visible else "INVISIBLE"
4870
4871        engine_attr = self.sql(expression, "engine_attr")
4872        if engine_attr:
4873            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4874
4875        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4876        if secondary_engine_attr:
4877            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4878
4879        self.unsupported("Unsupported index constraint option.")
4880        return ""
4881
4882    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4883        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4884        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4885
4886    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4887        kind = self.sql(expression, "kind")
4888        kind = f"{kind} INDEX" if kind else "INDEX"
4889        this = self.sql(expression, "this")
4890        this = f" {this}" if this else ""
4891        index_type = self.sql(expression, "index_type")
4892        index_type = f" USING {index_type}" if index_type else ""
4893        expressions = self.expressions(expression, flat=True)
4894        expressions = f" ({expressions})" if expressions else ""
4895        options = self.expressions(expression, key="options", sep=" ")
4896        options = f" {options}" if options else ""
4897        return f"{kind}{this}{index_type}{expressions}{options}"
4898
4899    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4900        if self.NVL2_SUPPORTED:
4901            return self.function_fallback_sql(expression)
4902
4903        case = exp.Case().when(
4904            expression.this.is_(exp.null()).not_(copy=False),
4905            expression.args["true"],
4906            copy=False,
4907        )
4908        else_cond = expression.args.get("false")
4909        if else_cond:
4910            case.else_(else_cond, copy=False)
4911
4912        return self.sql(case)
4913
4914    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4915        this = self.sql(expression, "this")
4916        expr = self.sql(expression, "expression")
4917        position = self.sql(expression, "position")
4918        position = f", {position}" if position else ""
4919        iterator = self.sql(expression, "iterator")
4920        condition = self.sql(expression, "condition")
4921        condition = f" IF {condition}" if condition else ""
4922        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4923
4924    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4925        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4926
4927    def opclass_sql(self, expression: exp.Opclass) -> str:
4928        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4929
4930    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4931        model = self.sql(expression, "this")
4932        model = f"MODEL {model}"
4933        expr = expression.expression
4934        if expr:
4935            expr_sql = self.sql(expression, "expression")
4936            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
4937        else:
4938            expr_sql = None
4939
4940        parameters = self.sql(expression, "params_struct") or None
4941
4942        return self.func(name, model, expr_sql, parameters)
4943
4944    def predict_sql(self, expression: exp.Predict) -> str:
4945        return self._ml_sql(expression, "PREDICT")
4946
4947    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4948        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4949        return self._ml_sql(expression, name)
4950
4951    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4952        return self._ml_sql(expression, "GENERATE_TEXT")
4953
4954    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4955        return self._ml_sql(expression, "GENERATE_TABLE")
4956
4957    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4958        return self._ml_sql(expression, "GENERATE_BOOL")
4959
4960    def generateint_sql(self, expression: exp.GenerateInt) -> str:
4961        return self._ml_sql(expression, "GENERATE_INT")
4962
4963    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
4964        return self._ml_sql(expression, "GENERATE_DOUBLE")
4965
4966    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4967        return self._ml_sql(expression, "TRANSLATE")
4968
4969    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4970        return self._ml_sql(expression, "FORECAST")
4971
4972    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
4973        this_sql = self.sql(expression, "this")
4974        if isinstance(expression.this, exp.Table):
4975            this_sql = f"TABLE {this_sql}"
4976
4977        return self.func(
4978            "FORECAST",
4979            this_sql,
4980            expression.args.get("data_col"),
4981            expression.args.get("timestamp_col"),
4982            expression.args.get("model"),
4983            expression.args.get("id_cols"),
4984            expression.args.get("horizon"),
4985            expression.args.get("forecast_end_timestamp"),
4986            expression.args.get("confidence_level"),
4987            expression.args.get("output_historical_time_series"),
4988            expression.args.get("context_window"),
4989        )
4990
4991    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4992        this_sql = self.sql(expression, "this")
4993        if isinstance(expression.this, exp.Table):
4994            this_sql = f"TABLE {this_sql}"
4995
4996        return self.func(
4997            "FEATURES_AT_TIME",
4998            this_sql,
4999            expression.args.get("time"),
5000            expression.args.get("num_rows"),
5001            expression.args.get("ignore_feature_nulls"),
5002        )
5003
5004    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5005        this_sql = self.sql(expression, "this")
5006        if isinstance(expression.this, exp.Table):
5007            this_sql = f"TABLE {this_sql}"
5008
5009        query_table = self.sql(expression, "query_table")
5010        if isinstance(expression.args["query_table"], exp.Table):
5011            query_table = f"TABLE {query_table}"
5012
5013        return self.func(
5014            "VECTOR_SEARCH",
5015            this_sql,
5016            expression.args.get("column_to_search"),
5017            query_table,
5018            expression.args.get("query_column_to_search"),
5019            expression.args.get("top_k"),
5020            expression.args.get("distance_type"),
5021            expression.args.get("options"),
5022        )
5023
5024    def forin_sql(self, expression: exp.ForIn) -> str:
5025        this = self.sql(expression, "this")
5026        expression_sql = self.sql(expression, "expression")
5027        return f"FOR {this} DO {expression_sql}"
5028
5029    def refresh_sql(self, expression: exp.Refresh) -> str:
5030        this = self.sql(expression, "this")
5031        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5032        return f"REFRESH {kind}{this}"
5033
5034    def toarray_sql(self, expression: exp.ToArray) -> str:
5035        arg = expression.this
5036        if not arg.type:
5037            import sqlglot.optimizer.annotate_types
5038
5039            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5040
5041        if arg.is_type(exp.DType.ARRAY):
5042            return self.sql(arg)
5043
5044        cond_for_null = arg.is_(exp.null())
5045        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5046
5047    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5048        this = expression.this
5049        time_format = self.format_time(expression)
5050
5051        if time_format:
5052            return self.sql(
5053                exp.cast(
5054                    exp.StrToTime(this=this, format=expression.args["format"]),
5055                    exp.DType.TIME,
5056                )
5057            )
5058
5059        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5060            return self.sql(this)
5061
5062        return self.sql(exp.cast(this, exp.DType.TIME))
5063
5064    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5065        this = expression.this
5066        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5067            return self.sql(this)
5068
5069        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5070
5071    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5072        this = expression.this
5073        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5074            return self.sql(this)
5075
5076        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5077
5078    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5079        this = expression.this
5080        time_format = self.format_time(expression)
5081        safe = expression.args.get("safe")
5082        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5083            return self.sql(
5084                exp.cast(
5085                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5086                    exp.DType.DATE,
5087                )
5088            )
5089
5090        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5091            return self.sql(this)
5092
5093        if safe:
5094            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5095
5096        return self.sql(exp.cast(this, exp.DType.DATE))
5097
5098    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5099        return self.sql(
5100            exp.func(
5101                "DATEDIFF",
5102                expression.this,
5103                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5104                "day",
5105            )
5106        )
5107
5108    def lastday_sql(self, expression: exp.LastDay) -> str:
5109        if self.LAST_DAY_SUPPORTS_DATE_PART:
5110            return self.function_fallback_sql(expression)
5111
5112        unit = expression.text("unit")
5113        if unit and unit != "MONTH":
5114            self.unsupported("Date parts are not supported in LAST_DAY.")
5115
5116        return self.func("LAST_DAY", expression.this)
5117
5118    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5119        import sqlglot.dialects.dialect
5120
5121        return self.func(
5122            "DATE_ADD",
5123            expression.this,
5124            expression.expression,
5125            sqlglot.dialects.dialect.unit_to_str(expression),
5126        )
5127
5128    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5129        if self.CAN_IMPLEMENT_ARRAY_ANY:
5130            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5131            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5132            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5133            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5134
5135        import sqlglot.dialects.dialect
5136
5137        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5138        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5139            self.unsupported("ARRAY_ANY is unsupported")
5140
5141        return self.function_fallback_sql(expression)
5142
5143    def struct_sql(self, expression: exp.Struct) -> str:
5144        expression.set(
5145            "expressions",
5146            [
5147                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5148                if isinstance(e, exp.PropertyEQ)
5149                else e
5150                for e in expression.expressions
5151            ],
5152        )
5153
5154        return self.function_fallback_sql(expression)
5155
5156    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5157        low = self.sql(expression, "this")
5158        high = self.sql(expression, "expression")
5159
5160        return f"{low} TO {high}"
5161
5162    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5163        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5164        tables = f" {self.expressions(expression)}"
5165
5166        exists = " IF EXISTS" if expression.args.get("exists") else ""
5167
5168        on_cluster = self.sql(expression, "cluster")
5169        on_cluster = f" {on_cluster}" if on_cluster else ""
5170
5171        identity = self.sql(expression, "identity")
5172        identity = f" {identity} IDENTITY" if identity else ""
5173
5174        option = self.sql(expression, "option")
5175        option = f" {option}" if option else ""
5176
5177        partition = self.sql(expression, "partition")
5178        partition = f" {partition}" if partition else ""
5179
5180        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5181
5182    # This transpiles T-SQL's CONVERT function
5183    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5184    def convert_sql(self, expression: exp.Convert) -> str:
5185        to = expression.this
5186        value = expression.expression
5187        style = expression.args.get("style")
5188        safe = expression.args.get("safe")
5189        strict = expression.args.get("strict")
5190
5191        if not to or not value:
5192            return ""
5193
5194        # Retrieve length of datatype and override to default if not specified
5195        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5196            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5197
5198        transformed: exp.Expr | None = None
5199        cast = exp.Cast if strict else exp.TryCast
5200
5201        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5202        if isinstance(style, exp.Literal) and style.is_int:
5203            import sqlglot.dialects.tsql
5204
5205            style_value = style.name
5206            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5207            if not converted_style:
5208                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5209
5210            fmt = exp.Literal.string(converted_style)
5211
5212            if to.this == exp.DType.DATE:
5213                transformed = exp.StrToDate(this=value, format=fmt)
5214            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5215                transformed = exp.StrToTime(this=value, format=fmt)
5216            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5217                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5218            elif to.this == exp.DType.TEXT:
5219                transformed = exp.TimeToStr(this=value, format=fmt)
5220
5221        if not transformed:
5222            transformed = cast(this=value, to=to, safe=safe)
5223
5224        return self.sql(transformed)
5225
5226    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5227        this = expression.this
5228        if isinstance(this, exp.JSONPathWildcard):
5229            this = self.json_path_part(this)
5230            return f".{this}" if this else ""
5231
5232        if self.SAFE_JSON_PATH_KEY_RE.match(this):
5233            return f".{this}"
5234
5235        this = self.json_path_part(this)
5236        return (
5237            f"[{this}]"
5238            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5239            else f".{this}"
5240        )
5241
5242    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5243        this = self.json_path_part(expression.this)
5244        return f"[{this}]" if this else ""
5245
5246    def _simplify_unless_literal(self, expression: E) -> E:
5247        if not isinstance(expression, exp.Literal):
5248            import sqlglot.optimizer.simplify
5249
5250            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5251
5252        return expression
5253
5254    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5255        this = expression.this
5256        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5257            self.unsupported(
5258                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5259            )
5260            return self.sql(this)
5261
5262        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5263            if self.IGNORE_NULLS_BEFORE_ORDER:
5264                # The first modifier here will be the one closest to the AggFunc's arg
5265                mods = sorted(
5266                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5267                    key=lambda x: (
5268                        0
5269                        if isinstance(x, exp.HavingMax)
5270                        else (1 if isinstance(x, exp.Order) else 2)
5271                    ),
5272                )
5273
5274                if mods:
5275                    mod = mods[0]
5276                    this = expression.__class__(this=mod.this.copy())
5277                    this.meta["inline"] = True
5278                    mod.this.replace(this)
5279                    return self.sql(expression.this)
5280
5281            agg_func = expression.find(exp.AggFunc)
5282
5283            if agg_func:
5284                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5285                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5286
5287        return f"{self.sql(expression, 'this')} {text}"
5288
5289    def _replace_line_breaks(self, string: str) -> str:
5290        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5291        if self.pretty:
5292            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5293        return string
5294
5295    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5296        option = self.sql(expression, "this")
5297
5298        if expression.expressions:
5299            upper = option.upper()
5300
5301            # Snowflake FILE_FORMAT options are separated by whitespace
5302            sep = " " if upper == "FILE_FORMAT" else ", "
5303
5304            # Databricks copy/format options do not set their list of values with EQ
5305            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5306            values = self.expressions(expression, flat=True, sep=sep)
5307            return f"{option}{op}({values})"
5308
5309        value = self.sql(expression, "expression")
5310
5311        if not value:
5312            return option
5313
5314        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5315
5316        return f"{option}{op}{value}"
5317
5318    def credentials_sql(self, expression: exp.Credentials) -> str:
5319        cred_expr = expression.args.get("credentials")
5320        if isinstance(cred_expr, exp.Literal):
5321            # Redshift case: CREDENTIALS <string>
5322            credentials = self.sql(expression, "credentials")
5323            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5324        else:
5325            # Snowflake case: CREDENTIALS = (...)
5326            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5327            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5328
5329        storage = self.sql(expression, "storage")
5330        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5331
5332        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5333        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5334
5335        iam_role = self.sql(expression, "iam_role")
5336        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5337
5338        region = self.sql(expression, "region")
5339        region = f" REGION {region}" if region else ""
5340
5341        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5342
5343    def copy_sql(self, expression: exp.Copy) -> str:
5344        this = self.sql(expression, "this")
5345        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5346
5347        credentials = self.sql(expression, "credentials")
5348        credentials = self.seg(credentials) if credentials else ""
5349        files = self.expressions(expression, key="files", flat=True)
5350        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5351
5352        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5353        params = self.expressions(
5354            expression,
5355            key="params",
5356            sep=sep,
5357            new_line=True,
5358            skip_last=True,
5359            skip_first=True,
5360            indent=self.COPY_PARAMS_ARE_WRAPPED,
5361        )
5362
5363        if params:
5364            if self.COPY_PARAMS_ARE_WRAPPED:
5365                params = f" WITH ({params})"
5366            elif not self.pretty and (files or credentials):
5367                params = f" {params}"
5368
5369        return f"COPY{this}{kind} {files}{credentials}{params}"
5370
5371    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5372        return ""
5373
5374    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5375        on_sql = "ON" if expression.args.get("on") else "OFF"
5376        filter_col: str | None = self.sql(expression, "filter_column")
5377        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5378        retention_period: str | None = self.sql(expression, "retention_period")
5379        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5380
5381        if filter_col or retention_period:
5382            on_sql = self.func("ON", filter_col, retention_period)
5383
5384        return f"DATA_DELETION={on_sql}"
5385
5386    def maskingpolicycolumnconstraint_sql(
5387        self, expression: exp.MaskingPolicyColumnConstraint
5388    ) -> str:
5389        this = self.sql(expression, "this")
5390        expressions = self.expressions(expression, flat=True)
5391        expressions = f" USING ({expressions})" if expressions else ""
5392        return f"MASKING POLICY {this}{expressions}"
5393
5394    def gapfill_sql(self, expression: exp.GapFill) -> str:
5395        this = self.sql(expression, "this")
5396        this = f"TABLE {this}"
5397        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5398
5399    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5400        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5401
5402    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5403        this = self.sql(expression, "this")
5404        expr = expression.expression
5405
5406        if isinstance(expr, exp.Func):
5407            # T-SQL's CLR functions are case sensitive
5408            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5409        else:
5410            expr = self.sql(expression, "expression")
5411
5412        return self.scope_resolution(expr, this)
5413
5414    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5415        if self.PARSE_JSON_NAME is None:
5416            return self.sql(expression.this)
5417
5418        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5419
5420    def rand_sql(self, expression: exp.Rand) -> str:
5421        lower = self.sql(expression, "lower")
5422        upper = self.sql(expression, "upper")
5423
5424        if lower and upper:
5425            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5426        return self.func("RAND", expression.this)
5427
5428    def changes_sql(self, expression: exp.Changes) -> str:
5429        information = self.sql(expression, "information")
5430        information = f"INFORMATION => {information}"
5431        at_before = self.sql(expression, "at_before")
5432        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5433        end = self.sql(expression, "end")
5434        end = f"{self.seg('')}{end}" if end else ""
5435
5436        return f"CHANGES ({information}){at_before}{end}"
5437
5438    def pad_sql(self, expression: exp.Pad) -> str:
5439        prefix = "L" if expression.args.get("is_left") else "R"
5440
5441        fill_pattern = self.sql(expression, "fill_pattern") or None
5442        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5443            fill_pattern = "' '"
5444
5445        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5446
5447    def summarize_sql(self, expression: exp.Summarize) -> str:
5448        table = " TABLE" if expression.args.get("table") else ""
5449        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5450
5451    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5452        generate_series = exp.GenerateSeries(**expression.args)
5453
5454        parent = expression.parent
5455        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5456            parent = parent.parent
5457
5458        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5459            return self.sql(exp.Unnest(expressions=[generate_series]))
5460
5461        if isinstance(parent, exp.Select):
5462            self.unsupported("GenerateSeries projection unnesting is not supported.")
5463
5464        return self.sql(generate_series)
5465
5466    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5467        if self.SUPPORTS_CONVERT_TIMEZONE:
5468            return self.function_fallback_sql(expression)
5469
5470        source_tz = expression.args.get("source_tz")
5471        target_tz = expression.args.get("target_tz")
5472        timestamp = expression.args.get("timestamp")
5473
5474        if source_tz and timestamp:
5475            timestamp = exp.AtTimeZone(
5476                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5477            )
5478
5479        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5480
5481        return self.sql(expr)
5482
5483    def json_sql(self, expression: exp.JSON) -> str:
5484        this = self.sql(expression, "this")
5485        this = f" {this}" if this else ""
5486
5487        _with = expression.args.get("with_")
5488
5489        if _with is None:
5490            with_sql = ""
5491        elif not _with:
5492            with_sql = " WITHOUT"
5493        else:
5494            with_sql = " WITH"
5495
5496        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5497
5498        return f"JSON{this}{with_sql}{unique_sql}"
5499
5500    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5501        path = self.sql(expression, "path")
5502        returning = self.sql(expression, "returning")
5503        returning = f" RETURNING {returning}" if returning else ""
5504
5505        on_condition = self.sql(expression, "on_condition")
5506        on_condition = f" {on_condition}" if on_condition else ""
5507
5508        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5509
5510    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5511        regexp = " REGEXP" if expression.args.get("regexp") else ""
5512        return f"SKIP{regexp} {self.sql(expression.expression)}"
5513
5514    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5515        else_ = "ELSE " if expression.args.get("else_") else ""
5516        condition = self.sql(expression, "expression")
5517        condition = f"WHEN {condition} THEN " if condition else else_
5518        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5519        return f"{condition}{insert}"
5520
5521    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5522        kind = self.sql(expression, "kind")
5523        expressions = self.seg(self.expressions(expression, sep=" "))
5524        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5525        return res
5526
5527    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5528        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5529        empty = expression.args.get("empty")
5530        empty = (
5531            f"DEFAULT {empty} ON EMPTY"
5532            if isinstance(empty, exp.Expr)
5533            else self.sql(expression, "empty")
5534        )
5535
5536        error = expression.args.get("error")
5537        error = (
5538            f"DEFAULT {error} ON ERROR"
5539            if isinstance(error, exp.Expr)
5540            else self.sql(expression, "error")
5541        )
5542
5543        if error and empty:
5544            error = (
5545                f"{empty} {error}"
5546                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5547                else f"{error} {empty}"
5548            )
5549            empty = ""
5550
5551        null = self.sql(expression, "null")
5552
5553        return f"{empty}{error}{null}"
5554
5555    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5556        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5557        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5558
5559    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5560        this = self.sql(expression, "this")
5561        path = self.sql(expression, "path")
5562
5563        passing = self.expressions(expression, "passing")
5564        passing = f" PASSING {passing}" if passing else ""
5565
5566        on_condition = self.sql(expression, "on_condition")
5567        on_condition = f" {on_condition}" if on_condition else ""
5568
5569        path = f"{path}{passing}{on_condition}"
5570
5571        return self.func("JSON_EXISTS", this, path)
5572
5573    def _add_arrayagg_null_filter(
5574        self,
5575        array_agg_sql: str,
5576        array_agg_expr: exp.ArrayAgg,
5577        column_expr: exp.Expr,
5578    ) -> str:
5579        """
5580        Add NULL filter to ARRAY_AGG if dialect requires it.
5581
5582        Args:
5583            array_agg_sql: The generated ARRAY_AGG SQL string
5584            array_agg_expr: The ArrayAgg expression node
5585            column_expr: The column/expression to filter (before ORDER BY wrapping)
5586
5587        Returns:
5588            SQL string with FILTER clause added if needed
5589        """
5590        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5591        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5592        if not (
5593            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5594        ):
5595            return array_agg_sql
5596
5597        parent = array_agg_expr.parent
5598        if isinstance(parent, exp.Filter):
5599            parent_cond = parent.expression.this
5600            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5601        elif column_expr.find(exp.Column):
5602            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5603            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5604            this_sql = (
5605                self.expressions(column_expr)
5606                if isinstance(column_expr, exp.Distinct)
5607                else self.sql(column_expr)
5608            )
5609            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5610
5611        return array_agg_sql
5612
5613    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5614        array_agg = self.function_fallback_sql(expression)
5615        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5616
5617    def slice_sql(self, expression: exp.Slice) -> str:
5618        step = self.sql(expression, "step")
5619        end = self.sql(expression.expression)
5620        begin = self.sql(expression.this)
5621
5622        sql = f"{end}:{step}" if step else end
5623        return f"{begin}:{sql}" if sql else f"{begin}:"
5624
5625    def apply_sql(self, expression: exp.Apply) -> str:
5626        this = self.sql(expression, "this")
5627        expr = self.sql(expression, "expression")
5628
5629        return f"{this} APPLY({expr})"
5630
5631    def _grant_or_revoke_sql(
5632        self,
5633        expression: exp.Grant | exp.Revoke,
5634        keyword: str,
5635        preposition: str,
5636        grant_option_prefix: str = "",
5637        grant_option_suffix: str = "",
5638    ) -> str:
5639        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5640
5641        kind = self.sql(expression, "kind")
5642        kind = f" {kind}" if kind else ""
5643
5644        securable = self.sql(expression, "securable")
5645        securable = f" {securable}" if securable else ""
5646
5647        principals = self.expressions(expression, key="principals", flat=True)
5648
5649        if not expression.args.get("grant_option"):
5650            grant_option_prefix = grant_option_suffix = ""
5651
5652        # cascade for revoke only
5653        cascade = self.sql(expression, "cascade")
5654        cascade = f" {cascade}" if cascade else ""
5655
5656        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5657
5658    def grant_sql(self, expression: exp.Grant) -> str:
5659        return self._grant_or_revoke_sql(
5660            expression,
5661            keyword="GRANT",
5662            preposition="TO",
5663            grant_option_suffix=" WITH GRANT OPTION",
5664        )
5665
5666    def revoke_sql(self, expression: exp.Revoke) -> str:
5667        return self._grant_or_revoke_sql(
5668            expression,
5669            keyword="REVOKE",
5670            preposition="FROM",
5671            grant_option_prefix="GRANT OPTION FOR ",
5672        )
5673
5674    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5675        this = self.sql(expression, "this")
5676        columns = self.expressions(expression, flat=True)
5677        columns = f"({columns})" if columns else ""
5678
5679        return f"{this}{columns}"
5680
5681    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5682        this = self.sql(expression, "this")
5683
5684        kind = self.sql(expression, "kind")
5685        kind = f"{kind} " if kind else ""
5686
5687        return f"{kind}{this}"
5688
5689    def columns_sql(self, expression: exp.Columns) -> str:
5690        func = self.function_fallback_sql(expression)
5691        if expression.args.get("unpack"):
5692            func = f"*{func}"
5693
5694        return func
5695
5696    def overlay_sql(self, expression: exp.Overlay) -> str:
5697        this = self.sql(expression, "this")
5698        expr = self.sql(expression, "expression")
5699        from_sql = self.sql(expression, "from_")
5700        for_sql = self.sql(expression, "for_")
5701        for_sql = f" FOR {for_sql}" if for_sql else ""
5702
5703        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5704
5705    @unsupported_args("format")
5706    def todouble_sql(self, expression: exp.ToDouble) -> str:
5707        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5708        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5709
5710    def string_sql(self, expression: exp.String) -> str:
5711        this = expression.this
5712        zone = expression.args.get("zone")
5713
5714        if zone:
5715            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5716            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5717            # set for source_tz to transpile the time conversion before the STRING cast
5718            this = exp.ConvertTimezone(
5719                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5720            )
5721
5722        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5723
5724    def median_sql(self, expression: exp.Median) -> str:
5725        if not self.SUPPORTS_MEDIAN:
5726            return self.sql(
5727                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5728            )
5729
5730        return self.function_fallback_sql(expression)
5731
5732    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5733        filler = self.sql(expression, "this")
5734        filler = f" {filler}" if filler else ""
5735        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5736        return f"TRUNCATE{filler} {with_count}"
5737
5738    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5739        if self.SUPPORTS_UNIX_SECONDS:
5740            return self.function_fallback_sql(expression)
5741
5742        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5743
5744        return self.sql(
5745            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5746        )
5747
5748    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5749        dim = expression.expression
5750
5751        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5752        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5753            if not (dim.is_int and dim.name == "1"):
5754                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5755            dim = None
5756
5757        # If dimension is required but not specified, default initialize it
5758        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5759            dim = exp.Literal.number(1)
5760
5761        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5762
5763    def attach_sql(self, expression: exp.Attach) -> str:
5764        this = self.sql(expression, "this")
5765        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5766        expressions = self.expressions(expression)
5767        expressions = f" ({expressions})" if expressions else ""
5768
5769        return f"ATTACH{exists_sql} {this}{expressions}"
5770
5771    def detach_sql(self, expression: exp.Detach) -> str:
5772        kind = self.sql(expression, "kind")
5773        kind = f" {kind}" if kind else ""
5774        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5775        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5776        exists = " IF EXISTS" if expression.args.get("exists") else ""
5777        if exists:
5778            kind = kind or " DATABASE"
5779
5780        this = self.sql(expression, "this")
5781        this = f" {this}" if this else ""
5782        cluster = self.sql(expression, "cluster")
5783        cluster = f" {cluster}" if cluster else ""
5784        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5785        sync = " SYNC" if expression.args.get("sync") else ""
5786        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5787
5788    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5789        this = self.sql(expression, "this")
5790        value = self.sql(expression, "expression")
5791        value = f" {value}" if value else ""
5792        return f"{this}{value}"
5793
5794    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5795        return (
5796            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5797        )
5798
5799    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5800        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5801        encode = f"{encode} {self.sql(expression, 'this')}"
5802
5803        properties = expression.args.get("properties")
5804        if properties:
5805            encode = f"{encode} {self.properties(properties)}"
5806
5807        return encode
5808
5809    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5810        this = self.sql(expression, "this")
5811        include = f"INCLUDE {this}"
5812
5813        column_def = self.sql(expression, "column_def")
5814        if column_def:
5815            include = f"{include} {column_def}"
5816
5817        alias = self.sql(expression, "alias")
5818        if alias:
5819            include = f"{include} AS {alias}"
5820
5821        return include
5822
5823    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5824        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5825        name = f"{prefix} {self.sql(expression, 'this')}"
5826        return self.func("XMLELEMENT", name, *expression.expressions)
5827
5828    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5829        this = self.sql(expression, "this")
5830        expr = self.sql(expression, "expression")
5831        expr = f"({expr})" if expr else ""
5832        return f"{this}{expr}"
5833
5834    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5835        partitions = self.expressions(expression, "partition_expressions")
5836        create = self.expressions(expression, "create_expressions")
5837        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5838
5839    def partitionbyrangepropertydynamic_sql(
5840        self, expression: exp.PartitionByRangePropertyDynamic
5841    ) -> str:
5842        start = self.sql(expression, "start")
5843        end = self.sql(expression, "end")
5844
5845        every = expression.args["every"]
5846        if isinstance(every, exp.Interval) and every.this.is_string:
5847            every.this.replace(exp.Literal.number(every.name))
5848
5849        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5850
5851    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5852        name = self.sql(expression, "this")
5853        values = self.expressions(expression, flat=True)
5854
5855        return f"NAME {name} VALUE {values}"
5856
5857    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5858        kind = self.sql(expression, "kind")
5859        sample = self.sql(expression, "sample")
5860        return f"SAMPLE {sample} {kind}"
5861
5862    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5863        kind = self.sql(expression, "kind")
5864        option = self.sql(expression, "option")
5865        option = f" {option}" if option else ""
5866        this = self.sql(expression, "this")
5867        this = f" {this}" if this else ""
5868        columns = self.expressions(expression)
5869        columns = f" {columns}" if columns else ""
5870        return f"{kind}{option} STATISTICS{this}{columns}"
5871
5872    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5873        this = self.sql(expression, "this")
5874        columns = self.expressions(expression)
5875        inner_expression = self.sql(expression, "expression")
5876        inner_expression = f" {inner_expression}" if inner_expression else ""
5877        update_options = self.sql(expression, "update_options")
5878        update_options = f" {update_options} UPDATE" if update_options else ""
5879        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5880
5881    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5882        kind = self.sql(expression, "kind")
5883        kind = f" {kind}" if kind else ""
5884        return f"DELETE{kind} STATISTICS"
5885
5886    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5887        inner_expression = self.sql(expression, "expression")
5888        return f"LIST CHAINED ROWS{inner_expression}"
5889
5890    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5891        kind = self.sql(expression, "kind")
5892        this = self.sql(expression, "this")
5893        this = f" {this}" if this else ""
5894        inner_expression = self.sql(expression, "expression")
5895        return f"VALIDATE {kind}{this}{inner_expression}"
5896
5897    def analyze_sql(self, expression: exp.Analyze) -> str:
5898        options = self.expressions(expression, key="options", sep=" ")
5899        options = f" {options}" if options else ""
5900        kind = self.sql(expression, "kind")
5901        kind = f" {kind}" if kind else ""
5902        this = self.sql(expression, "this")
5903        this = f" {this}" if this else ""
5904        mode = self.sql(expression, "mode")
5905        mode = f" {mode}" if mode else ""
5906        properties = self.sql(expression, "properties")
5907        properties = f" {properties}" if properties else ""
5908        partition = self.sql(expression, "partition")
5909        partition = f" {partition}" if partition else ""
5910        inner_expression = self.sql(expression, "expression")
5911        inner_expression = f" {inner_expression}" if inner_expression else ""
5912        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5913
5914    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5915        this = self.sql(expression, "this")
5916        namespaces = self.expressions(expression, key="namespaces")
5917        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5918        passing = self.expressions(expression, key="passing")
5919        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5920        columns = self.expressions(expression, key="columns")
5921        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5922        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5923        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5924
5925    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5926        this = self.sql(expression, "this")
5927        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5928
5929    def export_sql(self, expression: exp.Export) -> str:
5930        this = self.sql(expression, "this")
5931        connection = self.sql(expression, "connection")
5932        connection = f"WITH CONNECTION {connection} " if connection else ""
5933        options = self.sql(expression, "options")
5934        return f"EXPORT DATA {connection}{options} AS {this}"
5935
5936    def declare_sql(self, expression: exp.Declare) -> str:
5937        replace = "OR REPLACE " if expression.args.get("replace") else ""
5938        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
5939
5940    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5941        variables = self.expressions(expression, "this")
5942        default = self.sql(expression, "default")
5943        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5944
5945        kind = self.sql(expression, "kind")
5946        if isinstance(expression.args.get("kind"), exp.Schema):
5947            kind = f"TABLE {kind}"
5948
5949        kind = f" {kind}" if kind else ""
5950
5951        return f"{variables}{kind}{default}"
5952
5953    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5954        kind = self.sql(expression, "kind")
5955        this = self.sql(expression, "this")
5956        set = self.sql(expression, "expression")
5957        using = self.sql(expression, "using")
5958        using = f" USING {using}" if using else ""
5959
5960        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5961
5962        return f"{kind_sql} {this} SET {set}{using}"
5963
5964    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5965        params = self.expressions(expression, key="params", flat=True)
5966        return self.func(expression.name, *expression.expressions) + f"({params})"
5967
5968    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5969        return self.func(expression.name, *expression.expressions)
5970
5971    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5972        return self.anonymousaggfunc_sql(expression)
5973
5974    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5975        return self.parameterizedagg_sql(expression)
5976
5977    def show_sql(self, expression: exp.Show) -> str:
5978        self.unsupported("Unsupported SHOW statement")
5979        return ""
5980
5981    def install_sql(self, expression: exp.Install) -> str:
5982        self.unsupported("Unsupported INSTALL statement")
5983        return ""
5984
5985    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5986        # Snowflake GET/PUT statements:
5987        #   PUT <file> <internalStage> <properties>
5988        #   GET <internalStage> <file> <properties>
5989        props = expression.args.get("properties")
5990        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5991        this = self.sql(expression, "this")
5992        target = self.sql(expression, "target")
5993
5994        if isinstance(expression, exp.Put):
5995            return f"PUT {this} {target}{props_sql}"
5996        else:
5997            return f"GET {target} {this}{props_sql}"
5998
5999    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6000        this = self.sql(expression, "this")
6001        expr = self.sql(expression, "expression")
6002        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6003        return f"TRANSLATE({this} USING {expr}{with_error})"
6004
6005    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6006        if self.SUPPORTS_DECODE_CASE:
6007            return self.func("DECODE", *expression.expressions)
6008
6009        decode_expr, *expressions = expression.expressions
6010
6011        ifs = []
6012        for search, result in zip(expressions[::2], expressions[1::2]):
6013            if isinstance(search, exp.Literal):
6014                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6015            elif isinstance(search, exp.Null):
6016                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6017            else:
6018                if isinstance(search, exp.Binary):
6019                    search = exp.paren(search)
6020
6021                cond = exp.or_(
6022                    decode_expr.eq(search),
6023                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6024                    copy=False,
6025                )
6026                ifs.append(exp.If(this=cond, true=result))
6027
6028        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6029        return self.sql(case)
6030
6031    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6032        this = self.sql(expression, "this")
6033        this = self.seg(this, sep="")
6034        dimensions = self.expressions(
6035            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6036        )
6037        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6038        metrics = self.expressions(
6039            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6040        )
6041        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6042        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6043        facts = self.seg(f"FACTS {facts}") if facts else ""
6044        where = self.sql(expression, "where")
6045        where = self.seg(f"WHERE {where}") if where else ""
6046        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6047        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6048
6049    def getextract_sql(self, expression: exp.GetExtract) -> str:
6050        this = expression.this
6051        expr = expression.expression
6052
6053        if not this.type or not expression.type:
6054            import sqlglot.optimizer.annotate_types
6055
6056            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6057
6058        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6059            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6060
6061        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6062
6063    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6064        return self.sql(
6065            exp.DateAdd(
6066                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6067                expression=expression.this,
6068                unit=exp.var("DAY"),
6069            )
6070        )
6071
6072    def space_sql(self: Generator, expression: exp.Space) -> str:
6073        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6074
6075    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6076        return f"BUILD {self.sql(expression, 'this')}"
6077
6078    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6079        method = self.sql(expression, "method")
6080        kind = expression.args.get("kind")
6081        if not kind:
6082            return f"REFRESH {method}"
6083
6084        every = self.sql(expression, "every")
6085        unit = self.sql(expression, "unit")
6086        every = f" EVERY {every} {unit}" if every else ""
6087        starts = self.sql(expression, "starts")
6088        starts = f" STARTS {starts}" if starts else ""
6089
6090        return f"REFRESH {method} ON {kind}{every}{starts}"
6091
6092    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6093        self.unsupported("The model!attribute syntax is not supported")
6094        return ""
6095
6096    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6097        return self.func("DIRECTORY", expression.this)
6098
6099    def uuid_sql(self, expression: exp.Uuid) -> str:
6100        is_string = expression.args.get("is_string", False)
6101        uuid_func_sql = self.func("UUID")
6102
6103        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6104            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6105
6106        return uuid_func_sql
6107
6108    def initcap_sql(self, expression: exp.Initcap) -> str:
6109        delimiters = expression.expression
6110
6111        if delimiters:
6112            # do not generate delimiters arg if we are round-tripping from default delimiters
6113            if (
6114                delimiters.is_string
6115                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6116            ):
6117                delimiters = None
6118            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6119                self.unsupported("INITCAP does not support custom delimiters")
6120                delimiters = None
6121
6122        return self.func("INITCAP", expression.this, delimiters)
6123
6124    def localtime_sql(self, expression: exp.Localtime) -> str:
6125        this = expression.this
6126        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6127
6128    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6129        this = expression.this
6130        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6131
6132    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6133        this = expression.this.name.upper()
6134        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
6135            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
6136            return "WEEK"
6137
6138        return self.func("WEEK", expression.this)
6139
6140    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6141        this = self.expressions(expression)
6142        charset = self.sql(expression, "charset")
6143        using = f" USING {charset}" if charset else ""
6144        return self.func(name, this + using)
6145
6146    def block_sql(self, expression: exp.Block) -> str:
6147        expressions = self.expressions(expression, sep="; ", flat=True)
6148        return f"{expressions}" if expressions else ""
6149
6150    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6151        self.unsupported("Unsupported Stored Procedure syntax")
6152        return ""
6153
6154    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6155        self.unsupported("Unsupported If block syntax")
6156        return ""
6157
6158    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6159        self.unsupported("Unsupported While block syntax")
6160        return ""
6161
6162    def execute_sql(self, expression: exp.Execute) -> str:
6163        self.unsupported("Unsupported Execute syntax")
6164        return ""
6165
6166    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6167        self.unsupported("Unsupported Execute syntax")
6168        return ""
6169
6170    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6171        props = self.expressions(expression, sep=" ")
6172        return f"MODIFY {props}"
6173
6174    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6175        kind = expression.args.get("kind")
6176        return f"USING {kind} {self.sql(expression, 'this')}"
6177
6178    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6179        this = self.sql(expression, "this")
6180        to = self.sql(expression, "to")
6181        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)
857    def __init__(
858        self,
859        pretty: bool | int | None = None,
860        identify: str | bool = False,
861        normalize: bool = False,
862        pad: int = 2,
863        indent: int = 2,
864        normalize_functions: str | bool | None = None,
865        unsupported_level: ErrorLevel = ErrorLevel.WARN,
866        max_unsupported: int = 3,
867        leading_comma: bool = False,
868        max_text_width: int = 80,
869        comments: bool = True,
870        dialect: DialectType = None,
871    ):
872        import sqlglot
873        import sqlglot.dialects.dialect
874
875        self.pretty = pretty if pretty is not None else sqlglot.pretty
876        self.identify = identify
877        self.normalize = normalize
878        self.pad = pad
879        self._indent = indent
880        self.unsupported_level = unsupported_level
881        self.max_unsupported = max_unsupported
882        self.leading_comma = leading_comma
883        self.max_text_width = max_text_width
884        self.comments = comments
885        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
886
887        # This is both a Dialect property and a Generator argument, so we prioritize the latter
888        self.normalize_functions = (
889            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
890        )
891
892        self.unsupported_messages: list[str] = []
893        self._escaped_quote_end: str = (
894            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
895        )
896        self._escaped_byte_quote_end: str = (
897            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
898            if self.dialect.BYTE_END
899            else ""
900        )
901        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
902
903        self._next_name = name_sequence("_t")
904
905        self._identifier_start = self.dialect.IDENTIFIER_START
906        self._identifier_end = self.dialect.IDENTIFIER_END
907
908        self._quote_json_path_key_using_brackets = True
909
910        cls = type(self)
911        dispatch = _DISPATCH_CACHE.get(cls)
912        if dispatch is None:
913            dispatch = _build_dispatch(cls)
914            _DISPATCH_CACHE[cls] = dispatch
915        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.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.CaseSpecificColumnConstraint'>: <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.JSONBDeleteAtPath'>: <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
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
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
AGGREGATE_FILTER_SUPPORTED = 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'
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
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
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
CAN_IMPLEMENT_ARRAY_ANY = False
SUPPORTS_TO_NUMBER = True
SUPPORTS_WINDOW_EXCLUDE = False
SET_OP_MODIFIERS = 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.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.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: ClassVar[set[type[sqlglot.expressions.core.Expr]]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
SAFE_JSON_PATH_KEY_RE: ClassVar = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: list[str]
def generate( self, expression: sqlglot.expressions.core.Expr, copy: bool = True) -> str:
917    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
918        """
919        Generates the SQL string corresponding to the given syntax tree.
920
921        Args:
922            expression: The syntax tree.
923            copy: Whether to copy the expression. The generator performs mutations so
924                it is safer to copy.
925
926        Returns:
927            The SQL string corresponding to `expression`.
928        """
929        if copy:
930            expression = expression.copy()
931
932        expression = self.preprocess(expression)
933
934        self.unsupported_messages = []
935        sql = self.sql(expression).strip()
936
937        if self.pretty:
938            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
939
940        if self.unsupported_level == ErrorLevel.IGNORE:
941            return sql
942
943        if self.unsupported_level == ErrorLevel.WARN:
944            for msg in self.unsupported_messages:
945                logger.warning(msg)
946        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
947            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
948
949        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:
951    def preprocess(self, expression: exp.Expr) -> exp.Expr:
952        """Apply generic preprocessing transformations to a given expression."""
953        expression = self._move_ctes_to_top_level(expression)
954
955        if self.ENSURE_BOOLS:
956            import sqlglot.transforms
957
958            expression = sqlglot.transforms.ensure_bools(expression)
959
960        return expression

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
973    def unsupported(self, message: str) -> None:
974        if self.unsupported_level == ErrorLevel.IMMEDIATE:
975            raise UnsupportedError(message)
976        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
978    def sep(self, sep: str = " ") -> str:
979        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
981    def seg(self, sql: str, sep: str = " ") -> str:
982        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
984    def sanitize_comment(self, comment: str) -> str:
985        comment = " " + comment if comment[0].strip() else comment
986        comment = comment + " " if comment[-1].strip() else comment
987
988        # Escape block comment markers to prevent premature closure or unintended nesting.
989        # This is necessary because single-line comments (--) are converted to block comments
990        # (/* */) on output, and any */ in the original text would close the comment early.
991        comment = comment.replace("*/", "* /").replace("/*", "/ *")
992
993        return comment
def maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
 995    def maybe_comment(
 996        self,
 997        sql: str,
 998        expression: exp.Expr | None = None,
 999        comments: list[str] | None = None,
1000        separated: bool = False,
1001    ) -> str:
1002        comments = (
1003            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1004            if self.comments
1005            else None
1006        )
1007
1008        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1009            return sql
1010
1011        comments_sql = " ".join(
1012            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1013        )
1014
1015        if not comments_sql:
1016            return sql
1017
1018        comments_sql = self._replace_line_breaks(comments_sql)
1019
1020        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1021            return (
1022                f"{self.sep()}{comments_sql}{sql}"
1023                if not sql or sql[0].isspace()
1024                else f"{comments_sql}{self.sep()}{sql}"
1025            )
1026
1027        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.core.Expr | str) -> str:
1029    def wrap(self, expression: exp.Expr | str) -> str:
1030        this_sql = (
1031            self.sql(expression)
1032            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1033            else self.sql(expression, "this")
1034        )
1035        if not this_sql:
1036            return "()"
1037
1038        this_sql = self.indent(this_sql, level=1, pad=0)
1039        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
1041    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1042        original = self.identify
1043        self.identify = False
1044        result = func(*args, **kwargs)
1045        self.identify = original
1046        return result
def normalize_func(self, name: str) -> str:
1048    def normalize_func(self, name: str) -> str:
1049        if self.normalize_functions == "upper" or self.normalize_functions is True:
1050            return name.upper()
1051        if self.normalize_functions == "lower":
1052            return name.lower()
1053        return name
def indent( self, sql: str, level: int = 0, pad: int | None = None, skip_first: bool = False, skip_last: bool = False) -> str:
1055    def indent(
1056        self,
1057        sql: str,
1058        level: int = 0,
1059        pad: int | None = None,
1060        skip_first: bool = False,
1061        skip_last: bool = False,
1062    ) -> str:
1063        if not self.pretty or not sql:
1064            return sql
1065
1066        pad = self.pad if pad is None else pad
1067        lines = sql.split("\n")
1068
1069        return "\n".join(
1070            (
1071                line
1072                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1073                else f"{' ' * (level * self._indent + pad)}{line}"
1074            )
1075            for i, line in enumerate(lines)
1076        )
def sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1078    def sql(
1079        self,
1080        expression: str | exp.Expr | None,
1081        key: str | None = None,
1082        comment: bool = True,
1083    ) -> str:
1084        if not expression:
1085            return ""
1086
1087        if isinstance(expression, str):
1088            return expression
1089
1090        if key:
1091            value = expression.args.get(key)
1092            if value:
1093                return self.sql(value)
1094            return ""
1095
1096        handler = self._dispatch.get(expression.__class__)
1097
1098        if handler:
1099            sql = handler(self, expression)
1100        elif isinstance(expression, exp.Func):
1101            sql = self.function_fallback_sql(expression)
1102        elif isinstance(expression, exp.Property):
1103            sql = self.property_sql(expression)
1104        else:
1105            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1106
1107        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.core.Uncache) -> str:
1109    def uncache_sql(self, expression: exp.Uncache) -> str:
1110        table = self.sql(expression, "this")
1111        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1112        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.core.Cache) -> str:
1114    def cache_sql(self, expression: exp.Cache) -> str:
1115        lazy = " LAZY" if expression.args.get("lazy") else ""
1116        table = self.sql(expression, "this")
1117        options = expression.args.get("options")
1118        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1119        sql = self.sql(expression, "expression")
1120        sql = f" AS{self.sep()}{sql}" if sql else ""
1121        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1122        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.ddl.CharacterSet) -> str:
1124    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1125        default = "DEFAULT " if expression.args.get("default") else ""
1126        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.core.Column) -> str:
1128    def column_parts(self, expression: exp.Column) -> str:
1129        return ".".join(
1130            self.sql(part)
1131            for part in (
1132                expression.args.get("catalog"),
1133                expression.args.get("db"),
1134                expression.args.get("table"),
1135                expression.args.get("this"),
1136            )
1137            if part
1138        )
def column_sql(self, expression: sqlglot.expressions.core.Column) -> str:
1140    def column_sql(self, expression: exp.Column) -> str:
1141        join_mark = " (+)" if expression.args.get("join_mark") else ""
1142
1143        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1144            join_mark = ""
1145            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1146
1147        return f"{self.column_parts(expression)}{join_mark}"
def pseudocolumn_sql(self, expression: sqlglot.expressions.core.Pseudocolumn) -> str:
1149    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1150        return self.column_sql(expression)
def columnposition_sql(self, expression: sqlglot.expressions.query.ColumnPosition) -> str:
1152    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1153        this = self.sql(expression, "this")
1154        this = f" {this}" if this else ""
1155        position = self.sql(expression, "position")
1156        return f"{position}{this}"
def columndef_sql( self, expression: sqlglot.expressions.query.ColumnDef, sep: str = ' ') -> str:
1158    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1159        column = self.sql(expression, "this")
1160        kind = self.sql(expression, "kind")
1161        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1162        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1163        kind = f"{sep}{kind}" if kind else ""
1164        constraints = f" {constraints}" if constraints else ""
1165        position = self.sql(expression, "position")
1166        position = f" {position}" if position else ""
1167
1168        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1169            kind = ""
1170
1171        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql( self, expression: sqlglot.expressions.constraints.ColumnConstraint) -> str:
1173    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1174        this = self.sql(expression, "this")
1175        kind_sql = self.sql(expression, "kind").strip()
1176        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.ComputedColumnConstraint) -> str:
1178    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1179        this = self.sql(expression, "this")
1180        if expression.args.get("not_null"):
1181            persisted = " PERSISTED NOT NULL"
1182        elif expression.args.get("persisted"):
1183            persisted = " PERSISTED"
1184        else:
1185            persisted = ""
1186
1187        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql( self, _: sqlglot.expressions.constraints.AutoIncrementColumnConstraint) -> str:
1189    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1190        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CompressColumnConstraint) -> str:
1192    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1193        if isinstance(expression.this, list):
1194            this = self.wrap(self.expressions(expression, key="this", flat=True))
1195        else:
1196            this = self.sql(expression, "this")
1197
1198        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
1200    def generatedasidentitycolumnconstraint_sql(
1201        self, expression: exp.GeneratedAsIdentityColumnConstraint
1202    ) -> str:
1203        this = ""
1204        if expression.this is not None:
1205            on_null = " ON NULL" if expression.args.get("on_null") else ""
1206            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1207
1208        start = expression.args.get("start")
1209        start = f"START WITH {start}" if start else ""
1210        increment = expression.args.get("increment")
1211        increment = f" INCREMENT BY {increment}" if increment else ""
1212        minvalue = expression.args.get("minvalue")
1213        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1214        maxvalue = expression.args.get("maxvalue")
1215        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1216        cycle = expression.args.get("cycle")
1217        cycle_sql = ""
1218
1219        if cycle is not None:
1220            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1221            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1222
1223        sequence_opts = ""
1224        if start or increment or cycle_sql:
1225            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1226            sequence_opts = f" ({sequence_opts.strip()})"
1227
1228        expr = self.sql(expression, "expression")
1229        expr = f"({expr})" if expr else "IDENTITY"
1230
1231        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1233    def generatedasrowcolumnconstraint_sql(
1234        self, expression: exp.GeneratedAsRowColumnConstraint
1235    ) -> str:
1236        start = "START" if expression.args.get("start") else "END"
1237        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1238        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.constraints.PeriodForSystemTimeConstraint) -> str:
1240    def periodforsystemtimeconstraint_sql(
1241        self, expression: exp.PeriodForSystemTimeConstraint
1242    ) -> str:
1243        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.NotNullColumnConstraint) -> str:
1245    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1246        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.PrimaryKeyColumnConstraint) -> str:
1248    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1249        desc = expression.args.get("desc")
1250        if desc is not None:
1251            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1252        options = self.expressions(expression, key="options", flat=True, sep=" ")
1253        options = f" {options}" if options else ""
1254        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1256    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1257        this = self.sql(expression, "this")
1258        this = f" {this}" if this else ""
1259        index_type = expression.args.get("index_type")
1260        index_type = f" USING {index_type}" if index_type else ""
1261        on_conflict = self.sql(expression, "on_conflict")
1262        on_conflict = f" {on_conflict}" if on_conflict else ""
1263        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1264        options = self.expressions(expression, key="options", flat=True, sep=" ")
1265        options = f" {options}" if options else ""
1266        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1268    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1269        input_ = expression.args.get("input_")
1270        output = expression.args.get("output")
1271        variadic = expression.args.get("variadic")
1272
1273        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1274        if variadic:
1275            return "VARIADIC"
1276
1277        if input_ and output:
1278            return f"IN{self.INOUT_SEPARATOR}OUT"
1279        if input_:
1280            return "IN"
1281        if output:
1282            return "OUT"
1283
1284        return ""
def createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1286    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1287        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
1289    def create_sql(self, expression: exp.Create) -> str:
1290        kind = self.sql(expression, "kind")
1291        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1292
1293        properties = expression.args.get("properties")
1294
1295        if (
1296            kind == "TRIGGER"
1297            and properties
1298            and properties.expressions
1299            and isinstance(properties.expressions[0], exp.TriggerProperties)
1300            and properties.expressions[0].args.get("constraint")
1301        ):
1302            kind = f"CONSTRAINT {kind}"
1303
1304        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1305
1306        this = self.createable_sql(expression, properties_locs)
1307
1308        properties_sql = ""
1309        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1310            exp.Properties.Location.POST_WITH
1311        ):
1312            props_ast = exp.Properties(
1313                expressions=[
1314                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1315                    *properties_locs[exp.Properties.Location.POST_WITH],
1316                ]
1317            )
1318            props_ast.parent = expression
1319            properties_sql = self.sql(props_ast)
1320
1321            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1322                properties_sql = self.sep() + properties_sql
1323            elif not self.pretty:
1324                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1325                properties_sql = f" {properties_sql}"
1326
1327        begin = " BEGIN" if expression.args.get("begin") else ""
1328
1329        expression_sql = self.sql(expression, "expression")
1330        if expression_sql:
1331            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1332
1333            if not isinstance(expression.expression, exp.MacroOverloads) and (
1334                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1335            ):
1336                postalias_props_sql = ""
1337                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1338                    postalias_props_sql = self.properties(
1339                        exp.Properties(
1340                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1341                        ),
1342                        wrapped=False,
1343                    )
1344                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1345                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1346
1347        postindex_props_sql = ""
1348        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1349            postindex_props_sql = self.properties(
1350                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1351                wrapped=False,
1352                prefix=" ",
1353            )
1354
1355        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1356        indexes = f" {indexes}" if indexes else ""
1357        index_sql = indexes + postindex_props_sql
1358
1359        replace = " OR REPLACE" if expression.args.get("replace") else ""
1360        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1361        unique = " UNIQUE" if expression.args.get("unique") else ""
1362
1363        clustered = expression.args.get("clustered")
1364        if clustered is None:
1365            clustered_sql = ""
1366        elif clustered:
1367            clustered_sql = " CLUSTERED COLUMNSTORE"
1368        else:
1369            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1370
1371        postcreate_props_sql = ""
1372        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1373            postcreate_props_sql = self.properties(
1374                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1375                sep=" ",
1376                prefix=" ",
1377                wrapped=False,
1378            )
1379
1380        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1381
1382        postexpression_props_sql = ""
1383        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1384            postexpression_props_sql = self.properties(
1385                exp.Properties(
1386                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1387                ),
1388                sep=" ",
1389                prefix=" ",
1390                wrapped=False,
1391            )
1392
1393        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1394        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1395        no_schema_binding = (
1396            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1397        )
1398
1399        clone = self.sql(expression, "clone")
1400        clone = f" {clone}" if clone else ""
1401
1402        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1403            properties_expression = f"{expression_sql}{properties_sql}"
1404        else:
1405            properties_expression = f"{properties_sql}{expression_sql}"
1406
1407        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1408        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.ddl.SequenceProperties) -> str:
1410    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1411        start = self.sql(expression, "start")
1412        start = f"START WITH {start}" if start else ""
1413        increment = self.sql(expression, "increment")
1414        increment = f" INCREMENT BY {increment}" if increment else ""
1415        minvalue = self.sql(expression, "minvalue")
1416        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1417        maxvalue = self.sql(expression, "maxvalue")
1418        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1419        owned = self.sql(expression, "owned")
1420        owned = f" OWNED BY {owned}" if owned else ""
1421
1422        cache = expression.args.get("cache")
1423        if cache is None:
1424            cache_str = ""
1425        elif cache is True:
1426            cache_str = " CACHE"
1427        else:
1428            cache_str = f" CACHE {cache}"
1429
1430        options = self.expressions(expression, key="options", flat=True, sep=" ")
1431        options = f" {options}" if options else ""
1432
1433        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def triggerproperties_sql(self, expression: sqlglot.expressions.ddl.TriggerProperties) -> str:
1435    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1436        timing = expression.args.get("timing", "")
1437        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1438        timing_events = f"{timing} {events}".strip() if timing or events else ""
1439
1440        parts = [timing_events, "ON", self.sql(expression, "table")]
1441
1442        if referenced_table := expression.args.get("referenced_table"):
1443            parts.extend(["FROM", self.sql(referenced_table)])
1444
1445        if deferrable := expression.args.get("deferrable"):
1446            parts.append(deferrable)
1447
1448        if initially := expression.args.get("initially"):
1449            parts.append(f"INITIALLY {initially}")
1450
1451        if referencing := expression.args.get("referencing"):
1452            parts.append(self.sql(referencing))
1453
1454        if for_each := expression.args.get("for_each"):
1455            parts.append(f"FOR EACH {for_each}")
1456
1457        if when := expression.args.get("when"):
1458            parts.append(f"WHEN ({self.sql(when)})")
1459
1460        parts.append(self.sql(expression, "execute"))
1461
1462        return self.sep().join(parts)
def triggerreferencing_sql(self, expression: sqlglot.expressions.ddl.TriggerReferencing) -> str:
1464    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1465        parts = []
1466
1467        if old_alias := expression.args.get("old"):
1468            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1469
1470        if new_alias := expression.args.get("new"):
1471            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1472
1473        return f"REFERENCING {' '.join(parts)}"
def triggerevent_sql(self, expression: sqlglot.expressions.ddl.TriggerEvent) -> str:
1475    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1476        columns = expression.args.get("columns")
1477        if columns:
1478            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1479
1480        return self.sql(expression, "this")
def clone_sql(self, expression: sqlglot.expressions.ddl.Clone) -> str:
1482    def clone_sql(self, expression: exp.Clone) -> str:
1483        this = self.sql(expression, "this")
1484        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1485        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1486        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.ddl.Describe) -> str:
1488    def describe_sql(self, expression: exp.Describe) -> str:
1489        style = expression.args.get("style")
1490        style = f" {style}" if style else ""
1491        partition = self.sql(expression, "partition")
1492        partition = f" {partition}" if partition else ""
1493        format = self.sql(expression, "format")
1494        format = f" {format}" if format else ""
1495        as_json = " AS JSON" if expression.args.get("as_json") else ""
1496
1497        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
def heredoc_sql(self, expression: sqlglot.expressions.ddl.Heredoc) -> str:
1499    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1500        tag = self.sql(expression, "tag")
1501        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.core.Expr, sql: str) -> str:
1503    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1504        with_ = self.sql(expression, "with_")
1505        if with_:
1506            sql = f"{with_}{self.sep()}{sql}"
1507        return sql
def with_sql(self, expression: sqlglot.expressions.query.With) -> str:
1509    def with_sql(self, expression: exp.With) -> str:
1510        sql = self.expressions(expression, flat=True)
1511        recursive = (
1512            "RECURSIVE "
1513            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1514            else ""
1515        )
1516        search = self.sql(expression, "search")
1517        search = f" {search}" if search else ""
1518
1519        return f"WITH {recursive}{sql}{search}"
def cte_sql(self, expression: sqlglot.expressions.query.CTE) -> str:
1521    def cte_sql(self, expression: exp.CTE) -> str:
1522        alias = expression.args.get("alias")
1523        if alias:
1524            alias.add_comments(expression.pop_comments())
1525
1526        alias_sql = self.sql(expression, "alias")
1527
1528        materialized = expression.args.get("materialized")
1529        if materialized is False:
1530            materialized = "NOT MATERIALIZED "
1531        elif materialized:
1532            materialized = "MATERIALIZED "
1533
1534        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1535        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1536
1537        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.query.TableAlias) -> str:
1539    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1540        alias = self.sql(expression, "this")
1541        columns = self.expressions(expression, key="columns", flat=True)
1542        columns = f"({columns})" if columns else ""
1543
1544        if (
1545            columns
1546            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1547            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1548        ):
1549            columns = ""
1550            self.unsupported("Named columns are not supported in table alias.")
1551
1552        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1553            alias = self._next_name()
1554
1555        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.query.BitString) -> str:
1557    def bitstring_sql(self, expression: exp.BitString) -> str:
1558        this = self.sql(expression, "this")
1559        if self.dialect.BIT_START:
1560            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1561        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1563    def hexstring_sql(
1564        self, expression: exp.HexString, binary_function_repr: str | None = None
1565    ) -> str:
1566        this = self.sql(expression, "this")
1567        is_integer_type = expression.args.get("is_integer")
1568
1569        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1570            not self.dialect.HEX_START and not binary_function_repr
1571        ):
1572            # Integer representation will be returned if:
1573            # - The read dialect treats the hex value as integer literal but not the write
1574            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1575            return f"{int(this, 16)}"
1576
1577        if not is_integer_type:
1578            # Read dialect treats the hex value as BINARY/BLOB
1579            if binary_function_repr:
1580                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1581                return self.func(binary_function_repr, exp.Literal.string(this))
1582            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1583                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1584                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1585
1586        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.query.ByteString) -> str:
1588    def bytestring_sql(self, expression: exp.ByteString) -> str:
1589        this = self.sql(expression, "this")
1590        if self.dialect.BYTE_START:
1591            escaped_byte_string = self.escape_str(
1592                this,
1593                escape_backslash=False,
1594                delimiter=self.dialect.BYTE_END,
1595                escaped_delimiter=self._escaped_byte_quote_end,
1596                is_byte_string=True,
1597            )
1598            is_bytes = expression.args.get("is_bytes", False)
1599            delimited_byte_string = (
1600                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1601            )
1602            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1603                return self.sql(
1604                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1605                )
1606            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1607                return self.sql(
1608                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1609                )
1610
1611            return delimited_byte_string
1612
1613        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1614            return self.sql(exp.Literal.string(this))
1615
1616        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1617        return ""
def unicodestring_sql(self, expression: sqlglot.expressions.query.UnicodeString) -> str:
1619    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1620        this = self.sql(expression, "this")
1621        escape = expression.args.get("escape")
1622
1623        if self.dialect.UNICODE_START:
1624            escape_substitute = r"\\\1"
1625            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1626        else:
1627            escape_substitute = r"\\u\1"
1628            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1629
1630        if escape:
1631            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1632            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1633        else:
1634            escape_pattern = ESCAPED_UNICODE_RE
1635            escape_sql = ""
1636
1637        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1638            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1639
1640        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.query.RawString) -> str:
1642    def rawstring_sql(self, expression: exp.RawString) -> str:
1643        string = expression.this
1644        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1645            string = string.replace("\\", "\\\\")
1646
1647        string = self.escape_str(string, escape_backslash=False)
1648        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.datatypes.DataTypeParam) -> str:
1650    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1651        this = self.sql(expression, "this")
1652        specifier = self.sql(expression, "expression")
1653        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1654        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:
1656    def datatype_param_bound_limiter(
1657        self,
1658        expression: exp.DataType,
1659        type_value: exp.DType,
1660        defaults: tuple[int, ...],
1661        bounds: tuple[int | None, ...],
1662    ) -> exp.DataType:
1663        params = expression.expressions
1664
1665        if not params:
1666            if defaults:
1667                expression.set(
1668                    "expressions",
1669                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1670                )
1671            return expression
1672
1673        if not bounds:
1674            return expression
1675
1676        for i, param in enumerate(params):
1677            bound = bounds[i] if i < len(bounds) else None
1678            if bound is None:
1679                continue
1680
1681            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1682            if (
1683                isinstance(param_value, exp.Literal)
1684                and param_value.is_number
1685                and int(param_value.to_py()) > bound
1686            ):
1687                self.unsupported(
1688                    f"{type_value.value} parameter {param_value.name} exceeds "
1689                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1690                )
1691                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1692
1693        return expression
def datatype_sql(self, expression: sqlglot.expressions.datatypes.DataType) -> str:
1695    def datatype_sql(self, expression: exp.DataType) -> str:
1696        nested = ""
1697        values = ""
1698
1699        expr_nested = expression.args.get("nested")
1700        type_value = expression.this
1701
1702        if (
1703            not expr_nested
1704            and isinstance(type_value, exp.DType)
1705            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1706        ):
1707            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1708
1709        interior = (
1710            self.expressions(
1711                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1712            )
1713            if expr_nested and self.pretty
1714            else self.expressions(expression, flat=True)
1715        )
1716
1717        if type_value in self.UNSUPPORTED_TYPES:
1718            self.unsupported(
1719                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1720            )
1721
1722        type_sql: t.Any = ""
1723        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1724            type_sql = self.sql(expression, "kind")
1725        elif type_value == exp.DType.CHARACTER_SET:
1726            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1727        else:
1728            type_sql = (
1729                self.TYPE_MAPPING.get(type_value, type_value.value)
1730                if isinstance(type_value, exp.DType)
1731                else type_value
1732            )
1733
1734        if interior:
1735            if expr_nested:
1736                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1737                if expression.args.get("values") is not None:
1738                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1739                    values = self.expressions(expression, key="values", flat=True)
1740                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1741            elif type_value == exp.DType.INTERVAL:
1742                nested = f" {interior}"
1743            else:
1744                nested = f"({interior})"
1745
1746        type_sql = f"{type_sql}{nested}{values}"
1747        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1748            exp.DType.TIMETZ,
1749            exp.DType.TIMESTAMPTZ,
1750        ):
1751            type_sql = f"{type_sql} WITH TIME ZONE"
1752
1753        collate = self.sql(expression, "collate")
1754        if collate:
1755            type_sql = f"{type_sql} COLLATE {collate}"
1756
1757        return type_sql
def directory_sql(self, expression: sqlglot.expressions.dml.Directory) -> str:
1759    def directory_sql(self, expression: exp.Directory) -> str:
1760        local = "LOCAL " if expression.args.get("local") else ""
1761        row_format = self.sql(expression, "row_format")
1762        row_format = f" {row_format}" if row_format else ""
1763        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.dml.Delete) -> str:
1765    def delete_sql(self, expression: exp.Delete) -> str:
1766        hint = self.sql(expression, "hint")
1767        this = self.sql(expression, "this")
1768        this = f" FROM {this}" if this else ""
1769        using = self.expressions(expression, key="using")
1770        using = f" USING {using}" if using else ""
1771        cluster = self.sql(expression, "cluster")
1772        cluster = f" {cluster}" if cluster else ""
1773        where = self.sql(expression, "where")
1774        returning = self.sql(expression, "returning")
1775        order = self.sql(expression, "order")
1776        limit = self.sql(expression, "limit")
1777        tables = self.expressions(expression, key="tables")
1778        tables = f" {tables}" if tables else ""
1779        if self.RETURNING_END:
1780            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1781        else:
1782            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1783        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.ddl.Drop) -> str:
1785    def drop_sql(self, expression: exp.Drop) -> str:
1786        this = self.sql(expression, "this")
1787        expressions = self.expressions(expression, flat=True)
1788        expressions = f" ({expressions})" if expressions else ""
1789        kind = expression.args["kind"]
1790        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1791        iceberg = (
1792            " ICEBERG"
1793            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1794            else ""
1795        )
1796        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1797        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1798        on_cluster = self.sql(expression, "cluster")
1799        on_cluster = f" {on_cluster}" if on_cluster else ""
1800        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1801        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1802        cascade = " CASCADE" if expression.args.get("cascade") else ""
1803        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1804        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1805        purge = " PURGE" if expression.args.get("purge") else ""
1806        sync = " SYNC" if expression.args.get("sync") else ""
1807        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
def set_operation(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1809    def set_operation(self, expression: exp.SetOperation) -> str:
1810        op_type = type(expression)
1811        op_name = op_type.key.upper()
1812
1813        distinct = expression.args.get("distinct")
1814        if (
1815            distinct is False
1816            and op_type in (exp.Except, exp.Intersect)
1817            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1818        ):
1819            self.unsupported(f"{op_name} ALL is not supported")
1820
1821        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1822
1823        if distinct is None:
1824            distinct = default_distinct
1825            if distinct is None:
1826                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1827
1828        if distinct is default_distinct:
1829            distinct_or_all = ""
1830        else:
1831            distinct_or_all = " DISTINCT" if distinct else " ALL"
1832
1833        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1834        side_kind = f"{side_kind} " if side_kind else ""
1835
1836        by_name = " BY NAME" if expression.args.get("by_name") else ""
1837        on = self.expressions(expression, key="on", flat=True)
1838        on = f" ON ({on})" if on else ""
1839
1840        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1842    def set_operations(self, expression: exp.SetOperation) -> str:
1843        if not self.SET_OP_MODIFIERS:
1844            limit = expression.args.get("limit")
1845            order = expression.args.get("order")
1846
1847            if limit or order:
1848                select = self._move_ctes_to_top_level(
1849                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1850                )
1851
1852                if limit:
1853                    select = select.limit(limit.pop(), copy=False)
1854                if order:
1855                    select = select.order_by(order.pop(), copy=False)
1856                return self.sql(select)
1857
1858        sqls: list[str] = []
1859        stack: list[str | exp.Expr] = [expression]
1860
1861        while stack:
1862            node = stack.pop()
1863
1864            if isinstance(node, exp.SetOperation):
1865                stack.append(node.expression)
1866                stack.append(
1867                    self.maybe_comment(
1868                        self.set_operation(node), comments=node.comments, separated=True
1869                    )
1870                )
1871                stack.append(node.this)
1872            else:
1873                sqls.append(self.sql(node))
1874
1875        this = self.sep().join(sqls)
1876        this = self.query_modifiers(expression, this)
1877        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.query.Fetch) -> str:
1879    def fetch_sql(self, expression: exp.Fetch) -> str:
1880        direction = expression.args.get("direction")
1881        direction = f" {direction}" if direction else ""
1882        count = self.sql(expression, "count")
1883        count = f" {count}" if count else ""
1884        limit_options = self.sql(expression, "limit_options")
1885        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1886        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.query.LimitOptions) -> str:
1888    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1889        percent = " PERCENT" if expression.args.get("percent") else ""
1890        rows = " ROWS" if expression.args.get("rows") else ""
1891        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1892        if not with_ties and rows:
1893            with_ties = " ONLY"
1894        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.core.Filter) -> str:
1896    def filter_sql(self, expression: exp.Filter) -> str:
1897        if self.AGGREGATE_FILTER_SUPPORTED:
1898            this = self.sql(expression, "this")
1899            where = self.sql(expression, "expression").strip()
1900            return f"{this} FILTER({where})"
1901
1902        agg = expression.this
1903        agg_arg = agg.this
1904        cond = expression.expression.this
1905        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1906        return self.sql(agg)
def hint_sql(self, expression: sqlglot.expressions.core.Hint) -> str:
1908    def hint_sql(self, expression: exp.Hint) -> str:
1909        if not self.QUERY_HINTS:
1910            self.unsupported("Hints are not supported")
1911            return ""
1912
1913        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.constraints.IndexParameters) -> str:
1915    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1916        using = self.sql(expression, "using")
1917        using = f" USING {using}" if using else ""
1918        columns = self.expressions(expression, key="columns", flat=True)
1919        columns = f"({columns})" if columns else ""
1920        partition_by = self.expressions(expression, key="partition_by", flat=True)
1921        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1922        where = self.sql(expression, "where")
1923        include = self.expressions(expression, key="include", flat=True)
1924        if include:
1925            include = f" INCLUDE ({include})"
1926        with_storage = self.expressions(expression, key="with_storage", flat=True)
1927        with_storage = f" WITH ({with_storage})" if with_storage else ""
1928        tablespace = self.sql(expression, "tablespace")
1929        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1930        on = self.sql(expression, "on")
1931        on = f" ON {on}" if on else ""
1932
1933        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.query.Index) -> str:
1935    def index_sql(self, expression: exp.Index) -> str:
1936        unique = "UNIQUE " if expression.args.get("unique") else ""
1937        primary = "PRIMARY " if expression.args.get("primary") else ""
1938        amp = "AMP " if expression.args.get("amp") else ""
1939        name = self.sql(expression, "this")
1940        name = f"{name} " if name else ""
1941        table = self.sql(expression, "table")
1942        table = f"{self.INDEX_ON} {table}" if table else ""
1943
1944        index = "INDEX " if not table else ""
1945
1946        params = self.sql(expression, "params")
1947        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def identifier_sql(self, expression: sqlglot.expressions.core.Identifier) -> str:
1949    def identifier_sql(self, expression: exp.Identifier) -> str:
1950        text = expression.name
1951        lower = text.lower()
1952        quoted = expression.quoted
1953        text = lower if self.normalize and not quoted else text
1954        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1955        if (
1956            quoted
1957            or self.dialect.can_quote(expression, self.identify)
1958            or lower in self.RESERVED_KEYWORDS
1959            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1960        ):
1961            text = (
1962                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1963            )
1964        return text
def hex_sql(self, expression: sqlglot.expressions.string.Hex) -> str:
1966    def hex_sql(self, expression: exp.Hex) -> str:
1967        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1968        if self.dialect.HEX_LOWERCASE:
1969            text = self.func("LOWER", text)
1970
1971        return text
def lowerhex_sql(self, expression: sqlglot.expressions.string.LowerHex) -> str:
1973    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1974        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1975        if not self.dialect.HEX_LOWERCASE:
1976            text = self.func("LOWER", text)
1977        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.query.InputOutputFormat) -> str:
1979    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1980        input_format = self.sql(expression, "input_format")
1981        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1982        output_format = self.sql(expression, "output_format")
1983        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1984        return self.sep().join((input_format, output_format))
def national_sql( self, expression: sqlglot.expressions.query.National, prefix: str = 'N') -> str:
1986    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1987        string = self.sql(exp.Literal.string(expression.name))
1988        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.query.Partition) -> str:
1990    def partition_sql(self, expression: exp.Partition) -> str:
1991        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1992        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.properties.Properties) -> str:
1994    def properties_sql(self, expression: exp.Properties) -> str:
1995        root_properties = []
1996        with_properties = []
1997
1998        for p in expression.expressions:
1999            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2000            if p_loc == exp.Properties.Location.POST_WITH:
2001                with_properties.append(p)
2002            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2003                root_properties.append(p)
2004
2005        root_props_ast = exp.Properties(expressions=root_properties)
2006        root_props_ast.parent = expression.parent
2007
2008        with_props_ast = exp.Properties(expressions=with_properties)
2009        with_props_ast.parent = expression.parent
2010
2011        root_props = self.root_properties(root_props_ast)
2012        with_props = self.with_properties(with_props_ast)
2013
2014        if root_props and with_props and not self.pretty:
2015            with_props = " " + with_props
2016
2017        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
2019    def root_properties(self, properties: exp.Properties) -> str:
2020        if properties.expressions:
2021            return self.expressions(properties, indent=False, sep=" ")
2022        return ""
def properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2024    def properties(
2025        self,
2026        properties: exp.Properties,
2027        prefix: str = "",
2028        sep: str = ", ",
2029        suffix: str = "",
2030        wrapped: bool = True,
2031    ) -> str:
2032        if properties.expressions:
2033            expressions = self.expressions(properties, sep=sep, indent=False)
2034            if expressions:
2035                expressions = self.wrap(expressions) if wrapped else expressions
2036                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2037        return ""
def with_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
2039    def with_properties(self, properties: exp.Properties) -> str:
2040        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2042    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2043        properties_locs = defaultdict(list)
2044        for p in properties.expressions:
2045            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2046            if p_loc != exp.Properties.Location.UNSUPPORTED:
2047                properties_locs[p_loc].append(p)
2048            else:
2049                self.unsupported(f"Unsupported property {p.key}")
2050
2051        return properties_locs
def property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2053    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2054        if isinstance(expression.this, exp.Dot):
2055            return self.sql(expression, "this")
2056        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.properties.Property) -> str:
2058    def property_sql(self, expression: exp.Property) -> str:
2059        property_cls = expression.__class__
2060        if property_cls == exp.Property:
2061            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2062
2063        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2064        if not property_name:
2065            self.unsupported(f"Unsupported property {expression.key}")
2066
2067        return f"{property_name}={self.sql(expression, 'this')}"
def uuidproperty_sql(self, expression: sqlglot.expressions.properties.UuidProperty) -> str:
2069    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2070        return f"UUID {self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.properties.LikeProperty) -> str:
2072    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2073        if self.SUPPORTS_CREATE_TABLE_LIKE:
2074            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2075            options = f" {options}" if options else ""
2076
2077            like = f"LIKE {self.sql(expression, 'this')}{options}"
2078            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2079                like = f"({like})"
2080
2081            return like
2082
2083        if expression.expressions:
2084            self.unsupported("Transpilation of LIKE property options is unsupported")
2085
2086        select = exp.select("*").from_(expression.this).limit(0)
2087        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.properties.FallbackProperty) -> str:
2089    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2090        no = "NO " if expression.args.get("no") else ""
2091        protection = " PROTECTION" if expression.args.get("protection") else ""
2092        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.properties.JournalProperty) -> str:
2094    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2095        no = "NO " if expression.args.get("no") else ""
2096        local = expression.args.get("local")
2097        local = f"{local} " if local else ""
2098        dual = "DUAL " if expression.args.get("dual") else ""
2099        before = "BEFORE " if expression.args.get("before") else ""
2100        after = "AFTER " if expression.args.get("after") else ""
2101        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql( self, expression: sqlglot.expressions.properties.FreespaceProperty) -> str:
2103    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2104        freespace = self.sql(expression, "this")
2105        percent = " PERCENT" if expression.args.get("percent") else ""
2106        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.properties.ChecksumProperty) -> str:
2108    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2109        if expression.args.get("default"):
2110            property = "DEFAULT"
2111        elif expression.args.get("on"):
2112            property = "ON"
2113        else:
2114            property = "OFF"
2115        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql( self, expression: sqlglot.expressions.properties.MergeBlockRatioProperty) -> str:
2117    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2118        if expression.args.get("no"):
2119            return "NO MERGEBLOCKRATIO"
2120        if expression.args.get("default"):
2121            return "DEFAULT MERGEBLOCKRATIO"
2122
2123        percent = " PERCENT" if expression.args.get("percent") else ""
2124        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def moduleproperty_sql(self, expression: sqlglot.expressions.properties.ModuleProperty) -> str:
2126    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2127        expressions = self.expressions(expression, flat=True)
2128        expressions = f"({expressions})" if expressions else ""
2129        return f"USING {self.sql(expression, 'this')}{expressions}"
def datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2131    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2132        default = expression.args.get("default")
2133        minimum = expression.args.get("minimum")
2134        maximum = expression.args.get("maximum")
2135        if default or minimum or maximum:
2136            if default:
2137                prop = "DEFAULT"
2138            elif minimum:
2139                prop = "MINIMUM"
2140            else:
2141                prop = "MAXIMUM"
2142            return f"{prop} DATABLOCKSIZE"
2143        units = expression.args.get("units")
2144        units = f" {units}" if units else ""
2145        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2147    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2148        autotemp = expression.args.get("autotemp")
2149        always = expression.args.get("always")
2150        default = expression.args.get("default")
2151        manual = expression.args.get("manual")
2152        never = expression.args.get("never")
2153
2154        if autotemp is not None:
2155            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2156        elif always:
2157            prop = "ALWAYS"
2158        elif default:
2159            prop = "DEFAULT"
2160        elif manual:
2161            prop = "MANUAL"
2162        elif never:
2163            prop = "NEVER"
2164        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2166    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2167        no = expression.args.get("no")
2168        no = " NO" if no else ""
2169        concurrent = expression.args.get("concurrent")
2170        concurrent = " CONCURRENT" if concurrent else ""
2171        target = self.sql(expression, "target")
2172        target = f" {target}" if target else ""
2173        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2175    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2176        if isinstance(expression.this, list):
2177            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2178        if expression.this:
2179            modulus = self.sql(expression, "this")
2180            remainder = self.sql(expression, "expression")
2181            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2182
2183        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2184        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2185        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2187    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2188        this = self.sql(expression, "this")
2189
2190        for_values_or_default = expression.expression
2191        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2192            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2193        else:
2194            for_values_or_default = " DEFAULT"
2195
2196        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.properties.LockingProperty) -> str:
2198    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2199        kind = expression.args.get("kind")
2200        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2201        for_or_in = expression.args.get("for_or_in")
2202        for_or_in = f" {for_or_in}" if for_or_in else ""
2203        lock_type = expression.args.get("lock_type")
2204        override = " OVERRIDE" if expression.args.get("override") else ""
2205        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.properties.WithDataProperty) -> str:
2207    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2208        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2209        statistics = expression.args.get("statistics")
2210        statistics_sql = ""
2211        if statistics is not None:
2212            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2213        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2215    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2216        this = self.sql(expression, "this")
2217        this = f"HISTORY_TABLE={this}" if this else ""
2218        data_consistency: str | None = self.sql(expression, "data_consistency")
2219        data_consistency = (
2220            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2221        )
2222        retention_period: str | None = self.sql(expression, "retention_period")
2223        retention_period = (
2224            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2225        )
2226
2227        if this:
2228            on_sql = self.func("ON", this, data_consistency, retention_period)
2229        else:
2230            on_sql = "ON" if expression.args.get("on") else "OFF"
2231
2232        sql = f"SYSTEM_VERSIONING={on_sql}"
2233
2234        return f"WITH({sql})" if expression.args.get("with_") else sql
def insert_sql(self, expression: sqlglot.expressions.dml.Insert) -> str:
2236    def insert_sql(self, expression: exp.Insert) -> str:
2237        hint = self.sql(expression, "hint")
2238        overwrite = expression.args.get("overwrite")
2239
2240        if isinstance(expression.this, exp.Directory):
2241            this = " OVERWRITE" if overwrite else " INTO"
2242        else:
2243            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2244
2245        stored = self.sql(expression, "stored")
2246        stored = f" {stored}" if stored else ""
2247        alternative = expression.args.get("alternative")
2248        alternative = f" OR {alternative}" if alternative else ""
2249        ignore = " IGNORE" if expression.args.get("ignore") else ""
2250        is_function = expression.args.get("is_function")
2251        if is_function:
2252            this = f"{this} FUNCTION"
2253        this = f"{this} {self.sql(expression, 'this')}"
2254
2255        exists = " IF EXISTS" if expression.args.get("exists") else ""
2256        where = self.sql(expression, "where")
2257        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2258        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2259        on_conflict = self.sql(expression, "conflict")
2260        on_conflict = f" {on_conflict}" if on_conflict else ""
2261        by_name = " BY NAME" if expression.args.get("by_name") else ""
2262        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2263        returning = self.sql(expression, "returning")
2264
2265        if self.RETURNING_END:
2266            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2267        else:
2268            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2269
2270        partition_by = self.sql(expression, "partition")
2271        partition_by = f" {partition_by}" if partition_by else ""
2272        settings = self.sql(expression, "settings")
2273        settings = f" {settings}" if settings else ""
2274
2275        source = self.sql(expression, "source")
2276        source = f"TABLE {source}" if source else ""
2277
2278        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2279        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.query.Introducer) -> str:
2281    def introducer_sql(self, expression: exp.Introducer) -> str:
2282        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.ddl.Kill) -> str:
2284    def kill_sql(self, expression: exp.Kill) -> str:
2285        kind = self.sql(expression, "kind")
2286        kind = f" {kind}" if kind else ""
2287        this = self.sql(expression, "this")
2288        this = f" {this}" if this else ""
2289        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.datatypes.PseudoType) -> str:
2291    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2292        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.datatypes.ObjectIdentifier) -> str:
2294    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2295        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.dml.OnConflict) -> str:
2297    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2298        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2299
2300        constraint = self.sql(expression, "constraint")
2301        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2302
2303        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2304        if conflict_keys:
2305            conflict_keys = f"({conflict_keys})"
2306
2307        index_predicate = self.sql(expression, "index_predicate")
2308        conflict_keys = f"{conflict_keys}{index_predicate} "
2309
2310        action = self.sql(expression, "action")
2311
2312        expressions = self.expressions(expression, flat=True)
2313        if expressions:
2314            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2315            expressions = f" {set_keyword}{expressions}"
2316
2317        where = self.sql(expression, "where")
2318        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.dml.Returning) -> str:
2320    def returning_sql(self, expression: exp.Returning) -> str:
2321        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2323    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2324        fields = self.sql(expression, "fields")
2325        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2326        escaped = self.sql(expression, "escaped")
2327        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2328        items = self.sql(expression, "collection_items")
2329        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2330        keys = self.sql(expression, "map_keys")
2331        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2332        lines = self.sql(expression, "lines")
2333        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2334        null = self.sql(expression, "null")
2335        null = f" NULL DEFINED AS {null}" if null else ""
2336        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.query.WithTableHint) -> str:
2338    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2339        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.query.IndexTableHint) -> str:
2341    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2342        this = f"{self.sql(expression, 'this')} INDEX"
2343        target = self.sql(expression, "target")
2344        target = f" FOR {target}" if target else ""
2345        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.query.HistoricalData) -> str:
2347    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2348        this = self.sql(expression, "this")
2349        kind = self.sql(expression, "kind")
2350        expr = self.sql(expression, "expression")
2351        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.query.Table) -> str:
2353    def table_parts(self, expression: exp.Table) -> str:
2354        return ".".join(
2355            self.sql(part)
2356            for part in (
2357                expression.args.get("catalog"),
2358                expression.args.get("db"),
2359                expression.args.get("this"),
2360            )
2361            if part is not None
2362        )
def table_sql( self, expression: sqlglot.expressions.query.Table, sep: str = ' AS ') -> str:
2364    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2365        table = self.table_parts(expression)
2366        only = "ONLY " if expression.args.get("only") else ""
2367        partition = self.sql(expression, "partition")
2368        partition = f" {partition}" if partition else ""
2369        version = self.sql(expression, "version")
2370        version = f" {version}" if version else ""
2371        alias = self.sql(expression, "alias")
2372        alias = f"{sep}{alias}" if alias else ""
2373
2374        sample = self.sql(expression, "sample")
2375        post_alias = ""
2376        pre_alias = ""
2377
2378        if self.dialect.ALIAS_POST_TABLESAMPLE:
2379            pre_alias = sample
2380        else:
2381            post_alias = sample
2382
2383        if self.dialect.ALIAS_POST_VERSION:
2384            pre_alias = f"{pre_alias}{version}"
2385        else:
2386            post_alias = f"{post_alias}{version}"
2387
2388        hints = self.expressions(expression, key="hints", sep=" ")
2389        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2390        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2391        joins = self.indent(
2392            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2393        )
2394        laterals = self.expressions(expression, key="laterals", sep="")
2395
2396        file_format = self.sql(expression, "format")
2397        if file_format:
2398            pattern = self.sql(expression, "pattern")
2399            pattern = f", PATTERN => {pattern}" if pattern else ""
2400            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2401
2402        ordinality = expression.args.get("ordinality") or ""
2403        if ordinality:
2404            ordinality = f" WITH ORDINALITY{alias}"
2405            alias = ""
2406
2407        when = self.sql(expression, "when")
2408        if when:
2409            table = f"{table} {when}"
2410
2411        changes = self.sql(expression, "changes")
2412        changes = f" {changes}" if changes else ""
2413
2414        rows_from = self.expressions(expression, key="rows_from")
2415        if rows_from:
2416            table = f"ROWS FROM {self.wrap(rows_from)}"
2417
2418        indexed = expression.args.get("indexed")
2419        if indexed is not None:
2420            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2421        else:
2422            indexed = ""
2423
2424        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:
2426    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2427        table = self.func("TABLE", expression.this)
2428        alias = self.sql(expression, "alias")
2429        alias = f" AS {alias}" if alias else ""
2430        sample = self.sql(expression, "sample")
2431        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2432        joins = self.indent(
2433            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2434        )
2435        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2437    def tablesample_sql(
2438        self,
2439        expression: exp.TableSample,
2440        tablesample_keyword: str | None = None,
2441    ) -> str:
2442        method = self.sql(expression, "method")
2443        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2444        numerator = self.sql(expression, "bucket_numerator")
2445        denominator = self.sql(expression, "bucket_denominator")
2446        field = self.sql(expression, "bucket_field")
2447        field = f" ON {field}" if field else ""
2448        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2449        seed = self.sql(expression, "seed")
2450        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2451
2452        size = self.sql(expression, "size")
2453        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2454            size = f"{size} ROWS"
2455
2456        percent = self.sql(expression, "percent")
2457        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2458            percent = f"{percent} PERCENT"
2459
2460        expr = f"{bucket}{percent}{size}"
2461        if self.TABLESAMPLE_REQUIRES_PARENS:
2462            expr = f"({expr})"
2463
2464        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.query.Pivot) -> str:
2541    def pivot_sql(self, expression: exp.Pivot) -> str:
2542        expressions = self.expressions(expression, flat=True)
2543        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2544
2545        group = self.sql(expression, "group")
2546
2547        if expression.this:
2548            this = self.sql(expression, "this")
2549            if not expressions:
2550                sql = f"UNPIVOT {this}"
2551            else:
2552                on = f"{self.seg('ON')} {expressions}"
2553                into = self.sql(expression, "into")
2554                into = f"{self.seg('INTO')} {into}" if into else ""
2555                using = self.expressions(expression, key="using", flat=True)
2556                using = f"{self.seg('USING')} {using}" if using else ""
2557                sql = f"{direction} {this}{on}{into}{using}{group}"
2558            return self.prepend_ctes(expression, sql)
2559
2560        if not expression.unpivot:
2561            # Wrap IN-list values with explicit aliases where the target dialect would differ
2562            new_field_exprs = self._pivot_in_value_aliases(expression)
2563            if new_field_exprs is not None:
2564                expression.fields[0].set("expressions", new_field_exprs)
2565
2566        alias = self.sql(expression, "alias")
2567        alias = f" AS {alias}" if alias else ""
2568
2569        fields = self.expressions(
2570            expression,
2571            "fields",
2572            sep=" ",
2573            dynamic=True,
2574            new_line=True,
2575            skip_first=True,
2576            skip_last=True,
2577        )
2578
2579        include_nulls = expression.args.get("include_nulls")
2580        if include_nulls is not None:
2581            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2582        else:
2583            nulls = ""
2584
2585        default_on_null = self.sql(expression, "default_on_null")
2586        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2587        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2588        return self.prepend_ctes(expression, sql)
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
2590    def version_sql(self, expression: exp.Version) -> str:
2591        this = f"FOR {expression.name}"
2592        kind = expression.text("kind")
2593        expr = self.sql(expression, "expression")
2594        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.query.Tuple) -> str:
2596    def tuple_sql(self, expression: exp.Tuple) -> str:
2597        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:
2631    def update_sql(self, expression: exp.Update) -> str:
2632        hint = self.sql(expression, "hint")
2633        this = self.sql(expression, "this")
2634        join_sql, from_sql = self._update_from_joins_sql(expression)
2635        set_sql = self.expressions(expression, flat=True)
2636        where_sql = self.sql(expression, "where")
2637        returning = self.sql(expression, "returning")
2638        order = self.sql(expression, "order")
2639        limit = self.sql(expression, "limit")
2640        if self.RETURNING_END:
2641            expression_sql = f"{from_sql}{where_sql}{returning}"
2642        else:
2643            expression_sql = f"{returning}{from_sql}{where_sql}"
2644        options = self.expressions(expression, key="options")
2645        options = f" OPTION({options})" if options else ""
2646        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2647        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2649    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2650        values_as_table = values_as_table and self.VALUES_AS_TABLE
2651
2652        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2653        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2654            args = self.expressions(expression)
2655            alias = self.sql(expression, "alias")
2656            values = f"VALUES{self.seg('')}{args}"
2657            values = (
2658                f"({values})"
2659                if self.WRAP_DERIVED_VALUES
2660                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2661                else values
2662            )
2663            values = self.query_modifiers(expression, values)
2664            return f"{values} AS {alias}" if alias else values
2665
2666        # Converts `VALUES...` expression into a series of select unions.
2667        alias_node = expression.args.get("alias")
2668        column_names = alias_node and alias_node.columns
2669
2670        selects: list[exp.Query] = []
2671
2672        for i, tup in enumerate(expression.expressions):
2673            row = tup.expressions
2674
2675            if i == 0 and column_names:
2676                row = [
2677                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2678                ]
2679
2680            selects.append(exp.Select(expressions=row))
2681
2682        if self.pretty:
2683            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2684            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2685            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2686            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2687            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2688
2689        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2690        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2691        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.core.Var) -> str:
2693    def var_sql(self, expression: exp.Var) -> str:
2694        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2696    @unsupported_args("expressions")
2697    def into_sql(self, expression: exp.Into) -> str:
2698        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2699        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2700        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.query.From) -> str:
2702    def from_sql(self, expression: exp.From) -> str:
2703        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.query.GroupingSets) -> str:
2705    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2706        grouping_sets = self.expressions(expression, indent=False)
2707        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.query.Rollup) -> str:
2709    def rollup_sql(self, expression: exp.Rollup) -> str:
2710        expressions = self.expressions(expression, indent=False)
2711        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def rollupindex_sql(self, expression: sqlglot.expressions.properties.RollupIndex) -> str:
2713    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2714        this = self.sql(expression, "this")
2715
2716        columns = self.expressions(expression, flat=True)
2717
2718        from_sql = self.sql(expression, "from_index")
2719        from_sql = f" FROM {from_sql}" if from_sql else ""
2720
2721        properties = expression.args.get("properties")
2722        properties_sql = (
2723            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2724        )
2725
2726        return f"{this}({columns}){from_sql}{properties_sql}"
def rollupproperty_sql(self, expression: sqlglot.expressions.properties.RollupProperty) -> str:
2728    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2729        return f"ROLLUP ({self.expressions(expression, flat=True)})"
def cube_sql(self, expression: sqlglot.expressions.query.Cube) -> str:
2731    def cube_sql(self, expression: exp.Cube) -> str:
2732        expressions = self.expressions(expression, indent=False)
2733        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.query.Group) -> str:
2735    def group_sql(self, expression: exp.Group) -> str:
2736        group_by_all = expression.args.get("all")
2737        if group_by_all is True:
2738            modifier = " ALL"
2739        elif group_by_all is False:
2740            modifier = " DISTINCT"
2741        else:
2742            modifier = ""
2743
2744        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2745
2746        grouping_sets = self.expressions(expression, key="grouping_sets")
2747        cube = self.expressions(expression, key="cube")
2748        rollup = self.expressions(expression, key="rollup")
2749
2750        groupings = csv(
2751            self.seg(grouping_sets) if grouping_sets else "",
2752            self.seg(cube) if cube else "",
2753            self.seg(rollup) if rollup else "",
2754            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2755            sep=self.GROUPINGS_SEP,
2756        )
2757
2758        if (
2759            expression.expressions
2760            and groupings
2761            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2762        ):
2763            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2764
2765        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.query.Having) -> str:
2767    def having_sql(self, expression: exp.Having) -> str:
2768        this = self.indent(self.sql(expression, "this"))
2769        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.query.Connect) -> str:
2771    def connect_sql(self, expression: exp.Connect) -> str:
2772        start = self.sql(expression, "start")
2773        start = self.seg(f"START WITH {start}") if start else ""
2774        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2775        connect = self.sql(expression, "connect")
2776        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2777        return start + connect
def prior_sql(self, expression: sqlglot.expressions.query.Prior) -> str:
2779    def prior_sql(self, expression: exp.Prior) -> str:
2780        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.query.Join) -> str:
2782    def join_sql(self, expression: exp.Join) -> str:
2783        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2784            side = None
2785        else:
2786            side = expression.side
2787
2788        op_sql = " ".join(
2789            op
2790            for op in (
2791                expression.method,
2792                "GLOBAL" if expression.args.get("global_") else None,
2793                side,
2794                expression.kind,
2795                expression.hint if self.JOIN_HINTS else None,
2796                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2797            )
2798            if op
2799        )
2800        match_cond = self.sql(expression, "match_condition")
2801        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2802        on_sql = self.sql(expression, "on")
2803        using = expression.args.get("using")
2804
2805        if not on_sql and using:
2806            on_sql = csv(*(self.sql(column) for column in using))
2807
2808        this = expression.this
2809        this_sql = self.sql(this)
2810
2811        exprs = self.expressions(expression)
2812        if exprs:
2813            this_sql = f"{this_sql},{self.seg(exprs)}"
2814
2815        if on_sql:
2816            on_sql = self.indent(on_sql, skip_first=True)
2817            space = self.seg(" " * self.pad) if self.pretty else " "
2818            if using:
2819                on_sql = f"{space}USING ({on_sql})"
2820            else:
2821                on_sql = f"{space}ON {on_sql}"
2822        elif not op_sql:
2823            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2824                return f" {this_sql}"
2825
2826            return f", {this_sql}"
2827
2828        if op_sql != "STRAIGHT_JOIN":
2829            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2830
2831        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2832        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:
2834    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2835        args = self.expressions(expression, flat=True)
2836        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2837        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.query.Lateral) -> str:
2839    def lateral_op(self, expression: exp.Lateral) -> str:
2840        cross_apply = expression.args.get("cross_apply")
2841
2842        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2843        if cross_apply is True:
2844            op = "INNER JOIN "
2845        elif cross_apply is False:
2846            op = "LEFT JOIN "
2847        else:
2848            op = ""
2849
2850        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.query.Lateral) -> str:
2852    def lateral_sql(self, expression: exp.Lateral) -> str:
2853        this = self.sql(expression, "this")
2854
2855        if expression.args.get("view"):
2856            alias = expression.args["alias"]
2857            columns = self.expressions(alias, key="columns", flat=True)
2858            table = f" {alias.name}" if alias.name else ""
2859            columns = f" AS {columns}" if columns else ""
2860            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2861            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2862
2863        alias = self.sql(expression, "alias")
2864        alias = f" AS {alias}" if alias else ""
2865
2866        ordinality = expression.args.get("ordinality") or ""
2867        if ordinality:
2868            ordinality = f" WITH ORDINALITY{alias}"
2869            alias = ""
2870
2871        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql( self, expression: sqlglot.expressions.query.Limit, top: bool = False) -> str:
2873    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2874        this = self.sql(expression, "this")
2875
2876        args = [
2877            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2878            for e in (expression.args.get(k) for k in ("offset", "expression"))
2879            if e
2880        ]
2881
2882        args_sql = ", ".join(self.sql(e) for e in args)
2883        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2884        expressions = self.expressions(expression, flat=True)
2885        limit_options = self.sql(expression, "limit_options")
2886        expressions = f" BY {expressions}" if expressions else ""
2887
2888        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:
2890    def offset_sql(self, expression: exp.Offset) -> str:
2891        this = self.sql(expression, "this")
2892        value = expression.expression
2893        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2894        expressions = self.expressions(expression, flat=True)
2895        expressions = f" BY {expressions}" if expressions else ""
2896        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.ddl.SetItem) -> str:
2898    def setitem_sql(self, expression: exp.SetItem) -> str:
2899        kind = self.sql(expression, "kind")
2900        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2901            kind = ""
2902        else:
2903            kind = f"{kind} " if kind else ""
2904        this = self.sql(expression, "this")
2905        expressions = self.expressions(expression)
2906        collate = self.sql(expression, "collate")
2907        collate = f" COLLATE {collate}" if collate else ""
2908        global_ = "GLOBAL " if expression.args.get("global_") else ""
2909        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.ddl.Set) -> str:
2911    def set_sql(self, expression: exp.Set) -> str:
2912        expressions = f" {self.expressions(expression, flat=True)}"
2913        tag = " TAG" if expression.args.get("tag") else ""
2914        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def queryband_sql(self, expression: sqlglot.expressions.query.QueryBand) -> str:
2916    def queryband_sql(self, expression: exp.QueryBand) -> str:
2917        this = self.sql(expression, "this")
2918        update = " UPDATE" if expression.args.get("update") else ""
2919        scope = self.sql(expression, "scope")
2920        scope = f" FOR {scope}" if scope else ""
2921
2922        return f"QUERY_BAND = {this}{update}{scope}"
def pragma_sql(self, expression: sqlglot.expressions.ddl.Pragma) -> str:
2924    def pragma_sql(self, expression: exp.Pragma) -> str:
2925        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.query.Lock) -> str:
2927    def lock_sql(self, expression: exp.Lock) -> str:
2928        if not self.LOCKING_READS_SUPPORTED:
2929            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2930            return ""
2931
2932        update = expression.args["update"]
2933        key = expression.args.get("key")
2934        if update:
2935            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2936        else:
2937            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2938        expressions = self.expressions(expression, flat=True)
2939        expressions = f" OF {expressions}" if expressions else ""
2940        wait = expression.args.get("wait")
2941
2942        if wait is not None:
2943            if isinstance(wait, exp.Literal):
2944                wait = f" WAIT {self.sql(wait)}"
2945            else:
2946                wait = " NOWAIT" if wait else " SKIP LOCKED"
2947
2948        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.core.Literal) -> str:
2950    def literal_sql(self, expression: exp.Literal) -> str:
2951        text = expression.this or ""
2952        if expression.is_string:
2953            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2954        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:
2956    def escape_str(
2957        self,
2958        text: str,
2959        escape_backslash: bool = True,
2960        delimiter: str | None = None,
2961        escaped_delimiter: str | None = None,
2962        is_byte_string: bool = False,
2963    ) -> str:
2964        if is_byte_string:
2965            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2966        else:
2967            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2968
2969        if supports_escape_sequences:
2970            text = "".join(
2971                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2972                for ch in text
2973            )
2974
2975        delimiter = delimiter or self.dialect.QUOTE_END
2976        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2977
2978        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
def loaddata_sql(self, expression: sqlglot.expressions.dml.LoadData) -> str:
2980    def loaddata_sql(self, expression: exp.LoadData) -> str:
2981        is_overwrite = expression.args.get("overwrite")
2982        overwrite = " OVERWRITE" if is_overwrite else ""
2983        this = self.sql(expression, "this")
2984
2985        files = expression.args.get("files")
2986        if files:
2987            files_sql = self.expressions(files, flat=True)
2988            files_sql = f"FILES{self.wrap(files_sql)}"
2989            if is_overwrite:
2990                this = f" {this}"
2991            elif expression.args.get("temp"):
2992                this = f" INTO TEMP TABLE {this}"
2993            else:
2994                this = f" INTO TABLE {this}"
2995            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2996
2997        local = " LOCAL" if expression.args.get("local") else ""
2998        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2999        this = f" INTO TABLE {this}"
3000        partition = self.sql(expression, "partition")
3001        partition = f" {partition}" if partition else ""
3002        input_format = self.sql(expression, "input_format")
3003        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3004        serde = self.sql(expression, "serde")
3005        serde = f" SERDE {serde}" if serde else ""
3006        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
3008    def null_sql(self, *_) -> str:
3009        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.core.Boolean) -> str:
3011    def boolean_sql(self, expression: exp.Boolean) -> str:
3012        return "TRUE" if expression.this else "FALSE"
def booland_sql(self, expression: sqlglot.expressions.math.Booland) -> str:
3014    def booland_sql(self, expression: exp.Booland) -> str:
3015        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
def boolor_sql(self, expression: sqlglot.expressions.math.Boolor) -> str:
3017    def boolor_sql(self, expression: exp.Boolor) -> str:
3018        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
def order_sql( self, expression: sqlglot.expressions.query.Order, flat: bool = False) -> str:
3020    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3021        this = self.sql(expression, "this")
3022        this = f"{this} " if this else this
3023        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3024        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:
3026    def withfill_sql(self, expression: exp.WithFill) -> str:
3027        from_sql = self.sql(expression, "from_")
3028        from_sql = f" FROM {from_sql}" if from_sql else ""
3029        to_sql = self.sql(expression, "to")
3030        to_sql = f" TO {to_sql}" if to_sql else ""
3031        step_sql = self.sql(expression, "step")
3032        step_sql = f" STEP {step_sql}" if step_sql else ""
3033        interpolated_values = [
3034            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3035            if isinstance(e, exp.Alias)
3036            else self.sql(e, "this")
3037            for e in expression.args.get("interpolate") or []
3038        ]
3039        interpolate = (
3040            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3041        )
3042        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.query.Cluster) -> str:
3044    def cluster_sql(self, expression: exp.Cluster) -> str:
3045        return self.op_expressions("CLUSTER BY", expression)
def clusterproperty_sql(self, expression: sqlglot.expressions.properties.ClusterProperty) -> str:
3047    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3048        if expression.this:
3049            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3050            return ""
3051        expressions = self.expressions(expression, flat=True)
3052        return f"CLUSTER BY ({expressions})"
def distribute_sql(self, expression: sqlglot.expressions.query.Distribute) -> str:
3054    def distribute_sql(self, expression: exp.Distribute) -> str:
3055        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.query.Sort) -> str:
3057    def sort_sql(self, expression: exp.Sort) -> str:
3058        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.core.Ordered) -> str:
3094    def ordered_sql(self, expression: exp.Ordered) -> str:
3095        desc = expression.args.get("desc")
3096        asc = not desc
3097
3098        nulls_first = expression.args.get("nulls_first")
3099        nulls_last = not nulls_first
3100        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3101        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3102        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3103
3104        this = self.sql(expression, "this")
3105
3106        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3107        nulls_sort_change = ""
3108        if nulls_first and (
3109            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3110        ):
3111            nulls_sort_change = " NULLS FIRST"
3112        elif (
3113            nulls_last
3114            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3115            and not nulls_are_last
3116        ):
3117            nulls_sort_change = " NULLS LAST"
3118
3119        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3120        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3121            window = expression.find_ancestor(exp.Window, exp.Select)
3122
3123            if isinstance(window, exp.Window):
3124                window_this = window.this
3125                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3126                    window_this = window_this.this
3127                spec = window.args.get("spec")
3128            else:
3129                window_this = None
3130                spec = None
3131
3132            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3133            # without a spec or with a ROWS spec, but not with RANGE
3134            if not (
3135                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3136                and (not spec or spec.text("kind").upper() == "ROWS")
3137            ):
3138                if window_this and spec:
3139                    self.unsupported(
3140                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3141                    )
3142                    nulls_sort_change = ""
3143                elif self.NULL_ORDERING_SUPPORTED is False and (
3144                    (asc and nulls_sort_change == " NULLS LAST")
3145                    or (desc and nulls_sort_change == " NULLS FIRST")
3146                ):
3147                    # BigQuery does not allow these ordering/nulls combinations when used under
3148                    # an aggregation func or under a window containing one
3149                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3150
3151                    if isinstance(ancestor, exp.Window):
3152                        ancestor = ancestor.this
3153                    if isinstance(ancestor, exp.AggFunc):
3154                        self.unsupported(
3155                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3156                        )
3157                        nulls_sort_change = ""
3158                elif self.NULL_ORDERING_SUPPORTED is None:
3159                    if expression.this.is_int:
3160                        self.unsupported(
3161                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3162                        )
3163                    elif not isinstance(expression.this, exp.Rand):
3164                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3165                        target = self.sql(resolved) if resolved is not None else this
3166                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3167                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3168                    nulls_sort_change = ""
3169
3170        with_fill = self.sql(expression, "with_fill")
3171        with_fill = f" {with_fill}" if with_fill else ""
3172
3173        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3175    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3176        window_frame = self.sql(expression, "window_frame")
3177        window_frame = f"{window_frame} " if window_frame else ""
3178
3179        this = self.sql(expression, "this")
3180
3181        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.query.MatchRecognize) -> str:
3183    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3184        partition = self.partition_by_sql(expression)
3185        order = self.sql(expression, "order")
3186        measures = self.expressions(expression, key="measures")
3187        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3188        rows = self.sql(expression, "rows")
3189        rows = self.seg(rows) if rows else ""
3190        after = self.sql(expression, "after")
3191        after = self.seg(after) if after else ""
3192        pattern = self.sql(expression, "pattern")
3193        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3194        definition_sqls = [
3195            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3196            for definition in expression.args.get("define", [])
3197        ]
3198        definitions = self.expressions(sqls=definition_sqls)
3199        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3200        body = "".join(
3201            (
3202                partition,
3203                order,
3204                measures,
3205                rows,
3206                after,
3207                pattern,
3208                define,
3209            )
3210        )
3211        alias = self.sql(expression, "alias")
3212        alias = f" {alias}" if alias else ""
3213        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.core.Expr, *sqls: str) -> str:
3215    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3216        limit = expression.args.get("limit")
3217
3218        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3219            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3220        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3221            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3222
3223        return csv(
3224            *sqls,
3225            *[self.sql(join) for join in expression.args.get("joins") or []],
3226            self.sql(expression, "match"),
3227            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3228            self.sql(expression, "prewhere"),
3229            self.sql(expression, "where"),
3230            self.sql(expression, "connect"),
3231            self.sql(expression, "group"),
3232            self.sql(expression, "having"),
3233            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3234            self.sql(expression, "order"),
3235            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3236            *self.after_limit_modifiers(expression),
3237            self.options_modifier(expression),
3238            self.sql(expression, "for_"),
3239            sep="",
3240        )
def options_modifier(self, expression: sqlglot.expressions.core.Expr) -> str:
3242    def options_modifier(self, expression: exp.Expr) -> str:
3243        options = self.expressions(expression, key="options")
3244        return f" {options}" if options else ""
def forclause_sql(self, expression: sqlglot.expressions.query.ForClause) -> str:
3246    def forclause_sql(self, expression: exp.ForClause) -> str:
3247        kind = expression.args["kind"]
3248        if kind == "BROWSE":
3249            return f"{self.sep()}FOR BROWSE"
3250        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3251        # the target dialect doesn't support QueryOption, so we drop the clause.
3252        options = self.expressions(expression, key="expressions")
3253        if not options:
3254            return ""
3255        return f"{self.sep()}FOR {kind}{self.seg(options)}"
def queryoption_sql(self, expression: sqlglot.expressions.query.QueryOption) -> str:
3257    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3258        self.unsupported("Unsupported query option.")
3259        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]:
3261    def offset_limit_modifiers(
3262        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3263    ) -> list[str]:
3264        return [
3265            self.sql(expression, "offset") if fetch else self.sql(limit),
3266            self.sql(limit) if fetch else self.sql(expression, "offset"),
3267        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.core.Expr) -> list[str]:
3269    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3270        locks = self.expressions(expression, key="locks", sep=" ")
3271        locks = f" {locks}" if locks else ""
3272        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.query.Select) -> str:
3274    def select_sql(self, expression: exp.Select) -> str:
3275        into = expression.args.get("into")
3276        if not self.SUPPORTS_SELECT_INTO and into:
3277            into.pop()
3278
3279        hint = self.sql(expression, "hint")
3280        distinct = self.sql(expression, "distinct")
3281        distinct = f" {distinct}" if distinct else ""
3282        kind = self.sql(expression, "kind")
3283
3284        limit = expression.args.get("limit")
3285        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3286            top = self.limit_sql(limit, top=True)
3287            limit.pop()
3288        else:
3289            top = ""
3290
3291        expressions = self.expressions(expression)
3292
3293        if kind:
3294            if kind in self.SELECT_KINDS:
3295                kind = f" AS {kind}"
3296            else:
3297                if kind == "STRUCT":
3298                    expressions = self.expressions(
3299                        sqls=[
3300                            self.sql(
3301                                exp.Struct(
3302                                    expressions=[
3303                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3304                                        if isinstance(e, exp.Alias)
3305                                        else e
3306                                        for e in expression.expressions
3307                                    ]
3308                                )
3309                            )
3310                        ]
3311                    )
3312                kind = ""
3313
3314        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3315        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3316
3317        exclude = expression.args.get("exclude")
3318
3319        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3320            exclude_sql = self.expressions(sqls=exclude, flat=True)
3321            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3322
3323        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3324        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3325        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3326        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3327        sql = self.query_modifiers(
3328            expression,
3329            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3330            self.sql(expression, "into", comment=False),
3331            self.sql(expression, "from_", comment=False),
3332        )
3333
3334        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3335        if expression.args.get("with_"):
3336            sql = self.maybe_comment(sql, expression)
3337            expression.pop_comments()
3338
3339        sql = self.prepend_ctes(expression, sql)
3340
3341        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3342            expression.set("exclude", None)
3343            subquery = expression.subquery(copy=False)
3344            star = exp.Star(except_=exclude)
3345            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3346
3347        if not self.SUPPORTS_SELECT_INTO and into:
3348            if into.args.get("temporary"):
3349                table_kind = " TEMPORARY"
3350            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3351                table_kind = " UNLOGGED"
3352            else:
3353                table_kind = ""
3354            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3355
3356        return sql
def schema_sql(self, expression: sqlglot.expressions.query.Schema) -> str:
3358    def schema_sql(self, expression: exp.Schema) -> str:
3359        this = self.sql(expression, "this")
3360        sql = self.schema_columns_sql(expression)
3361        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
3363    def schema_columns_sql(self, expression: exp.Expr) -> str:
3364        if expression.expressions:
3365            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3366        return ""
def star_sql(self, expression: sqlglot.expressions.core.Star) -> str:
3368    def star_sql(self, expression: exp.Star) -> str:
3369        except_ = self.expressions(expression, key="except_", flat=True)
3370        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3371        replace = self.expressions(expression, key="replace", flat=True)
3372        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3373        rename = self.expressions(expression, key="rename", flat=True)
3374        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3375        return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.core.Parameter) -> str:
3377    def parameter_sql(self, expression: exp.Parameter) -> str:
3378        this = self.sql(expression, "this")
3379        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.core.SessionParameter) -> str:
3381    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3382        this = self.sql(expression, "this")
3383        kind = expression.text("kind")
3384        if kind:
3385            kind = f"{kind}."
3386        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.core.Placeholder) -> str:
3388    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3389        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:
3391    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3392        alias = self.sql(expression, "alias")
3393        alias = f"{sep}{alias}" if alias else ""
3394        sample = self.sql(expression, "sample")
3395        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3396            alias = f"{sample}{alias}"
3397
3398            # Set to None so it's not generated again by self.query_modifiers()
3399            expression.set("sample", None)
3400
3401        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3402        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3403        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.query.Qualify) -> str:
3405    def qualify_sql(self, expression: exp.Qualify) -> str:
3406        this = self.indent(self.sql(expression, "this"))
3407        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.array.Unnest) -> str:
3409    def unnest_sql(self, expression: exp.Unnest) -> str:
3410        args = self.expressions(expression, flat=True)
3411
3412        alias = expression.args.get("alias")
3413        offset = expression.args.get("offset")
3414
3415        if self.UNNEST_WITH_ORDINALITY:
3416            if alias and isinstance(offset, exp.Expr):
3417                alias.append("columns", offset)
3418                expression.set("offset", None)
3419
3420        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3421            columns = alias.columns
3422            alias = self.sql(columns[0]) if columns else ""
3423        else:
3424            alias = self.sql(alias)
3425
3426        alias = f" AS {alias}" if alias else alias
3427        if self.UNNEST_WITH_ORDINALITY:
3428            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3429        else:
3430            if isinstance(offset, exp.Expr):
3431                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3432            elif offset:
3433                suffix = f"{alias} WITH OFFSET"
3434            else:
3435                suffix = alias
3436
3437        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.query.PreWhere) -> str:
3439    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3440        return ""
def where_sql(self, expression: sqlglot.expressions.query.Where) -> str:
3442    def where_sql(self, expression: exp.Where) -> str:
3443        this = self.indent(self.sql(expression, "this"))
3444        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.query.Window) -> str:
3446    def window_sql(self, expression: exp.Window) -> str:
3447        this = self.sql(expression, "this")
3448        partition = self.partition_by_sql(expression)
3449        order = expression.args.get("order")
3450        order = self.order_sql(order, flat=True) if order else ""
3451        spec = self.sql(expression, "spec")
3452        alias = self.sql(expression, "alias")
3453        over = self.sql(expression, "over") or "OVER"
3454
3455        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3456
3457        first = expression.args.get("first")
3458        if first is None:
3459            first = ""
3460        else:
3461            first = "FIRST" if first else "LAST"
3462
3463        if not partition and not order and not spec and alias:
3464            return f"{this} {alias}"
3465
3466        args = self.format_args(
3467            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3468        )
3469        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3471    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3472        partition = self.expressions(expression, key="partition_by", flat=True)
3473        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.query.WindowSpec) -> str:
3475    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3476        kind = self.sql(expression, "kind")
3477        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3478        end = (
3479            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3480            or "CURRENT ROW"
3481        )
3482
3483        window_spec = f"{kind} BETWEEN {start} AND {end}"
3484
3485        exclude = self.sql(expression, "exclude")
3486        if exclude:
3487            if self.SUPPORTS_WINDOW_EXCLUDE:
3488                window_spec += f" EXCLUDE {exclude}"
3489            else:
3490                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3491
3492        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.core.WithinGroup) -> str:
3494    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3495        this = self.sql(expression, "this")
3496        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3497        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.core.Between) -> str:
3499    def between_sql(self, expression: exp.Between) -> str:
3500        this = self.sql(expression, "this")
3501        low = self.sql(expression, "low")
3502        high = self.sql(expression, "high")
3503        symmetric = expression.args.get("symmetric")
3504
3505        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3506            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3507
3508        flag = (
3509            " SYMMETRIC"
3510            if symmetric
3511            else " ASYMMETRIC"
3512            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3513            else ""  # silently drop ASYMMETRIC – semantics identical
3514        )
3515        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]:
3517    def bracket_offset_expressions(
3518        self, expression: exp.Bracket, index_offset: int | None = None
3519    ) -> list[exp.Expr]:
3520        if expression.args.get("json_access"):
3521            return expression.expressions
3522
3523        return apply_index_offset(
3524            expression.this,
3525            expression.expressions,
3526            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3527            dialect=self.dialect,
3528        )
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
3530    def bracket_sql(self, expression: exp.Bracket) -> str:
3531        expressions = self.bracket_offset_expressions(expression)
3532        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3533        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.core.All) -> str:
3535    def all_sql(self, expression: exp.All) -> str:
3536        this = self.sql(expression, "this")
3537        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3538            this = self.wrap(this)
3539        return f"ALL {this}"
def any_sql(self, expression: sqlglot.expressions.core.Any) -> str:
3541    def any_sql(self, expression: exp.Any) -> str:
3542        this = self.sql(expression, "this")
3543        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3544            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3545                this = self.wrap(this)
3546            return f"ANY{this}"
3547        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.functions.Exists) -> str:
3549    def exists_sql(self, expression: exp.Exists) -> str:
3550        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.functions.Case) -> str:
3552    def case_sql(self, expression: exp.Case) -> str:
3553        this = self.sql(expression, "this")
3554        statements = [f"CASE {this}" if this else "CASE"]
3555
3556        for e in expression.args["ifs"]:
3557            statements.append(f"WHEN {self.sql(e, 'this')}")
3558            statements.append(f"THEN {self.sql(e, 'true')}")
3559
3560        default = self.sql(expression, "default")
3561
3562        if default:
3563            statements.append(f"ELSE {default}")
3564
3565        statements.append("END")
3566
3567        if self.pretty and self.too_wide(statements):
3568            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3569
3570        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.constraints.Constraint) -> str:
3572    def constraint_sql(self, expression: exp.Constraint) -> str:
3573        this = self.sql(expression, "this")
3574        expressions = self.expressions(expression, flat=True)
3575        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.ddl.NextValueFor) -> str:
3577    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3578        order = expression.args.get("order")
3579        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3580        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
3582    def extract_sql(self, expression: exp.Extract) -> str:
3583        import sqlglot.dialects.dialect
3584
3585        this = (
3586            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3587            if self.NORMALIZE_EXTRACT_DATE_PARTS
3588            else expression.this
3589        )
3590        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3591        expression_sql = self.sql(expression, "expression")
3592
3593        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.string.Trim) -> str:
3595    def trim_sql(self, expression: exp.Trim) -> str:
3596        trim_type = self.sql(expression, "position")
3597
3598        if trim_type == "LEADING":
3599            func_name = "LTRIM"
3600        elif trim_type == "TRAILING":
3601            func_name = "RTRIM"
3602        else:
3603            func_name = "TRIM"
3604
3605        return self.func(func_name, expression.this, expression.expression)
def convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3607    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3608        args = expression.expressions
3609        if isinstance(expression, exp.ConcatWs):
3610            args = args[1:]  # Skip the delimiter
3611
3612        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3613            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3614
3615        concat_coalesce = (
3616            self.dialect.CONCAT_WS_COALESCE
3617            if isinstance(expression, exp.ConcatWs)
3618            else self.dialect.CONCAT_COALESCE
3619        )
3620
3621        if not concat_coalesce and expression.args.get("coalesce"):
3622
3623            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3624                if not e.type:
3625                    import sqlglot.optimizer.annotate_types
3626
3627                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3628
3629                if e.is_string or e.is_type(exp.DType.ARRAY):
3630                    return e
3631
3632                return exp.func("coalesce", e, exp.Literal.string(""))
3633
3634            args = [_wrap_with_coalesce(e) for e in args]
3635
3636        return args
def concat_sql(self, expression: sqlglot.expressions.string.Concat) -> str:
3638    def concat_sql(self, expression: exp.Concat) -> str:
3639        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3640            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3641            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3642            # instead of coalescing them to empty string.
3643            import sqlglot.dialects.dialect
3644
3645            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3646
3647        expressions = self.convert_concat_args(expression)
3648
3649        # Some dialects don't allow a single-argument CONCAT call
3650        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3651            return self.sql(expressions[0])
3652
3653        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.string.ConcatWs) -> str:
3655    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3656        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3657            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3658            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3659            all_args = expression.expressions
3660            expression.set("coalesce", True)
3661            return self.sql(
3662                exp.case()
3663                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3664                .else_(expression)
3665            )
3666
3667        return self.func(
3668            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3669        )
def check_sql(self, expression: sqlglot.expressions.core.Check) -> str:
3671    def check_sql(self, expression: exp.Check) -> str:
3672        this = self.sql(expression, key="this")
3673        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.constraints.ForeignKey) -> str:
3675    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3676        expressions = self.expressions(expression, flat=True)
3677        expressions = f" ({expressions})" if expressions else ""
3678        reference = self.sql(expression, "reference")
3679        reference = f" {reference}" if reference else ""
3680        delete = self.sql(expression, "delete")
3681        delete = f" ON DELETE {delete}" if delete else ""
3682        update = self.sql(expression, "update")
3683        update = f" ON UPDATE {update}" if update else ""
3684        options = self.expressions(expression, key="options", flat=True, sep=" ")
3685        options = f" {options}" if options else ""
3686        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.constraints.PrimaryKey) -> str:
3688    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3689        this = self.sql(expression, "this")
3690        this = f" {this}" if this else ""
3691        expressions = self.expressions(expression, flat=True)
3692        include = self.sql(expression, "include")
3693        options = self.expressions(expression, key="options", flat=True, sep=" ")
3694        options = f" {options}" if options else ""
3695        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
def if_sql(self, expression: sqlglot.expressions.functions.If) -> str:
3697    def if_sql(self, expression: exp.If) -> str:
3698        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.string.MatchAgainst) -> str:
3700    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3701        if self.MATCH_AGAINST_TABLE_PREFIX:
3702            expressions = []
3703            for expr in expression.expressions:
3704                if isinstance(expr, exp.Table):
3705                    expressions.append(f"TABLE {self.sql(expr)}")
3706                else:
3707                    expressions.append(expr)
3708        else:
3709            expressions = expression.expressions
3710
3711        modifier = expression.args.get("modifier")
3712        modifier = f" {modifier}" if modifier else ""
3713        return (
3714            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3715        )
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.query.JSONKeyValue) -> str:
3717    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3718        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:
3720    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3721        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3722
3723        if expression.args.get("escape"):
3724            path = self.escape_str(path)
3725
3726        if self.QUOTE_JSON_PATH:
3727            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3728
3729        return path
def json_path_part( self, expression: int | str | sqlglot.expressions.query.JSONPathPart) -> str:
3731    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3732        if isinstance(expression, exp.JSONPathPart):
3733            transform = self.TRANSFORMS.get(expression.__class__)
3734            if not callable(transform):
3735                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3736                return ""
3737
3738            return transform(self, expression)
3739
3740        if isinstance(expression, int):
3741            return str(expression)
3742
3743        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3744            escaped = expression.replace("'", "\\'")
3745            escaped = f"\\'{expression}\\'"
3746        else:
3747            escaped = expression.replace('"', '\\"')
3748            escaped = f'"{escaped}"'
3749
3750        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.query.FormatJson) -> str:
3752    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3753        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.core.FormatPhrase) -> str:
3755    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3756        # Output the Teradata column FORMAT override.
3757        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3758        this = self.sql(expression, "this")
3759        fmt = self.sql(expression, "format")
3760        return f"{this} (FORMAT {fmt})"
def jsonarray_sql(self, expression: sqlglot.expressions.json.JSONArray) -> str:
3788    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3789        null_handling = expression.args.get("null_handling")
3790        null_handling = f" {null_handling}" if null_handling else ""
3791        return_type = self.sql(expression, "return_type")
3792        return_type = f" RETURNING {return_type}" if return_type else ""
3793        strict = " STRICT" if expression.args.get("strict") else ""
3794        return self.func(
3795            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3796        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.json.JSONArrayAgg) -> str:
3798    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3799        this = self.sql(expression, "this")
3800        order = self.sql(expression, "order")
3801        null_handling = expression.args.get("null_handling")
3802        null_handling = f" {null_handling}" if null_handling else ""
3803        return_type = self.sql(expression, "return_type")
3804        return_type = f" RETURNING {return_type}" if return_type else ""
3805        strict = " STRICT" if expression.args.get("strict") else ""
3806        return self.func(
3807            "JSON_ARRAYAGG",
3808            this,
3809            suffix=f"{order}{null_handling}{return_type}{strict})",
3810        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.query.JSONColumnDef) -> str:
3812    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3813        path = self.sql(expression, "path")
3814        path = f" PATH {path}" if path else ""
3815        nested_schema = self.sql(expression, "nested_schema")
3816
3817        if nested_schema:
3818            return f"NESTED{path} {nested_schema}"
3819
3820        this = self.sql(expression, "this")
3821        kind = self.sql(expression, "kind")
3822        kind = f" {kind}" if kind else ""
3823        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3824
3825        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3826        return f"{this}{kind}{format_json}{path}{ordinality}"
def jsonschema_sql(self, expression: sqlglot.expressions.query.JSONSchema) -> str:
3828    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3829        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.json.JSONTable) -> str:
3831    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3832        this = self.sql(expression, "this")
3833        path = self.sql(expression, "path")
3834        path = f", {path}" if path else ""
3835        error_handling = expression.args.get("error_handling")
3836        error_handling = f" {error_handling}" if error_handling else ""
3837        empty_handling = expression.args.get("empty_handling")
3838        empty_handling = f" {empty_handling}" if empty_handling else ""
3839        schema = self.sql(expression, "schema")
3840        return self.func(
3841            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3842        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.query.OpenJSONColumnDef) -> str:
3844    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3845        this = self.sql(expression, "this")
3846        kind = self.sql(expression, "kind")
3847        path = self.sql(expression, "path")
3848        path = f" {path}" if path else ""
3849        as_json = " AS JSON" if expression.args.get("as_json") else ""
3850        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.json.OpenJSON) -> str:
3852    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3853        this = self.sql(expression, "this")
3854        path = self.sql(expression, "path")
3855        path = f", {path}" if path else ""
3856        expressions = self.expressions(expression)
3857        with_ = (
3858            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3859            if expressions
3860            else ""
3861        )
3862        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.core.In) -> str:
3864    def in_sql(self, expression: exp.In) -> str:
3865        query = expression.args.get("query")
3866        unnest = expression.args.get("unnest")
3867        field = expression.args.get("field")
3868        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3869
3870        if query:
3871            in_sql = self.sql(query)
3872        elif unnest:
3873            in_sql = self.in_unnest_op(unnest)
3874        elif field:
3875            in_sql = self.sql(field)
3876        else:
3877            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3878
3879        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.array.Unnest) -> str:
3881    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3882        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
3884    def interval_sql(self, expression: exp.Interval) -> str:
3885        unit_expression = expression.args.get("unit")
3886        unit = self.sql(unit_expression) if unit_expression else ""
3887        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3888            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3889        unit = f" {unit}" if unit else ""
3890
3891        if self.SINGLE_STRING_INTERVAL:
3892            this = expression.this.name if expression.this else ""
3893            if this:
3894                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3895                    return f"INTERVAL '{this}'{unit}"
3896                return f"INTERVAL '{this}{unit}'"
3897            return f"INTERVAL{unit}"
3898
3899        this = self.sql(expression, "this")
3900        if this:
3901            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3902            this = f" {this}" if unwrapped else f" ({this})"
3903
3904        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.query.Return) -> str:
3906    def return_sql(self, expression: exp.Return) -> str:
3907        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.constraints.Reference) -> str:
3909    def reference_sql(self, expression: exp.Reference) -> str:
3910        this = self.sql(expression, "this")
3911        expressions = self.expressions(expression, flat=True)
3912        expressions = f"({expressions})" if expressions else ""
3913        options = self.expressions(expression, key="options", flat=True, sep=" ")
3914        options = f" {options}" if options else ""
3915        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.core.Anonymous) -> str:
3917    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3918        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3919        parent = expression.parent
3920        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3921
3922        return self.func(
3923            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3924        )
def paren_sql(self, expression: sqlglot.expressions.core.Paren) -> str:
3926    def paren_sql(self, expression: exp.Paren) -> str:
3927        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3928        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.core.Neg) -> str:
3930    def neg_sql(self, expression: exp.Neg) -> str:
3931        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3932        this_sql = self.sql(expression, "this")
3933        sep = " " if this_sql[0] == "-" else ""
3934        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.core.Not) -> str:
3936    def not_sql(self, expression: exp.Not) -> str:
3937        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.core.Alias) -> str:
3939    def alias_sql(self, expression: exp.Alias) -> str:
3940        alias = self.sql(expression, "alias")
3941        alias = f" AS {alias}" if alias else ""
3942        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.core.PivotAlias) -> str:
3944    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3945        alias = expression.args["alias"]
3946
3947        parent = expression.parent
3948        pivot = parent and parent.parent
3949
3950        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3951            identifier_alias = isinstance(alias, exp.Identifier)
3952            literal_alias = isinstance(alias, exp.Literal)
3953
3954            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3955                alias.replace(exp.Literal.string(alias.output_name))
3956            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3957                alias.replace(exp.to_identifier(alias.output_name))
3958
3959        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.core.Aliases) -> str:
3961    def aliases_sql(self, expression: exp.Aliases) -> str:
3962        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.core.AtIndex) -> str:
3964    def atindex_sql(self, expression: exp.AtIndex) -> str:
3965        this = self.sql(expression, "this")
3966        index = self.sql(expression, "expression")
3967        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.core.AtTimeZone) -> str:
3969    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3970        this = self.sql(expression, "this")
3971        zone = self.sql(expression, "zone")
3972        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.core.FromTimeZone) -> str:
3974    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3975        this = self.sql(expression, "this")
3976        zone = self.sql(expression, "zone")
3977        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def add_sql(self, expression: sqlglot.expressions.core.Add) -> str:
3979    def add_sql(self, expression: exp.Add) -> str:
3980        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
3982    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
3983        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:
3985    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
3986        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:
3988    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
3989        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:
3991    def connector_sql(
3992        self,
3993        expression: exp.Connector,
3994        op: str,
3995        stack: list[str | exp.Expr] | None = None,
3996    ) -> str:
3997        if stack is not None:
3998            if expression.expressions:
3999                stack.append(self.expressions(expression, sep=f" {op} "))
4000            else:
4001                stack.append(expression.right)
4002                if expression.comments and self.comments:
4003                    op = self.maybe_comment(op, comments=expression.comments)
4004                stack.extend((op, expression.left))
4005            return op
4006
4007        stack = [expression]
4008        sqls: list[str] = []
4009        ops = set()
4010
4011        while stack:
4012            node = stack.pop()
4013            if isinstance(node, exp.Connector):
4014                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4015            else:
4016                sql = self.sql(node)
4017                if sqls and sqls[-1] in ops:
4018                    sqls[-1] += f" {sql}"
4019                else:
4020                    sqls.append(sql)
4021
4022        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4023        return sep.join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.core.BitwiseAnd) -> str:
4025    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4026        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.core.BitwiseLeftShift) -> str:
4028    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4029        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.core.BitwiseNot) -> str:
4031    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4032        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.core.BitwiseOr) -> str:
4034    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4035        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.core.BitwiseRightShift) -> str:
4037    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4038        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.core.BitwiseXor) -> str:
4040    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4041        return self.binary(expression, "^")
def cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4043    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4044        format_sql = self.sql(expression, "format")
4045        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4046        to_sql = self.sql(expression, "to")
4047        to_sql = f" {to_sql}" if to_sql else ""
4048        action = self.sql(expression, "action")
4049        action = f" {action}" if action else ""
4050        default = self.sql(expression, "default")
4051        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4052        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:
4055    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4056        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
def currentdate_sql(self, expression: sqlglot.expressions.temporal.CurrentDate) -> str:
4058    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4059        zone = self.sql(expression, "this")
4060        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.functions.Collate) -> str:
4062    def collate_sql(self, expression: exp.Collate) -> str:
4063        if self.COLLATE_IS_FUNC:
4064            return self.function_fallback_sql(expression)
4065        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.ddl.Command) -> str:
4067    def command_sql(self, expression: exp.Command) -> str:
4068        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.ddl.Comment) -> str:
4070    def comment_sql(self, expression: exp.Comment) -> str:
4071        this = self.sql(expression, "this")
4072        kind = expression.args["kind"]
4073        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4074        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4075        expression_sql = self.sql(expression, "expression")
4076        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
def mergetreettlaction_sql(self, expression: sqlglot.expressions.ddl.MergeTreeTTLAction) -> str:
4078    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4079        this = self.sql(expression, "this")
4080        delete = " DELETE" if expression.args.get("delete") else ""
4081        recompress = self.sql(expression, "recompress")
4082        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4083        to_disk = self.sql(expression, "to_disk")
4084        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4085        to_volume = self.sql(expression, "to_volume")
4086        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4087        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
def mergetreettl_sql(self, expression: sqlglot.expressions.ddl.MergeTreeTTL) -> str:
4089    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4090        where = self.sql(expression, "where")
4091        group = self.sql(expression, "group")
4092        aggregates = self.expressions(expression, key="aggregates")
4093        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4094
4095        if not (where or group or aggregates) and len(expression.expressions) == 1:
4096            return f"TTL {self.expressions(expression, flat=True)}"
4097
4098        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
4100    def transaction_sql(self, expression: exp.Transaction) -> str:
4101        modes = self.expressions(expression, key="modes")
4102        modes = f" {modes}" if modes else ""
4103        return f"BEGIN{modes}"
def commit_sql(self, expression: sqlglot.expressions.ddl.Commit) -> str:
4105    def commit_sql(self, expression: exp.Commit) -> str:
4106        chain = expression.args.get("chain")
4107        if chain is not None:
4108            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4109
4110        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.ddl.Rollback) -> str:
4112    def rollback_sql(self, expression: exp.Rollback) -> str:
4113        savepoint = expression.args.get("savepoint")
4114        savepoint = f" TO {savepoint}" if savepoint else ""
4115        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.ddl.AlterColumn) -> str:
4117    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4118        this = self.sql(expression, "this")
4119
4120        dtype = self.sql(expression, "dtype")
4121        if dtype:
4122            collate = self.sql(expression, "collate")
4123            collate = f" COLLATE {collate}" if collate else ""
4124            using = self.sql(expression, "using")
4125            using = f" USING {using}" if using else ""
4126            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4127            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
4128
4129        default = self.sql(expression, "default")
4130        if default:
4131            return f"ALTER COLUMN {this} SET DEFAULT {default}"
4132
4133        comment = self.sql(expression, "comment")
4134        if comment:
4135            return f"ALTER COLUMN {this} COMMENT {comment}"
4136
4137        visible = expression.args.get("visible")
4138        if visible:
4139            return f"ALTER COLUMN {this} SET {visible}"
4140
4141        allow_null = expression.args.get("allow_null")
4142        drop = expression.args.get("drop")
4143
4144        if not drop and not allow_null:
4145            self.unsupported("Unsupported ALTER COLUMN syntax")
4146
4147        if allow_null is not None:
4148            keyword = "DROP" if drop else "SET"
4149            return f"ALTER COLUMN {this} {keyword} NOT NULL"
4150
4151        return f"ALTER COLUMN {this} DROP DEFAULT"
def modifycolumn_sql(self, expression: sqlglot.expressions.ddl.ModifyColumn) -> str:
4153    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4154        this = self.sql(expression, "this")
4155        rename_from = self.sql(expression, "rename_from")
4156        if rename_from:
4157            if not self.SUPPORTS_CHANGE_COLUMN:
4158                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4159            return f"CHANGE COLUMN {rename_from} {this}"
4160        if not self.SUPPORTS_MODIFY_COLUMN:
4161            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4162        return f"MODIFY COLUMN {this}"
def alterindex_sql(self, expression: sqlglot.expressions.ddl.AlterIndex) -> str:
4164    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4165        this = self.sql(expression, "this")
4166
4167        visible = expression.args.get("visible")
4168        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4169
4170        return f"ALTER INDEX {this} {visible_sql}"
def alterdiststyle_sql(self, expression: sqlglot.expressions.ddl.AlterDistStyle) -> str:
4172    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4173        this = self.sql(expression, "this")
4174        if not isinstance(expression.this, exp.Var):
4175            this = f"KEY DISTKEY {this}"
4176        return f"ALTER DISTSTYLE {this}"
def altersortkey_sql(self, expression: sqlglot.expressions.ddl.AlterSortKey) -> str:
4178    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4179        compound = " COMPOUND" if expression.args.get("compound") else ""
4180        this = self.sql(expression, "this")
4181        expressions = self.expressions(expression, flat=True)
4182        expressions = f"({expressions})" if expressions else ""
4183        return f"ALTER{compound} SORTKEY {this or expressions}"
def alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4185    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4186        if not self.RENAME_TABLE_WITH_DB:
4187            # Remove db from tables
4188            expression = expression.transform(
4189                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4190            ).assert_is(exp.AlterRename)
4191        this = self.sql(expression, "this")
4192        to_kw = " TO" if include_to else ""
4193        return f"RENAME{to_kw} {this}"
def renamecolumn_sql(self, expression: sqlglot.expressions.ddl.RenameColumn) -> str:
4195    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4196        exists = " IF EXISTS" if expression.args.get("exists") else ""
4197        old_column = self.sql(expression, "this")
4198        new_column = self.sql(expression, "to")
4199        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
def alterset_sql(self, expression: sqlglot.expressions.ddl.AlterSet) -> str:
4201    def alterset_sql(self, expression: exp.AlterSet) -> str:
4202        exprs = self.expressions(expression, flat=True)
4203        if self.ALTER_SET_WRAPPED:
4204            exprs = f"({exprs})"
4205
4206        return f"SET {exprs}"
def alter_sql(self, expression: sqlglot.expressions.ddl.Alter) -> str:
4208    def alter_sql(self, expression: exp.Alter) -> str:
4209        actions = expression.args["actions"]
4210
4211        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4212            actions[0], exp.ColumnDef
4213        ):
4214            actions_sql = self.expressions(expression, key="actions", flat=True)
4215            actions_sql = f"ADD {actions_sql}"
4216        else:
4217            actions_list = []
4218            for action in actions:
4219                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4220                    action_sql = self.add_column_sql(action)
4221                else:
4222                    action_sql = self.sql(action)
4223                    if isinstance(action, exp.Query):
4224                        action_sql = f"AS {action_sql}"
4225
4226                actions_list.append(action_sql)
4227
4228            actions_sql = self.format_args(*actions_list).lstrip("\n")
4229
4230        iceberg = (
4231            "ICEBERG "
4232            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4233            else ""
4234        )
4235        exists = " IF EXISTS" if expression.args.get("exists") else ""
4236        on_cluster = self.sql(expression, "cluster")
4237        on_cluster = f" {on_cluster}" if on_cluster else ""
4238        only = " ONLY" if expression.args.get("only") else ""
4239        options = self.expressions(expression, key="options")
4240        options = f", {options}" if options else ""
4241        kind = self.sql(expression, "kind")
4242        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4243        check = " WITH CHECK" if expression.args.get("check") else ""
4244        cascade = (
4245            " CASCADE"
4246            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4247            else ""
4248        )
4249        this = self.sql(expression, "this")
4250        this = f" {this}" if this else ""
4251
4252        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:
4254    def altersession_sql(self, expression: exp.AlterSession) -> str:
4255        items_sql = self.expressions(expression, flat=True)
4256        keyword = "UNSET" if expression.args.get("unset") else "SET"
4257        return f"{keyword} {items_sql}"
def add_column_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
4259    def add_column_sql(self, expression: exp.Expr) -> str:
4260        sql = self.sql(expression)
4261        if isinstance(expression, exp.Schema):
4262            column_text = " COLUMNS"
4263        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4264            column_text = " COLUMN"
4265        else:
4266            column_text = ""
4267
4268        return f"ADD{column_text} {sql}"
def droppartition_sql(self, expression: sqlglot.expressions.query.DropPartition) -> str:
4270    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4271        expressions = self.expressions(expression)
4272        exists = " IF EXISTS " if expression.args.get("exists") else " "
4273        return f"DROP{exists}{expressions}"
def dropprimarykey_sql(self, expression: sqlglot.expressions.ddl.DropPrimaryKey) -> str:
4275    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4276        return "DROP PRIMARY KEY"
def addconstraint_sql(self, expression: sqlglot.expressions.constraints.AddConstraint) -> str:
4278    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4279        return f"ADD {self.expressions(expression, indent=False)}"
def addpartition_sql(self, expression: sqlglot.expressions.query.AddPartition) -> str:
4281    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4282        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4283        location = self.sql(expression, "location")
4284        location = f" {location}" if location else ""
4285        return f"ADD {exists}{self.sql(expression.this)}{location}"
def distinct_sql(self, expression: sqlglot.expressions.core.Distinct) -> str:
4287    def distinct_sql(self, expression: exp.Distinct) -> str:
4288        this = self.expressions(expression, flat=True)
4289
4290        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4291            case = exp.case()
4292            for arg in expression.expressions:
4293                case = case.when(arg.is_(exp.null()), exp.null())
4294            this = self.sql(case.else_(f"({this})"))
4295
4296        this = f" {this}" if this else ""
4297
4298        on = self.sql(expression, "on")
4299        on = f" ON {on}" if on else ""
4300        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.core.IgnoreNulls) -> str:
4302    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4303        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
def respectnulls_sql(self, expression: sqlglot.expressions.core.RespectNulls) -> str:
4305    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4306        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
def havingmax_sql(self, expression: sqlglot.expressions.core.HavingMax) -> str:
4308    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4309        this_sql = self.sql(expression, "this")
4310        expression_sql = self.sql(expression, "expression")
4311        kind = "MAX" if expression.args.get("max") else "MIN"
4312        return f"{this_sql} HAVING {kind} {expression_sql}"
def intdiv_sql(self, expression: sqlglot.expressions.core.IntDiv) -> str:
4314    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4315        return self.sql(
4316            exp.Cast(
4317                this=exp.Div(this=expression.this, expression=expression.expression),
4318                to=exp.DataType(this=exp.DType.INT),
4319            )
4320        )
def dpipe_sql(self, expression: sqlglot.expressions.core.DPipe) -> str:
4322    def dpipe_sql(self, expression: exp.DPipe) -> str:
4323        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4324            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4325        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.core.Div) -> str:
4327    def div_sql(self, expression: exp.Div) -> str:
4328        l, r = expression.left, expression.right
4329
4330        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4331            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4332
4333        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4334            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4335                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4336
4337        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4338            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4339                return self.sql(
4340                    exp.cast(
4341                        l / r,
4342                        to=exp.DType.BIGINT,
4343                    )
4344                )
4345
4346        return self.binary(expression, "/")
def safedivide_sql(self, expression: sqlglot.expressions.math.SafeDivide) -> str:
4348    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4349        n = exp._wrap(expression.this, exp.Binary)
4350        d = exp._wrap(expression.expression, exp.Binary)
4351        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:
4353    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4354        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.core.Distance) -> str:
4356    def distance_sql(self, expression: exp.Distance) -> str:
4357        return self.binary(expression, "<->")
def distancend_sql(self, expression: sqlglot.expressions.core.DistanceNd) -> str:
4359    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4360        return self.binary(expression, "<<->>")
def dot_sql(self, expression: sqlglot.expressions.core.Dot) -> str:
4362    def dot_sql(self, expression: exp.Dot) -> str:
4363        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.core.EQ) -> str:
4365    def eq_sql(self, expression: exp.EQ) -> str:
4366        return self.binary(expression, "=")
def propertyeq_sql(self, expression: sqlglot.expressions.core.PropertyEQ) -> str:
4368    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4369        return self.binary(expression, ":=")
def escape_sql(self, expression: sqlglot.expressions.core.Escape) -> str:
4371    def escape_sql(self, expression: exp.Escape) -> str:
4372        this = expression.this
4373        if (
4374            isinstance(this, (exp.Like, exp.ILike))
4375            and isinstance(this.expression, (exp.All, exp.Any))
4376            and not self.SUPPORTS_LIKE_QUANTIFIERS
4377        ):
4378            return self._like_sql(this, escape=expression)
4379        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.core.Glob) -> str:
4381    def glob_sql(self, expression: exp.Glob) -> str:
4382        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.core.GT) -> str:
4384    def gt_sql(self, expression: exp.GT) -> str:
4385        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.core.GTE) -> str:
4387    def gte_sql(self, expression: exp.GTE) -> str:
4388        return self.binary(expression, ">=")
def is_sql(self, expression: sqlglot.expressions.core.Is) -> str:
4390    def is_sql(self, expression: exp.Is) -> str:
4391        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4392            return self.sql(
4393                expression.this if expression.expression.this else exp.not_(expression.this)
4394            )
4395        return self.binary(expression, "IS")
def like_sql(self, expression: sqlglot.expressions.core.Like) -> str:
4447    def like_sql(self, expression: exp.Like) -> str:
4448        return self._like_sql(expression)
def ilike_sql(self, expression: sqlglot.expressions.core.ILike) -> str:
4450    def ilike_sql(self, expression: exp.ILike) -> str:
4451        return self._like_sql(expression)
def match_sql(self, expression: sqlglot.expressions.core.Match) -> str:
4453    def match_sql(self, expression: exp.Match) -> str:
4454        return self.binary(expression, "MATCH")
def similarto_sql(self, expression: sqlglot.expressions.core.SimilarTo) -> str:
4456    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4457        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.core.LT) -> str:
4459    def lt_sql(self, expression: exp.LT) -> str:
4460        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.core.LTE) -> str:
4462    def lte_sql(self, expression: exp.LTE) -> str:
4463        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.core.Mod) -> str:
4465    def mod_sql(self, expression: exp.Mod) -> str:
4466        return self.binary(expression, "%")
def mul_sql(self, expression: sqlglot.expressions.core.Mul) -> str:
4468    def mul_sql(self, expression: exp.Mul) -> str:
4469        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.core.NEQ) -> str:
4471    def neq_sql(self, expression: exp.NEQ) -> str:
4472        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.core.NullSafeEQ) -> str:
4474    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4475        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.core.NullSafeNEQ) -> str:
4477    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4478        return self.binary(expression, "IS DISTINCT FROM")
def sub_sql(self, expression: sqlglot.expressions.core.Sub) -> str:
4480    def sub_sql(self, expression: exp.Sub) -> str:
4481        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.functions.TryCast) -> str:
4483    def trycast_sql(self, expression: exp.TryCast) -> str:
4484        return self.cast_sql(expression, safe_prefix="TRY_")
def jsoncast_sql(self, expression: sqlglot.expressions.functions.JSONCast) -> str:
4486    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4487        return self.cast_sql(expression)
def try_sql(self, expression: sqlglot.expressions.functions.Try) -> str:
4489    def try_sql(self, expression: exp.Try) -> str:
4490        if not self.TRY_SUPPORTED:
4491            self.unsupported("Unsupported TRY function")
4492            return self.sql(expression, "this")
4493
4494        return self.func("TRY", expression.this)
def log_sql(self, expression: sqlglot.expressions.math.Log) -> str:
4496    def log_sql(self, expression: exp.Log) -> str:
4497        this = expression.this
4498        expr = expression.expression
4499
4500        if self.dialect.LOG_BASE_FIRST is False:
4501            this, expr = expr, this
4502        elif self.dialect.LOG_BASE_FIRST is None and expr:
4503            if this.name in ("2", "10"):
4504                return self.func(f"LOG{this.name}", expr)
4505
4506            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4507
4508        return self.func("LOG", this, expr)
def use_sql(self, expression: sqlglot.expressions.ddl.Use) -> str:
4510    def use_sql(self, expression: exp.Use) -> str:
4511        kind = self.sql(expression, "kind")
4512        kind = f" {kind}" if kind else ""
4513        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4514        this = f" {this}" if this else ""
4515        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.core.Binary, op: str) -> str:
4517    def binary(self, expression: exp.Binary, op: str) -> str:
4518        sqls: list[str] = []
4519        stack: list[None | str | exp.Expr] = [expression]
4520        binary_type = type(expression)
4521
4522        while stack:
4523            node = stack.pop()
4524
4525            if type(node) is binary_type:
4526                op_func = node.args.get("operator")
4527                if op_func:
4528                    op = f"OPERATOR({self.sql(op_func)})"
4529
4530                stack.append(node.args.get("expression"))
4531                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4532                stack.append(node.args.get("this"))
4533            else:
4534                sqls.append(self.sql(node))
4535
4536        return "".join(sqls)
def ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4538    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4539        to_clause = self.sql(expression, "to")
4540        if to_clause:
4541            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4542
4543        return self.function_fallback_sql(expression)
def function_fallback_sql(self, expression: sqlglot.expressions.core.Func) -> str:
4545    def function_fallback_sql(self, expression: exp.Func) -> str:
4546        args = []
4547
4548        for key in expression.arg_types:
4549            arg_value = expression.args.get(key)
4550
4551            if isinstance(arg_value, list):
4552                for value in arg_value:
4553                    args.append(value)
4554            elif arg_value is not None:
4555                args.append(arg_value)
4556
4557        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4558            name = expression.meta_get("name") or expression.sql_name()
4559        else:
4560            name = expression.sql_name()
4561
4562        return self.func(name, *args)
def func( self, name: str, *args: Any, prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
4564    def func(
4565        self,
4566        name: str,
4567        *args: t.Any,
4568        prefix: str = "(",
4569        suffix: str = ")",
4570        normalize: bool = True,
4571    ) -> str:
4572        name = self.normalize_func(name) if normalize else name
4573        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def format_args(self, *args: Any, sep: str = ', ') -> str:
4575    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4576        arg_sqls = tuple(
4577            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4578        )
4579        if self.pretty and self.too_wide(arg_sqls):
4580            return self.indent(
4581                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4582            )
4583        return sep.join(arg_sqls)
def too_wide(self, args: Iterable) -> bool:
4585    def too_wide(self, args: t.Iterable) -> bool:
4586        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:
4588    def format_time(
4589        self,
4590        expression: exp.Expr,
4591        inverse_time_mapping: dict[str, str] | None = None,
4592        inverse_time_trie: dict | None = None,
4593    ) -> str | None:
4594        return format_time(
4595            self.sql(expression, "format"),
4596            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4597            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4598        )
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:
4600    def expressions(
4601        self,
4602        expression: exp.Expr | None = None,
4603        key: str | None = None,
4604        sqls: t.Collection[str | exp.Expr] | None = None,
4605        flat: bool = False,
4606        indent: bool = True,
4607        skip_first: bool = False,
4608        skip_last: bool = False,
4609        sep: str = ", ",
4610        prefix: str = "",
4611        dynamic: bool = False,
4612        new_line: bool = False,
4613    ) -> str:
4614        expressions = expression.args.get(key or "expressions") if expression else sqls
4615
4616        if not expressions:
4617            return ""
4618
4619        if flat:
4620            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4621
4622        num_sqls = len(expressions)
4623        result_sqls = []
4624
4625        for i, e in enumerate(expressions):
4626            sql = self.sql(e, comment=False)
4627            if not sql:
4628                continue
4629
4630            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4631
4632            if self.pretty:
4633                if self.leading_comma:
4634                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4635                else:
4636                    result_sqls.append(
4637                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4638                    )
4639            else:
4640                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4641
4642        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4643            if new_line:
4644                result_sqls.insert(0, "")
4645                result_sqls.append("")
4646            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4647        else:
4648            result_sql = "".join(result_sqls)
4649
4650        return (
4651            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4652            if indent
4653            else result_sql
4654        )
def op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4656    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4657        flat = flat or isinstance(expression.parent, exp.Properties)
4658        expressions_sql = self.expressions(expression, flat=flat)
4659        if flat:
4660            return f"{op} {expressions_sql}"
4661        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.properties.Property) -> str:
4663    def naked_property(self, expression: exp.Property) -> str:
4664        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4665        if not property_name:
4666            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4667        return f"{property_name} {self.sql(expression, 'this')}"
def tag_sql(self, expression: sqlglot.expressions.query.Tag) -> str:
4669    def tag_sql(self, expression: exp.Tag) -> str:
4670        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:
4672    def token_sql(self, token_type: TokenType) -> str:
4673        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.ddl.UserDefinedFunction) -> str:
4675    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4676        this = self.sql(expression, "this")
4677        expressions = self.no_identify(self.expressions, expression)
4678        expressions = (
4679            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4680        )
4681        return f"{this}{expressions}" if expressions.strip() != "" else this
def macrooverloads_sql(self, expression: sqlglot.expressions.ddl.MacroOverloads) -> str:
4683    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4684        return self.expressions(expression, flat=True)
def macrooverload_sql(self, expression: sqlglot.expressions.ddl.MacroOverload) -> str:
4686    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4687        params = self.no_identify(self.expressions, expression, flat=True)
4688        body = self.sql(expression, "this")
4689        prefix = "TABLE " if expression.args.get("is_table") else ""
4690        return f"({params}) AS {prefix}{body}"
def joinhint_sql(self, expression: sqlglot.expressions.core.JoinHint) -> str:
4692    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4693        this = self.sql(expression, "this")
4694        expressions = self.expressions(expression, flat=True)
4695        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.core.Kwarg) -> str:
4697    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4698        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.dml.When) -> str:
4700    def when_sql(self, expression: exp.When) -> str:
4701        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4702        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4703        condition = self.sql(expression, "condition")
4704        condition = f" AND {condition}" if condition else ""
4705
4706        then_expression = expression.args.get("then")
4707        if isinstance(then_expression, exp.Insert):
4708            this = self.sql(then_expression, "this")
4709            this = f"INSERT {this}" if this else "INSERT"
4710            then = self.sql(then_expression, "expression")
4711            then = f"{this} VALUES {then}" if then else this
4712        elif isinstance(then_expression, exp.Update):
4713            if isinstance(then_expression.args.get("expressions"), exp.Star):
4714                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4715            else:
4716                expressions_sql = self.expressions(then_expression)
4717                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4718        else:
4719            then = self.sql(then_expression)
4720
4721        if isinstance(then_expression, (exp.Insert, exp.Update)):
4722            where = self.sql(then_expression, "where")
4723            if where and not self.SUPPORTS_MERGE_WHERE:
4724                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4725                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4726                where = ""
4727            then = f"{then}{where}"
4728        return f"WHEN {matched}{source}{condition} THEN {then}"
def whens_sql(self, expression: sqlglot.expressions.dml.Whens) -> str:
4730    def whens_sql(self, expression: exp.Whens) -> str:
4731        return self.expressions(expression, sep=" ", indent=False)
def merge_sql(self, expression: sqlglot.expressions.dml.Merge) -> str:
4733    def merge_sql(self, expression: exp.Merge) -> str:
4734        table = expression.this
4735        table_alias = ""
4736
4737        hints = table.args.get("hints")
4738        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4739            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4740            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4741
4742        this = self.sql(table)
4743        using = f"USING {self.sql(expression, 'using')}"
4744        whens = self.sql(expression, "whens")
4745
4746        on = self.sql(expression, "on")
4747        on = f"ON {on}" if on else ""
4748
4749        if not on:
4750            on = self.expressions(expression, key="using_cond")
4751            on = f"USING ({on})" if on else ""
4752
4753        returning = self.sql(expression, "returning")
4754        if returning:
4755            whens = f"{whens}{returning}"
4756
4757        sep = self.sep()
4758
4759        return self.prepend_ctes(
4760            expression,
4761            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4762        )
@unsupported_args('format')
def tochar_sql(self, expression: sqlglot.expressions.string.ToChar) -> str:
4764    @unsupported_args("format")
4765    def tochar_sql(self, expression: exp.ToChar) -> str:
4766        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
@unsupported_args('default')
def tonumber_sql(self, expression: sqlglot.expressions.string.ToNumber) -> str:
4768    @unsupported_args("default")
4769    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4770        if not self.SUPPORTS_TO_NUMBER:
4771            self.unsupported("Unsupported TO_NUMBER function")
4772            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4773
4774        fmt = expression.args.get("format")
4775        if not fmt:
4776            self.unsupported("Conversion format is required for TO_NUMBER")
4777            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4778
4779        return self.func("TO_NUMBER", expression.this, fmt)
def dictproperty_sql(self, expression: sqlglot.expressions.properties.DictProperty) -> str:
4781    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4782        this = self.sql(expression, "this")
4783        kind = self.sql(expression, "kind")
4784        settings_sql = self.expressions(expression, key="settings", sep=" ")
4785        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4786        return f"{this}({kind}{args})"
def dictrange_sql(self, expression: sqlglot.expressions.properties.DictRange) -> str:
4788    def dictrange_sql(self, expression: exp.DictRange) -> str:
4789        this = self.sql(expression, "this")
4790        max = self.sql(expression, "max")
4791        min = self.sql(expression, "min")
4792        return f"{this}(MIN {min} MAX {max})"
def dictsubproperty_sql(self, expression: sqlglot.expressions.properties.DictSubProperty) -> str:
4794    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4795        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
def duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
4797    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4798        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
def uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4801    def uniquekeyproperty_sql(
4802        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4803    ) -> str:
4804        return f"{prefix} ({self.expressions(expression, flat=True)})"
def distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
4807    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4808        expressions = self.expressions(expression, flat=True)
4809        expressions = f" {self.wrap(expressions)}" if expressions else ""
4810        buckets = self.sql(expression, "buckets")
4811        kind = self.sql(expression, "kind")
4812        buckets = f" BUCKETS {buckets}" if buckets else ""
4813        order = self.sql(expression, "order")
4814        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def oncluster_sql(self, expression: sqlglot.expressions.properties.OnCluster) -> str:
4816    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4817        return ""
def clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4819    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4820        expressions = self.expressions(expression, key="expressions", flat=True)
4821        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4822        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4823        buckets = self.sql(expression, "buckets")
4824        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
def anyvalue_sql(self, expression: sqlglot.expressions.aggregate.AnyValue) -> str:
4826    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4827        this = self.sql(expression, "this")
4828        having = self.sql(expression, "having")
4829
4830        if having:
4831            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4832
4833        return self.func("ANY_VALUE", this)
def querytransform_sql(self, expression: sqlglot.expressions.properties.QueryTransform) -> str:
4835    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4836        transform = self.func("TRANSFORM", *expression.expressions)
4837        row_format_before = self.sql(expression, "row_format_before")
4838        row_format_before = f" {row_format_before}" if row_format_before else ""
4839        record_writer = self.sql(expression, "record_writer")
4840        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4841        using = f" USING {self.sql(expression, 'command_script')}"
4842        schema = self.sql(expression, "schema")
4843        schema = f" AS {schema}" if schema else ""
4844        row_format_after = self.sql(expression, "row_format_after")
4845        row_format_after = f" {row_format_after}" if row_format_after else ""
4846        record_reader = self.sql(expression, "record_reader")
4847        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4848        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:
4850    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4851        key_block_size = self.sql(expression, "key_block_size")
4852        if key_block_size:
4853            return f"KEY_BLOCK_SIZE = {key_block_size}"
4854
4855        using = self.sql(expression, "using")
4856        if using:
4857            return f"USING {using}"
4858
4859        parser = self.sql(expression, "parser")
4860        if parser:
4861            return f"WITH PARSER {parser}"
4862
4863        comment = self.sql(expression, "comment")
4864        if comment:
4865            return f"COMMENT {comment}"
4866
4867        visible = expression.args.get("visible")
4868        if visible is not None:
4869            return "VISIBLE" if visible else "INVISIBLE"
4870
4871        engine_attr = self.sql(expression, "engine_attr")
4872        if engine_attr:
4873            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4874
4875        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4876        if secondary_engine_attr:
4877            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4878
4879        self.unsupported("Unsupported index constraint option.")
4880        return ""
def checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
4882    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4883        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4884        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
def indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4886    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4887        kind = self.sql(expression, "kind")
4888        kind = f"{kind} INDEX" if kind else "INDEX"
4889        this = self.sql(expression, "this")
4890        this = f" {this}" if this else ""
4891        index_type = self.sql(expression, "index_type")
4892        index_type = f" USING {index_type}" if index_type else ""
4893        expressions = self.expressions(expression, flat=True)
4894        expressions = f" ({expressions})" if expressions else ""
4895        options = self.expressions(expression, key="options", sep=" ")
4896        options = f" {options}" if options else ""
4897        return f"{kind}{this}{index_type}{expressions}{options}"
def nvl2_sql(self, expression: sqlglot.expressions.functions.Nvl2) -> str:
4899    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4900        if self.NVL2_SUPPORTED:
4901            return self.function_fallback_sql(expression)
4902
4903        case = exp.Case().when(
4904            expression.this.is_(exp.null()).not_(copy=False),
4905            expression.args["true"],
4906            copy=False,
4907        )
4908        else_cond = expression.args.get("false")
4909        if else_cond:
4910            case.else_(else_cond, copy=False)
4911
4912        return self.sql(case)
def comprehension_sql(self, expression: sqlglot.expressions.ddl.Comprehension) -> str:
4914    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4915        this = self.sql(expression, "this")
4916        expr = self.sql(expression, "expression")
4917        position = self.sql(expression, "position")
4918        position = f", {position}" if position else ""
4919        iterator = self.sql(expression, "iterator")
4920        condition = self.sql(expression, "condition")
4921        condition = f" IF {condition}" if condition else ""
4922        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def columnprefix_sql(self, expression: sqlglot.expressions.constraints.ColumnPrefix) -> str:
4924    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4925        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
def opclass_sql(self, expression: sqlglot.expressions.core.Opclass) -> str:
4927    def opclass_sql(self, expression: exp.Opclass) -> str:
4928        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def predict_sql(self, expression: sqlglot.expressions.functions.Predict) -> str:
4944    def predict_sql(self, expression: exp.Predict) -> str:
4945        return self._ml_sql(expression, "PREDICT")
def generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
4947    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4948        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4949        return self._ml_sql(expression, name)
def generatetext_sql(self, expression: sqlglot.expressions.functions.GenerateText) -> str:
4951    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4952        return self._ml_sql(expression, "GENERATE_TEXT")
def generatetable_sql(self, expression: sqlglot.expressions.functions.GenerateTable) -> str:
4954    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4955        return self._ml_sql(expression, "GENERATE_TABLE")
def generatebool_sql(self, expression: sqlglot.expressions.functions.GenerateBool) -> str:
4957    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4958        return self._ml_sql(expression, "GENERATE_BOOL")
def generateint_sql(self, expression: sqlglot.expressions.functions.GenerateInt) -> str:
4960    def generateint_sql(self, expression: exp.GenerateInt) -> str:
4961        return self._ml_sql(expression, "GENERATE_INT")
def generatedouble_sql(self, expression: sqlglot.expressions.functions.GenerateDouble) -> str:
4963    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
4964        return self._ml_sql(expression, "GENERATE_DOUBLE")
def mltranslate_sql(self, expression: sqlglot.expressions.functions.MLTranslate) -> str:
4966    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4967        return self._ml_sql(expression, "TRANSLATE")
def mlforecast_sql(self, expression: sqlglot.expressions.functions.MLForecast) -> str:
4969    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4970        return self._ml_sql(expression, "FORECAST")
def aiforecast_sql(self, expression: sqlglot.expressions.functions.AIForecast) -> str:
4972    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
4973        this_sql = self.sql(expression, "this")
4974        if isinstance(expression.this, exp.Table):
4975            this_sql = f"TABLE {this_sql}"
4976
4977        return self.func(
4978            "FORECAST",
4979            this_sql,
4980            expression.args.get("data_col"),
4981            expression.args.get("timestamp_col"),
4982            expression.args.get("model"),
4983            expression.args.get("id_cols"),
4984            expression.args.get("horizon"),
4985            expression.args.get("forecast_end_timestamp"),
4986            expression.args.get("confidence_level"),
4987            expression.args.get("output_historical_time_series"),
4988            expression.args.get("context_window"),
4989        )
def featuresattime_sql(self, expression: sqlglot.expressions.functions.FeaturesAtTime) -> str:
4991    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4992        this_sql = self.sql(expression, "this")
4993        if isinstance(expression.this, exp.Table):
4994            this_sql = f"TABLE {this_sql}"
4995
4996        return self.func(
4997            "FEATURES_AT_TIME",
4998            this_sql,
4999            expression.args.get("time"),
5000            expression.args.get("num_rows"),
5001            expression.args.get("ignore_feature_nulls"),
5002        )
def vectorsearch_sql(self, expression: sqlglot.expressions.functions.VectorSearch) -> str:
5004    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5005        this_sql = self.sql(expression, "this")
5006        if isinstance(expression.this, exp.Table):
5007            this_sql = f"TABLE {this_sql}"
5008
5009        query_table = self.sql(expression, "query_table")
5010        if isinstance(expression.args["query_table"], exp.Table):
5011            query_table = f"TABLE {query_table}"
5012
5013        return self.func(
5014            "VECTOR_SEARCH",
5015            this_sql,
5016            expression.args.get("column_to_search"),
5017            query_table,
5018            expression.args.get("query_column_to_search"),
5019            expression.args.get("top_k"),
5020            expression.args.get("distance_type"),
5021            expression.args.get("options"),
5022        )
def forin_sql(self, expression: sqlglot.expressions.core.ForIn) -> str:
5024    def forin_sql(self, expression: exp.ForIn) -> str:
5025        this = self.sql(expression, "this")
5026        expression_sql = self.sql(expression, "expression")
5027        return f"FOR {this} DO {expression_sql}"
def refresh_sql(self, expression: sqlglot.expressions.core.Refresh) -> str:
5029    def refresh_sql(self, expression: exp.Refresh) -> str:
5030        this = self.sql(expression, "this")
5031        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5032        return f"REFRESH {kind}{this}"
def toarray_sql(self, expression: sqlglot.expressions.array.ToArray) -> str:
5034    def toarray_sql(self, expression: exp.ToArray) -> str:
5035        arg = expression.this
5036        if not arg.type:
5037            import sqlglot.optimizer.annotate_types
5038
5039            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5040
5041        if arg.is_type(exp.DType.ARRAY):
5042            return self.sql(arg)
5043
5044        cond_for_null = arg.is_(exp.null())
5045        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:
5047    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5048        this = expression.this
5049        time_format = self.format_time(expression)
5050
5051        if time_format:
5052            return self.sql(
5053                exp.cast(
5054                    exp.StrToTime(this=this, format=expression.args["format"]),
5055                    exp.DType.TIME,
5056                )
5057            )
5058
5059        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5060            return self.sql(this)
5061
5062        return self.sql(exp.cast(this, exp.DType.TIME))
def tsordstotimestamp_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToTimestamp) -> str:
5064    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5065        this = expression.this
5066        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5067            return self.sql(this)
5068
5069        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
def tsordstodatetime_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToDatetime) -> str:
5071    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5072        this = expression.this
5073        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5074            return self.sql(this)
5075
5076        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
def tsordstodate_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToDate) -> str:
5078    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5079        this = expression.this
5080        time_format = self.format_time(expression)
5081        safe = expression.args.get("safe")
5082        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5083            return self.sql(
5084                exp.cast(
5085                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5086                    exp.DType.DATE,
5087                )
5088            )
5089
5090        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5091            return self.sql(this)
5092
5093        if safe:
5094            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5095
5096        return self.sql(exp.cast(this, exp.DType.DATE))
def unixdate_sql(self, expression: sqlglot.expressions.temporal.UnixDate) -> str:
5098    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5099        return self.sql(
5100            exp.func(
5101                "DATEDIFF",
5102                expression.this,
5103                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5104                "day",
5105            )
5106        )
def lastday_sql(self, expression: sqlglot.expressions.temporal.LastDay) -> str:
5108    def lastday_sql(self, expression: exp.LastDay) -> str:
5109        if self.LAST_DAY_SUPPORTS_DATE_PART:
5110            return self.function_fallback_sql(expression)
5111
5112        unit = expression.text("unit")
5113        if unit and unit != "MONTH":
5114            self.unsupported("Date parts are not supported in LAST_DAY.")
5115
5116        return self.func("LAST_DAY", expression.this)
def dateadd_sql(self, expression: sqlglot.expressions.temporal.DateAdd) -> str:
5118    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5119        import sqlglot.dialects.dialect
5120
5121        return self.func(
5122            "DATE_ADD",
5123            expression.this,
5124            expression.expression,
5125            sqlglot.dialects.dialect.unit_to_str(expression),
5126        )
def arrayany_sql(self, expression: sqlglot.expressions.array.ArrayAny) -> str:
5128    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5129        if self.CAN_IMPLEMENT_ARRAY_ANY:
5130            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5131            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5132            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5133            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5134
5135        import sqlglot.dialects.dialect
5136
5137        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5138        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5139            self.unsupported("ARRAY_ANY is unsupported")
5140
5141        return self.function_fallback_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
5143    def struct_sql(self, expression: exp.Struct) -> str:
5144        expression.set(
5145            "expressions",
5146            [
5147                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5148                if isinstance(e, exp.PropertyEQ)
5149                else e
5150                for e in expression.expressions
5151            ],
5152        )
5153
5154        return self.function_fallback_sql(expression)
def partitionrange_sql(self, expression: sqlglot.expressions.query.PartitionRange) -> str:
5156    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5157        low = self.sql(expression, "this")
5158        high = self.sql(expression, "expression")
5159
5160        return f"{low} TO {high}"
def truncatetable_sql(self, expression: sqlglot.expressions.ddl.TruncateTable) -> str:
5162    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5163        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5164        tables = f" {self.expressions(expression)}"
5165
5166        exists = " IF EXISTS" if expression.args.get("exists") else ""
5167
5168        on_cluster = self.sql(expression, "cluster")
5169        on_cluster = f" {on_cluster}" if on_cluster else ""
5170
5171        identity = self.sql(expression, "identity")
5172        identity = f" {identity} IDENTITY" if identity else ""
5173
5174        option = self.sql(expression, "option")
5175        option = f" {option}" if option else ""
5176
5177        partition = self.sql(expression, "partition")
5178        partition = f" {partition}" if partition else ""
5179
5180        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
def convert_sql(self, expression: sqlglot.expressions.functions.Convert) -> str:
5184    def convert_sql(self, expression: exp.Convert) -> str:
5185        to = expression.this
5186        value = expression.expression
5187        style = expression.args.get("style")
5188        safe = expression.args.get("safe")
5189        strict = expression.args.get("strict")
5190
5191        if not to or not value:
5192            return ""
5193
5194        # Retrieve length of datatype and override to default if not specified
5195        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5196            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5197
5198        transformed: exp.Expr | None = None
5199        cast = exp.Cast if strict else exp.TryCast
5200
5201        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5202        if isinstance(style, exp.Literal) and style.is_int:
5203            import sqlglot.dialects.tsql
5204
5205            style_value = style.name
5206            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5207            if not converted_style:
5208                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5209
5210            fmt = exp.Literal.string(converted_style)
5211
5212            if to.this == exp.DType.DATE:
5213                transformed = exp.StrToDate(this=value, format=fmt)
5214            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5215                transformed = exp.StrToTime(this=value, format=fmt)
5216            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5217                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5218            elif to.this == exp.DType.TEXT:
5219                transformed = exp.TimeToStr(this=value, format=fmt)
5220
5221        if not transformed:
5222            transformed = cast(this=value, to=to, safe=safe)
5223
5224        return self.sql(transformed)
def copyparameter_sql(self, expression: sqlglot.expressions.dml.CopyParameter) -> str:
5295    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5296        option = self.sql(expression, "this")
5297
5298        if expression.expressions:
5299            upper = option.upper()
5300
5301            # Snowflake FILE_FORMAT options are separated by whitespace
5302            sep = " " if upper == "FILE_FORMAT" else ", "
5303
5304            # Databricks copy/format options do not set their list of values with EQ
5305            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5306            values = self.expressions(expression, flat=True, sep=sep)
5307            return f"{option}{op}({values})"
5308
5309        value = self.sql(expression, "expression")
5310
5311        if not value:
5312            return option
5313
5314        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5315
5316        return f"{option}{op}{value}"
def credentials_sql(self, expression: sqlglot.expressions.dml.Credentials) -> str:
5318    def credentials_sql(self, expression: exp.Credentials) -> str:
5319        cred_expr = expression.args.get("credentials")
5320        if isinstance(cred_expr, exp.Literal):
5321            # Redshift case: CREDENTIALS <string>
5322            credentials = self.sql(expression, "credentials")
5323            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5324        else:
5325            # Snowflake case: CREDENTIALS = (...)
5326            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5327            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5328
5329        storage = self.sql(expression, "storage")
5330        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5331
5332        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5333        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5334
5335        iam_role = self.sql(expression, "iam_role")
5336        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5337
5338        region = self.sql(expression, "region")
5339        region = f" REGION {region}" if region else ""
5340
5341        return f"{credentials}{storage}{encryption}{iam_role}{region}"
def copy_sql(self, expression: sqlglot.expressions.dml.Copy) -> str:
5343    def copy_sql(self, expression: exp.Copy) -> str:
5344        this = self.sql(expression, "this")
5345        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5346
5347        credentials = self.sql(expression, "credentials")
5348        credentials = self.seg(credentials) if credentials else ""
5349        files = self.expressions(expression, key="files", flat=True)
5350        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5351
5352        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5353        params = self.expressions(
5354            expression,
5355            key="params",
5356            sep=sep,
5357            new_line=True,
5358            skip_last=True,
5359            skip_first=True,
5360            indent=self.COPY_PARAMS_ARE_WRAPPED,
5361        )
5362
5363        if params:
5364            if self.COPY_PARAMS_ARE_WRAPPED:
5365                params = f" WITH ({params})"
5366            elif not self.pretty and (files or credentials):
5367                params = f" {params}"
5368
5369        return f"COPY{this}{kind} {files}{credentials}{params}"
def semicolon_sql(self, expression: sqlglot.expressions.query.Semicolon) -> str:
5371    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5372        return ""
def datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5374    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5375        on_sql = "ON" if expression.args.get("on") else "OFF"
5376        filter_col: str | None = self.sql(expression, "filter_column")
5377        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5378        retention_period: str | None = self.sql(expression, "retention_period")
5379        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5380
5381        if filter_col or retention_period:
5382            on_sql = self.func("ON", filter_col, retention_period)
5383
5384        return f"DATA_DELETION={on_sql}"
def maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5386    def maskingpolicycolumnconstraint_sql(
5387        self, expression: exp.MaskingPolicyColumnConstraint
5388    ) -> str:
5389        this = self.sql(expression, "this")
5390        expressions = self.expressions(expression, flat=True)
5391        expressions = f" USING ({expressions})" if expressions else ""
5392        return f"MASKING POLICY {this}{expressions}"
def gapfill_sql(self, expression: sqlglot.expressions.temporal.GapFill) -> str:
5394    def gapfill_sql(self, expression: exp.GapFill) -> str:
5395        this = self.sql(expression, "this")
5396        this = f"TABLE {this}"
5397        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:
5399    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5400        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
def scoperesolution_sql(self, expression: sqlglot.expressions.query.ScopeResolution) -> str:
5402    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5403        this = self.sql(expression, "this")
5404        expr = expression.expression
5405
5406        if isinstance(expr, exp.Func):
5407            # T-SQL's CLR functions are case sensitive
5408            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5409        else:
5410            expr = self.sql(expression, "expression")
5411
5412        return self.scope_resolution(expr, this)
def parsejson_sql(self, expression: sqlglot.expressions.json.ParseJSON) -> str:
5414    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5415        if self.PARSE_JSON_NAME is None:
5416            return self.sql(expression.this)
5417
5418        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
def rand_sql(self, expression: sqlglot.expressions.functions.Rand) -> str:
5420    def rand_sql(self, expression: exp.Rand) -> str:
5421        lower = self.sql(expression, "lower")
5422        upper = self.sql(expression, "upper")
5423
5424        if lower and upper:
5425            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5426        return self.func("RAND", expression.this)
def changes_sql(self, expression: sqlglot.expressions.query.Changes) -> str:
5428    def changes_sql(self, expression: exp.Changes) -> str:
5429        information = self.sql(expression, "information")
5430        information = f"INFORMATION => {information}"
5431        at_before = self.sql(expression, "at_before")
5432        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5433        end = self.sql(expression, "end")
5434        end = f"{self.seg('')}{end}" if end else ""
5435
5436        return f"CHANGES ({information}){at_before}{end}"
def pad_sql(self, expression: sqlglot.expressions.string.Pad) -> str:
5438    def pad_sql(self, expression: exp.Pad) -> str:
5439        prefix = "L" if expression.args.get("is_left") else "R"
5440
5441        fill_pattern = self.sql(expression, "fill_pattern") or None
5442        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5443            fill_pattern = "' '"
5444
5445        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def summarize_sql(self, expression: sqlglot.expressions.ddl.Summarize) -> str:
5447    def summarize_sql(self, expression: exp.Summarize) -> str:
5448        table = " TABLE" if expression.args.get("table") else ""
5449        return f"SUMMARIZE{table} {self.sql(expression.this)}"
def explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5451    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5452        generate_series = exp.GenerateSeries(**expression.args)
5453
5454        parent = expression.parent
5455        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5456            parent = parent.parent
5457
5458        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5459            return self.sql(exp.Unnest(expressions=[generate_series]))
5460
5461        if isinstance(parent, exp.Select):
5462            self.unsupported("GenerateSeries projection unnesting is not supported.")
5463
5464        return self.sql(generate_series)
def converttimezone_sql(self, expression: sqlglot.expressions.temporal.ConvertTimezone) -> str:
5466    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5467        if self.SUPPORTS_CONVERT_TIMEZONE:
5468            return self.function_fallback_sql(expression)
5469
5470        source_tz = expression.args.get("source_tz")
5471        target_tz = expression.args.get("target_tz")
5472        timestamp = expression.args.get("timestamp")
5473
5474        if source_tz and timestamp:
5475            timestamp = exp.AtTimeZone(
5476                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5477            )
5478
5479        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5480
5481        return self.sql(expr)
def json_sql(self, expression: sqlglot.expressions.query.JSON) -> str:
5483    def json_sql(self, expression: exp.JSON) -> str:
5484        this = self.sql(expression, "this")
5485        this = f" {this}" if this else ""
5486
5487        _with = expression.args.get("with_")
5488
5489        if _with is None:
5490            with_sql = ""
5491        elif not _with:
5492            with_sql = " WITHOUT"
5493        else:
5494            with_sql = " WITH"
5495
5496        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5497
5498        return f"JSON{this}{with_sql}{unique_sql}"
def jsonvalue_sql(self, expression: sqlglot.expressions.query.JSONValue) -> str:
5500    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5501        path = self.sql(expression, "path")
5502        returning = self.sql(expression, "returning")
5503        returning = f" RETURNING {returning}" if returning else ""
5504
5505        on_condition = self.sql(expression, "on_condition")
5506        on_condition = f" {on_condition}" if on_condition else ""
5507
5508        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
def skipjsoncolumn_sql(self, expression: sqlglot.expressions.query.SkipJSONColumn) -> str:
5510    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5511        regexp = " REGEXP" if expression.args.get("regexp") else ""
5512        return f"SKIP{regexp} {self.sql(expression.expression)}"
def conditionalinsert_sql(self, expression: sqlglot.expressions.query.ConditionalInsert) -> str:
5514    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5515        else_ = "ELSE " if expression.args.get("else_") else ""
5516        condition = self.sql(expression, "expression")
5517        condition = f"WHEN {condition} THEN " if condition else else_
5518        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5519        return f"{condition}{insert}"
def multitableinserts_sql(self, expression: sqlglot.expressions.query.MultitableInserts) -> str:
5521    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5522        kind = self.sql(expression, "kind")
5523        expressions = self.seg(self.expressions(expression, sep=" "))
5524        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5525        return res
def oncondition_sql(self, expression: sqlglot.expressions.query.OnCondition) -> str:
5527    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5528        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5529        empty = expression.args.get("empty")
5530        empty = (
5531            f"DEFAULT {empty} ON EMPTY"
5532            if isinstance(empty, exp.Expr)
5533            else self.sql(expression, "empty")
5534        )
5535
5536        error = expression.args.get("error")
5537        error = (
5538            f"DEFAULT {error} ON ERROR"
5539            if isinstance(error, exp.Expr)
5540            else self.sql(expression, "error")
5541        )
5542
5543        if error and empty:
5544            error = (
5545                f"{empty} {error}"
5546                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5547                else f"{error} {empty}"
5548            )
5549            empty = ""
5550
5551        null = self.sql(expression, "null")
5552
5553        return f"{empty}{error}{null}"
def jsonextractquote_sql(self, expression: sqlglot.expressions.query.JSONExtractQuote) -> str:
5555    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5556        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5557        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
def jsonexists_sql(self, expression: sqlglot.expressions.json.JSONExists) -> str:
5559    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5560        this = self.sql(expression, "this")
5561        path = self.sql(expression, "path")
5562
5563        passing = self.expressions(expression, "passing")
5564        passing = f" PASSING {passing}" if passing else ""
5565
5566        on_condition = self.sql(expression, "on_condition")
5567        on_condition = f" {on_condition}" if on_condition else ""
5568
5569        path = f"{path}{passing}{on_condition}"
5570
5571        return self.func("JSON_EXISTS", this, path)
def arrayagg_sql(self, expression: sqlglot.expressions.aggregate.ArrayAgg) -> str:
5613    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5614        array_agg = self.function_fallback_sql(expression)
5615        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
def slice_sql(self, expression: sqlglot.expressions.core.Slice) -> str:
5617    def slice_sql(self, expression: exp.Slice) -> str:
5618        step = self.sql(expression, "step")
5619        end = self.sql(expression.expression)
5620        begin = self.sql(expression.this)
5621
5622        sql = f"{end}:{step}" if step else end
5623        return f"{begin}:{sql}" if sql else f"{begin}:"
def apply_sql(self, expression: sqlglot.expressions.array.Apply) -> str:
5625    def apply_sql(self, expression: exp.Apply) -> str:
5626        this = self.sql(expression, "this")
5627        expr = self.sql(expression, "expression")
5628
5629        return f"{this} APPLY({expr})"
def grant_sql(self, expression: sqlglot.expressions.query.Grant) -> str:
5658    def grant_sql(self, expression: exp.Grant) -> str:
5659        return self._grant_or_revoke_sql(
5660            expression,
5661            keyword="GRANT",
5662            preposition="TO",
5663            grant_option_suffix=" WITH GRANT OPTION",
5664        )
def revoke_sql(self, expression: sqlglot.expressions.query.Revoke) -> str:
5666    def revoke_sql(self, expression: exp.Revoke) -> str:
5667        return self._grant_or_revoke_sql(
5668            expression,
5669            keyword="REVOKE",
5670            preposition="FROM",
5671            grant_option_prefix="GRANT OPTION FOR ",
5672        )
def grantprivilege_sql(self, expression: sqlglot.expressions.properties.GrantPrivilege) -> str:
5674    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5675        this = self.sql(expression, "this")
5676        columns = self.expressions(expression, flat=True)
5677        columns = f"({columns})" if columns else ""
5678
5679        return f"{this}{columns}"
def grantprincipal_sql(self, expression: sqlglot.expressions.properties.GrantPrincipal) -> str:
5681    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5682        this = self.sql(expression, "this")
5683
5684        kind = self.sql(expression, "kind")
5685        kind = f"{kind} " if kind else ""
5686
5687        return f"{kind}{this}"
def columns_sql(self, expression: sqlglot.expressions.functions.Columns) -> str:
5689    def columns_sql(self, expression: exp.Columns) -> str:
5690        func = self.function_fallback_sql(expression)
5691        if expression.args.get("unpack"):
5692            func = f"*{func}"
5693
5694        return func
def overlay_sql(self, expression: sqlglot.expressions.string.Overlay) -> str:
5696    def overlay_sql(self, expression: exp.Overlay) -> str:
5697        this = self.sql(expression, "this")
5698        expr = self.sql(expression, "expression")
5699        from_sql = self.sql(expression, "from_")
5700        for_sql = self.sql(expression, "for_")
5701        for_sql = f" FOR {for_sql}" if for_sql else ""
5702
5703        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5705    @unsupported_args("format")
5706    def todouble_sql(self, expression: exp.ToDouble) -> str:
5707        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5708        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
def string_sql(self, expression: sqlglot.expressions.string.String) -> str:
5710    def string_sql(self, expression: exp.String) -> str:
5711        this = expression.this
5712        zone = expression.args.get("zone")
5713
5714        if zone:
5715            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5716            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5717            # set for source_tz to transpile the time conversion before the STRING cast
5718            this = exp.ConvertTimezone(
5719                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5720            )
5721
5722        return self.sql(exp.cast(this, exp.DType.VARCHAR))
def median_sql(self, expression: sqlglot.expressions.aggregate.Median) -> str:
5724    def median_sql(self, expression: exp.Median) -> str:
5725        if not self.SUPPORTS_MEDIAN:
5726            return self.sql(
5727                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5728            )
5729
5730        return self.function_fallback_sql(expression)
def overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5732    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5733        filler = self.sql(expression, "this")
5734        filler = f" {filler}" if filler else ""
5735        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5736        return f"TRUNCATE{filler} {with_count}"
def unixseconds_sql(self, expression: sqlglot.expressions.temporal.UnixSeconds) -> str:
5738    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5739        if self.SUPPORTS_UNIX_SECONDS:
5740            return self.function_fallback_sql(expression)
5741
5742        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5743
5744        return self.sql(
5745            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5746        )
def arraysize_sql(self, expression: sqlglot.expressions.array.ArraySize) -> str:
5748    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5749        dim = expression.expression
5750
5751        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5752        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5753            if not (dim.is_int and dim.name == "1"):
5754                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5755            dim = None
5756
5757        # If dimension is required but not specified, default initialize it
5758        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5759            dim = exp.Literal.number(1)
5760
5761        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
def attach_sql(self, expression: sqlglot.expressions.ddl.Attach) -> str:
5763    def attach_sql(self, expression: exp.Attach) -> str:
5764        this = self.sql(expression, "this")
5765        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5766        expressions = self.expressions(expression)
5767        expressions = f" ({expressions})" if expressions else ""
5768
5769        return f"ATTACH{exists_sql} {this}{expressions}"
def detach_sql(self, expression: sqlglot.expressions.ddl.Detach) -> str:
5771    def detach_sql(self, expression: exp.Detach) -> str:
5772        kind = self.sql(expression, "kind")
5773        kind = f" {kind}" if kind else ""
5774        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5775        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5776        exists = " IF EXISTS" if expression.args.get("exists") else ""
5777        if exists:
5778            kind = kind or " DATABASE"
5779
5780        this = self.sql(expression, "this")
5781        this = f" {this}" if this else ""
5782        cluster = self.sql(expression, "cluster")
5783        cluster = f" {cluster}" if cluster else ""
5784        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5785        sync = " SYNC" if expression.args.get("sync") else ""
5786        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def attachoption_sql(self, expression: sqlglot.expressions.query.AttachOption) -> str:
5788    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5789        this = self.sql(expression, "this")
5790        value = self.sql(expression, "expression")
5791        value = f" {value}" if value else ""
5792        return f"{this}{value}"
def watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5794    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5795        return (
5796            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5797        )
def encodeproperty_sql(self, expression: sqlglot.expressions.properties.EncodeProperty) -> str:
5799    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5800        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5801        encode = f"{encode} {self.sql(expression, 'this')}"
5802
5803        properties = expression.args.get("properties")
5804        if properties:
5805            encode = f"{encode} {self.properties(properties)}"
5806
5807        return encode
def includeproperty_sql(self, expression: sqlglot.expressions.properties.IncludeProperty) -> str:
5809    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5810        this = self.sql(expression, "this")
5811        include = f"INCLUDE {this}"
5812
5813        column_def = self.sql(expression, "column_def")
5814        if column_def:
5815            include = f"{include} {column_def}"
5816
5817        alias = self.sql(expression, "alias")
5818        if alias:
5819            include = f"{include} AS {alias}"
5820
5821        return include
def xmlelement_sql(self, expression: sqlglot.expressions.functions.XMLElement) -> str:
5823    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5824        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5825        name = f"{prefix} {self.sql(expression, 'this')}"
5826        return self.func("XMLELEMENT", name, *expression.expressions)
def xmlkeyvalueoption_sql(self, expression: sqlglot.expressions.query.XMLKeyValueOption) -> str:
5828    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5829        this = self.sql(expression, "this")
5830        expr = self.sql(expression, "expression")
5831        expr = f"({expr})" if expr else ""
5832        return f"{this}{expr}"
def partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5834    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5835        partitions = self.expressions(expression, "partition_expressions")
5836        create = self.expressions(expression, "create_expressions")
5837        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5839    def partitionbyrangepropertydynamic_sql(
5840        self, expression: exp.PartitionByRangePropertyDynamic
5841    ) -> str:
5842        start = self.sql(expression, "start")
5843        end = self.sql(expression, "end")
5844
5845        every = expression.args["every"]
5846        if isinstance(every, exp.Interval) and every.this.is_string:
5847            every.this.replace(exp.Literal.number(every.name))
5848
5849        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:
5851    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5852        name = self.sql(expression, "this")
5853        values = self.expressions(expression, flat=True)
5854
5855        return f"NAME {name} VALUE {values}"
def analyzesample_sql(self, expression: sqlglot.expressions.query.AnalyzeSample) -> str:
5857    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5858        kind = self.sql(expression, "kind")
5859        sample = self.sql(expression, "sample")
5860        return f"SAMPLE {sample} {kind}"
def analyzestatistics_sql(self, expression: sqlglot.expressions.query.AnalyzeStatistics) -> str:
5862    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5863        kind = self.sql(expression, "kind")
5864        option = self.sql(expression, "option")
5865        option = f" {option}" if option else ""
5866        this = self.sql(expression, "this")
5867        this = f" {this}" if this else ""
5868        columns = self.expressions(expression)
5869        columns = f" {columns}" if columns else ""
5870        return f"{kind}{option} STATISTICS{this}{columns}"
def analyzehistogram_sql(self, expression: sqlglot.expressions.query.AnalyzeHistogram) -> str:
5872    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5873        this = self.sql(expression, "this")
5874        columns = self.expressions(expression)
5875        inner_expression = self.sql(expression, "expression")
5876        inner_expression = f" {inner_expression}" if inner_expression else ""
5877        update_options = self.sql(expression, "update_options")
5878        update_options = f" {update_options} UPDATE" if update_options else ""
5879        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def analyzedelete_sql(self, expression: sqlglot.expressions.query.AnalyzeDelete) -> str:
5881    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5882        kind = self.sql(expression, "kind")
5883        kind = f" {kind}" if kind else ""
5884        return f"DELETE{kind} STATISTICS"
def analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5886    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5887        inner_expression = self.sql(expression, "expression")
5888        return f"LIST CHAINED ROWS{inner_expression}"
def analyzevalidate_sql(self, expression: sqlglot.expressions.query.AnalyzeValidate) -> str:
5890    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5891        kind = self.sql(expression, "kind")
5892        this = self.sql(expression, "this")
5893        this = f" {this}" if this else ""
5894        inner_expression = self.sql(expression, "expression")
5895        return f"VALIDATE {kind}{this}{inner_expression}"
def analyze_sql(self, expression: sqlglot.expressions.query.Analyze) -> str:
5897    def analyze_sql(self, expression: exp.Analyze) -> str:
5898        options = self.expressions(expression, key="options", sep=" ")
5899        options = f" {options}" if options else ""
5900        kind = self.sql(expression, "kind")
5901        kind = f" {kind}" if kind else ""
5902        this = self.sql(expression, "this")
5903        this = f" {this}" if this else ""
5904        mode = self.sql(expression, "mode")
5905        mode = f" {mode}" if mode else ""
5906        properties = self.sql(expression, "properties")
5907        properties = f" {properties}" if properties else ""
5908        partition = self.sql(expression, "partition")
5909        partition = f" {partition}" if partition else ""
5910        inner_expression = self.sql(expression, "expression")
5911        inner_expression = f" {inner_expression}" if inner_expression else ""
5912        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
def xmltable_sql(self, expression: sqlglot.expressions.functions.XMLTable) -> str:
5914    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5915        this = self.sql(expression, "this")
5916        namespaces = self.expressions(expression, key="namespaces")
5917        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5918        passing = self.expressions(expression, key="passing")
5919        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5920        columns = self.expressions(expression, key="columns")
5921        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5922        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5923        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:
5925    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5926        this = self.sql(expression, "this")
5927        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
def export_sql(self, expression: sqlglot.expressions.dml.Export) -> str:
5929    def export_sql(self, expression: exp.Export) -> str:
5930        this = self.sql(expression, "this")
5931        connection = self.sql(expression, "connection")
5932        connection = f"WITH CONNECTION {connection} " if connection else ""
5933        options = self.sql(expression, "options")
5934        return f"EXPORT DATA {connection}{options} AS {this}"
def declare_sql(self, expression: sqlglot.expressions.ddl.Declare) -> str:
5936    def declare_sql(self, expression: exp.Declare) -> str:
5937        replace = "OR REPLACE " if expression.args.get("replace") else ""
5938        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
def declareitem_sql(self, expression: sqlglot.expressions.ddl.DeclareItem) -> str:
5940    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5941        variables = self.expressions(expression, "this")
5942        default = self.sql(expression, "default")
5943        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5944
5945        kind = self.sql(expression, "kind")
5946        if isinstance(expression.args.get("kind"), exp.Schema):
5947            kind = f"TABLE {kind}"
5948
5949        kind = f" {kind}" if kind else ""
5950
5951        return f"{variables}{kind}{default}"
def recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
5953    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5954        kind = self.sql(expression, "kind")
5955        this = self.sql(expression, "this")
5956        set = self.sql(expression, "expression")
5957        using = self.sql(expression, "using")
5958        using = f" USING {using}" if using else ""
5959
5960        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5961
5962        return f"{kind_sql} {this} SET {set}{using}"
def parameterizedagg_sql(self, expression: sqlglot.expressions.core.ParameterizedAgg) -> str:
5964    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5965        params = self.expressions(expression, key="params", flat=True)
5966        return self.func(expression.name, *expression.expressions) + f"({params})"
def anonymousaggfunc_sql(self, expression: sqlglot.expressions.core.AnonymousAggFunc) -> str:
5968    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5969        return self.func(expression.name, *expression.expressions)
def combinedaggfunc_sql(self, expression: sqlglot.expressions.core.CombinedAggFunc) -> str:
5971    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5972        return self.anonymousaggfunc_sql(expression)
def combinedparameterizedagg_sql( self, expression: sqlglot.expressions.core.CombinedParameterizedAgg) -> str:
5974    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5975        return self.parameterizedagg_sql(expression)
def show_sql(self, expression: sqlglot.expressions.ddl.Show) -> str:
5977    def show_sql(self, expression: exp.Show) -> str:
5978        self.unsupported("Unsupported SHOW statement")
5979        return ""
def install_sql(self, expression: sqlglot.expressions.ddl.Install) -> str:
5981    def install_sql(self, expression: exp.Install) -> str:
5982        self.unsupported("Unsupported INSTALL statement")
5983        return ""
def get_put_sql( self, expression: sqlglot.expressions.query.Put | sqlglot.expressions.query.Get) -> str:
5985    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5986        # Snowflake GET/PUT statements:
5987        #   PUT <file> <internalStage> <properties>
5988        #   GET <internalStage> <file> <properties>
5989        props = expression.args.get("properties")
5990        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5991        this = self.sql(expression, "this")
5992        target = self.sql(expression, "target")
5993
5994        if isinstance(expression, exp.Put):
5995            return f"PUT {this} {target}{props_sql}"
5996        else:
5997            return f"GET {target} {this}{props_sql}"
def translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
5999    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6000        this = self.sql(expression, "this")
6001        expr = self.sql(expression, "expression")
6002        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6003        return f"TRANSLATE({this} USING {expr}{with_error})"
def decodecase_sql(self, expression: sqlglot.expressions.functions.DecodeCase) -> str:
6005    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6006        if self.SUPPORTS_DECODE_CASE:
6007            return self.func("DECODE", *expression.expressions)
6008
6009        decode_expr, *expressions = expression.expressions
6010
6011        ifs = []
6012        for search, result in zip(expressions[::2], expressions[1::2]):
6013            if isinstance(search, exp.Literal):
6014                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6015            elif isinstance(search, exp.Null):
6016                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6017            else:
6018                if isinstance(search, exp.Binary):
6019                    search = exp.paren(search)
6020
6021                cond = exp.or_(
6022                    decode_expr.eq(search),
6023                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6024                    copy=False,
6025                )
6026                ifs.append(exp.If(this=cond, true=result))
6027
6028        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6029        return self.sql(case)
def semanticview_sql(self, expression: sqlglot.expressions.properties.SemanticView) -> str:
6031    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6032        this = self.sql(expression, "this")
6033        this = self.seg(this, sep="")
6034        dimensions = self.expressions(
6035            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6036        )
6037        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6038        metrics = self.expressions(
6039            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6040        )
6041        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6042        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6043        facts = self.seg(f"FACTS {facts}") if facts else ""
6044        where = self.sql(expression, "where")
6045        where = self.seg(f"WHERE {where}") if where else ""
6046        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6047        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
def getextract_sql(self, expression: sqlglot.expressions.temporal.GetExtract) -> str:
6049    def getextract_sql(self, expression: exp.GetExtract) -> str:
6050        this = expression.this
6051        expr = expression.expression
6052
6053        if not this.type or not expression.type:
6054            import sqlglot.optimizer.annotate_types
6055
6056            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6057
6058        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6059            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6060
6061        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def datefromunixdate_sql(self, expression: sqlglot.expressions.temporal.DateFromUnixDate) -> str:
6063    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6064        return self.sql(
6065            exp.DateAdd(
6066                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6067                expression=expression.this,
6068                unit=exp.var("DAY"),
6069            )
6070        )
def space_sql( self: Generator, expression: sqlglot.expressions.string.Space) -> str:
6072    def space_sql(self: Generator, expression: exp.Space) -> str:
6073        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
def buildproperty_sql(self, expression: sqlglot.expressions.properties.BuildProperty) -> str:
6075    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6076        return f"BUILD {self.sql(expression, 'this')}"
def refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6078    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6079        method = self.sql(expression, "method")
6080        kind = expression.args.get("kind")
6081        if not kind:
6082            return f"REFRESH {method}"
6083
6084        every = self.sql(expression, "every")
6085        unit = self.sql(expression, "unit")
6086        every = f" EVERY {every} {unit}" if every else ""
6087        starts = self.sql(expression, "starts")
6088        starts = f" STARTS {starts}" if starts else ""
6089
6090        return f"REFRESH {method} ON {kind}{every}{starts}"
def modelattribute_sql(self, expression: sqlglot.expressions.query.ModelAttribute) -> str:
6092    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6093        self.unsupported("The model!attribute syntax is not supported")
6094        return ""
def directorystage_sql(self, expression: sqlglot.expressions.dml.DirectoryStage) -> str:
6096    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6097        return self.func("DIRECTORY", expression.this)
def uuid_sql(self, expression: sqlglot.expressions.functions.Uuid) -> str:
6099    def uuid_sql(self, expression: exp.Uuid) -> str:
6100        is_string = expression.args.get("is_string", False)
6101        uuid_func_sql = self.func("UUID")
6102
6103        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6104            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6105
6106        return uuid_func_sql
def initcap_sql(self, expression: sqlglot.expressions.string.Initcap) -> str:
6108    def initcap_sql(self, expression: exp.Initcap) -> str:
6109        delimiters = expression.expression
6110
6111        if delimiters:
6112            # do not generate delimiters arg if we are round-tripping from default delimiters
6113            if (
6114                delimiters.is_string
6115                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6116            ):
6117                delimiters = None
6118            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6119                self.unsupported("INITCAP does not support custom delimiters")
6120                delimiters = None
6121
6122        return self.func("INITCAP", expression.this, delimiters)
def localtime_sql(self, expression: sqlglot.expressions.temporal.Localtime) -> str:
6124    def localtime_sql(self, expression: exp.Localtime) -> str:
6125        this = expression.this
6126        return self.func("LOCALTIME", this) if this else "LOCALTIME"
def localtimestamp_sql(self, expression: sqlglot.expressions.temporal.Localtimestamp) -> str:
6128    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6129        this = expression.this
6130        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
def weekstart_sql(self, expression: sqlglot.expressions.functions.WeekStart) -> str:
6132    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6133        this = expression.this.name.upper()
6134        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
6135            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
6136            return "WEEK"
6137
6138        return self.func("WEEK", expression.this)
def chr_sql( self, expression: sqlglot.expressions.string.Chr, name: str = 'CHR') -> str:
6140    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6141        this = self.expressions(expression)
6142        charset = self.sql(expression, "charset")
6143        using = f" USING {charset}" if charset else ""
6144        return self.func(name, this + using)
def block_sql(self, expression: sqlglot.expressions.query.Block) -> str:
6146    def block_sql(self, expression: exp.Block) -> str:
6147        expressions = self.expressions(expression, sep="; ", flat=True)
6148        return f"{expressions}" if expressions else ""
def storedprocedure_sql(self, expression: sqlglot.expressions.query.StoredProcedure) -> str:
6150    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6151        self.unsupported("Unsupported Stored Procedure syntax")
6152        return ""
def ifblock_sql(self, expression: sqlglot.expressions.query.IfBlock) -> str:
6154    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6155        self.unsupported("Unsupported If block syntax")
6156        return ""
def whileblock_sql(self, expression: sqlglot.expressions.query.WhileBlock) -> str:
6158    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6159        self.unsupported("Unsupported While block syntax")
6160        return ""
def execute_sql(self, expression: sqlglot.expressions.ddl.Execute) -> str:
6162    def execute_sql(self, expression: exp.Execute) -> str:
6163        self.unsupported("Unsupported Execute syntax")
6164        return ""
def executesql_sql(self, expression: sqlglot.expressions.ddl.ExecuteSql) -> str:
6166    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6167        self.unsupported("Unsupported Execute syntax")
6168        return ""
def altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str:
6170    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6171        props = self.expressions(expression, sep=" ")
6172        return f"MODIFY {props}"
def usingproperty_sql(self, expression: sqlglot.expressions.properties.UsingProperty) -> str:
6174    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6175        kind = expression.args.get("kind")
6176        return f"USING {kind} {self.sql(expression, 'this')}"
def renameindex_sql(self, expression: sqlglot.expressions.ddl.RenameIndex) -> str:
6178    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6179        this = self.sql(expression, "this")
6180        to = self.sql(expression, "to")
6181        return f"RENAME INDEX {this} TO {to}"