Edit on GitHub

sqlglot.generator

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

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

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

Apply generic preprocessing transformations to a given expression.

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