Edit on GitHub

sqlglot.generator

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

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

Apply generic preprocessing transformations to a given expression.

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